diff --git a/config.js b/config.js
index 1a1aa1f..2cbcf82 100644
--- a/config.js
+++ b/config.js
@@ -6,6 +6,7 @@
const fs = require("fs")
const path = require('path')
const utils = require('./lib/utils')
+const DeviceDetector = require('./lib/device-detector')
const version = require('./package.json').version;
const model = utils.model()
@@ -14,13 +15,25 @@ const global = {
// Use the higher resolution video stream
'highResVideo': model !== 'c100x'
}
-const doorUnlock = {
- // Default behaviour is device ID 20, if you need more, add them to additionalLocks in config.json
- openSequence: '*8*19*20##' ,
- closeSequence: '*8*20*20##',
-};
-const additionalLocks = {}
+// Expose locks marked as invisible (visible: 0) in mymodules to HomeKit
+let exposeInvisibleLocks = false
+
+// Auto-detect locks from mymodules file
+const detector = DeviceDetector.create()
+const detectedLocks = detector.detectLocks()
+
+const locks = {}
+for (const lock of detectedLocks) {
+ const lockKey = `lock-${lock.deviceId}`
+ locks[lockKey] = {
+ openSequence: `*8*19*${lock.deviceId}##`,
+ closeSequence: `*8*20*${lock.deviceId}##`,
+ name: `${lock.name} ${lock.buttonId}`,
+ visible: lock.visible,
+ deviceId: lock.deviceId
+ }
+}
const mqtt_config = {
// Set to enable to publish events to an external MQTT server
@@ -87,8 +100,9 @@ if( detectedPath ) {
console.log(`FOUND config.json file at '${detectedPath}' and overriding the values from it.\r\n`)
const config = JSON.parse( fs.readFileSync(detectedPath) )
overrideAndPrintValue( "global", global, config.global)
- overrideAndPrintValue( "doorUnlock", doorUnlock, config.doorUnlock)
- overrideAndPrintValue( "additionalLocks", additionalLocks, config.additionalLocks)
+ if (config.exposeInvisibleLocks !== undefined) {
+ exposeInvisibleLocks = config.exposeInvisibleLocks
+ }
overrideAndPrintValue( "mqtt_config", mqtt_config, config.mqtt_config)
overrideAndPrintValue( "sip", sip, config.sip)
overrideAndPrintValue( "homeassistant", homeassistant, config.homeassistant)
@@ -104,9 +118,9 @@ if( global.highResVideo && utils.model() === 'c100x' ) {
}
console.log(`============================== final config =====================================
-\x1b[33m${JSON.stringify( { global, doorUnlock, additionalLocks, mqtt_config, sip }, null, 2 )}\x1b[0m
+\x1b[33m${JSON.stringify( { global, exposeInvisibleLocks, locks, mqtt_config, sip }, null, 2 )}\x1b[0m
=================================================================================`)
module.exports = {
- doorUnlock, additionalLocks, mqtt_config, global, sip, homeassistant, version
+ locks, mqtt_config, global, exposeInvisibleLocks, sip, homeassistant, version
}
diff --git a/config.json.example b/config.json.example
index 7cb7d93..06366de 100755
--- a/config.json.example
+++ b/config.json.example
@@ -1,12 +1,6 @@
{
"ignoredUnknownValue": {},
- "doorUnlock": {
- "openSequence" : "*8*XX*20##"
- },
- "additionalLocks": {
- "back-door": { "openSequence": "*8*19*21##", "closeSequence": "*8*20*21##" },
- "side-door": { "openSequence": "*8*19*22##", "closeSequence": "*8*20*22##" }
- },
+ "exposeInvisibleLocks": false,
"mqtt_config": {
"enabled" : true,
"host": "192.168.0.2"
diff --git a/controller-homekit.js b/controller-homekit.js
index 064f58b..a8e77f1 100644
--- a/controller-homekit.js
+++ b/controller-homekit.js
@@ -60,29 +60,33 @@ base.eventbus.on('doorbell:pressed', () => {
base.eventbus.emit('homekit:pressed')
})
-const locks = ["default", ...Object.keys(config.additionalLocks)]
+for (const lockKey in config.locks) {
+ const lock = config.locks[lockKey]
+
+ if (lock.visible === 0 && !config.exposeInvisibleLocks) {
+ continue
+ }
-for (const lock of locks) {
- const doorHomekitSettings = filestore.read(lock, () => { return { 'displayName': lock, 'hidden': false } })
+ const doorHomekitSettings = filestore.read(lockKey, () => {
+ return { 'displayName': lock.name || lockKey, 'hidden': false }
+ })
- if( doorHomekitSettings && doorHomekitSettings.hidden )
+ if (doorHomekitSettings && doorHomekitSettings.hidden) {
continue
-
- let door = config.additionalLocks[lock];
- const { openSequence, closeSequence } = lock === "default" ? { openSequence: config.doorUnlock.openSequence, closeSequence: config.doorUnlock.closeSequence } : { openSequence: door.openSequence, closeSequence: door.closeSequence }
+ }
+
+ const { openSequence, closeSequence } = lock
base.eventbus
.on('lock:unlocked:' + openSequence, () => {
- //console.log('received lock:unlocked:' + openSequence)
- base.eventbus.emit('homekit:locked:' + lock, false)
+ base.eventbus.emit('homekit:locked:' + lockKey, false)
}).on('lock:locked:' + closeSequence, () => {
- //console.log('received lock:locked:' + closeSequence)
- base.eventbus.emit('homekit:locked:' + lock, true)
+ base.eventbus.emit('homekit:locked:' + lockKey, true)
})
- homekitManager.addLock( lock, doorHomekitSettings.displayName )
- .unlocked( () => {
+ homekitManager.addLock(lockKey, doorHomekitSettings.displayName)
+ .unlocked(() => {
openwebnet.run("doorUnlock", openSequence, closeSequence)
- } )
+ })
}
homekitManager.addSwitch('Muted' )
diff --git a/lib/apis/door-unlock.js b/lib/apis/door-unlock.js
index 004c690..1e2c54d 100644
--- a/lib/apis/door-unlock.js
+++ b/lib/apis/door-unlock.js
@@ -12,25 +12,21 @@ module.exports = class Api {
handle(request, response, url, q) {
response.write("
")
- response.write("Default
")
- if( config.additionalLocks )
- {
- for( const lock in config.additionalLocks )
- {
- response.write("" + lock + "
")
+ if (config.locks) {
+ for (const lockKey in config.locks) {
+ const lock = config.locks[lockKey]
+ const displayName = lock.name || lockKey
+ response.write("" + displayName + "
")
}
}
response.write("")
- if( q.id ) {
- let door = config.additionalLocks[q.id];
- if( door ) {
- openwebnet.run("doorUnlock", door.openSequence, door.closeSequence )
- response.write("Opened lock: " + q.id + "
")
- } else if( q.id === "default" ) {
- openwebnet.run("doorUnlock", config.doorUnlock.openSequence, config.doorUnlock.closeSequence)
- response.write("Opened default lock
")
+ if (q.id) {
+ const lock = config.locks[q.id]
+ if (lock) {
+ openwebnet.run("doorUnlock", lock.openSequence, lock.closeSequence)
+ response.write("Opened lock: " + (lock.name || q.id) + "
")
} else {
- console.error("Door with id: " + q.id + " not found.")
+ console.error("Lock with id: " + q.id + " not found.")
}
}
}
diff --git a/lib/device-detector.js b/lib/device-detector.js
new file mode 100644
index 0000000..854fd8f
--- /dev/null
+++ b/lib/device-detector.js
@@ -0,0 +1,101 @@
+const fs = require("fs")
+const filestore = require('../json-store')
+
+const C100X_MODULES = "/home/bticino/cfg/extra/.bt_eliot/mymodules"
+
+class DeviceDetector {
+ constructor(mymodulesPath = C100X_MODULES) {
+ this.mymodulesPath = mymodulesPath
+ this.devices = null
+ }
+
+ static create(mymodulesPath) {
+ return new DeviceDetector(mymodulesPath)
+ }
+
+ _loadDevices() {
+ if (this.devices !== null) {
+ return this.devices
+ }
+
+ if (!fs.existsSync(this.mymodulesPath)) {
+ console.log(`[DeviceDetector] mymodules file not found at ${this.mymodulesPath}`)
+ this.devices = []
+ return this.devices
+ }
+
+ try {
+ const store = filestore.create(this.mymodulesPath)
+ this.devices = store.data.modules || []
+ console.log(`[DeviceDetector] Loaded ${this.devices.length} devices from ${this.mymodulesPath}`)
+ } catch (e) {
+ console.error(`[DeviceDetector] Error reading mymodules file: ${e.message}`)
+ this.devices = []
+ }
+
+ return this.devices
+ }
+
+ detectCameras() {
+ const devices = this._loadDevices()
+ const cameras = devices.filter(m =>
+ m.system === 'videodoorentry' &&
+ m.deviceType === 'EU' &&
+ m.privateAddress?.addressValues?.length > 0
+ ).map(m => {
+ const addressValue = m.privateAddress.addressValues.find(a => a.name === 'address')
+ return {
+ id: m.id,
+ deviceId: addressValue?.value,
+ name: m.name,
+ buttonId: m.privateAddress.buttonId,
+ visible: m.privateAddress.visible
+ }
+ }).filter(c => c.deviceId !== undefined)
+
+ console.log(`[DeviceDetector] Detected ${cameras.length} camera(s):`, cameras.map(c => `${c.name} (ID: ${c.deviceId})`).join(', '))
+ return cameras
+ }
+
+ detectLocks() {
+ const devices = this._loadDevices()
+ const locks = devices.filter(m =>
+ m.system === 'automation' &&
+ m.device === 'lock' &&
+ m.privateAddress?.addressValues?.length > 0
+ ).map(m => {
+ const addressValue = m.privateAddress.addressValues.find(a => a.name === 'address')
+ return {
+ id: m.id,
+ deviceId: addressValue?.value,
+ name: m.name,
+ buttonId: m.privateAddress.buttonId,
+ visible: m.privateAddress.visible
+ }
+ }).filter(l => l.deviceId !== undefined)
+
+ console.log(`[DeviceDetector] Detected ${locks.length} lock(s):`, locks.map(l => `${l.name} (ID: ${l.deviceId})`).join(', '))
+ return locks
+ }
+
+ detectInternalUnit() {
+ const devices = this._loadDevices()
+ const internalUnits = devices.filter(m =>
+ m.system === 'videodoorentry' &&
+ m.deviceType === 'IU'
+ ).map(m => {
+ return {
+ id: m.id,
+ deviceId: m.privateAddress?.addressValues?.find(a => a.name === 'address')?.value,
+ EUaddress: m.EUaddress
+ }
+ })
+
+ if (internalUnits.length > 0) {
+ console.log(`[DeviceDetector] Detected ${internalUnits.length} internal unit(s)`)
+ }
+ return internalUnits
+ }
+}
+
+module.exports = DeviceDetector
diff --git a/lib/handlers/openwebnet-handler.js b/lib/handlers/openwebnet-handler.js
index bea2d6f..88b0187 100644
--- a/lib/handlers/openwebnet-handler.js
+++ b/lib/handlers/openwebnet-handler.js
@@ -49,7 +49,7 @@ class OpenwebnetHandler {
}, 100);
break
case msg.startsWith('*8*1#1#4#') ? msg : undefined:
- //this.#eventbus.emit('doorbell:pressed', msg)
+ this.#eventbus.emit('doorbell:pressed', msg)
this.#mqtt.dispatchMessage(msg)
this.#mqtt.dispatchDoorbellEvent(msg)
this.#registry.dispatchEvent('pressed')
diff --git a/package-lock.json b/package-lock.json
index f28ed28..b878e50 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -436,6 +436,7 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
"dev": true,
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -457,6 +458,7 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -624,6 +626,7 @@
"url": "https://github.com/sponsors/ai"
}
],
+ "peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001629",
"electron-to-chromium": "^1.4.796",
@@ -3325,6 +3328,7 @@
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.92.1.tgz",
"integrity": "sha512-JECQ7IwJb+7fgUFBlrJzbyu3GEuNBcdqr1LD7IbSzwkSmIevTm8PF+wej3Oxuz/JFBUZ6O1o43zsPkwm1C4TmA==",
"dev": true,
+ "peer": true,
"dependencies": {
"@types/eslint-scope": "^3.7.3",
"@types/estree": "^1.0.5",
@@ -3372,6 +3376,7 @@
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz",
"integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==",
"dev": true,
+ "peer": true,
"dependencies": {
"@discoveryjs/json-ext": "^0.5.0",
"@webpack-cli/configtest": "^2.1.1",
@@ -3947,7 +3952,8 @@
"version": "8.12.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
- "dev": true
+ "dev": true,
+ "peer": true
},
"acorn-import-attributes": {
"version": "1.9.5",
@@ -3961,6 +3967,7 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
+ "peer": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -4075,6 +4082,7 @@
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz",
"integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==",
"dev": true,
+ "peer": true,
"requires": {
"caniuse-lite": "^1.0.30001629",
"electron-to-chromium": "^1.4.796",
@@ -6097,6 +6105,7 @@
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.92.1.tgz",
"integrity": "sha512-JECQ7IwJb+7fgUFBlrJzbyu3GEuNBcdqr1LD7IbSzwkSmIevTm8PF+wej3Oxuz/JFBUZ6O1o43zsPkwm1C4TmA==",
"dev": true,
+ "peer": true,
"requires": {
"@types/eslint-scope": "^3.7.3",
"@types/estree": "^1.0.5",
@@ -6129,6 +6138,7 @@
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz",
"integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==",
"dev": true,
+ "peer": true,
"requires": {
"@discoveryjs/json-ext": "^0.5.0",
"@webpack-cli/configtest": "^2.1.1",