@camera.ui/cli 0.0.48 → 0.0.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +10 -1
  3. package/dist/commands/artifacts.d.ts +9 -0
  4. package/dist/commands/artifacts.js +107 -0
  5. package/dist/commands/artifacts.js.map +1 -0
  6. package/dist/commands/bundle.d.ts +19 -1
  7. package/dist/commands/bundle.js +368 -56
  8. package/dist/commands/bundle.js.map +1 -1
  9. package/dist/commands/create.js +140 -93
  10. package/dist/commands/create.js.map +1 -1
  11. package/dist/commands/publish.js +77 -36
  12. package/dist/commands/publish.js.map +1 -1
  13. package/dist/index.js +11 -4
  14. package/dist/index.js.map +1 -1
  15. package/dist/types.d.ts +21 -5
  16. package/dist/utils/banners.d.ts +3 -1
  17. package/dist/utils/banners.js +15 -11
  18. package/dist/utils/banners.js.map +1 -1
  19. package/dist/utils/logger.d.ts +12 -24
  20. package/dist/utils/logger.js +57 -83
  21. package/dist/utils/logger.js.map +1 -1
  22. package/dist/utils/parser.d.ts +11 -4
  23. package/dist/utils/parser.js +27 -186
  24. package/dist/utils/parser.js.map +1 -1
  25. package/dist/utils/templates.d.ts +13 -1
  26. package/dist/utils/templates.js +148 -68
  27. package/dist/utils/templates.js.map +1 -1
  28. package/dist/utils/utils.js +13 -11
  29. package/dist/utils/utils.js.map +1 -1
  30. package/dist/utils/versions.js +1 -2
  31. package/dist/utils/versions.js.map +1 -1
  32. package/package.json +29 -43
  33. package/templates/base/README.md +2 -2
  34. package/templates/base/SECURITY.md +1 -1
  35. package/templates/base/package.json +2 -3
  36. package/templates/base/updates.config.js +1 -1
  37. package/templates/go/go.mod +5 -0
  38. package/templates/go/postinstall.js +35 -0
  39. package/templates/go/src/main.go +51 -0
  40. package/templates/python/requirements.txt +1 -1
  41. package/templates/python/ruff.toml +2 -2
  42. package/templates/python/src/main.py +299 -28
  43. package/templates/typescript/contract.ts +36 -0
  44. package/templates/typescript/eslint.config.js +13 -3
  45. package/templates/typescript/package.eslint.json +2 -2
  46. package/templates/typescript/package.json +4 -4
  47. package/templates/typescript/src/index.ts +96 -27
  48. package/templates/typescript/src/sensor.ts +207 -0
  49. package/CHANGELOG.md +0 -8
  50. package/CONTRIBUTING.md +0 -1
  51. package/scripts/chmod.js +0 -17
  52. package/templates/base/cameraui.config.js +0 -10
  53. package/templates/base/eslint.config.js +0 -64
  54. package/templates/base/src/index.js +0 -41
