homebridge-wemo
Version:
Homebridge plugin to integrate Wemo devices into HomeKit.
156 lines (137 loc) • 5.24 kB
JavaScript
import { request } from 'node:http'
import platformConsts from '../utils/constants.js'
import { parseError } from '../utils/functions.js'
import platformLang from '../utils/lang-en.js'
export default class {
constructor(platform, accessory) {
// Set up global vars from the platform
this.platform = platform
this.upnpInterval = platform.config.upnpInterval
this.upnpIntervalMilli = this.upnpInterval * 1000
// Set up other variables we need
this.accessory = accessory
this.services = accessory.context.serviceList
this.subs = {}
}
startSubscriptions() {
// Subscribe to each of the services that the device supports, that the plugin uses
Object.keys(this.services)
.filter(el => platformConsts.servicesToSubscribe.includes(el))
.forEach((service) => {
// Subscript to the service
this.subs[service] = {}
this.subscribe(service)
})
}
stopSubscriptions() {
Object.entries(this.subs).forEach((entry) => {
const [serviceType, sub] = entry
if (sub.timeout) {
clearTimeout(sub.timeout)
}
if (sub.status) {
this.unsubscribe(serviceType)
}
})
this.accessory.logDebugWarn(platformLang.stoppedSubs)
}
subscribe(serviceType) {
try {
// Check to see an already sent request is still pending
if (this.subs[serviceType].status === 'PENDING') {
this.accessory.logDebug(`[${serviceType}] ${platformLang.subPending}`)
return
}
// Set up the options for the subscription request
const timeout = this.upnpInterval + 10
const options = {
host: this.accessory.context.ipAddress,
port: this.accessory.context.port,
path: this.services[serviceType].eventSubURL,
method: 'SUBSCRIBE',
headers: { TIMEOUT: `Second-${timeout}` },
}
// The remaining options depend on whether the subscription already exists
if (this.subs[serviceType].status) {
// Subscription already exists so renew
options.headers.SID = this.subs[serviceType].status
} else {
// Subscription doesn't exist yet to set up for new subscription
this.subs[serviceType].status = 'PENDING'
this.accessory.logDebug(`[${serviceType}] ${platformLang.subInit}`)
options.headers.CALLBACK = `<http://${this.accessory.context.cbURL}/${this.accessory.UUID}>`
options.headers.NT = 'upnp:event'
}
// Execute the subscription request
const req = request(options, (res) => {
if (res.statusCode === 200) {
// Subscription request successful
this.subs[serviceType].status = res.headers.sid
// Renew subscription after 150 seconds
this.subs[serviceType].timeout = setTimeout(
() => this.subscribe(serviceType),
this.upnpIntervalMilli,
)
} else {
// Subscription request failure
this.accessory.logDebugWarn(`[${serviceType}] ${platformLang.subError} [${res.statusCode}]`)
this.subs[serviceType].status = null
// Try to recover from a failed subscription after 10 seconds
this.subs[serviceType].timeout = setTimeout(() => this.subscribe(serviceType), 10000)
}
})
// Listen for errors on the subscription
req.removeAllListeners('error')
req.on('error', (err) => {
if (!err) {
return
}
// Stop the subscriptions
this.stopSubscriptions()
// Use the platform function to disable the upnp client for this accessory
this.platform.disableUPNP(this.accessory, err)
})
req.end()
} catch (err) {
// Catch any errors during the process
this.accessory.logDebugWarn(`[${serviceType}] ${platformLang.subscribeError} ${parseError(err)}`)
}
}
unsubscribe(serviceType) {
try {
// Check to see an already sent request is still pending
if (!this.subs[serviceType] || this.subs[serviceType].status === 'PENDING') {
return
}
// Set up the options for the subscription request
const options = {
host: this.accessory.context.ipAddress,
port: this.accessory.context.port,
path: this.services[serviceType].eventSubURL,
method: 'UNSUBSCRIBE',
headers: { SID: this.subs[serviceType].status },
}
// Execute the subscription request
const req = request(options, (res) => {
if (res.statusCode === 200) {
// Unsubscribed
this.accessory.logDebug(`[${serviceType}] unsubscribe successful`)
} else {
// Subscription request failure
this.accessory.logDebugWarn(`[${serviceType}] ${platformLang.unsubFail} [${res.statusCode}]`)
}
})
// Listen for errors on the subscription
req.removeAllListeners('error')
req.on('error', (err) => {
if (!err) {
return
}
this.accessory.logDebugWarn(`[${serviceType}] ${platformLang.unsubFail} [${err.message}]`)
})
req.end()
} catch (err) {
this.accessory.logDebugWarn(`[${serviceType}] ${platformLang.unsubFail} [${parseError(err)}]`)
}
}
}