homebridge / bonjour

A Bonjour/Zeroconf protocol implementation in JavaScript
MIT License
12 stars 7 forks source link

/api/accessories returns different results depending on mDNS Advertiser. #24

Open teh-hippo opened 1 month ago

teh-hippo commented 1 month ago

Analysis

I have a custom NodeJS program I've written that pulls data from the /api/accessories endpoint to make decisions about my home setup. E.g., the air con's current mode.

I noticed that the application broke when I reset the mDNS provider to the default Avahi, instead of Ciao. I don't recall why I selected Ciao, or if that's a legacy value, but I didn't see any need to use something other that the default. But after doing that, the size of the results I get from /api/accessories is roughly a 1/4 of that I got when using Ciao.

I believe only old accessory style plugins are exposed. Platform-provided plugins, of which my aircon is one, don't seem to be exposed.

Expected Behavior

All Homebridge accessories are available via /api/accessories, regardless of any Homebridge setting.

Steps To Reproduce

  1. Create an accessory using homebridge-dummy.
  2. Create a platform-accessory (your pick).
  3. Set Homebridge to use Ciao.
  4. Set Homebridge to use insecure mode (required for the accessories endpoint).
  5. GET /api/accessories. Confirm all accessories are visible.
  6. Change Homebridge to use Avahi.
  7. GET /api/accessories. Observe the platform accessories are no longer returned.

Logs

I don't think this is required here.  The missing accessories are just not present at all.

Configuration

As above; I don't believe this is beneficial here.

Environment

Process Supervisor

hb-service

Additional Context

No response

NorthernMan54 commented 1 month ago

Could you provide a small standalone test program that demonstrates the issue ?

Also when you look at the Accessories tab, is it fully populated or is it also incomplete ?

PS Personally I use node-red for this type of thing, so I can avoid the direct coding etc to the interface

teh-hippo commented 1 month ago

Description

Once the requirements are fulfilled, and the config added, the node script below will use the no-auth endpoint to get a token, and call the list of accessories, emitting the count. When I run this locally, I have a discrepancy between what is returned when using Ciao to using Avahi.

I thought it was that all platforms are missing, but it doesn't seem to be as clear cut as that.

Requirements

Files

Homebridge Config:

{
    "bridge": {
        "advertiser": "ciao"
    },
    "platforms": [
        {
            "platform": "HomebridgeYAP",
            "accessories": [
                {
                    "name": "RPI",
                    "ip": "192.168.0.1"
                },
                {
                    "name": "Self",
                    "ip": "127.0.0.1"
                }
            ]
        }
    ],
    "accessories": [
        {
            "name": "Test-Switch",
            "accessory": "DummySwitch"
        }
    ]
}

The Script:

const http = require('http');

function makeRequest(options, postData = null) {
  return new Promise((resolve, reject) => {
    const req = http.request(options, (res) => {
      let data = '';
      res.on('data', (chunk) => data += chunk);
      res.on('end', () => resolve(JSON.parse(data)));
    });
    req.on('error', reject);
    if (postData) req.write(postData);
    req.end();
  });
}

async function main() {
  console.log('Step 1: Requesting authentication token...');
  const tokenResponse = await makeRequest({
    hostname: 'raspberrypi',
    port: 80,
    path: '/api/auth/noauth',
    method: 'POST',
    headers: { 'Content-Type': 'application/json' }
  }, JSON.stringify({}));

  const token = tokenResponse.access_token;
  console.log('Authentication token received:', token);

  console.log('Step 2: Fetching accessories using the token...');
  const accessories = await makeRequest({
    hostname: 'raspberrypi',
    port: 80,
    path: '/api/accessories',
    method: 'GET',
    headers: { 'Authorization': `Bearer ${token}` }
  });

  // Filter accessories by Model 'YAP' or 'Dummy Switch'
  const filteredAccessories = accessories.filter(item => {
    const model = item.accessoryInformation?.Model;
    return model === 'YAP' || model === 'Dummy Switch';
  });

  console.log('Filtered Accessories:', filteredAccessories);
  console.log('Returning ', filteredAccessories.length, 'items.')
}

main().catch(console.error);

Execution

node file.cjs

NorthernMan54 commented 1 month ago

Slightly modified your program ( removed filtering as it did not apply to my setup, and made hostname/port constants.

#! /usr/bin/env node

const http = require('http');
const fs = require('fs');

const hostname = 'jessie.local';
const port = 8581;

function makeRequest(options, postData = null) {
  return new Promise((resolve, reject) => {
    const req = http.request(options, (res) => {
      let data = '';
      res.on('data', (chunk) => data += chunk);
      res.on('end', () => resolve(JSON.parse(data)));
    });
    req.on('error', reject);
    if (postData) req.write(postData);
    req.end();
  });
}

async function main() {
  console.log('Step 1: Requesting authentication token...');
  const tokenResponse = await makeRequest({
    hostname: hostname,
    port: port,
    path: '/api/auth/noauth',
    method: 'POST',
    headers: { 'Content-Type': 'application/json' }
  }, JSON.stringify({}));

  const token = tokenResponse.access_token;
  console.log('Authentication token received:', token);

  console.log('Step 2: Fetching accessories using the token...');
  const accessories = await makeRequest({
    hostname: hostname,
    port: port,
    path: '/api/accessories',
    method: 'GET',
    headers: { 'Authorization': `Bearer ${token}` }
  });

  const filename = 'accessories-'+process.argv[2]+'.json';
  fs.writeFileSync(filename, JSON.stringify(accessories, null, 2));
  console.log('Filtered Accessories:', accessories);
  console.log('Accessories saved to file:', filename);
  console.log('Returning ', accessories.length, 'items.')
}

main().catch(console.error);

With avahi I get 71 Accessories and with ciao I get 228

This is another instance of this - https://github.com/homebridge/bonjour/issues/12

PS I also found that with avahi, it was slow to populate the accessories tab and with ciao very quick