DouweM / homebridge-unifi-occupancy

Homebridge plugin that adds HomeKit occupancy sensors for selected devices (and people) on your UniFi network to the iOS Home app: quickly see who's where and automate accordingly.
Apache License 2.0
41 stars 1 forks source link

Add support for non-UniFi OS controllers #17

Closed bc2297 closed 1 year ago

bc2297 commented 1 year ago

Describe The Bug: Error when loading plugin after update

To Reproduce: Load plugin

Expected behavior: No error

Logs:

[3/15/2023, 11:38:43 AM] [homebridge-unifi-occupancy] Connecting with UniFi Controller...
[3/15/2023, 11:38:43 AM] [homebridge-unifi-occupancy] Loading device fingerprints...
[3/15/2023, 11:38:43 AM] Homebridge v1.6.0 (HAP v0.11.0) (homebridge-unifi-occupancy) is running on port 31973.
[3/15/2023, 11:38:43 AM] [homebridge-unifi-occupancy] ERROR: Failed to load device fingerprints StatusCodeError: 404 - "<!doctype html><html lang=\"en\"><head><title>HTTP Status 404 – Not Found</title><style type=\"text/css\">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;} h1 {font-size:22px;} h2 {font-size:16px;} h3 {font-size:14px;} p {font-size:12px;} a {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 404 – Not Found</h1></body></html>"
    at new StatusCodeError (/usr/local/lib/node_modules/homebridge-unifi-occupancy/node_modules/request-promise-core/lib/errors.js:32:15)
    at Request.plumbing.callback (/usr/local/lib/node_modules/homebridge-unifi-occupancy/node_modules/request-promise-core/lib/plumbing.js:104:33)
    at Request.RP$callback [as _callback] (/usr/local/lib/node_modules/homebridge-unifi-occupancy/node_modules/request-promise-core/lib/plumbing.js:46:31)
    at Request.self.callback (/usr/local/lib/node_modules/homebridge-unifi-occupancy/node_modules/request/request.js:185:22)
    at Request.emit (node:events:512:28)
    at Request.<anonymous> (/usr/local/lib/node_modules/homebridge-unifi-occupancy/node_modules/request/request.js:1154:10)
    at Request.emit (node:events:512:28)
    at IncomingMessage.<anonymous> (/usr/local/lib/node_modules/homebridge-unifi-occupancy/node_modules/request/request.js:1076:12)
    at Object.onceWrapper (node:events:626:28)
    at IncomingMessage.emit (node:events:524:35)
    at endReadableNT (node:internal/streams/readable:1359:12)
    at processTicksAndRejections (node:internal/process/task_queues:82:21) {
  statusCode: 404,
  error: '<!doctype html><html lang="en"><head><title>HTTP Status 404 – Not Found</title><style type="text/css">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;} h1 {font-size:22px;} h2 {font-size:16px;} h3 {font-size:14px;} p {font-size:12px;} a {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 404 – Not Found</h1></body></html>',
  options: {
    rejectUnauthorized: false,
    jar: RequestJar { _jar: [CookieJar] },
    headers: { 'User-Agent': 'node.js unifi-events UniFi Events' },
    json: true,
    uri: 'https://192.168.1.141:8443/proxy/network//v2/api/fingerprint_devices/0',
    method: 'GET',
    callback: [Function: RP$callback],
    transform: undefined,
    simple: true,
    resolveWithFullResponse: false,
    transform2xxOnly: false
  },
  response: <ref *1> IncomingMessage {
    _readableState: ReadableState {
      objectMode: false,
      highWaterMark: 16384,
      buffer: BufferList { head: null, tail: null, length: 0 },
      length: 0,
      pipes: [],
      flowing: true,
      ended: true,
      endEmitted: true,
      reading: false,
      constructed: true,
      sync: true,
      needReadable: false,
      emittedReadable: false,
      readableListening: false,
      resumeScheduled: false,
      errorEmitted: false,
      emitClose: true,
      autoDestroy: true,
      destroyed: true,
      errored: null,
      closed: true,
      closeEmitted: true,
      defaultEncoding: 'utf8',
      awaitDrainWriters: null,
      multiAwaitDrain: false,
      readingMore: true,
      dataEmitted: true,
      decoder: null,
      encoding: null,
      [Symbol(kPaused)]: false
    },
    _events: [Object: null prototype] {
      end: [Array],
      close: [Array],
      data: [Function (anonymous)],
      error: [Function (anonymous)]
    },
    _eventsCount: 4,
    _maxListeners: undefined,
    socket: TLSSocket {
      _tlsOptions: [Object],
      _secureEstablished: true,
      _securePending: false,
      _newSessionPending: false,
      _controlReleased: true,
      secureConnecting: false,
      _SNICallback: null,
      servername: false,
      alpnProtocol: false,
      authorized: false,
      authorizationError: 'CERT_HAS_EXPIRED',
      encrypted: true,
      _events: [Object: null prototype],
      _eventsCount: 9,
      connecting: false,
      _hadError: false,
      _parent: null,
      _host: null,
      _closeAfterHandlingError: false,
      _readableState: [ReadableState],
      _maxListeners: undefined,
      _writableState: [WritableState],
      allowHalfOpen: false,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: undefined,
      _server: null,
      ssl: null,
      _requestCert: true,
      _rejectUnauthorized: false,
      parser: null,
      _httpMessage: [ClientRequest],
      write: [Function: writeAfterFIN],
      [Symbol(res)]: [TLSWrap],
      [Symbol(verified)]: true,
      [Symbol(pendingSession)]: null,
      [Symbol(async_id_symbol)]: 128,
      [Symbol(kHandle)]: null,
      [Symbol(lastWriteQueueSize)]: 0,
      [Symbol(timeout)]: null,
      [Symbol(kBuffer)]: null,
      [Symbol(kBufferCb)]: null,
      [Symbol(kBufferGen)]: null,
      [Symbol(kCapture)]: false,
      [Symbol(kSetNoDelay)]: false,
      [Symbol(kSetKeepAlive)]: false,
      [Symbol(kSetKeepAliveInitialDelay)]: 0,
      [Symbol(kBytesRead)]: 586,
      [Symbol(kBytesWritten)]: 179,
      [Symbol(connect-options)]: [Object]
    },
    httpVersionMajor: 1,
    httpVersionMinor: 1,
    httpVersion: '1.1',
    complete: true,
    rawHeaders: [
      'Content-Type',
      'text/html;charset=utf-8',
      'Content-Language',
      'en',
      'Content-Length',
      '431',
      'Date',
      'Wed, 15 Mar 2023 18:38:43 GMT',
      'Connection',
      'close'
    ],
    rawTrailers: [],
    joinDuplicateHeaders: undefined,
    aborted: false,
    upgrade: false,
    url: '',
    method: null,
    statusCode: 404,
    statusMessage: '',
    client: TLSSocket {
      _tlsOptions: [Object],
      _secureEstablished: true,
      _securePending: false,
      _newSessionPending: false,
      _controlReleased: true,
      secureConnecting: false,
      _SNICallback: null,
      servername: false,
      alpnProtocol: false,
      authorized: false,
      authorizationError: 'CERT_HAS_EXPIRED',
      encrypted: true,
      _events: [Object: null prototype],
      _eventsCount: 9,
      connecting: false,
      _hadError: false,
      _parent: null,
      _host: null,
      _closeAfterHandlingError: false,
      _readableState: [ReadableState],
      _maxListeners: undefined,
      _writableState: [WritableState],
      allowHalfOpen: false,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: undefined,
      _server: null,
      ssl: null,
      _requestCert: true,
      _rejectUnauthorized: false,
      parser: null,
      _httpMessage: [ClientRequest],
      write: [Function: writeAfterFIN],
      [Symbol(res)]: [TLSWrap],
      [Symbol(verified)]: true,
      [Symbol(pendingSession)]: null,
      [Symbol(async_id_symbol)]: 128,
      [Symbol(kHandle)]: null,
      [Symbol(lastWriteQueueSize)]: 0,
      [Symbol(timeout)]: null,
      [Symbol(kBuffer)]: null,
      [Symbol(kBufferCb)]: null,
      [Symbol(kBufferGen)]: null,
      [Symbol(kCapture)]: false,
      [Symbol(kSetNoDelay)]: false,
      [Symbol(kSetKeepAlive)]: false,
      [Symbol(kSetKeepAliveInitialDelay)]: 0,
      [Symbol(kBytesRead)]: 586,
      [Symbol(kBytesWritten)]: 179,
      [Symbol(connect-options)]: [Object]
    },
    _consuming: false,
    _dumped: false,
    req: ClientRequest {
      _events: [Object: null prototype],
      _eventsCount: 5,
      _maxListeners: undefined,
      outputData: [],
      outputSize: 0,
      writable: true,
      destroyed: false,
      _last: true,
      chunkedEncoding: false,
      shouldKeepAlive: false,
      maxRequestsOnConnectionReached: false,
      _defaultKeepAlive: true,
      useChunkedEncodingByDefault: false,
      sendDate: false,
      _removedConnection: false,
      _removedContLen: false,
      _removedTE: false,
      strictContentLength: false,
      _contentLength: 0,
      _hasBody: true,
      _trailer: '',
      finished: true,
      _headerSent: true,
      _closed: false,
      socket: [TLSSocket],
      _header: 'GET /proxy/network//v2/api/fingerprint_devices/0 HTTP/1.1\r\n' +
        'User-Agent: node.js unifi-events UniFi Events\r\n' +
        'host: 192.168.1.141:8443\r\n' +
        'accept: application/json\r\n' +
        'Connection: close\r\n' +
        '\r\n',
      _keepAliveTimeout: 0,
      _onPendingData: [Function: nop],
      agent: [Agent],
      socketPath: undefined,
      method: 'GET',
      maxHeaderSize: undefined,
      insecureHTTPParser: undefined,
      joinDuplicateHeaders: undefined,
      path: '/proxy/network//v2/api/fingerprint_devices/0',
      _ended: true,
      res: [Circular *1],
      aborted: false,
      timeoutCb: null,
      upgradeOrConnect: false,
      parser: null,
      maxHeadersCount: null,
      reusedSocket: false,
      host: '192.168.1.141',
      protocol: 'https:',
      [Symbol(kCapture)]: false,
      [Symbol(kBytesWritten)]: 0,
      [Symbol(kNeedDrain)]: false,
      [Symbol(corked)]: 0,
      [Symbol(kOutHeaders)]: [Object: null prototype],
      [Symbol(errored)]: null,
      [Symbol(kUniqueHeaders)]: null
    },
    request: Request {
      _events: [Object: null prototype],
      _eventsCount: 5,
      _maxListeners: undefined,
      rejectUnauthorized: false,
      headers: [Object],
      uri: [Url],
      method: 'GET',
      readable: true,
      writable: true,
      explicitMethod: true,
      _qs: [Querystring],
      _auth: [Auth],
      _oauth: [OAuth],
      _multipart: [Multipart],
      _redirect: [Redirect],
      _tunnel: [Tunnel],
      _rp_resolve: [Function (anonymous)],
      _rp_reject: [Function (anonymous)],
      _rp_promise: [Promise [Object]],
      _rp_callbackOrig: undefined,
      callback: [Function (anonymous)],
      _rp_options: [Object],
      setHeader: [Function (anonymous)],
      hasHeader: [Function (anonymous)],
      getHeader: [Function (anonymous)],
      removeHeader: [Function (anonymous)],
      localAddress: undefined,
      pool: [Object],
      dests: [],
      __isRequestRequest: true,
      _callback: [Function: RP$callback],
      proxy: null,
      tunnel: true,
      setHost: true,
      originalCookieHeader: undefined,
      _jar: [RequestJar],
      port: '8443',
      host: '192.168.1.141',
      path: '/proxy/network//v2/api/fingerprint_devices/0',
      _json: true,
      httpModule: [Object],
      agentClass: [Function: Agent],
      agent: [Agent],
      _started: true,
      href: 'https://192.168.1.141:8443/proxy/network//v2/api/fingerprint_devices/0',
      req: [ClientRequest],
      ntick: true,
      response: [Circular *1],
      originalHost: '192.168.1.141:8443',
      originalHostHeaderName: 'host',
      responseContent: [Circular *1],
      _destdata: true,
      _ended: true,
      _callbackCalled: true,
      [Symbol(kCapture)]: false
    },
    toJSON: [Function: responseToJSON],
    caseless: Caseless { dict: [Object] },
    body: '<!doctype html><html lang="en"><head><title>HTTP Status 404 – Not Found</title><style type="text/css">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;} h1 {font-size:22px;} h2 {font-size:16px;} h3 {font-size:14px;} p {font-size:12px;} a {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 404 – Not Found</h1></body></html>',
    [Symbol(kCapture)]: false,
    [Symbol(kHeaders)]: {
      'content-type': 'text/html;charset=utf-8',
      'content-language': 'en',
      'content-length': '431',
      date: 'Wed, 15 Mar 2023 18:38:43 GMT',
      connection: 'close'
    },
    [Symbol(kHeadersCount)]: 10,
    [Symbol(kTrailers)]: null,
    [Symbol(kTrailersCount)]: 0
  }
}
Unhandled rejection StatusCodeError: 404 - "<!doctype html><html lang=\"en\"><head><title>HTTP Status 404 – Not Found</title><style type=\"text/css\">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;} h1 {font-size:22px;} h2 {font-size:16px;} h3 {font-size:14px;} p {font-size:12px;} a {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 404 – Not Found</h1></body></html>"
    at new StatusCodeError (/usr/local/lib/node_modules/homebridge-unifi-occupancy/node_modules/request-promise-core/lib/errors.js:32:15)
    at Request.plumbing.callback (/usr/local/lib/node_modules/homebridge-unifi-occupancy/node_modules/request-promise-core/lib/plumbing.js:104:33)
    at Request.RP$callback [as _callback] (/usr/local/lib/node_modules/homebridge-unifi-occupancy/node_modules/request-promise-core/lib/plumbing.js:46:31)
    at Request.self.callback (/usr/local/lib/node_modules/homebridge-unifi-occupancy/node_modules/request/request.js:185:22)
    at Request.emit (node:events:512:28)
    at Request.<anonymous> (/usr/local/lib/node_modules/homebridge-unifi-occupancy/node_modules/request/request.js:1154:10)
    at Request.emit (node:events:512:28)
    at IncomingMessage.<anonymous> (/usr/local/lib/node_modules/homebridge-unifi-occupancy/node_modules/request/request.js:1076:12)
    at Object.onceWrapper (node:events:626:28)
    at IncomingMessage.emit (node:events:524:35)
    at endReadableNT (node:internal/streams/readable:1359:12)
    at processTicksAndRejections (node:internal/process/task_queues:82:21)

