Skip to content

Commit

Permalink
Refactoring to make to code more clear
Browse files Browse the repository at this point in the history
Work in progress
  • Loading branch information
Kevin authored and Kevin committed Oct 19, 2019
1 parent 3637635 commit fcbf9d8
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 106 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ src/unifi/*.js
src/unifi/*.js.map
src/utils/*.js
src/utils/*.js.map
src/motion/*.js
src/motion/*.js.map

resources/test-config/accessories/
resources/test-config/persist/
2 changes: 2 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ src/unifi/*.js
src/unifi/*.js.map
src/utils/*.js
src/utils/*.js.map
src/motion/*.js
src/motion/*.js.map
114 changes: 16 additions & 98 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
const path = require("path");

const FFMPEG = require('./ffmpeg/ffmpeg').FFMPEG;
const FFMPEG = require("./ffmpeg/ffmpeg").FFMPEG;

const Unifi = require("./unifi/unifi").Unifi;
const UnifiFlows = require("./unifi/unifi-flows").UnifiFlows;
const Loader = require("./coco/loader").Loader;
const MotionDetector = require("./motion/motion").MotionDetector;

let Accessory, Service, Characteristic, hap, UUIDGen;
let Homebridge, Accessory, Service, Characteristic, hap, UUIDGen;

module.exports = function (homebridge) {
Homebridge = homebridge;
Accessory = homebridge.platformAccessory;
hap = homebridge.hap;
Service = homebridge.hap.Service;
Expand Down Expand Up @@ -44,14 +43,14 @@ UnifiProtectCameraMotion.prototype.didFinishLaunching = function () {
if (self.config.videoConfig) {
const configuredAccessories = [];

const unifi = new Unifi(self.config.unifi.controller, self.config.unifi.motion_score, self.config.unifi.motion_interval, 500, 2, self.log);
const uFlows = new UnifiFlows(unifi, self.config.unifi.username, self.config.unifi.password, self.log);
const unifi = new Unifi(self.config.unifi, 500, 2, self.log);
const uFlows = new UnifiFlows(unifi, self.config.unifi, self.log);

uFlows
.enumerateCameras()
.then((cameras) => {
cameras.forEach((camera) => {
if(camera.streams.length === 0) {
if (camera.streams.length === 0) {
return;
}

Expand Down Expand Up @@ -81,10 +80,17 @@ UnifiProtectCameraMotion.prototype.didFinishLaunching = function () {
cameraAccessory.configureCameraSource(cameraSource);
configuredAccessories.push(cameraAccessory);
});
self.log('Cameras: ' + configuredAccessories.length);

setMotionCheckInterval(self.config.unifi ,uFlows, cameras, configuredAccessories);
const motionDetector = new MotionDetector(Homebridge, self.config.unifi, uFlows, cameras, self.log);
motionDetector.setupMotionChecking(configuredAccessories)
.then(() => {
self.log('Motion checking setup done!');
})
.catch((error) => {
self.log('Error during motion checking setup or interval loop: ' + error);
});

console.log('Cams: ' + configuredAccessories.length);
self.api.publishCameraAccessories('Unifi-Protect-Camera-Motion', configuredAccessories);
self.log('Setup done');
})
Expand All @@ -93,91 +99,3 @@ UnifiProtectCameraMotion.prototype.didFinishLaunching = function () {
});
}
};

function setMotionCheckInterval(unifiConfig, unifiFlows, cameras, configuredAccessories) {
if (unifiConfig.enhanced_motion) {
Loader
.loadCoco(false, '../') //Uncomment this line and comment the line belows for local development & testing
//.loadCoco(false, path.dirname(require.resolve('homebridge-unifi-protect-camera-motion/package.json')))
.then((detector) => {
setInterval(
checkMotionEnhanced.bind(
this, unifiConfig.enhanced_classes, unifiFlows, cameras, configuredAccessories, detector, unifiConfig.debug ? unifiConfig.debug : false
), unifiConfig.motion_interval
);
});
} else {
setInterval(checkMotion.bind(this, unifiFlows, cameras, configuredAccessories), unifiConfig.motion_interval);
}
}

function checkMotion(unifiFlows, cameras, configuredAccessories) {
unifiFlows
.detectMotion(cameras)
.then((motionEvents) => {
outer: for (const configuredAccessory of configuredAccessories) {
configuredAccessory.getService(Service.MotionSensor).setCharacteristic(Characteristic.MotionDetected, 0);

for (const motionEvent of motionEvents) {
if (motionEvent.camera.id === configuredAccessory.context.id) {
console.log('!!!! Motion detected (' + motionEvent.score + '%) by camera ' + motionEvent.camera.name + ' !!!!');
configuredAccessory.getService(Service.MotionSensor).setCharacteristic(Characteristic.MotionDetected, 1);

continue outer;
}
}
}
});
}

function checkMotionEnhanced(classesToDetect, unifiFlows, cameras, configuredAccessories, detector, debugEnabled) {
unifiFlows
.detectMotion(cameras)
.then((motionEvents) => {

outer: for (const configuredAccessory of configuredAccessories) {
configuredAccessory.getService(Service.MotionSensor).setCharacteristic(Characteristic.MotionDetected, 0);

for (const motionEvent of motionEvents) {
let image;

if (motionEvent.camera.id === configuredAccessory.context.id) {
if (debugEnabled) {
console.log('Motion detected, running CoCo detection...');
}

Loader
.createImage('http://' + motionEvent.camera.ip + '/snap.jpeg')
.then((snapshot) => {
image = snapshot;
return detector.detect(snapshot, debugEnabled);
})
.then((detectedClasses) => {
for (const classToDetect of classesToDetect) {
const cls = getClass(classToDetect.toLowerCase(), detectedClasses);
if (cls) {
console.log('!!!! ' + classToDetect +' detected (' + Math.round(cls.score * 100) + '%) by camera ' + motionEvent.camera.name + ' !!!!');
configuredAccessory.getService(Service.MotionSensor).setCharacteristic(Characteristic.MotionDetected, 1);
Loader.saveAnnotatedImage(image, [cls]);
}
}
})
.catch((error) => {
console.log('Error with enhanced detection: ' + error);
});

continue outer;
}
}
}
});
}

function getClass(className, classes) {
for (const cls of classes) {
if(cls.class.toLowerCase() === className.toLowerCase()) {
return cls;
}
}
return null;
}
110 changes: 110 additions & 0 deletions src/motion/motion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import {UnifiCamera, UnifiConfig, UnifiMotionEvent} from "../unifi/unifi";
import {Detection, Detector, Loader} from "../coco/loader";
import {UnifiFlows} from "../unifi/unifi-flows";
import {Image} from "canvas";

export class MotionDetector {

private homebridge: any;
private Service: any;
private Characteristic: any;

private config: UnifiConfig;
private flows: UnifiFlows;
private cameras: UnifiCamera[];
private detector: Detector;
private log: Function;

private configuredAccessories: any[];

constructor(homebridge: any, unifiConfig: UnifiConfig, unifiFlows: UnifiFlows, cameras: UnifiCamera[], logger: Function) {
this.homebridge = homebridge;
this.Service = homebridge.hap.Service;
this.Characteristic = homebridge.hap.Characteristic;

this.config = unifiConfig;
this.flows = unifiFlows;
this.cameras = cameras;

this.log = logger;

this.detector = null;
}

public async setupMotionChecking(configuredAccessories: any[]): Promise<any> {
this.configuredAccessories = configuredAccessories;

let intervalFunction: Function;
if (this.config.enhanced_motion) {
//this.detector = await Loader.loadCoco(false, path.dirname(require.resolve('homebridge-unifi-protect-camera-motion/package.json')));
this.detector = await Loader.loadCoco(false, '../');
intervalFunction = this.checkMotionEnhanced.bind(this);
} else {
intervalFunction = this.checkMotion.bind(this);
}
setInterval(intervalFunction, this.config.motion_interval);
return;
}

private async checkMotion(): Promise<any> {
const motionEvents: UnifiMotionEvent[] = await this.flows.detectMotion(this.cameras);

outer: for (const configuredAccessory of this.configuredAccessories) {
configuredAccessory.getService(this.Service.MotionSensor).setCharacteristic(this.Characteristic.MotionDetected, 0);

for (const motionEvent of motionEvents) {
if (motionEvent.camera.id === configuredAccessory.context.id) {
console.log('!!!! Motion detected (' + motionEvent.score + '%) by camera ' + motionEvent.camera.name + ' !!!!');
configuredAccessory.getService(this.Service.MotionSensor).setCharacteristic(this.Characteristic.MotionDetected, 1);

continue outer;
}
}
}
}

private async checkMotionEnhanced(): Promise<any> {
const motionEvents: UnifiMotionEvent[] = await this.flows.detectMotion(this.cameras);

outer: for (const configuredAccessory of this.configuredAccessories) {
configuredAccessory.getService(this.Service.MotionSensor).setCharacteristic(this.Characteristic.MotionDetected, 0);

for (const motionEvent of motionEvents) {
if (motionEvent.camera.id === configuredAccessory.context.id) {
if (this.config.debug) {
console.log('Motion detected, running CoCo object detection...');
}

const snapshot: Image = await Loader.createImage('http://' + motionEvent.camera.ip + '/snap.jpeg');
const detections: Detection[] = await this.detector.detect(snapshot, this.config.debug);

for (const classToDetect of this.config.enhanced_classes) {
const detection: Detection = this.getDetectionForClassName(classToDetect, detections);

if (detection) {
console.log('!!!! ' + classToDetect +' detected (' + Math.round(detection.score * 100) + '%) by camera ' + motionEvent.camera.name + ' !!!!');
configuredAccessory.getService(this.Service.MotionSensor).setCharacteristic(this.Characteristic.MotionDetected, 1);
Loader.saveAnnotatedImage(snapshot, [detection]);

continue outer;
}
}

continue outer;
}
}
}

//TODO: Error handling!
// console.log('Error with enhanced detection: ' + error);
}

private getDetectionForClassName(className: string, detections: Detection[]) {
for (const detection of detections) {
if(detection.class.toLowerCase() === className.toLowerCase()) {
return detection;
}
}
return null;
}
}
8 changes: 4 additions & 4 deletions src/unifi/unifi-flows.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Unifi, UnifiMotionEvent, UnifiCamera, UnifiSession} from "./unifi";
import {Unifi, UnifiMotionEvent, UnifiCamera, UnifiSession, UnifiConfig} from "./unifi";

export class UnifiFlows {

Expand All @@ -9,10 +9,10 @@ export class UnifiFlows {

private session: UnifiSession;

constructor(unifi: Unifi, username: string, password: string, logger: any) {
constructor(unifi: Unifi, config: UnifiConfig, logger: Function) {
this.unifi = unifi;
this.username = username;
this.password = password;
this.username = config.username;
this.password = config.password;
this.log = logger;
}

Expand Down
21 changes: 17 additions & 4 deletions src/unifi/unifi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ export class Unifi {

private readonly log: any;

constructor(controller: string, motionScore: number, motionIntervalDelay: number, initialBackoffDelay: number, maxRetries: number, logger: any) {
this.controller = controller;
this.motionScore = motionScore;
this.motionIntervaldelay = motionIntervalDelay;
constructor(config: UnifiConfig, initialBackoffDelay: number, maxRetries: number, logger: Function) {
this.controller = config.controller;
this.motionScore = config.motion_score;
this.motionIntervaldelay = config.motion_interval;

this.initialBackoffDelay = initialBackoffDelay;
this.maxRetries = maxRetries;
Expand Down Expand Up @@ -190,3 +190,16 @@ export interface UnifiMotionEvent {
score: number;
timestamp: number;
}

export interface UnifiConfig {
"controller": string;
"controller_rtsp": string;
"username": string;
"password": string;
"motion_interval": number;
"motion_score": number;
"enhanced_motion": boolean;
"enhanced_motion_score": number;
"enhanced_classes": string[];
"debug": boolean;
}

0 comments on commit fcbf9d8

Please sign in to comment.