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.
- package/.github/copilot-instructions.md +1134 -0
- package/.github/dependabot.yml +11 -9
- package/.github/workflows/automerge-dependabot.yml +35 -0
- package/.github/workflows/check-copilot-template.yml +196 -0
- package/.github/workflows/initial-copilot-setup.yml +187 -0
- package/.github/workflows/test-and-release.yml +5 -4
- package/.vscode/settings.json +13 -4
- package/CHANGELOG_OLD.md +28 -0
- package/LICENSE +1 -1
- package/README.md +7 -29
- package/io-package.json +18 -5
- package/package.json +18 -38
- /package/.github/workflows/{dependabot-auto-merge.yml → dependabot-auto-merge.yml.OLD} +0 -0
|
@@ -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.
|