iobroker.botvac 2.2.0 → 2.3.0

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.
@@ -0,0 +1,1134 @@
1
+ # ioBroker Adapter Development with GitHub Copilot
2
+
3
+ **Version:** 0.4.2
4
+ **Template Source:** https://github.com/DrozmotiX/ioBroker-Copilot-Instructions
5
+
6
+ This file contains instructions and best practices for GitHub Copilot when working on ioBroker adapter development.
7
+
8
+ ## Project Context
9
+
10
+ You are working on an ioBroker adapter. ioBroker is an integration platform for the Internet of Things, focused on building smart home and industrial IoT solutions. Adapters are plugins that connect ioBroker to external systems, devices, or services.
11
+
12
+ ### Adapter-Specific Context: Neato Botvac Integration
13
+ This is the **ioBroker.botvac** adapter that provides integration with Neato Botvac robotic vacuum cleaners. The adapter allows ioBroker to control and monitor Neato Botvac robots through their cloud API.
14
+
15
+ **Key Features:**
16
+ - **Robot Control**: Start/stop cleaning, pause, return to base
17
+ - **Cleaning Modes**: Normal and eco cleaning modes
18
+ - **Spot Cleaning**: Clean specific areas with configurable width/height
19
+ - **Status Monitoring**: Battery level, dock status, cleaning state, error conditions
20
+ - **Command Validation**: Uses can* states to determine valid operations
21
+
22
+ **External Dependencies:**
23
+ - **node-botvac**: Third-party library for Neato Botvac API communication (uses forked version with schedule write support)
24
+ - **Neato Cloud API**: Requires user credentials for cloud-based robot communication
25
+ - **Robot Hardware**: Communicates with physical Neato Botvac vacuum cleaners
26
+
27
+ **Configuration Requirements:**
28
+ - User credentials (email/password) for Neato cloud service
29
+ - Configurable polling interval (minimum 60 seconds to respect API limits)
30
+ - Robot discovery and state synchronization
31
+
32
+ ## Testing
33
+
34
+ ### Unit Testing
35
+ - Use Jest as the primary testing framework for ioBroker adapters
36
+ - Create tests for all adapter main functions and helper methods
37
+ - Test error handling scenarios and edge cases
38
+ - Mock external API calls and hardware dependencies
39
+ - For adapters connecting to APIs/devices not reachable by internet, provide example data files to allow testing of functionality without live connections
40
+ - Example test structure:
41
+ ```javascript
42
+ describe('AdapterName', () => {
43
+ let adapter;
44
+
45
+ beforeEach(() => {
46
+ // Setup test adapter instance
47
+ });
48
+
49
+ test('should initialize correctly', () => {
50
+ // Test adapter initialization
51
+ });
52
+ });
53
+ ```
54
+
55
+ ### Integration Testing
56
+
57
+ **IMPORTANT**: Use the official `@iobroker/testing` framework for all integration tests. This is the ONLY correct way to test ioBroker adapters.
58
+
59
+ **Official Documentation**: https://github.com/ioBroker/testing
60
+
61
+ #### Framework Structure
62
+ Integration tests MUST follow this exact pattern:
63
+
64
+ ```javascript
65
+ const path = require('path');
66
+ const { tests } = require('@iobroker/testing');
67
+
68
+ // Define test coordinates or configuration
69
+ const TEST_COORDINATES = '52.520008,13.404954'; // Berlin
70
+ const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
71
+
72
+ // Use tests.integration() with defineAdditionalTests
73
+ tests.integration(path.join(__dirname, '..'), {
74
+ defineAdditionalTests({ suite }) {
75
+ suite('Test adapter with specific configuration', (getHarness) => {
76
+ let harness;
77
+
78
+ before(() => {
79
+ harness = getHarness();
80
+ });
81
+
82
+ it('should configure and start adapter', function () {
83
+ return new Promise(async (resolve, reject) => {
84
+ try {
85
+ harness = getHarness();
86
+
87
+ // Get adapter object using promisified pattern
88
+ const obj = await new Promise((res, rej) => {
89
+ harness.objects.getObject('system.adapter.your-adapter.0', (err, o) => {
90
+ if (err) return rej(err);
91
+ res(o);
92
+ });
93
+ });
94
+
95
+ if (!obj) {
96
+ return reject(new Error('Adapter object not found'));
97
+ }
98
+
99
+ // Configure adapter properties
100
+ Object.assign(obj.native, {
101
+ position: TEST_COORDINATES,
102
+ createCurrently: true,
103
+ createHourly: true,
104
+ createDaily: true,
105
+ // Add other configuration as needed
106
+ });
107
+
108
+ // Set the updated configuration
109
+ harness.objects.setObject(obj._id, obj);
110
+
111
+ console.log('✅ Step 1: Configuration written, starting adapter...');
112
+
113
+ // Start adapter and wait
114
+ await harness.startAdapterAndWait();
115
+
116
+ console.log('✅ Step 2: Adapter started');
117
+
118
+ // Wait for adapter to process data
119
+ const waitMs = 15000;
120
+ await wait(waitMs);
121
+
122
+ console.log('🔍 Step 3: Checking states after adapter run...');
123
+
124
+ // Get all states created by adapter
125
+ const stateIds = await harness.dbConnection.getStateIDs('your-adapter.0.*');
126
+
127
+ console.log(`📊 Found ${stateIds.length} states`);
128
+
129
+ if (stateIds.length > 0) {
130
+ console.log('✅ Adapter successfully created states');
131
+
132
+ // Show sample of created states
133
+ const allStates = await new Promise((res, rej) => {
134
+ harness.states.getStates(stateIds, (err, states) => {
135
+ if (err) return rej(err);
136
+ res(states || []);
137
+ });
138
+ });
139
+
140
+ console.log('📋 Sample states created:');
141
+ stateIds.slice(0, 5).forEach((stateId, index) => {
142
+ const state = allStates[index];
143
+ console.log(` ${stateId}: ${state && state.val !== undefined ? state.val : 'undefined'}`);
144
+ });
145
+
146
+ await harness.stopAdapter();
147
+ resolve(true);
148
+ } else {
149
+ console.log('❌ No states were created by the adapter');
150
+ reject(new Error('Adapter did not create any states'));
151
+ }
152
+ } catch (error) {
153
+ reject(error);
154
+ }
155
+ });
156
+ }).timeout(40000);
157
+ });
158
+ }
159
+ });
160
+ ```
161
+
162
+ #### Testing Both Success AND Failure Scenarios
163
+
164
+ **IMPORTANT**: For every "it works" test, implement corresponding "it doesn't work and fails" tests. This ensures proper error handling and validates that your adapter fails gracefully when expected.
165
+
166
+ ```javascript
167
+ // Example: Testing successful configuration
168
+ it('should configure and start adapter with valid configuration', function () {
169
+ return new Promise(async (resolve, reject) => {
170
+ // ... successful configuration test as shown above
171
+ });
172
+ }).timeout(40000);
173
+
174
+ // Example: Testing failure scenarios
175
+ it('should NOT create daily states when daily is disabled', function () {
176
+ return new Promise(async (resolve, reject) => {
177
+ try {
178
+ harness = getHarness();
179
+
180
+ console.log('🔍 Step 1: Fetching adapter object...');
181
+ const obj = await new Promise((res, rej) => {
182
+ harness.objects.getObject('system.adapter.your-adapter.0', (err, o) => {
183
+ if (err) return rej(err);
184
+ res(o);
185
+ });
186
+ });
187
+
188
+ if (!obj) return reject(new Error('Adapter object not found'));
189
+ console.log('✅ Step 1.5: Adapter object loaded');
190
+
191
+ console.log('🔍 Step 2: Updating adapter config...');
192
+ Object.assign(obj.native, {
193
+ position: TEST_COORDINATES,
194
+ createCurrently: false,
195
+ createHourly: true,
196
+ createDaily: false, // Daily disabled for this test
197
+ });
198
+
199
+ await new Promise((res, rej) => {
200
+ harness.objects.setObject(obj._id, obj, (err) => {
201
+ if (err) return rej(err);
202
+ console.log('✅ Step 2.5: Adapter object updated');
203
+ res(undefined);
204
+ });
205
+ });
206
+
207
+ console.log('🔍 Step 3: Starting adapter...');
208
+ await harness.startAdapterAndWait();
209
+ console.log('✅ Step 4: Adapter started');
210
+
211
+ console.log('⏳ Step 5: Waiting 20 seconds for states...');
212
+ await new Promise((res) => setTimeout(res, 20000));
213
+
214
+ console.log('🔍 Step 6: Fetching state IDs...');
215
+ const stateIds = await harness.dbConnection.getStateIDs('your-adapter.0.*');
216
+
217
+ console.log(`📊 Step 7: Found ${stateIds.length} total states`);
218
+
219
+ const hourlyStates = stateIds.filter((key) => key.includes('hourly'));
220
+ if (hourlyStates.length > 0) {
221
+ console.log(`✅ Step 8: Correctly ${hourlyStates.length} hourly weather states created`);
222
+ } else {
223
+ console.log('❌ Step 8: No hourly states created (test failed)');
224
+ return reject(new Error('Expected hourly states but found none'));
225
+ }
226
+
227
+ // Check daily states should NOT be present
228
+ const dailyStates = stateIds.filter((key) => key.includes('daily'));
229
+ if (dailyStates.length === 0) {
230
+ console.log(`✅ Step 9: No daily states found as expected`);
231
+ } else {
232
+ console.log(`❌ Step 9: Daily states present (${dailyStates.length}) (test failed)`);
233
+ return reject(new Error('Expected no daily states but found some'));
234
+ }
235
+
236
+ await harness.stopAdapter();
237
+ console.log('🛑 Step 10: Adapter stopped');
238
+
239
+ resolve(true);
240
+ } catch (error) {
241
+ reject(error);
242
+ }
243
+ });
244
+ }).timeout(40000);
245
+
246
+ // Example: Testing missing required configuration
247
+ it('should handle missing required configuration properly', function () {
248
+ return new Promise(async (resolve, reject) => {
249
+ try {
250
+ harness = getHarness();
251
+
252
+ console.log('🔍 Step 1: Fetching adapter object...');
253
+ const obj = await new Promise((res, rej) => {
254
+ harness.objects.getObject('system.adapter.your-adapter.0', (err, o) => {
255
+ if (err) return rej(err);
256
+ res(o);
257
+ });
258
+ });
259
+
260
+ if (!obj) return reject(new Error('Adapter object not found'));
261
+
262
+ console.log('🔍 Step 2: Removing required configuration...');
263
+ // Remove required configuration to test failure handling
264
+ delete obj.native.position; // This should cause failure or graceful handling
265
+
266
+ await new Promise((res, rej) => {
267
+ harness.objects.setObject(obj._id, obj, (err) => {
268
+ if (err) return rej(err);
269
+ res(undefined);
270
+ });
271
+ });
272
+
273
+ console.log('🔍 Step 3: Starting adapter...');
274
+ await harness.startAdapterAndWait();
275
+
276
+ console.log('⏳ Step 4: Waiting for adapter to process...');
277
+ await new Promise((res) => setTimeout(res, 10000));
278
+
279
+ console.log('🔍 Step 5: Checking adapter behavior...');
280
+ const stateIds = await harness.dbConnection.getStateIDs('your-adapter.0.*');
281
+
282
+ // Check if adapter handled missing configuration gracefully
283
+ if (stateIds.length === 0) {
284
+ console.log('✅ Adapter properly handled missing configuration - no invalid states created');
285
+ resolve(true);
286
+ } else {
287
+ // If states were created, check if they're in error state
288
+ const connectionState = await new Promise((res, rej) => {
289
+ harness.states.getState('your-adapter.0.info.connection', (err, state) => {
290
+ if (err) return rej(err);
291
+ res(state);
292
+ });
293
+ });
294
+
295
+ if (!connectionState || connectionState.val === false) {
296
+ console.log('✅ Adapter properly failed with missing configuration');
297
+ resolve(true);
298
+ } else {
299
+ console.log('❌ Adapter should have failed or handled missing config gracefully');
300
+ reject(new Error('Adapter should have handled missing configuration'));
301
+ }
302
+ }
303
+
304
+ await harness.stopAdapter();
305
+ } catch (error) {
306
+ console.log('✅ Adapter correctly threw error with missing configuration:', error.message);
307
+ resolve(true);
308
+ }
309
+ });
310
+ }).timeout(40000);
311
+ ```
312
+
313
+ #### Advanced State Access Patterns
314
+
315
+ For testing adapters that create multiple states, use bulk state access methods to efficiently verify large numbers of states:
316
+
317
+ ```javascript
318
+ it('should create and verify multiple states', () => new Promise(async (resolve, reject) => {
319
+ // Configure and start adapter first...
320
+ harness.objects.getObject('system.adapter.tagesschau.0', async (err, obj) => {
321
+ if (err) {
322
+ console.error('Error getting adapter object:', err);
323
+ reject(err);
324
+ return;
325
+ }
326
+
327
+ // Configure adapter as needed
328
+ obj.native.someConfig = 'test-value';
329
+ harness.objects.setObject(obj._id, obj);
330
+
331
+ await harness.startAdapterAndWait();
332
+
333
+ // Wait for adapter to create states
334
+ setTimeout(() => {
335
+ // Access bulk states using pattern matching
336
+ harness.dbConnection.getStateIDs('tagesschau.0.*').then(stateIds => {
337
+ if (stateIds && stateIds.length > 0) {
338
+ harness.states.getStates(stateIds, (err, allStates) => {
339
+ if (err) {
340
+ console.error('❌ Error getting states:', err);
341
+ reject(err); // Properly fail the test instead of just resolving
342
+ return;
343
+ }
344
+
345
+ // Verify states were created and have expected values
346
+ const expectedStates = ['tagesschau.0.info.connection', 'tagesschau.0.articles.0.title'];
347
+ let foundStates = 0;
348
+
349
+ for (const stateId of expectedStates) {
350
+ if (allStates[stateId]) {
351
+ foundStates++;
352
+ console.log(`✅ Found expected state: ${stateId}`);
353
+ } else {
354
+ console.log(`❌ Missing expected state: ${stateId}`);
355
+ }
356
+ }
357
+
358
+ if (foundStates === expectedStates.length) {
359
+ console.log('✅ All expected states were created successfully');
360
+ resolve();
361
+ } else {
362
+ reject(new Error(`Only ${foundStates}/${expectedStates.length} expected states were found`));
363
+ }
364
+ });
365
+ } else {
366
+ reject(new Error('No states found matching pattern tagesschau.0.*'));
367
+ }
368
+ }).catch(reject);
369
+ }, 20000); // Allow more time for multiple state creation
370
+ });
371
+ })).timeout(45000);
372
+ ```
373
+
374
+ #### Key Integration Testing Rules
375
+
376
+ 1. **NEVER test API URLs directly** - Let the adapter handle API calls
377
+ 2. **ALWAYS use the harness** - `getHarness()` provides the testing environment
378
+ 3. **Configure via objects** - Use `harness.objects.setObject()` to set adapter configuration
379
+ 4. **Start properly** - Use `harness.startAdapterAndWait()` to start the adapter
380
+ 5. **Check states** - Use `harness.states.getState()` to verify results
381
+ 6. **Use timeouts** - Allow time for async operations with appropriate timeouts
382
+ 7. **Test real workflow** - Initialize → Configure → Start → Verify States
383
+
384
+ #### Workflow Dependencies
385
+ Integration tests should run ONLY after lint and adapter tests pass:
386
+
387
+ ```yaml
388
+ integration-tests:
389
+ needs: [check-and-lint, adapter-tests]
390
+ runs-on: ubuntu-latest
391
+ steps:
392
+ - name: Run integration tests
393
+ run: npx mocha test/integration-*.js --exit
394
+ ```
395
+
396
+ #### What NOT to Do
397
+ ❌ Direct API testing: `axios.get('https://api.example.com')`
398
+ ❌ Mock adapters: `new MockAdapter()`
399
+ ❌ Direct internet calls in tests
400
+ ❌ Bypassing the harness system
401
+
402
+ #### What TO Do
403
+ ✅ Use `@iobroker/testing` framework
404
+ ✅ Configure via `harness.objects.setObject()`
405
+ ✅ Start via `harness.startAdapterAndWait()`
406
+ ✅ Test complete adapter lifecycle
407
+ ✅ Verify states via `harness.states.getState()`
408
+ ✅ Allow proper timeouts for async operations
409
+
410
+ ### API Testing with Credentials
411
+ For adapters that connect to external APIs requiring authentication, implement comprehensive credential testing:
412
+
413
+ #### Password Encryption for Integration Tests
414
+ When creating integration tests that need encrypted passwords (like those marked as `encryptedNative` in io-package.json):
415
+
416
+ 1. **Read system secret**: Use `harness.objects.getObjectAsync("system.config")` to get `obj.native.secret`
417
+ 2. **Apply XOR encryption**: Implement the encryption algorithm:
418
+ ```javascript
419
+ async function encryptPassword(harness, password) {
420
+ const systemConfig = await harness.objects.getObjectAsync("system.config");
421
+ if (!systemConfig || !systemConfig.native || !systemConfig.native.secret) {
422
+ throw new Error("Could not retrieve system secret for password encryption");
423
+ }
424
+
425
+ const secret = systemConfig.native.secret;
426
+ let result = '';
427
+ for (let i = 0; i < password.length; ++i) {
428
+ result += String.fromCharCode(secret[i % secret.length].charCodeAt(0) ^ password.charCodeAt(i));
429
+ }
430
+ return result;
431
+ }
432
+ ```
433
+ 3. **Store encrypted password**: Set the encrypted result in adapter config, not the plain text
434
+ 4. **Result**: Adapter will properly decrypt and use credentials, enabling full API connectivity testing
435
+
436
+ #### Demo Credentials Testing Pattern
437
+ - Use provider demo credentials when available (e.g., `demo@api-provider.com` / `demo`)
438
+ - Create separate test file (e.g., `test/integration-demo.js`) for credential-based tests
439
+ - Add npm script: `"test:integration-demo": "mocha test/integration-demo --exit"`
440
+ - Implement clear success/failure criteria with recognizable log messages
441
+ - Expected success pattern: Look for specific adapter initialization messages
442
+ - Test should fail clearly with actionable error messages for debugging
443
+
444
+ #### Enhanced Test Failure Handling
445
+ ```javascript
446
+ it("Should connect to API with demo credentials", async () => {
447
+ // ... setup and encryption logic ...
448
+
449
+ const connectionState = await harness.states.getStateAsync("adapter.0.info.connection");
450
+
451
+ if (connectionState && connectionState.val === true) {
452
+ console.log("✅ SUCCESS: API connection established");
453
+ return true;
454
+ } else {
455
+ throw new Error("API Test Failed: Expected API connection to be established with demo credentials. " +
456
+ "Check logs above for specific API errors (DNS resolution, 401 Unauthorized, network issues, etc.)");
457
+ }
458
+ }).timeout(120000); // Extended timeout for API calls
459
+ ```
460
+
461
+ ### Testing for Botvac Adapter
462
+ - **Mock Neato API Responses**: Create fixture data for different robot states, errors, and API responses
463
+ - **Test Command Validation**: Verify can* states correctly determine valid operations
464
+ - **Test State Management**: Ensure robot status is properly mapped to ioBroker states
465
+ - **Test Error Scenarios**: Network failures, invalid credentials, unreachable robots
466
+ - **Test Polling Logic**: Verify appropriate API call intervals and backoff strategies
467
+
468
+ ## README Updates
469
+
470
+ ### Required Sections
471
+ When updating README.md files, ensure these sections are present and well-documented:
472
+
473
+ 1. **Installation** - Clear npm/ioBroker admin installation steps
474
+ 2. **Configuration** - Detailed configuration options with examples
475
+ 3. **Usage** - Practical examples and use cases
476
+ 4. **Changelog** - Version history and changes (use "## **WORK IN PROGRESS**" section for ongoing changes following AlCalzone release-script standard)
477
+ 5. **License** - License information (typically MIT for ioBroker adapters)
478
+ 6. **Support** - Links to issues, discussions, and community support
479
+
480
+ ### Documentation Standards
481
+ - Use clear, concise language
482
+ - Include code examples for configuration
483
+ - Add screenshots for admin interface when applicable
484
+ - Maintain multilingual support (at minimum English and German)
485
+ - When creating PRs, add entries to README under "## **WORK IN PROGRESS**" section following ioBroker release script standard
486
+ - Always reference related issues in commits and PR descriptions (e.g., "solves #xx" or "fixes #xx")
487
+
488
+ ### Mandatory README Updates for PRs
489
+ For **every PR or new feature**, always add a user-friendly entry to README.md:
490
+
491
+ - Add entries under `## **WORK IN PROGRESS**` section before committing
492
+ - Use format: `* (author) **TYPE**: Description of user-visible change`
493
+ - Types: **NEW** (features), **FIXED** (bugs), **ENHANCED** (improvements), **TESTING** (test additions), **CI/CD** (automation)
494
+ - Focus on user impact, not technical implementation details
495
+ - Example: `* (DutchmanNL) **FIXED**: Adapter now properly validates login credentials instead of always showing "credentials missing"`
496
+
497
+ ### Documentation Workflow Standards
498
+ - **Mandatory README updates**: Establish requirement to update README.md for every PR/feature
499
+ - **Standardized documentation**: Create consistent format and categories for changelog entries
500
+ - **Enhanced development workflow**: Integrate documentation requirements into standard development process
501
+
502
+ ### Changelog Management with AlCalzone Release-Script
503
+ Follow the [AlCalzone release-script](https://github.com/AlCalzone/release-script) standard for changelog management:
504
+
505
+ #### Format Requirements
506
+ - Always use `## **WORK IN PROGRESS**` as the placeholder for new changes
507
+ - Add all PR/commit changes under this section until ready for release
508
+ - Never modify version numbers manually - only when merging to main branch
509
+ - Maintain this format in README.md or CHANGELOG.md:
510
+
511
+ ```markdown
512
+ # Changelog
513
+
514
+ <!--
515
+ Placeholder for the next version (at the beginning of the line):
516
+ ## **WORK IN PROGRESS**
517
+ -->
518
+
519
+ ## **WORK IN PROGRESS**
520
+
521
+ - Did some changes
522
+ - Did some more changes
523
+
524
+ ## v0.1.0 (2023-01-01)
525
+ Initial release
526
+ ```
527
+
528
+ #### Workflow Process
529
+ - **During Development**: All changes go under `## **WORK IN PROGRESS**`
530
+ - **For Every PR**: Add user-facing changes to the WORK IN PROGRESS section
531
+ - **Before Merge**: Version number and date are only added when merging to main
532
+ - **Release Process**: The release-script automatically converts the placeholder to the actual version
533
+
534
+ #### Change Entry Format
535
+ Use this consistent format for changelog entries:
536
+ - `- (author) **TYPE**: User-friendly description of the change`
537
+ - Types: **NEW** (features), **FIXED** (bugs), **ENHANCED** (improvements)
538
+ - Focus on user impact, not technical implementation details
539
+ - Reference related issues: "fixes #XX" or "solves #XX"
540
+
541
+ #### Example Entry
542
+ ```markdown
543
+ ## **WORK IN PROGRESS**
544
+
545
+ - (DutchmanNL) **FIXED**: Adapter now properly validates login credentials instead of always showing "credentials missing" (fixes #25)
546
+ - (DutchmanNL) **NEW**: Added support for device discovery to simplify initial setup
547
+ ```
548
+
549
+ ## Dependency Updates
550
+
551
+ ### Package Management
552
+ - Always use `npm` for dependency management in ioBroker adapters
553
+ - When working on new features in a repository with an existing package-lock.json file, use `npm ci` to install dependencies. Use `npm install` only when adding or updating dependencies.
554
+ - Keep dependencies minimal and focused
555
+ - Only update dependencies to latest stable versions when necessary or in separate Pull Requests. Avoid updating dependencies when adding features that don't require these updates.
556
+ - When you modify `package.json`:
557
+ 1. Run `npm install` to update and sync `package-lock.json`.
558
+ 2. If `package-lock.json` was updated, commit both `package.json` and `package-lock.json`.
559
+
560
+ ### Dependency Best Practices
561
+ - Prefer built-in Node.js modules when possible
562
+ - Use `@iobroker/adapter-core` for adapter base functionality
563
+ - Avoid deprecated packages
564
+ - Document any specific version requirements
565
+
566
+ ## JSON-Config Admin Instructions
567
+
568
+ ### Configuration Schema
569
+ When creating admin configuration interfaces:
570
+
571
+ - Use JSON-Config format for modern ioBroker admin interfaces
572
+ - Provide clear labels and help text for all configuration options
573
+ - Include input validation and error messages
574
+ - Group related settings logically
575
+ - Example structure:
576
+ ```json
577
+ {
578
+ "type": "panel",
579
+ "items": {
580
+ "host": {
581
+ "type": "text",
582
+ "label": "Host address",
583
+ "help": "IP address or hostname of the device"
584
+ }
585
+ }
586
+ }
587
+ ```
588
+
589
+ ### Admin Interface Guidelines
590
+ - Use consistent naming conventions
591
+ - Provide sensible default values
592
+ - Include validation for required fields
593
+ - Add tooltips for complex configuration options
594
+ - Ensure translations are available for all supported languages (minimum English and German)
595
+ - Write end-user friendly labels and descriptions, avoiding technical jargon where possible
596
+
597
+ ## Best Practices for Dependencies
598
+
599
+ ### HTTP Client Libraries
600
+ - **Preferred:** Use native `fetch` API (Node.js 20+ required for adapters; built-in since Node.js 18)
601
+ - **Avoid:** `axios` unless specific features are required (reduces bundle size)
602
+
603
+ ### Example with fetch:
604
+ ```javascript
605
+ try {
606
+ const response = await fetch('https://api.example.com/data');
607
+ if (!response.ok) {
608
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
609
+ }
610
+ const data = await response.json();
611
+ } catch (error) {
612
+ this.log.error(`API request failed: ${error.message}`);
613
+ }
614
+ ```
615
+
616
+ ### Other Dependency Recommendations
617
+ - **Logging:** Use adapter built-in logging (`this.log.*`)
618
+ - **Scheduling:** Use adapter built-in timers and intervals
619
+ - **File operations:** Use Node.js `fs/promises` for async file operations
620
+ - **Configuration:** Use adapter config system rather than external config libraries
621
+
622
+ ## Coding Standards
623
+
624
+ ### General JavaScript/TypeScript Standards
625
+ - Use meaningful variable names (no single letters except for loops)
626
+ - Add JSDoc comments for all public functions
627
+ - Use async/await instead of Promise chains when possible
628
+ - Handle errors gracefully with proper logging
629
+ - Use strict mode (`'use strict';`)
630
+ - Follow ESLint rules configured for the project
631
+ - Use consistent indentation (4 spaces for ioBroker projects)
632
+
633
+ ### ioBroker-Specific Standards
634
+ - Always use adapter logging methods: `this.log.error()`, `this.log.warn()`, `this.log.info()`, `this.log.debug()`
635
+ - Use appropriate log levels - avoid excessive logging at info level
636
+ - Always check if adapter is connected before API calls: `if (!this.connected) return;`
637
+ - Clean up resources in `unload()` method (timers, intervals, connections)
638
+ - Use `this.setState()` for updating object values
639
+ - Use `this.setObjectNotExists()` for creating objects
640
+ - Always provide proper object definitions with type, role, read/write flags
641
+ - Use meaningful state IDs following ioBroker naming conventions
642
+
643
+ ### Botvac Adapter Code Patterns
644
+ - **State Management**: Use hierarchical object structure (commands.*, status.*, info.*)
645
+ - **Command States**: Set writable states to false after processing commands
646
+ - **API Error Handling**: Implement retry logic for transient failures, proper error state reporting
647
+ - **Polling Implementation**: Respect minimum 60-second intervals, implement exponential backoff on errors
648
+ - **Credential Validation**: Validate user credentials on startup, provide clear error messages
649
+
650
+ ## State Management
651
+
652
+ ### ioBroker Object Structure
653
+ - Create objects with proper metadata: name, type, role, min/max values
654
+ - Use appropriate data types: `boolean`, `number`, `string`
655
+ - Set `read`, `write` flags correctly
656
+ - Provide meaningful descriptions in multiple languages when possible
657
+ - Use proper roles: `switch.power`, `level.battery`, `text.status`, etc.
658
+
659
+ ### State Synchronization
660
+ - Only update states when values actually change
661
+ - Use proper data types when setting states
662
+ - Always acknowledge state changes for command states
663
+ - Implement proper cleanup for unused objects
664
+
665
+ ### Botvac State Structure
666
+ ```javascript
667
+ // Command states (writable)
668
+ commands: {
669
+ clean: boolean, // Start cleaning
670
+ stop: boolean, // Stop cleaning
671
+ pause: boolean, // Pause cleaning
672
+ goToBase: boolean, // Return to dock
673
+ cleanSpot: boolean, // Spot cleaning
674
+ spotWidth: number, // Spot width in cm
675
+ spotHeight: number, // Spot height in cm
676
+ eco: boolean // Eco mode toggle
677
+ }
678
+
679
+ // Status states (read-only)
680
+ status: {
681
+ canStart: boolean, // Can start cleaning
682
+ canStop: boolean, // Can stop cleaning
683
+ canPause: boolean, // Can pause cleaning
684
+ canResume: boolean, // Can resume cleaning
685
+ canGoToBase: boolean, // Can return to base
686
+ dockHasBeenSeen: boolean, // Dock is known
687
+ charge: number, // Battery percentage
688
+ state: string // Current robot state
689
+ }
690
+ ```
691
+
692
+ ## Error Handling
693
+
694
+ ### Adapter Error Patterns
695
+ - Always catch and log errors appropriately
696
+ - Use adapter log levels (error, warn, info, debug)
697
+ - Provide meaningful, user-friendly error messages that help users understand what went wrong
698
+ - Handle network failures gracefully
699
+ - Implement retry mechanisms where appropriate
700
+ - Always clean up timers, intervals, and other resources in the `unload()` method
701
+
702
+ ### Example Error Handling:
703
+ ```javascript
704
+ try {
705
+ await this.connectToDevice();
706
+ } catch (error) {
707
+ this.log.error(`Failed to connect to device: ${error.message}`);
708
+ this.setState('info.connection', false, true);
709
+ // Implement retry logic if needed
710
+ }
711
+ ```
712
+
713
+ ### Timer and Resource Cleanup:
714
+ ```javascript
715
+ // In your adapter class
716
+ private connectionTimer?: NodeJS.Timeout;
717
+
718
+ async onReady() {
719
+ this.connectionTimer = setInterval(() => {
720
+ this.checkConnection();
721
+ }, 30000);
722
+ }
723
+
724
+ onUnload(callback) {
725
+ try {
726
+ // Clean up timers and intervals
727
+ if (this.connectionTimer) {
728
+ clearInterval(this.connectionTimer);
729
+ this.connectionTimer = undefined;
730
+ }
731
+ // Close connections, clean up resources
732
+ callback();
733
+ } catch (e) {
734
+ callback();
735
+ }
736
+ }
737
+ ```
738
+
739
+ ### Botvac-Specific Error Handling
740
+ - **Authentication Errors**: Clear error messages for invalid credentials, guide users to reconfigure
741
+ - **Robot Unreachable**: Mark robot as offline, implement recovery when robot comes back online
742
+ - **API Rate Limits**: Respect API limits, increase polling intervals when rate limited
743
+ - **Invalid Commands**: Check can* states before sending commands, provide user feedback for invalid operations
744
+
745
+ ## Configuration Management
746
+
747
+ ### ioBroker Configuration
748
+ - Define all configuration options in `io-package.json`
749
+ - Provide proper input validation
750
+ - Use appropriate input types: text, password, number, checkbox, select
751
+ - Provide helpful descriptions and default values
752
+ - Implement configuration change handling in adapter
753
+
754
+ ### Credential Management
755
+ - Store sensitive data securely using adapter configuration encryption
756
+ - Never log credentials or sensitive information
757
+ - Validate credentials on adapter startup
758
+ - Provide clear feedback when authentication fails
759
+
760
+ ### Botvac Configuration Requirements
761
+ ```javascript
762
+ // Required configuration fields
763
+ config: {
764
+ email: string, // Neato account email (required)
765
+ password: string, // Neato account password (required, encrypted)
766
+ pollInterval: number // Polling interval in seconds (min: 60, default: 120)
767
+ }
768
+ ```
769
+
770
+ ## API Integration Patterns
771
+
772
+ ### HTTP Client Usage
773
+ - Use adapter's built-in request capabilities or well-established libraries
774
+ - Implement proper timeout handling
775
+ - Handle different HTTP status codes appropriately
776
+ - Add proper request/response logging for debugging
777
+ - Implement authentication handling
778
+
779
+ ### External Library Integration
780
+ - Keep external dependencies minimal and well-maintained
781
+ - Handle library-specific errors appropriately
782
+ - Document any special requirements or limitations
783
+ - Use proper versioning to avoid breaking changes
784
+
785
+ ### Neato Botvac API Integration
786
+ - **Library Usage**: Uses `node-botvac` library for API communication
787
+ - **Authentication**: Supports username/password authentication via Neato cloud
788
+ - **Rate Limiting**: Implement minimum 60-second polling intervals
789
+ - **Robot Discovery**: Handle multiple robots per account
790
+ - **Command Queue**: Ensure commands are processed in order, handle concurrent command conflicts
791
+
792
+ ## Async Operations
793
+
794
+ ### Promise Handling
795
+ - Use async/await syntax for better readability
796
+ - Always handle promise rejections
797
+ - Don't mix callback and promise patterns
798
+ - Use Promise.all() for parallel operations when safe
799
+ - Implement proper timeout handling for long-running operations
800
+
801
+ ### Timer and Interval Management
802
+ - Store timer/interval references for cleanup
803
+ - Clear all timers in unload() method
804
+ - Use adapter.setTimeout() and adapter.setInterval() when available
805
+ - Handle timer errors gracefully
806
+
807
+ ### Botvac Async Patterns
808
+ - **Polling Implementation**: Use setInterval for regular status updates, clear in unload()
809
+ - **Command Processing**: Use async/await for command execution, handle timeouts
810
+ - **API Calls**: Implement retry logic with exponential backoff for failed API calls
811
+
812
+ ## Performance Considerations
813
+
814
+ ### Memory Management
815
+ - Avoid memory leaks by properly cleaning up event listeners
816
+ - Don't store large amounts of data in memory unnecessarily
817
+ - Use streaming for large data operations
818
+ - Monitor adapter memory usage
819
+
820
+ ### CPU Usage
821
+ - Avoid blocking the event loop with heavy computations
822
+ - Use appropriate polling intervals to balance responsiveness and resource usage
823
+ - Implement efficient data parsing and processing
824
+
825
+ ### Network Usage
826
+ - Implement intelligent polling strategies
827
+ - Cache data when appropriate to reduce API calls
828
+ - Use compression when supported by external APIs
829
+ - Handle network errors gracefully
830
+
831
+ ### Botvac Performance Optimization
832
+ - **Polling Strategy**: Only poll when necessary, increase intervals during extended idle periods
833
+ - **State Caching**: Cache robot state to avoid redundant API calls
834
+ - **Command Batching**: Group related commands when possible to reduce API usage
835
+
836
+ ## Security Best Practices
837
+
838
+ ### Credential Security
839
+ - Never log or expose user credentials
840
+ - Use secure storage for sensitive configuration data
841
+ - Implement proper input validation and sanitization
842
+ - Handle authentication tokens securely
843
+
844
+ ### Input Validation
845
+ - Validate all user inputs and configuration values
846
+ - Sanitize data before processing or storage
847
+ - Implement proper bounds checking for numeric values
848
+ - Handle edge cases and malformed data gracefully
849
+
850
+ ### Network Security
851
+ - Use HTTPS for all external communications
852
+ - Validate SSL certificates
853
+ - Implement proper timeout and retry logic
854
+ - Avoid exposing internal network details in logs
855
+
856
+ ## Code Style and Standards
857
+
858
+ - Follow JavaScript/TypeScript best practices
859
+ - Use async/await for asynchronous operations
860
+ - Implement proper resource cleanup in `unload()` method
861
+ - Use semantic versioning for adapter releases
862
+ - Include proper JSDoc comments for public methods
863
+
864
+ ## CI/CD and Testing Integration
865
+
866
+ ### GitHub Actions for API Testing
867
+ For adapters with external API dependencies, implement separate CI/CD jobs:
868
+
869
+ ```yaml
870
+ # Tests API connectivity with demo credentials (runs separately)
871
+ demo-api-tests:
872
+ if: contains(github.event.head_commit.message, '[skip ci]') == false
873
+
874
+ runs-on: ubuntu-22.04
875
+
876
+ steps:
877
+ - name: Checkout code
878
+ uses: actions/checkout@v4
879
+
880
+ - name: Use Node.js 20.x
881
+ uses: actions/setup-node@v4
882
+ with:
883
+ node-version: 20.x
884
+ cache: 'npm'
885
+
886
+ - name: Install dependencies
887
+ run: npm ci
888
+
889
+ - name: Run demo API tests
890
+ run: npm run test:integration-demo
891
+ ```
892
+
893
+ ### CI/CD Best Practices
894
+ - Run credential tests separately from main test suite
895
+ - Use ubuntu-22.04 for consistency
896
+ - Don't make credential tests required for deployment
897
+ - Provide clear failure messages for API connectivity issues
898
+ - Use appropriate timeouts for external API calls (120+ seconds)
899
+
900
+ ### Package.json Script Integration
901
+ Add dedicated script for credential testing:
902
+ ```json
903
+ {
904
+ "scripts": {
905
+ "test:integration-demo": "mocha test/integration-demo --exit"
906
+ }
907
+ }
908
+ ```
909
+
910
+ ### Practical Example: Complete API Testing Implementation
911
+ Here's a complete example based on lessons learned from the Discovergy adapter:
912
+
913
+ #### test/integration-demo.js
914
+ ```javascript
915
+ const path = require("path");
916
+ const { tests } = require("@iobroker/testing");
917
+
918
+ // Helper function to encrypt password using ioBroker's encryption method
919
+ async function encryptPassword(harness, password) {
920
+ const systemConfig = await harness.objects.getObjectAsync("system.config");
921
+
922
+ if (!systemConfig || !systemConfig.native || !systemConfig.native.secret) {
923
+ throw new Error("Could not retrieve system secret for password encryption");
924
+ }
925
+
926
+ const secret = systemConfig.native.secret;
927
+ let result = '';
928
+ for (let i = 0; i < password.length; ++i) {
929
+ result += String.fromCharCode(secret[i % secret.length].charCodeAt(0) ^ password.charCodeAt(i));
930
+ }
931
+
932
+ return result;
933
+ }
934
+
935
+ // Run integration tests with demo credentials
936
+ tests.integration(path.join(__dirname, ".."), {
937
+ defineAdditionalTests({ suite }) {
938
+ suite("API Testing with Demo Credentials", (getHarness) => {
939
+ let harness;
940
+
941
+ before(() => {
942
+ harness = getHarness();
943
+ });
944
+
945
+ it("Should connect to API and initialize with demo credentials", async () => {
946
+ console.log("Setting up demo credentials...");
947
+
948
+ if (harness.isAdapterRunning()) {
949
+ await harness.stopAdapter();
950
+ }
951
+
952
+ const encryptedPassword = await encryptPassword(harness, "demo_password");
953
+
954
+ await harness.changeAdapterConfig("your-adapter", {
955
+ native: {
956
+ username: "demo@provider.com",
957
+ password: encryptedPassword,
958
+ // other config options
959
+ }
960
+ });
961
+
962
+ console.log("Starting adapter with demo credentials...");
963
+ await harness.startAdapter();
964
+
965
+ // Wait for API calls and initialization
966
+ await new Promise(resolve => setTimeout(resolve, 60000));
967
+
968
+ const connectionState = await harness.states.getStateAsync("your-adapter.0.info.connection");
969
+
970
+ if (connectionState && connectionState.val === true) {
971
+ console.log("✅ SUCCESS: API connection established");
972
+ return true;
973
+ } else {
974
+ throw new Error("API Test Failed: Expected API connection to be established with demo credentials. " +
975
+ "Check logs above for specific API errors (DNS resolution, 401 Unauthorized, network issues, etc.)");
976
+ }
977
+ }).timeout(120000);
978
+ });
979
+ }
980
+ });
981
+ ```
982
+
983
+ ## Debugging and Troubleshooting
984
+
985
+ ### Logging Best Practices
986
+ - Use appropriate log levels (error, warn, info, debug)
987
+ - Include relevant context in log messages
988
+ - Don't log sensitive information
989
+ - Use structured logging when possible
990
+ - Provide actionable information in error messages
991
+
992
+ ### Debug Information
993
+ - Include relevant state information in debug logs
994
+ - Log API requests/responses at debug level
995
+ - Provide clear error messages with resolution steps
996
+ - Include version information in logs when relevant
997
+
998
+ ### Botvac Debugging Strategies
999
+ - **API Communication**: Log request/response details at debug level for troubleshooting
1000
+ - **Robot State Changes**: Log significant state transitions for monitoring
1001
+ - **Command Execution**: Track command success/failure with detailed context
1002
+ - **Connection Issues**: Provide clear diagnostics for network and authentication problems
1003
+
1004
+ ## Development Workflow
1005
+
1006
+ ### Version Control
1007
+ - Use meaningful commit messages following conventional commit format
1008
+ - Create feature branches for new functionality
1009
+ - Tag releases appropriately
1010
+ - Maintain CHANGELOG.md with release notes
1011
+
1012
+ ### Code Quality
1013
+ - Run linting tools before committing
1014
+ - Use automated testing in CI/CD pipeline
1015
+ - Perform code reviews for all changes
1016
+ - Monitor code coverage and maintain reasonable thresholds
1017
+
1018
+ ### Release Management
1019
+ - Follow semantic versioning (SemVer)
1020
+ - Update io-package.json version and changelog
1021
+ - Test thoroughly before releases
1022
+ - Coordinate with ioBroker repository maintainers for updates
1023
+
1024
+ ## Common Patterns and Examples
1025
+
1026
+ ### Adapter Initialization
1027
+ ```javascript
1028
+ class BotVac extends utils.Adapter {
1029
+ constructor(options) {
1030
+ super({
1031
+ ...options,
1032
+ name: 'botvac',
1033
+ });
1034
+ this.on('ready', this.onReady.bind(this));
1035
+ this.on('stateChange', this.onStateChange.bind(this));
1036
+ this.on('unload', this.onUnload.bind(this));
1037
+ }
1038
+
1039
+ async onReady() {
1040
+ // Validate configuration
1041
+ if (!this.config.email || !this.config.password) {
1042
+ this.log.error('Missing credentials in configuration');
1043
+ return;
1044
+ }
1045
+
1046
+ // Initialize API client
1047
+ await this.initializeClient();
1048
+
1049
+ // Start polling
1050
+ this.startPolling();
1051
+ }
1052
+ }
1053
+ ```
1054
+
1055
+ ### State Creation Pattern
1056
+ ```javascript
1057
+ async createStates() {
1058
+ // Command states (writable)
1059
+ await this.setObjectNotExists('commands.clean', {
1060
+ type: 'state',
1061
+ common: {
1062
+ name: 'Start cleaning',
1063
+ type: 'boolean',
1064
+ role: 'button.start',
1065
+ read: true,
1066
+ write: true,
1067
+ def: false
1068
+ },
1069
+ native: {}
1070
+ });
1071
+
1072
+ // Status states (read-only)
1073
+ await this.setObjectNotExists('status.canStart', {
1074
+ type: 'state',
1075
+ common: {
1076
+ name: 'Can start cleaning',
1077
+ type: 'boolean',
1078
+ role: 'indicator',
1079
+ read: true,
1080
+ write: false,
1081
+ def: false
1082
+ },
1083
+ native: {}
1084
+ });
1085
+ }
1086
+ ```
1087
+
1088
+ ### Command Processing Pattern
1089
+ ```javascript
1090
+ async onStateChange(id, state) {
1091
+ if (!state || state.ack) return;
1092
+
1093
+ const command = id.split('.').pop();
1094
+
1095
+ try {
1096
+ switch (command) {
1097
+ case 'clean':
1098
+ if (state.val && await this.canStart()) {
1099
+ await this.startCleaning();
1100
+ await this.setState(id, false, true);
1101
+ }
1102
+ break;
1103
+
1104
+ case 'stop':
1105
+ if (state.val && await this.canStop()) {
1106
+ await this.stopCleaning();
1107
+ await this.setState(id, false, true);
1108
+ }
1109
+ break;
1110
+ }
1111
+ } catch (error) {
1112
+ this.log.error(`Command ${command} failed: ${error.message}`);
1113
+ await this.setState(id, false, true);
1114
+ }
1115
+ }
1116
+ ```
1117
+
1118
+ ## Troubleshooting Guide
1119
+
1120
+ ### Common Issues
1121
+ 1. **Adapter won't start**: Check configuration, credentials, and log files
1122
+ 2. **States not updating**: Verify polling is working and API connectivity
1123
+ 3. **Commands not working**: Check can* states and robot availability
1124
+ 4. **High CPU usage**: Review polling intervals and async operation handling
1125
+
1126
+ ### Botvac-Specific Issues
1127
+ 1. **Robot not found**: Check Neato account access and robot registration
1128
+ 2. **Commands ignored**: Verify robot is in a state that allows the command
1129
+ 3. **Frequent disconnections**: Check network stability and API rate limiting
1130
+ 4. **Authentication failures**: Verify credentials and account status
1131
+
1132
+ ---
1133
+
1134
+ This comprehensive guide should help GitHub Copilot understand the context and patterns specific to ioBroker adapter development, particularly for the Neato Botvac integration.