228 lines
6.6 KiB
TypeScript
228 lines
6.6 KiB
TypeScript
import type { UnifiProtect } from './classes.unifi-protect.js';
|
|
import { UnifiCamera } from './classes.camera.js';
|
|
import type { IProtectCamera, IProtectMotionEvent } from './interfaces/index.js';
|
|
import { logger } from './unifi.logger.js';
|
|
|
|
/**
|
|
* Manager for UniFi Protect cameras
|
|
*/
|
|
export class CameraManager {
|
|
private protect: UnifiProtect;
|
|
|
|
constructor(protect: UnifiProtect) {
|
|
this.protect = protect;
|
|
}
|
|
|
|
/**
|
|
* List all cameras
|
|
*/
|
|
public async listCameras(): Promise<UnifiCamera[]> {
|
|
logger.log('debug', 'Fetching cameras from Protect');
|
|
|
|
const cameras = this.protect.getCamerasFromBootstrap();
|
|
return cameras.map((cameraData) =>
|
|
UnifiCamera.createFromApiObject(cameraData, this.protect)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get a camera by ID
|
|
*/
|
|
public async getCameraById(cameraId: string): Promise<UnifiCamera | null> {
|
|
const cameras = await this.listCameras();
|
|
return cameras.find((camera) => camera.id === cameraId) || null;
|
|
}
|
|
|
|
/**
|
|
* Get a camera by MAC address
|
|
*/
|
|
public async getCameraByMac(mac: string): Promise<UnifiCamera | null> {
|
|
const normalizedMac = mac.toLowerCase().replace(/[:-]/g, ':');
|
|
const cameras = await this.listCameras();
|
|
return cameras.find((camera) => camera.mac.toLowerCase() === normalizedMac) || null;
|
|
}
|
|
|
|
/**
|
|
* Find a camera by name
|
|
*/
|
|
public async getCameraByName(name: string): Promise<UnifiCamera | null> {
|
|
const cameras = await this.listCameras();
|
|
return cameras.find(
|
|
(camera) => camera.name.toLowerCase() === name.toLowerCase()
|
|
) || null;
|
|
}
|
|
|
|
/**
|
|
* Get online cameras only
|
|
*/
|
|
public async getOnlineCameras(): Promise<UnifiCamera[]> {
|
|
const cameras = await this.listCameras();
|
|
return cameras.filter((camera) => camera.isOnline());
|
|
}
|
|
|
|
/**
|
|
* Get offline cameras only
|
|
*/
|
|
public async getOfflineCameras(): Promise<UnifiCamera[]> {
|
|
const cameras = await this.listCameras();
|
|
return cameras.filter((camera) => !camera.isOnline());
|
|
}
|
|
|
|
/**
|
|
* Get doorbells only
|
|
*/
|
|
public async getDoorbells(): Promise<UnifiCamera[]> {
|
|
const cameras = await this.listCameras();
|
|
return cameras.filter((camera) => camera.isDoorbell());
|
|
}
|
|
|
|
/**
|
|
* Get cameras with smart detection
|
|
*/
|
|
public async getSmartDetectCameras(): Promise<UnifiCamera[]> {
|
|
const cameras = await this.listCameras();
|
|
return cameras.filter((camera) => camera.hasSmartDetect());
|
|
}
|
|
|
|
/**
|
|
* Get cameras currently recording
|
|
*/
|
|
public async getRecordingCameras(): Promise<UnifiCamera[]> {
|
|
const cameras = await this.listCameras();
|
|
return cameras.filter((camera) => camera.isRecording === true);
|
|
}
|
|
|
|
/**
|
|
* Get cameras with recent motion
|
|
*/
|
|
public async getCamerasWithRecentMotion(seconds: number = 60): Promise<UnifiCamera[]> {
|
|
const cameras = await this.listCameras();
|
|
return cameras.filter((camera) => camera.hasRecentMotion(seconds));
|
|
}
|
|
|
|
/**
|
|
* Update camera settings
|
|
*/
|
|
public async updateCamera(
|
|
cameraId: string,
|
|
settings: Partial<IProtectCamera>
|
|
): Promise<void> {
|
|
logger.log('info', `Updating camera: ${cameraId}`);
|
|
|
|
await this.protect.request('PATCH', `/cameras/${cameraId}`, settings);
|
|
}
|
|
|
|
/**
|
|
* Get motion events for a camera
|
|
*/
|
|
public async getMotionEvents(
|
|
cameraId: string,
|
|
options: { start?: number; end?: number; limit?: number } = {}
|
|
): Promise<IProtectMotionEvent[]> {
|
|
logger.log('debug', `Fetching motion events for camera: ${cameraId}`);
|
|
|
|
const params = new URLSearchParams();
|
|
params.append('cameras', cameraId);
|
|
if (options.start) params.append('start', options.start.toString());
|
|
if (options.end) params.append('end', options.end.toString());
|
|
if (options.limit) params.append('limit', options.limit.toString());
|
|
|
|
const response = await this.protect.request<IProtectMotionEvent[] | { data: IProtectMotionEvent[] }>(
|
|
'GET',
|
|
`/events?${params.toString()}`
|
|
);
|
|
|
|
// Handle both array and {data: [...]} response formats
|
|
return Array.isArray(response) ? response : (response?.data || []);
|
|
}
|
|
|
|
/**
|
|
* Get all recent motion events
|
|
*/
|
|
public async getAllMotionEvents(
|
|
options: { start?: number; end?: number; limit?: number } = {}
|
|
): Promise<IProtectMotionEvent[]> {
|
|
logger.log('debug', 'Fetching all motion events');
|
|
|
|
const params = new URLSearchParams();
|
|
if (options.start) params.append('start', options.start.toString());
|
|
if (options.end) params.append('end', options.end.toString());
|
|
if (options.limit) params.append('limit', options.limit.toString());
|
|
|
|
const queryString = params.toString() ? `?${params.toString()}` : '';
|
|
const response = await this.protect.request<IProtectMotionEvent[] | { data: IProtectMotionEvent[] }>(
|
|
'GET',
|
|
`/events${queryString}`
|
|
);
|
|
|
|
// Handle both array and {data: [...]} response formats
|
|
return Array.isArray(response) ? response : (response?.data || []);
|
|
}
|
|
|
|
/**
|
|
* Get snapshot for a camera
|
|
*/
|
|
public async getSnapshot(
|
|
cameraId: string,
|
|
options: { width?: number; height?: number; ts?: number } = {}
|
|
): Promise<ArrayBuffer> {
|
|
logger.log('debug', `Fetching snapshot for camera: ${cameraId}`);
|
|
|
|
const params = new URLSearchParams();
|
|
if (options.width) params.append('w', options.width.toString());
|
|
if (options.height) params.append('h', options.height.toString());
|
|
if (options.ts) params.append('ts', options.ts.toString());
|
|
|
|
const queryString = params.toString() ? `?${params.toString()}` : '';
|
|
|
|
// Note: This returns binary data, may need different handling
|
|
const response = await this.protect.request<ArrayBuffer>(
|
|
'GET',
|
|
`/cameras/${cameraId}/snapshot${queryString}`
|
|
);
|
|
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
* Set recording mode for all cameras
|
|
*/
|
|
public async setAllRecordingMode(
|
|
mode: 'always' | 'detections' | 'never' | 'schedule'
|
|
): Promise<void> {
|
|
logger.log('info', `Setting all cameras to recording mode: ${mode}`);
|
|
|
|
const cameras = await this.listCameras();
|
|
const updatePromises = cameras.map((camera) =>
|
|
this.updateCamera(camera.id, {
|
|
recordingSettings: {
|
|
...camera.recordingSettings,
|
|
mode,
|
|
},
|
|
})
|
|
);
|
|
|
|
await Promise.all(updatePromises);
|
|
}
|
|
|
|
/**
|
|
* Adopt a camera
|
|
*/
|
|
public async adoptCamera(mac: string): Promise<void> {
|
|
logger.log('info', `Adopting camera: ${mac}`);
|
|
|
|
await this.protect.request('POST', '/cameras/adopt', {
|
|
mac: mac.toLowerCase(),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Remove a camera
|
|
*/
|
|
public async removeCamera(cameraId: string): Promise<void> {
|
|
logger.log('info', `Removing camera: ${cameraId}`);
|
|
|
|
await this.protect.request('DELETE', `/cameras/${cameraId}`);
|
|
}
|
|
}
|