I’m building an app for iTAG keyfinders. I built a test snippet to check if everything works, but subscribing to notifications failed. I then asked Claude to check my code for issues, but after many attempts it still didn’t work. The error message:
Error: Cannot read property 'characteristics' of undefined
at Remote Process
at HomeyClient.emit (/opt/homey-client/system/manager/ManagerApps/AppProcess/node_modules/@athombv/homey-apps-sdk-v3/lib/HomeyClient.js:1:312)
at Object.emit (/opt/homey-client/system/manager/ManagerApps/AppProcess/node_modules/@athombv/homey-apps-sdk-v3/manager/ble.js:116:32)
at Object.emit (/opt/homey-client/system/manager/ManagerApps/AppProcess/node_modules/@athombv/homey-apps-sdk-v3/lib/BlePeripheral.js:1:3125)
at Object.emit (/opt/homey-client/system/manager/ManagerApps/AppProcess/node_modules/@athombv/homey-apps-sdk-v3/lib/BleService.js:1:1409)
at BleCharacteristic.subscribeToNotifications (/opt/homey-client/system/manager/ManagerApps/AppProcess/node_modules/@athombv/homey-apps-sdk-v3/lib/BleCharacteristic.js:1:1303)
at iTAGDevice.onInit (/drivers/itag/device.js:89:24)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async iTAGDevice._onInit (/opt/homey-client/system/manager/ManagerApps/AppProcess/node_modules/@athombv/homey-apps-sdk-v3/lib/Device.js:1:1743)
I decided to add some logging, and found that “id“ of the characteristic buttonChar was undefined. Claude then gave me a workaround, but it still didn’t work. It then said that it’s a bug in the Homey Apps SDK, but I don’t think so since there are no reports about it online.
I decided to check some examples online, but couldn’t find any that used notifications (I clicked the “Bluetooth LE“ category in the Appstore and checked through the GitHub repos listed there). I was able to find a few examples, but they worked in the same way that I already tried. Could it be something with the iTAG device itself? Disabling the “LinkLost“ alarm works fine.
My full code:
'use strict';
const Homey = require('homey');
module.exports = class iTAGDevice extends Homey.Device {
/**
* onInit is called when the device is initialized.
*/
async onInit() {
this.log('iTAG device has been initialized');
this.log(`Homey version: ${this.homey.version}`);
// Only try notifications if Homey >= 6.0
if (parseInt(this.homey.version, 10) < 6) {
this.log('Homey version does not support BLE notifications');
return;
}
try {
const address = this.getStoreValue('address');
const uuid = address.replace(/:/g, '').toLowerCase();
this.log('Finding iTAG device...');
const advertisement = await this.homey.ble.find(uuid);
this.log('Connecting to iTAG device...');
const peripheral = await advertisement.connect();
this.log('Connected successfully');
// Register the peripheral for notifications
this.log('Registering peripheral for notifications...');
this.homey.ble.__registerPeripheral(peripheral);
this.log('Peripheral registered');
// Discover all services first
this.log('Discovering all services...');
const services = await peripheral.discoverServices();
this.log('Discovered', services.length, 'services');
// Find the FFE0 service from the discovered services
const service = services.find(s => s.uuid === '0000ffe000001000800000805f9b34fb');
if (!service) {
throw new Error('FFE0 service not found');
}
this.log('Found FFE0 service, ID:', service.id);
// WORKAROUND: If service.id is undefined, manually set it to the UUID
if (!service.id) {
this.log('WARNING: service.id is undefined, manually setting it');
service.id = service.uuid;
}
// Now discover characteristics for THIS specific service
this.log('Discovering characteristics for FFE0 service...');
const characteristics = await service.discoverCharacteristics();
this.log('Discovered', characteristics.length, 'characteristics');
// Find the characteristics we need
const linkLostChar = characteristics.find(c => c.uuid === '0000ffe200001000800000805f9b34fb');
const buttonChar = characteristics.find(c => c.uuid === '0000ffe100001000800000805f9b34fb');
if (!linkLostChar) {
throw new Error('LinkLost characteristic (FFE2) not found');
}
if (!buttonChar) {
throw new Error('Button characteristic (FFE1) not found');
}
// WORKAROUND: Set IDs for characteristics if they're undefined
if (!linkLostChar.id) {
linkLostChar.id = linkLostChar.uuid;
}
if (!buttonChar.id) {
buttonChar.id = buttonChar.uuid;
}
this.log('Button char', buttonChar);
this.log('Found both characteristics');
// Write to LinkLost characteristic to disable alarm
this.log('Writing to LinkLost characteristic to disable alarm...');
await linkLostChar.write(Buffer.from([0x00]));
this.log('Successfully disabled LinkLost alarm');
// Subscribe to notifications
this.log('Subscribing to button notifications...');
await buttonChar.subscribeToNotifications((data) => {
this.log('Received notification from iTAG:', data);
if (data && data.length > 0) {
this.log('Button data:', Array.from(data));
if (data[0] === 0xFF) {
this.log('Button pressed!');
// Trigger a flow card here if needed
}
}
});
this.log('Successfully subscribed to notifications');
// Store the peripheral for later use
this.peripheral = peripheral;
// Listen for disconnects
peripheral.once('disconnect', () => {
this.log('iTAG device disconnected');
// Unregister the peripheral
this.homey.ble.__unregisterPeripheral(peripheral);
this.peripheral = null;
this.reconnect();
});
} catch (error) {
this.log('Error connecting to iTAG device during onInit:', error);
this.log('Error message:', error.message);
this.log('Error stack:', error.stack);
// Try to reconnect after a delay
this.reconnectTimeout = this.homey.setTimeout(() => {
this.log('Attempting reconnect after error...');
this.onInit();
}, 30000);
}
}
async reconnect() {
this.log('Attempting to reconnect...');
this.reconnectTimeout = this.homey.setTimeout(() => {
this.onInit();
}, 10000);
}
async onAdded() {
this.log('iTAG device has been added');
}
async onSettings({ oldSettings, newSettings, changedKeys }) {
this.log('iTAG device settings were changed');
}
async onRenamed(name) {
this.log('iTAG device was renamed');
}
async onDeleted() {
this.log('iTAG device has been deleted');
if (this.reconnectTimeout) {
this.homey.clearTimeout(this.reconnectTimeout);
}
if (this.peripheral) {
try {
this.homey.ble.__unregisterPeripheral(this.peripheral);
await this.peripheral.disconnect();
} catch (error) {
this.log('Error disconnecting:', error);
}
}
}
};
I already tried with the Homey Developer Tools and there the notifications do come through.
I also tried it this way:
const Homey = require('homey');
module.exports = class iTAGDevice extends Homey.Device {
/\*\*
\* onInit is called when the device is initialized.
\*/
async onInit() {
this.log('iTAG device has been initialized');
this.log(\`Homey version: ${this.homey.version}\`);
// Only try notifications if Homey >= 6.0
if (parseInt(this.homey.version, 10) < 6) {
this.log('Homey version does not support BLE notifications');
return;
}
try {
const address = this.getStoreValue('address');
const uuid = address.replace(/:/g, '').toLowerCase();
this.log('Finding iTAG device...');
const advertisement = await this.homey.ble.find(uuid);
this.log('Connecting to iTAG device...');
const peripheral = await advertisement.connect();
this.log('Connected successfully');
// Register the peripheral for notifications
this.log('Registering peripheral for notifications...');
this.homey.ble.\__registerPeripheral(peripheral);
this.log('Peripheral registered');
// Discover all services first
this.log('Discovering all services...');
const services = await peripheral.discoverServices();
this.log('Discovered', services.length, 'services');
// Find the FFE0 service from the discovered services
const service = await peripheral.getService('0000ffe000001000800000805f9b34fb');
if (!service) {
throw new Error('FFE0 service not found');
}
this.log('Found FFE0 service, ID:', service.id);
// WORKAROUND: If service.id is undefined, manually set it to the UUID
if (!service.id) {
this.log('WARNING: service.id is undefined, manually setting it');
service.id = service.uuid;
}
// Find the characteristics we need
const linkLostChar = await service.getCharacteristic('0000ffe200001000800000805f9b34fb');
const buttonChar = await service.getCharacteristic('0000ffe100001000800000805f9b34fb');
if (!linkLostChar) {
throw new Error('LinkLost characteristic (FFE2) not found');
}
if (!buttonChar) {
throw new Error('Button characteristic (FFE1) not found');
}
// WORKAROUND: Set IDs for characteristics if they're undefined
if (!linkLostChar.id) {
linkLostChar.id = linkLostChar.uuid;
}
if (!buttonChar.id) {
buttonChar.id = buttonChar.uuid;
}
this.log('Button char', buttonChar);
this.log('Found both characteristics');
// Write to LinkLost characteristic to disable alarm
this.log('Writing to LinkLost characteristic to disable alarm...');
await linkLostChar.write(Buffer.from(\[0x00\]));
this.log('Successfully disabled LinkLost alarm');
// Subscribe to notifications
this.log('Subscribing to button notifications...');
await buttonChar.subscribeToNotifications((data) => {
this.log('Received notification from iTAG:', data);
if (data && data.length > 0) {
this.log('Button data:', Array.from(data));
if (data\[0\] === 0xFF) {
this.log('Button pressed!');
// Trigger a flow card here if needed
}
}
});
this.log('Successfully subscribed to notifications');
// Store the peripheral for later use
this.peripheral = peripheral;
// Listen for disconnects
peripheral.once('disconnect', () => {
this.log('iTAG device disconnected');
// Unregister the peripheral
this.homey.ble.\__unregisterPeripheral(peripheral);
this.peripheral = null;
this.reconnect();
});
} catch (error) {
this.log('Error connecting to iTAG device during onInit:', error);
this.log('Error message:', error.message);
this.log('Error stack:', error.stack);
// Try to reconnect after a delay
this.reconnectTimeout = this.homey.setTimeout(() => {
this.log('Attempting reconnect after error...');
this.onInit();
}, 30000);
}
}
async reconnect() {
this.log('Attempting to reconnect...');
this.reconnectTimeout = this.homey.setTimeout(() => {
this.onInit();
}, 10000);
}
async onAdded() {
this.log('iTAG device has been added');
}
async onSettings({ oldSettings, newSettings, changedKeys }) {
this.log('iTAG device settings were changed');
}
async onRenamed(name) {
this.log('iTAG device was renamed');
}
async onDeleted() {
this.log('iTAG device has been deleted');
if (this.reconnectTimeout) {
this.homey.clearTimeout(this.reconnectTimeout);
}
if (this.peripheral) {
try {
this.homey.ble.\__unregisterPeripheral(this.peripheral);
await this.peripheral.disconnect();
} catch (error) {
this.log('Error disconnecting:', error);
}
}
}
};```
Does anyone know of good examples with BLE notifications? Is this a known issue?
But that gets the same error message. I found this example on GitHub which appears to do the exact same thing as I’m doing:
Is it a known issue? Are there any other examples with BLE notifications? Or am I doing something wrong here?