Plugin Config:

{
            "unifi": {
                "controller": "https://192.168.1.141:8443",
                "username": "username",
                "password": "password",
                "site": "default",
                "secure": false,
                "unifios": false
            },
            "interval": 180,
            "accessPointAliases": [
                {
                    "accessPoint": "Basement",
                    "alias": "Basement"
                },
                {
                    "accessPoint": "New living room",
                    "alias": "Living Room"
                },
                {
                    "accessPoint": "Back Yard Roof",
                    "alias": "Back Yard"
                },
                {
                    "accessPoint": "Master Bedroom",
                    "alias": "Main Bedroom"
                },
                {
                    "accessPoint": "Front Room",
                    "alias": "Front Room"
                },
                {
                    "accessPoint": "Living Room",
                    "alias": "Old Living Room"
                }
            ],
            "_bridge": {
                "username": "<MAC>",
                "port": 31973
            },
            "platform": "UnifiOccupancy"
        }

Screenshots:

Environment:

DouweM commented 1 year ago

What version of UniFi OS are you running?

bc2297 commented 1 year ago

Network Application 7.2.94 on MacOS

DouweM commented 1 year ago

If you sign into the Network UI in your browser and visit https://192.168.1.141:8443/proxy/network//v2/api/fingerprint_devices/0 or https://192.168.1.141:8443/v2/api/fingerprint_devices/0, do either of those work or do they both 404?

