feat(unifi): implement comprehensive UniFi API client with controllers, protect, access, account, managers, resources, HTTP client, interfaces, logging, plugins, and tests
This commit is contained in:
227
ts/classes.cameramanager.ts
Normal file
227
ts/classes.cameramanager.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user