@@ -0,0 +1,51 @@
1
+ package main
2
+
3
+ import (
4
+ sdk "github.com/seydx/camera-ui-sdk"
5
+ )
6
+
7
+ // {{pluginName}}Plugin is the main plugin struct.
8
+ type {{pluginName}}Plugin struct {
9
+ logger *sdk.Logger
10
+ api *sdk.PluginAPI
11
+ storage *sdk.DeviceStorage
12
+ }
13
+
14
+ // NewPlugin creates a new plugin instance.
15
+ // This is called by the SDK during plugin initialization.
16
+ func NewPlugin(logger *sdk.Logger, api *sdk.PluginAPI, storage *sdk.DeviceStorage) sdk.Plugin {
17
+ return &{{pluginName}}Plugin{
18
+ logger: logger,
19
+ api: api,
20
+ storage: storage,
21
+ }
22
+ }
23
+
24
+ // ConfigureCameras is called on startup with all assigned cameras.
25
+ func (p *{{pluginName}}Plugin) ConfigureCameras(cameras []*sdk.CameraDevice) error {
26
+ p.logger.Log("Configuring cameras:", len(cameras))
27
+
28
+ for _, camera := range cameras {
29
+ if err := p.OnCameraAdded(camera); err != nil {
30
+ p.logger.Error("Failed to configure camera:", camera.Name(), err)
31
+ }
32
+ }
33
+
34
+ return nil
35
+ }
36
+
37
+ // OnCameraAdded is called when a camera is added/assigned at runtime.
38
+ func (p *{{pluginName}}Plugin) OnCameraAdded(camera *sdk.CameraDevice) error {
39
+ p.logger.Log("Camera added:", camera.Name())
40
+ return nil
41
+ }
42
+
43
+ // OnCameraReleased is called when a camera is removed/unassigned at runtime.
44
+ func (p *{{pluginName}}Plugin) OnCameraReleased(cameraID string) error {
45
+ p.logger.Log("Camera released:", cameraID)
46
+ return nil
47
+ }
48
+
49
+ func main() {
50
+ sdk.Run(NewPlugin)
51
+ }
@@ -1 +1 @@
1
- camera-ui-python-types==2.0.3
1
+ camera-ui-sdk==1.0.93
@@ -32,8 +32,8 @@ exclude = [
32
32
  line-length = 110
33
33
  indent-width = 4
34
34
 
35
- # Assume Python 3.8
36
- target-version = "py39"
35
+ # Assume Python 3.11
36
+ target-version = "py311"
37
37
 
38
38
  [lint]
39
39
  # Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
@@ -1,54 +1,325 @@
1
- from camera_ui_python_types import (
1
+ """
2
+ Sample camera.ui Plugin (Python)
3
+
4
+ Demonstrates the camera.ui plugin architecture with sensors:
5
+ - MotionSensor: External motion events (webhooks, ONVIF, etc.)
6
+ - LightControl: Controllable light with on/brightness
7
+ - ClassifierDetectorSensor: Multi-provider frame-based classification
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import TYPE_CHECKING, Any
13
+
14
+ from camera_ui_sdk import (
15
+ API_EVENT,
16
+ BasePlugin,
2
17
  CameraDevice,
3
- CameraExtension,
4
- CuiPlugin,
5
- CuiPluginCapability,
18
+ ClassifierDetectorSensor,
19
+ ClassifierResult,
20
+ Detection,
21
+ DeviceStorage,
22
+ JsonSchema,
23
+ LightControl,
6
24
  LoggerService,
25
+ ModelSpec,
26
+ MotionSensor,
7
27
  PluginAPI,
28
+ VideoFrameData,
8
29
  )
9
30
 
31
+ if TYPE_CHECKING:
32
+ from camera_ui_sdk import SensorType
33
+
34
+
35
+ # ============ MOTION SENSOR (External Events) ============
36
+
37
+
38
+ class ExampleMotionSensor(MotionSensor):
39
+ """
40
+ Example motion sensor for external triggers.
41
+
42
+ Use this for external motion sources:
43
+ - ONVIF camera events
44
+ - SMTP notifications
45
+ - Webhook triggers
46
+ - API polling
47
+
48
+ For frame-based detection, extend MotionDetectorSensor instead.
49
+ """
50
+
51
+ def __init__(self, name: str) -> None:
52
+ super().__init__(name)
53
+
54
+ def trigger(self, detections: list[Detection] | None = None) -> None:
55
+ """Trigger motion from external event."""
56
+ self.detected = True
57
+ self.detections = detections or []
58
+
59
+ def reset(self) -> None:
60
+ """Clear motion state."""
61
+ self.detected = False
62
+ self.detections = []
63
+
64
+
65
+ # ============ LIGHT CONTROL ============
66
+
67
+
68
+ class ExampleLightControl(LightControl):
69
+ """
70
+ Example light control sensor.
71
+
72
+ Bidirectional control sensor - consumers can read and write state.
73
+ Implements setOn/setBrightness to handle state changes.
74
+ """
75
+
76
+ def __init__(self, camera: CameraDevice, name: str = "Light") -> None:
77
+ super().__init__(name)
78
+ self._camera = camera
79
+
80
+ # Initialize state
81
+ self.on = False
82
+ self.brightness = 100
83
+
84
+ # Log state changes
85
+ self.onPropertyChanged.subscribe(
86
+ lambda event: self._camera.logger.debug(f"{self.name}: {event['property']} = {event['value']}")
87
+ )
88
+
89
+ @property
90
+ def storage_schema(self) -> list[JsonSchema]:
91
+ return [
92
+ {
93
+ "type": "number",
94
+ "key": "defaultBrightness",
95
+ "title": "Default Brightness",
96
+ "description": "Default brightness level (0-100)",
97
+ "defaultValue": 100,
98
+ "minimum": 0,
99
+ "maximum": 100,
100
+ "store": True,
101
+ },
102
+ {
103
+ "type": "boolean",
104
+ "key": "autoOff",
105
+ "title": "Auto-Off",
106
+ "description": "Automatically turn off after timeout",
107
+ "defaultValue": False,
108
+ "store": True,
109
+ },
110
+ ]
111
+
112
+ async def setOn(self, value: bool) -> None:
113
+ """Called when consumer sets 'on' property."""
114
+ self._camera.logger.log(f"Light turned {'ON' if value else 'OFF'}")
115
+ self.on = value
116
+
117
+ # Apply default brightness when turning on
118
+ if value and self.storage:
119
+ default_brightness = self.storage.values.get("defaultBrightness", 100)
120
+ self.brightness = default_brightness
121
+
122
+ async def setBrightness(self, value: int) -> None:
123
+ """Called when consumer sets 'brightness' property."""
124
+ self._camera.logger.log(f"Light brightness: {value}%")
125
+ self.brightness = value
126
+
127
+
128
+ # ============ CLASSIFIER (Multi-Provider Example) ============
129
+
10
130
 
11
- class SamplePlugin(CuiPlugin):
12
- def __init__(self, logger: LoggerService, api: PluginAPI):
13
- self.api = api
14
- self.logger = logger
131
+ class ExampleClassifier(ClassifierDetectorSensor[dict[str, Any]]):
132
+ """
133
+ Example classifier sensor.
134
+
135
+ Multi-provider sensor: Multiple classifiers can be registered per camera.
136
+ Example use cases:
137
+ - Bird species classifier (triggers on 'bird' from object detection)
138
+ - Dog breed classifier (triggers on 'dog')
139
+ - Plant species classifier
140
+
141
+ The DetectionCoordinator calls detectClassifications() when triggerLabels are detected.
142
+ """
143
+
144
+ def __init__(self, camera: CameraDevice, name: str = "Classifier") -> None:
145
+ super().__init__(name)
146
+ self._camera = camera
147
+
148
+ @property
149
+ def storage_schema(self) -> list[JsonSchema]:
150
+ return [
151
+ {
152
+ "type": "number",
153
+ "key": "confidenceThreshold",
154
+ "title": "Confidence Threshold",
155
+ "description": "Minimum confidence for classifications (0-1)",
156
+ "defaultValue": 0.5,
157
+ "minimum": 0.1,
158
+ "maximum": 1.0,
159
+ "step": 0.05,
160
+ "store": True,
161
+ },
162
+ ]
163
+
164
+ @property
165
+ def modelSpec(self) -> ModelSpec:
166
+ """
167
+ Model specification.
168
+
169
+ - input: Frame size and format expected by the model
170
+ - outputLabels: Labels this classifier can output
171
+ - triggerLabels: Object labels that trigger classification
172
+ """
173
+ return {
174
+ "input": {
175
+ "width": 224,
176
+ "height": 224,
177
+ "format": "rgb",
178
+ },
179
+ # Trigger when object detection finds these labels
180
+ "triggerLabels": ["animal"],
181
+ }
182
+
183
+ async def detectClassifications(
184
+ self,
185
+ frame: VideoFrameData,
186
+ triggerRegions: list[Detection] | None = None, # noqa: ARG002
187
+ ) -> ClassifierResult:
188
+ """
189
+ Classify objects in a frame.
190
+
191
+ Called by DetectionCoordinator when triggerLabels are detected.
192
+ The frame is pre-scaled to modelSpec.input dimensions.
193
+ """
194
+ threshold = 0.5
195
+ if self.storage:
196
+ threshold = self.storage.values.get("confidenceThreshold", 0.5)
197
+
198
+ # TODO: Implement your classification model here
199
+ # Example: Load TensorFlow model and run inference
200
+ #
201
+ # predictions = await self.model.classify(frame["data"])
202
+ # return {
203
+ # "detected": len(predictions) > 0,
204
+ # "detections": [
205
+ # {
206
+ # "label": p.label,
207
+ # "confidence": p.score,
208
+ # "box": triggerRegions[0]["box"] if triggerRegions else {"x": 0, "y": 0, "width": 1, "height": 1},
209
+ # }
210
+ # for p in predictions
211
+ # ],
212
+ # }
213
+
214
+ self._camera.logger.debug(
215
+ f"Classifying frame {frame['width']}x{frame['height']}, threshold: {threshold}"
216
+ )
217
+
218
+ # Return empty result (placeholder)
219
+ return {
220
+ "detected": False,
221
+ "detections": [],
222
+ }
223
+
224
+ async def destroy(self) -> None:
225
+ """Cleanup when sensor is destroyed."""
226
+ # Release model resources if needed
227
+ pass
228
+
229
+
230
+ # ============ PLUGIN ============
231
+
232
+
233
+ class SamplePlugin(BasePlugin):
234
+ """
235
+ Sample plugin demonstrating camera.ui SDK usage.
236
+
237
+ The contract (provides/consumes) is defined in contract.ts.
238
+ """
239
+
240
+ def __init__(self, logger: LoggerService, api: PluginAPI, storage: DeviceStorage[Any]) -> None:
241
+ super().__init__(logger, api, storage)
242
+
243
+ # Maps to track cameras and sensors
15
244
  self.cameras: dict[str, CameraDevice] = {}
16
- self.model = None
245
+ self.motion_sensors: dict[str, ExampleMotionSensor] = {}
246
+ self.light_controls: dict[str, ExampleLightControl] = {}
17
247
 
18
- self.api.on("finishLaunching", self.start)
19
- self.api.on("shutdown", self.stop)
248
+ # Register lifecycle event handlers
249
+ self.api.on(API_EVENT.FINISH_LAUNCHING, self._on_finish_launching)
250
+ self.api.on(API_EVENT.SHUTDOWN, self._on_shutdown)
20
251
 
21
- self.api.device_manager.on("cameraSelected", self.camera_selected)
22
- self.api.device_manager.on("cameraDeselected", self.camera_deselected)
252
+ async def configureCameras(self, cameraDevices: list[CameraDevice]) -> None:
253
+ """
254
+ Configure cameras at startup.
255
+ Called for cameras already assigned to this plugin.
256
+ """
257
+ for camera in cameraDevices:
258
+ await self._setup_camera(camera)
23
259
 
24
- async def camera_selected(self, camera: CameraDevice, extension: CameraExtension) -> None:
260
+ async def onCameraAdded(self, camera: CameraDevice, _sensor_type: SensorType | None = None) -> None:
261
+ """Called when a camera is selected for this plugin at runtime."""
25
262
  self.logger.log(f"Camera selected: {camera.name}")
263
+ await self._setup_camera(camera)
264
+
265
+ async def onCameraReleased(self, cameraId: str) -> None:
266
+ """Called when a camera is deselected from this plugin."""
267
+ camera = self.cameras.get(cameraId)
268
+ if not camera:
269
+ return
26
270
 
271
+ self.logger.log(f"Camera deselected: {camera.name}")
272
+
273
+ # Remove sensors
274
+ motion = self.motion_sensors.get(cameraId)
275
+ if motion:
276
+ await camera.removeSensor(motion.id)
277
+ del self.motion_sensors[cameraId]
278
+
279
+ light = self.light_controls.get(cameraId)
280
+ if light:
281
+ await camera.removeSensor(light.id)
282
+ del self.light_controls[cameraId]
283
+
284
+ del self.cameras[cameraId]
285
+
286
+ async def _setup_camera(self, camera: CameraDevice) -> None:
287
+ """Set up sensors for a camera."""
27
288
  if camera.id in self.cameras:
28
289
  return
29
290
 
30
291
  self.cameras[camera.id] = camera
31
292
 
32
- def camera_deselected(self, id: str, extension: CameraExtension) -> None:
33
- camera = self.cameras.get(id)
293
+ # Create motion sensor
294
+ motion = ExampleMotionSensor(f"Motion - {camera.name}")
295
+ self.motion_sensors[camera.id] = motion
296
+ await camera.addSensor(motion)
34
297
 
35
- if camera:
36
- self.logger.log(f"Camera deselected: {self.cameras[id].name}")
37
- del self.cameras[id]
298
+ # Create light control
299
+ light = ExampleLightControl(camera, f"Light - {camera.name}")
300
+ self.light_controls[camera.id] = light
301
+ await camera.addSensor(light)
38
302
 
39
- def start(self) -> None:
40
- self.logger.log("Finished launching plugin")
303
+ self.logger.log(f"Sensors registered for {camera.name}")
41
304
 
42
- def stop(self) -> None:
43
- self.logger.log("Shutting down plugin")
305
+ # Example: Trigger motion after 5 seconds (for testing)
306
+ # import asyncio
307
+ # asyncio.get_event_loop().call_later(5, motion.trigger)
308
+
309
+ def _on_finish_launching(self) -> None:
310
+ """Called when the plugin has finished launching."""
311
+ self.logger.log("Plugin started")
44
312
 
45
- async def configureCameras(self, cameras: list[CameraDevice]) -> None:
46
- for camera in cameras:
47
- self.cameras[camera.id] = camera
313
+ def _on_shutdown(self) -> None:
314
+ """Called when camera.ui is shutting down."""
315
+ self.logger.log("Shutting down plugin")
48
316
 
49
- async def getCapabilities(self) -> list[CuiPluginCapability]:
50
- return []
317
+ # Cleanup all sensors
318
+ self.motion_sensors.clear()
319
+ self.light_controls.clear()
320
+ self.cameras.clear()
51
321
 
52
322
 
53
323
  def __main__() -> type[SamplePlugin]:
324
+ """Plugin entry point - returns the plugin class."""
54
325
  return SamplePlugin
@@ -0,0 +1,36 @@
1
+ import { PluginRole, SensorType } from '@camera.ui/sdk';
2
+
3
+ import type { PluginContract } from '@camera.ui/sdk';
4
+
5
+ /**
6
+ * Plugin Contract
7
+ *
8
+ * Defines what sensors this plugin provides and consumes.
9
+ * This is used by camera.ui to determine plugin compatibility.
10
+ */
11
+ export const contract: PluginContract = {
12
+ // Plugin display name
13
+ name: 'Sample Plugin',
14
+
15
+ // Plugin role - what this plugin does
16
+ role: PluginRole.SensorProvider,
17
+
18
+ // Sensors this plugin provides (add the sensor types you implement)
19
+ provides: [
20
+ SensorType.Motion,
21
+ SensorType.Light,
22
+ SensorType.Classifier, // Multi-provider: multiple classifiers per camera
23
+ // SensorType.Object, // Object detection
24
+ // SensorType.Battery, // Battery level
25
+ // SensorType.Doorbell, // Doorbell trigger
26
+ // SensorType.Contact, // Contact sensor
27
+ // SensorType.Siren, // Siren control
28
+ // SensorType.Switch, // Switch control
29
+ // SensorType.SecuritySystem, // Security system
30
+ ],
31
+
32
+ // Sensors this plugin consumes from other plugins (empty if standalone)
33
+ consumes: [],
34
+ };
35
+
36
+ export default contract;
@@ -42,7 +42,6 @@ export default [
42
42
  '@typescript-eslint/consistent-type-imports': 'error',
43
43
  '@typescript-eslint/await-thenable': 'error',
44
44
  '@typescript-eslint/no-explicit-any': 'off',
45
- '@typescript-eslint/no-unused-vars': ['error'],
46
45
  '@typescript-eslint/no-unsafe-assignment': 'off',
47
46
  '@typescript-eslint/no-unsafe-member-access': 'off',
48
47
  '@typescript-eslint/no-unsafe-argument': 'off',
@@ -90,14 +89,25 @@ export default [
90
89
  'eol-last': ['error', 'always'],
91
90
  'space-before-function-paren': ['error', { named: 'never' }],
92
91
 
93
- 'no-unused-vars': 'off',
92
+ '@typescript-eslint/no-unused-vars': [
93
+ 'error',
94
+ {
95
+ args: 'all',
96
+ argsIgnorePattern: '^_',
97
+ caughtErrors: 'all',
98
+ caughtErrorsIgnorePattern: '^_',
99
+ destructuredArrayIgnorePattern: '^_',
100
+ varsIgnorePattern: '^_',
101
+ ignoreRestSiblings: true,
102
+ },
103
+ ],
94
104
  'no-case-declarations': 'off',
95
105
  'no-async-promise-executor': 'off',
96
106
  'no-control-regex': 'off',
97
107
  },
98
108
  },
99
109
  {
100
- files: ['**/*.js', '**/*.cjs', '**/*.mjs', '*.d.ts', '*.config.ts'],
110
+ files: ['**/*.js', '**/*.cjs', '**/*.mjs', '*.d.ts', '*.config.ts', 'contract.ts'],
101
111
  ...tsLint.configs.disableTypeChecked,
102
112
  },
103
113
  ];
@@ -1,4 +1,4 @@
1
1
  {
2
- "@typescript-eslint/parser": "^8.31.1",
3
- "typescript-eslint": "^8.31.1"
2
+ "@typescript-eslint/parser": "^8.48.1",
3
+ "typescript-eslint": "^8.48.1"
4
4
  }
@@ -1,6 +1,6 @@
1
1
  {
2
- "@camera.ui/types": "^0.0.87",
3
- "@types/node": "^22.15.3",
4
- "rimraf": "^6.0.1",
5
- "typescript": "^5.8.3"
2
+ "@camera.ui/sdk": "^0.0.3",
3
+ "@types/node": "^24.10.1",
4
+ "rimraf": "^6.1.2",
5
+ "typescript": "5.9.3"
6
6
  }
@@ -1,46 +1,115 @@
1
- import type { CameraDevice, CuiPlugin, CuiPluginCapability, LoggerService, PluginAPI } from '@camera.ui/types';
1
+ import { API_EVENT, BasePlugin } from '@camera.ui/sdk';
2
2
 
3
- export default class SamplePlugin implements CuiPlugin {
4
- public logger: LoggerService;
5
- public api: PluginAPI;
3
+ import { ExampleLightControl, ExampleMotionSensor } from './sensor.js';
6
4
 
5
+ import type { CameraDevice, DeviceStorage, LightControl, LoggerService, MotionSensor, PluginAPI } from '@camera.ui/sdk';
6
+
7
+ /**
8
+ * Sample Plugin
9
+ *
10
+ * Demonstrates the camera.ui plugin architecture with sensors:
11
+ * - MotionSensor: External motion events (webhooks, ONVIF, etc.)
12
+ * - LightControl: Controllable light with on/brightness
13
+ *
14
+ * The contract (provides/consumes) is defined in contract.ts.
15
+ */
16
+ export default class SamplePlugin extends BasePlugin {
17
+ // Maps to track cameras and sensors
7
18
  private cameras = new Map<string, CameraDevice>();
19
+ private motionSensors = new Map<string, MotionSensor>();
20
+ private lightControls = new Map<string, LightControl>();
21
+
22
+ constructor(logger: LoggerService, api: PluginAPI, storage: DeviceStorage<any>) {
23
+ super(logger, api, storage);
24
+
25
+ // Register lifecycle event handlers
26
+ this.api.on(API_EVENT.FINISH_LAUNCHING, this.onFinishLaunching.bind(this));
27
+ this.api.on(API_EVENT.SHUTDOWN, this.onShutdown.bind(this));
28
+ }
8
29
 
9
- constructor(logger: LoggerService, api: PluginAPI) {
10
- this.logger = logger;
11
- this.api = api;
30
+ /**
31
+ * Configure cameras at startup.
32
+ * Called for cameras already assigned to this plugin.
33
+ */
34
+ public async configureCameras(cameras: CameraDevice[]): Promise<void> {
35
+ for (const camera of cameras) {
36
+ await this.setupCamera(camera);
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Called when a camera is selected for this plugin at runtime.
42
+ */
43
+ public async onCameraAdded(camera: CameraDevice): Promise<void> {
44
+ this.logger.log('Camera selected:', camera.name);
45
+ await this.setupCamera(camera);
46
+ }
12
47
 
13
- this.api.on('finishLaunching', this.start.bind(this));
14
- this.api.on('shutdown', this.stop.bind(this));
48
+ /**
49
+ * Called when a camera is deselected from this plugin.
50
+ */
51
+ public async onCameraReleased(cameraId: string): Promise<void> {
52
+ const camera = this.cameras.get(cameraId);
53
+ if (!camera) return;
15
54
 
16
- this.api.deviceManager.on('cameraSelected', (cameraDevice: CameraDevice) => {
17
- this.logger.log('Camera selected:', cameraDevice.name);
55
+ this.logger.log('Camera deselected:', camera.name);
18
56
 
19
- if (!this.cameras.has(cameraDevice.id)) {
20
- this.cameras.set(cameraDevice.id, cameraDevice);
21
- }
22
- });
57
+ // Remove sensors
58
+ const motion = this.motionSensors.get(cameraId);
59
+ if (motion) {
60
+ await camera.removeSensor(motion.id);
61
+ this.motionSensors.delete(cameraId);
62
+ }
23
63
 
24
- this.api.deviceManager.on('cameraDeselected', (cameraId: string) => {
25
- const cameraDevice = this.cameras.get(cameraId);
26
- if (cameraDevice) {
27
- this.logger.log('Camera deselected:', cameraDevice.name);
28
- this.cameras.delete(cameraId);
29
- }
30
- });
64
+ const light = this.lightControls.get(cameraId);
65
+ if (light) {
66
+ await camera.removeSensor(light.id);
67
+ this.lightControls.delete(cameraId);
68
+ }
69
+
70
+ this.cameras.delete(cameraId);
31
71
  }
32
72
 
33
- public async configureCameras(): Promise<void> {}
73
+ /**
74
+ * Set up sensors for a camera.
75
+ */
76
+ private async setupCamera(camera: CameraDevice): Promise<void> {
77
+ if (this.cameras.has(camera.id)) return;
78
+
79
+ this.cameras.set(camera.id, camera);
80
+
81
+ // Create motion sensor
82
+ const motion = new ExampleMotionSensor(`Motion - ${camera.name}`);
83
+ this.motionSensors.set(camera.id, motion);
84
+ await camera.addSensor(motion);
34
85
 
35
- public async getCapabilities(): Promise<CuiPluginCapability[]> {
36
- return [];
86
+ // Create light control
87
+ const light = new ExampleLightControl(camera, `Light - ${camera.name}`);
88
+ this.lightControls.set(camera.id, light);
89
+ await camera.addSensor(light);
90
+
91
+ this.logger.log(`Sensors registered for ${camera.name}`);
92
+
93
+ // Example: Trigger motion after 5 seconds (for testing)
94
+ // setTimeout(() => motion.trigger(), 5000);
37
95
  }
38
96
 
39
- private start(): void {
97
+ /**
98
+ * Called when the plugin has finished launching.
99
+ */
100
+ private onFinishLaunching(): void {
40
101
  this.logger.log('Plugin started');
41
102
  }
42
103
 
43
- private stop(): void {
104
+ /**
105
+ * Called when camera.ui is shutting down.
106
+ */
107
+ private async onShutdown(): Promise<void> {
44
108
  this.logger.log('Shutting down plugin');
109
+
110
+ // Cleanup all sensors
111
+ this.motionSensors.clear();
112
+ this.lightControls.clear();
113
+ this.cameras.clear();
45
114
  }
46
115
  }