UNPKG

homebridge-tapo-camera

Version:

Homebridge plugin for TP-Link TAPO security cameras

216 lines 9.98 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CameraAccessory = void 0; const streamingDelegate_1 = require("homebridge-camera-ffmpeg/dist/streamingDelegate"); const logger_1 = require("homebridge-camera-ffmpeg/dist/logger"); const tapoCamera_1 = require("./tapoCamera"); const pkg_1 = require("./pkg"); class CameraAccessory { platform; config; log; api; camera; pullIntervalTick; accessory; infoAccessory; toggleAccessories = {}; cachedStatus = {}; motionSensorService; randomSeed = Math.random(); constructor(platform, config) { this.platform = platform; this.config = config; // @ts-expect-error - private property this.log = { ...this.platform.log, prefix: this.platform.log.prefix + `/${this.config.name}`, }; this.api = this.platform.api; this.accessory = new this.api.platformAccessory(this.config.name, this.api.hap.uuid.generate(this.config.name), 17 /* this.api.hap.Categories.CAMERA */); this.camera = new tapoCamera_1.TAPOCamera(this.log, this.config); } setupInfoAccessory(basicInfo) { this.infoAccessory = this.accessory.getService(this.api.hap.Service.AccessoryInformation) || this.accessory.addService(this.api.hap.Service.AccessoryInformation); this.infoAccessory .setCharacteristic(this.api.hap.Characteristic.Manufacturer, "TAPO") .setCharacteristic(this.api.hap.Characteristic.Model, basicInfo.device_info) .setCharacteristic(this.api.hap.Characteristic.SerialNumber, basicInfo.mac) .setCharacteristic(this.api.hap.Characteristic.FirmwareRevision, basicInfo.sw_version); } setupToggleAccessory(name, tapoServiceStr) { try { const toggleService = this.accessory.addService(this.api.hap.Service.Switch, name, tapoServiceStr); this.toggleAccessories[tapoServiceStr] = toggleService; toggleService.addOptionalCharacteristic(this.api.hap.Characteristic.ConfiguredName); toggleService.setCharacteristic(this.api.hap.Characteristic.ConfiguredName, name); toggleService .getCharacteristic(this.api.hap.Characteristic.On) .onGet(async () => { try { this.log.debug(`Getting "${tapoServiceStr}" status...`); const cachedValue = this.cachedStatus[tapoServiceStr]; if (cachedValue !== undefined) { return cachedValue; } const currentValue = toggleService.getCharacteristic(this.api.hap.Characteristic.On).value; void this.getStatusAndNotify(); if (typeof currentValue === "boolean") { this.log.debug(`No cached status for "${tapoServiceStr}", returning Homebridge cached value`); return currentValue; } this.log.debug(`No cached status for "${tapoServiceStr}", returning fallback value`); return false; } catch (err) { this.log.error("Error getting status:", err); return false; } }) .onSet(async (newValue) => { try { const value = Boolean(newValue); this.log.debug(`Setting "${tapoServiceStr}" to ${value ? "on" : "off"}...`); await this.camera.setStatus(tapoServiceStr, value); this.cachedStatus[tapoServiceStr] = value; toggleService .getCharacteristic(this.api.hap.Characteristic.On) .updateValue(value); } catch (err) { this.log.error("Error setting status:", err); throw new this.api.hap.HapStatusError(-70409 /* this.api.hap.HAPStatus.RESOURCE_DOES_NOT_EXIST */); } }); } catch (err) { this.log.error("Error setting up toggle accessory", name, tapoServiceStr, err); } } getVideoConfig() { const streamUrl = this.camera.getAuthenticatedStreamUrl(Boolean(this.config.lowQuality)); const vcodec = this.config.videoCodec ?? "copy"; const config = { audio: true, // Set audio as true as most of TAPO cameras have audio vcodec: vcodec, maxWidth: this.config.videoMaxWidth, maxHeight: this.config.videoMaxHeight, maxFPS: this.config.videoMaxFPS, maxBitrate: this.config.videoMaxBirate, packetSize: this.config.videoPacketSize, forceMax: this.config.videoForceMax, ...(this.config.videoConfig || {}), // We add this at the end as the user most not be able to override it source: `-i ${streamUrl}`, }; this.log.debug("Video config", config); return config; } async setupCameraStreaming(basicInfo) { try { const delegate = new streamingDelegate_1.StreamingDelegate(new logger_1.Logger(this.log), { name: this.config.name, manufacturer: "TAPO", model: basicInfo.device_info, serialNumber: basicInfo.mac, firmwareRevision: basicInfo.sw_version, unbridge: true, videoConfig: this.getVideoConfig(), }, this.api, this.api.hap); this.accessory.configureController(delegate.controller); this.log.debug("Camera streaming setup done"); } catch (err) { this.log.error("Error setting up camera streaming:", err); } } async setupMotionSensorAccessory() { try { this.motionSensorService = this.accessory.addService(this.platform.api.hap.Service.MotionSensor, "Motion Sensor", "motion"); this.motionSensorService.addOptionalCharacteristic(this.api.hap.Characteristic.ConfiguredName); this.motionSensorService.setCharacteristic(this.api.hap.Characteristic.ConfiguredName, "Motion Sensor"); const eventEmitter = await this.camera.getEventEmitter(); eventEmitter.addListener("motion", (motionDetected) => { this.log.debug("Motion detected", motionDetected); this.motionSensorService?.updateCharacteristic(this.api.hap.Characteristic.MotionDetected, motionDetected); }); } catch (err) { this.log.error("Error setting up motion sensor accessory:", err); } } setupPolling() { if (this.pullIntervalTick) { clearInterval(this.pullIntervalTick); } this.pullIntervalTick = setInterval(() => { this.log.debug("Polling status..."); this.getStatusAndNotify(); }, this.config.pullInterval || this.platform.kDefaultPullInterval); } async getStatusAndNotify() { try { const cameraStatus = await this.camera.getStatus(); this.cachedStatus = { ...this.cachedStatus, ...cameraStatus, }; this.log.debug("Notifying new values...", cameraStatus); for (const [key, value] of Object.entries(cameraStatus)) { const toggleService = this.toggleAccessories[key]; if (toggleService && value !== undefined) { toggleService .getCharacteristic(this.api.hap.Characteristic.On) .updateValue(value); } } } catch (err) { this.log.error("Error getting status:", err); } } async setup() { const basicInfo = await this.camera.getBasicInfo(); this.log.debug("Basic info", basicInfo); this.accessory.on("identify" /* PlatformAccessoryEvent.IDENTIFY */, () => { this.log.info("Identify requested", basicInfo); }); this.setupInfoAccessory(basicInfo); if (!this.config.disableStreaming) { this.setupCameraStreaming(basicInfo); } if (!this.config.disableEyesToggleAccessory) { this.setupToggleAccessory(this.config.eyesToggleAccessoryName || "Eyes", "eyes"); } if (!this.config.disableAlarmToggleAccessory) { this.setupToggleAccessory(this.config.alarmToggleAccessoryName || "Alarm", "alarm"); } if (!this.config.disableNotificationsToggleAccessory) { this.setupToggleAccessory(this.config.notificationsToggleAccessoryName || "Notifications", "notifications"); } if (!this.config.disableMotionDetectionToggleAccessory) { this.setupToggleAccessory(this.config.motionDetectionToggleAccessoryName || "Motion Detection", "motionDetection"); } if (!this.config.disableLEDToggleAccessory) { this.setupToggleAccessory(this.config.ledToggleAccessoryName || "LED", "led"); } if (!this.config.disableMotionSensorAccessory) { this.setupMotionSensorAccessory(); } // // Publish as external accessory this.log.debug("Publishing accessory..."); this.api.publishExternalAccessories(pkg_1.PLUGIN_ID, [this.accessory]); // Setup the polling by giving a random delay // to avoid all the cameras starting at the same time this.log.debug("Setting up polling..."); setTimeout(() => { this.setupPolling(); }, this.randomSeed * 3_000); this.log.debug("Notifying initial values..."); await this.getStatusAndNotify(); } } exports.CameraAccessory = CameraAccessory; //# sourceMappingURL=cameraAccessory.js.map