bc2297 commented 1 year ago

If you sign into the Network UI in your browser and visit https://192.168.1.141:8443/proxy/network//v2/api/fingerprint_devices/0 or

Does not work (not sure if extra slash was intentional)

https://192.168.1.141:8443/v2/api/fingerprint_devices/0, do either of those work or do they both 404?

Does work

DouweM commented 1 year ago

@bc2297 Thanks, that helps. The plugin currently assumes https://192.168.1.141:8443 runs UniFi OS and https://192.168.1.141:8443/proxy/network the Network application, but I can likely work around that and make it work without proxy/network. There's one more possible difference between pointing at UniFi OS or directly at the Network application, though. Do either or both of these links work? https://192.168.1.141:8443/api/users/self and https://192.168.1.141:8443/api/self

bc2297 commented 1 year ago

Neither 404, but the first returns useless data {"meta”:{"rc":"error","msg":"api.err.NoSiteContext"},"data":[]}

DouweM commented 1 year ago

@bc2297 Thanks, I think I've fixed this by reintroducing the "UniFi OS" setting. Can you install v1.2.5, disable that setting, restart, and see if it works now?

bc2297 commented 1 year ago

It boots now! But, it’s creating sensors based on

  1. Wired devices (switches and USG)
  2. Disabled APs

do you want me to make a separate issue for this?

DouweM commented 1 year ago

@bc2297 Ah yes, please do! It'd be great if you can share the sections for the relevant devices from https://192.168.1.141:8443/v2/api/site/default/device, so I know what to filter for.

bc2297 commented 1 year ago

@DouweM Do you know if there are any confidential fields I should avoid posting publicly? Or is the whole thing safe to dump?

DouweM commented 1 year ago

@bc2297 There's nothing sensitive in there, the only thing you may want to mask are the MAC addresses.