Skip to content

Commit 099ff25

Browse files
committed
fix(skill): Refine add sensor component v1 skill for clarity and consistency, fix def url links and sourcing nested drivers
1 parent 15bb074 commit 099ff25

1 file changed

Lines changed: 99 additions & 109 deletions

File tree

  • .agents/skills/add_sensor_component_v1

.agents/skills/add_sensor_component_v1/SKILL.md

Lines changed: 99 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,22 @@ description: >
1111

1212
# Add I2C Sensor Component to WipperSnapper v1
1313

14-
You are guiding the user through adding a new I2C sensor to Adafruit IO WipperSnapper. This
15-
involves changes in **two repositories**:
16-
14+
Changes span **two repositories**:
1715
1. **Adafruit_Wippersnapper_Arduino** — C++ firmware: new driver + registration
1816
2. **Wippersnapper_Components** — JSON definition + product image
1917

20-
The user will supply a sensor name (e.g. "TMP119"). Your job is to research the sensor, identify
21-
the right Adafruit Arduino library, find the closest existing driver as a template, and then walk
22-
through every file change needed.
18+
The user supplies a sensor name (e.g. "TMP119"). Research the sensor, find the Adafruit Arduino
19+
library, identify the closest existing driver, and walk through every file change.
2320

2421
### Naming convention
2522

26-
Two naming styles are used throughout — keep them consistent:
27-
28-
- **PascalCase** for C++ identifiers: class name `WipperSnapper_I2C_Driver_TMP119`, file name
29-
`WipperSnapper_I2C_Driver_TMP119.h`, member pointer `_tmp119`, include guard `_TMP119_H`
30-
- **lowercase** for the component folder name and the `strcmp` device name string: `tmp119`
23+
- **PascalCase** for C++: class `WipperSnapper_I2C_Driver_TMP119`, file
24+
`WipperSnapper_I2C_Driver_TMP119.h`, pointer `_tmp119`
25+
- **lowercase** for component folder and `strcmp` string: `tmp119`
3126

32-
Decide on the canonical name early (Step 0) and use it everywhere.
27+
Decide the canonical name in Step 0 and use it everywhere.
3328

34-
> **Proto files are off-limits.** Contributors never touch `.proto` files — only Adafruit staff
35-
> modify those. The existing `SensorType` enum already covers all common readings.
29+
> **Proto files are off-limits.** Only Adafruit staff modify `.proto` files.
3630
3731
## Reference
3832

@@ -47,6 +41,39 @@ Wippersnapper_Components repo setup, image requirements, and testing in Adafruit
4741

4842
This skill accepts a sensor name as its argument (e.g. `/add_sensor_component_v1 TMP119`).
4943

44+
## Environment Check
45+
46+
Before starting, determine connectivity level — this affects whether you can use `gh` commands
47+
or must fall back to plain `git`:
48+
49+
```bash
50+
curl -s -o /dev/null -w "%{http_code}" https://www.bbc.com # general web access
51+
curl -s -o /dev/null -w "%{http_code}" https://api.github.com # GitHub API (needed for gh)
52+
git ls-remote https://github.com/adafruit/Wippersnapper_Components HEAD # git clone access
53+
gh auth status # see if cli/token/login present
54+
55+
```
56+
57+
| Result | Capability |
58+
|--------|-----------|
59+
| All 3 succeed | Full access — use `gh` for forking, PRs, API queries |
60+
| BBC fails, GitHub API works | Restricted web but `gh` works — skip web fetches for product pages |
61+
| BBC + API fail, git works | Git-only — use `git clone`/`git push` instead of `gh`, create PRs manually via browser |
62+
| All fail | Offline — can only write code, user must handle git/PRs |
63+
64+
## CI Checks
65+
66+
PRs to both repos run CI. Key checks to pass before submitting:
67+
68+
**Adafruit_Wippersnapper_Arduino:**
69+
- **clang-format** — code formatting must match `.clang-format` config. Run `clang-format -i` on all changed files.
70+
- **Doxygen** — all public/protected methods need Doxygen-style `/*! @brief ... */` comment blocks. CI will fail if these are missing or malformed. Follow the existing driver style exactly.
71+
- **Build** — firmware must compile for all target boards in `platformio.ini`.
72+
73+
**Wippersnapper_Components:**
74+
- **JSON schema validation**`definition.json` must conform to `schema.json` in the repo root.
75+
- **Image validation** — dimensions, file size, and format are checked.
76+
5077
---
5178

5279
## Step 0 — Research the Sensor
@@ -55,17 +82,17 @@ Before writing any code, gather this information:
5582

5683
| What | Where to look |
5784
|------|---------------|
58-
| Sensor's Adafruit Arduino library | `gh search repos "Adafruit <SENSOR>" --owner adafruit` or check if it lives inside another library (e.g. TMP119 is in `Adafruit_TMP117`) |
85+
| Product page | Search the web for "adafruit <SENSOR>" to find the product page. The product page links to the learn guide and Arduino library. This is the fastest route to all other info. |
86+
| Adafruit Arduino library | The learn guide (linked from product page) shows which library to use. Or: `gh search repos "<SENSOR>" --owner adafruit`. If no dedicated library, check related chips (e.g. TMP119 lives inside `Adafruit_TMP117`). Try partial matches like `TMP11` if exact fails. |
5987
| Library API | Read the library header on GitHub — find `begin()` signature and sensor read methods (`getEvent`, `readTempC`, etc.) |
6088
| I2C addresses | Sensor datasheet or Adafruit product page. Check https://learn.adafruit.com/i2c-addresses/the-list |
61-
| What the sensor measures | Datasheet — temperature? humidity? pressure? Map each to a subcomponent type (see list below) |
89+
| What it measures | Datasheet — map each reading to a subcomponent type (see table below) |
6290
| Closest existing driver | Browse `src/components/i2c/drivers/` for a sensor in the same family or with identical reading types |
63-
| Adafruit product URL | `https://www.adafruit.com/product/<ID>` |
91+
| Documentation URL | Prefer: Adafruit learn guide (from product page) > manufacturer datasheet. Non-Adafruit products are accepted — use the manufacturer's product/datasheet URL. Note: third-party domain URLs may initially fail CI URL validation until a maintainer adds the domain to the allowlist. |
6492

6593
### Subcomponent type reference
6694

67-
These are the valid values for `subcomponents` in `definition.json` and map 1:1 to `getEvent*()`
68-
methods in the base driver class:
95+
Valid `subcomponents` values in `definition.json`, mapping 1:1 to base driver `getEvent*()` methods:
6996

7097
| Subcomponent | getEvent method | `sensors_event_t` field | SI unit |
7198
|---|---|---|---|
@@ -95,34 +122,14 @@ When using raw reads (not Unified Sensor `getEvent()`), assign to the correct fi
95122

96123
Temperature sensors almost always include both `ambient-temp` and `ambient-temp-fahrenheit`.
97124

98-
**Important:** Fahrenheit conversions (`getEventAmbientTempF`, `getEventObjectTempF`) are already
99-
implemented in the base class — they call the Celsius method and convert. Drivers only need to
100-
implement the Celsius version (`getEventAmbientTemp`, `getEventObjectTemp`). Never implement
101-
the Fahrenheit variant in your driver.
125+
**Fahrenheit:** `getEventAmbientTempF` and `getEventObjectTempF` are in the base class — they
126+
call the Celsius method and convert. Only implement the Celsius version. Never implement °F.
102127

103-
Because the base class Fahrenheit method calls the Celsius method, and the calling order is not
104-
guaranteed (°F may be called before °C), each `getEvent*()` method should go through a shared
105-
read-and-cache function with a "recently read" time guard. This way, whichever method is called
106-
first does the actual I2C read and caches the result; subsequent calls within the window return
107-
cached data without hitting the bus again.
108-
109-
See `WipperSnapper_I2C_Driver_SCD30.h` for the canonical pattern:
110-
```cpp
111-
bool HasBeenReadInLastSecond() {
112-
return _lastRead != 0 && millis() - _lastRead < 1000;
113-
}
114-
bool ReadSensorData() {
115-
if (HasBeenReadInLastSecond()) return true; // use cached values
116-
// ... do actual I2C read, cache results ...
117-
_lastRead = millis();
118-
return true;
119-
}
120-
bool getEventAmbientTemp(sensors_event_t *tempEvent) {
121-
if (!ReadSensorData()) return false;
122-
*tempEvent = _cachedTemp;
123-
return true;
124-
}
125-
```
128+
**Read-and-cache requirement:** The calling order of `getEvent*()` methods is not guaranteed (°F
129+
may be called before °C). Every `getEvent*()` must go through a shared `_readSensor()` with a
130+
millis-based time guard so only the first call per cycle does the I2C read; subsequent calls
131+
return cached data. See the driver template in Step 1 and `WipperSnapper_I2C_Driver_SCD30.h` and SGP30
132+
for the canonical patterns.
126133

127134
This is especially important for multi-reading sensors but also applies to temperature sensors
128135
where both °C and °F subcomponents are enabled.
@@ -136,7 +143,7 @@ where both °C and °F subcomponents are enabled.
136143
### First: Read the library's example sketch
137144

138145
Before writing any driver code, find and read the library's `simpletest` or `basic_test` or some example not using interrupts (data ready flags are okay)
139-
on GitHub. This is your source of truth for how the sensor is meant to be used:
146+
on GitHub. Check all matches for suitable usage suggestions. This is your source of truth for how the sensor is meant to be used:
140147

141148
```bash
142149
gh api repos/adafruit/<Library_Repo>/contents/examples --jq '.[].name'
@@ -158,7 +165,7 @@ are often not visible in the example sketch but they affect sensor behavior. If
158165
update changes any of these defaults, it would silently change WipperSnapper's behavior too.
159166

160167
When writing the WipperSnapper driver, **explicitly set every configuration parameter that the
161-
library sets as a default in its `begin()` or `_init()`**. This pins the behavior so that library
168+
library sets as a default in its `begin()` or `_init()`** chain. This pins the behavior so that library
162169
updates cannot break WipperSnapper without a deliberate driver change on our side.
163170

164171
For example, if the library's `_init()` sets continuous mode with 8x averaging as defaults:
@@ -331,15 +338,13 @@ protected:
331338
332339
Two changes, both in **alphabetical order** among existing entries:
333340
334-
1. Add the include near the top, with the other driver includes:
335-
```cpp
336-
#include "drivers/WipperSnapper_I2C_Driver_<SENSOR>.h"
337-
```
341+
```cpp
342+
// With other driver includes:
343+
#include "drivers/WipperSnapper_I2C_Driver_<SENSOR>.h"
338344
339-
2. Add a private member pointer:
340-
```cpp
341-
WipperSnapper_I2C_Driver_<SENSOR> *_<sensor> = nullptr;
342-
```
345+
// In private section:
346+
WipperSnapper_I2C_Driver_<SENSOR> *_<sensor> = nullptr;
347+
```
343348

344349
---
345350

@@ -366,51 +371,33 @@ Wippersnapper_Components repo.
366371
}
367372
```
368373

369-
`configureDriver()` is inherited from the base class — it reads the sensor periods from the init
370-
request message. You never need to implement it yourself.
374+
`configureDriver()` is inherited — reads sensor periods from the init request. Never reimplement.
371375

372376
---
373377

374-
## Step 4 — Add Library Dependencies
378+
## Step 4 — Add Library Dependencies in library.properties and platformio.ini
375379

376-
Two files need updating when adding a new library. If the sensor class lives inside an existing
377-
library that's already listed (like TMP119 inside Adafruit_TMP117), skip this step entirely.
380+
Skip if the sensor class lives in an already-listed library (e.g. TMP119 in Adafruit_TMP117).
378381

379382
### 4a. platformio.ini
380383

381-
In the `[env]` section's `lib_deps`, add the library in **alphabetical order** among existing
382-
entries. The format depends on whether the library is published on the Arduino Library Manager:
383-
384-
**Released Arduino library** (most Adafruit libraries):
384+
Add to `[env]` `lib_deps` in **alphabetical order** ignoring any purpose specific sections of the lib_deps:
385385
```ini
386-
adafruit/Adafruit <Library Name>
387-
```
388-
389-
**Unreleased / third-party library** (use full GitHub URL):
390-
```ini
391-
https://github.com/<owner>/<repo>.git
392-
```
393-
394-
Examples from the current file:
395-
```ini
396-
adafruit/Adafruit TMP117
397-
https://github.com/Sensirion/arduino-i2c-scd4x.git
398-
https://github.com/tyeth/omron-devhub_d6t-arduino.git
386+
adafruit/Adafruit <Library Name> # released Arduino library
387+
https://github.com/<owner>/<repo>.git # unreleased / third-party
399388
```
400389

401390
### 4b. library.properties
402391

403392
Add the library's **Arduino Library Manager name** to the comma-separated `depends=` line. This
404-
is the name as it appears in the Arduino IDE Library Manager, not the GitHub repo name. Add it in
393+
is the name as it appears in the Arduino IDE Library Manager + library.properties, not the GitHub repo name. Add it in
405394
a logical position among the existing entries.
406395

407396
Example: to add a library called "Adafruit FooBar":
408397
```
409398
depends=..., Adafruit FooBar, ...
410399
```
411-
412-
For non-Arduino-Library-Manager libraries (GitHub-only), they won't be in `library.properties`
413-
since the Arduino IDE can't auto-install them — note this in the PR description.
400+
GitHub-only libraries can't go in `library.properties` — note this in the PR description (and advise to fork n release ardu lib).
414401

415402
---
416403

@@ -455,10 +442,13 @@ The `<sensor_name>` folder name is lowercase and must exactly match the string u
455442

456443
Field notes:
457444
- `published` — always `false` for new contributions. Adafruit sets it to `true` after release.
445+
- `productURL` — Adafruit product page if available, place of sale, otherwise the manufacturer's product page.
446+
- `documentationURL` — Adafruit learn guide (preferred), or manufacturer docs page / wiki, or datasheet URL as
447+
fallback. Third-party domain URLs may initially fail CI URL validation until a maintainer adds
448+
the domain to the allowlist — note this in the PR if using a non-Adafruit/non-TI URL.
458449
- `i2cAddresses` — hex strings, all addresses the chip can use (check datasheet for ADDR pin
459450
configurations).
460-
- `subcomponents` — can be either simple strings or objects. Use the exact type strings from the
461-
table in Step 0.
451+
- `subcomponents` — mixed array of simple strings or objects. Use exact type strings from the Step 0 table.
462452

463453
### Subcomponent formats
464454

@@ -475,22 +465,20 @@ Field notes:
475465
]
476466
```
477467

478-
Use the object format when:
479-
1. **The sensor type name is ambiguous** — e.g. "light" could mean visible, UV, or IR. Give it a
480-
descriptive `displayName` so the user knows what they're looking at in the Adafruit IO UI.
481-
2. **The sensor reports two readings of the same physical type** — the v1 schema forbids
482-
duplicate `sensorType` values in one component. Use `"raw"` as the sensorType for the second
483-
reading and set `displayName` to describe what it actually is. For example, the LTR-329 reports
484-
both ambient light and infrared light — both are "light", but the definition uses `"light"` for
485-
ambient and `"raw"` for infrared with `displayName: "Infrared"`.
468+
Use objects when:
469+
1. **Type name is ambiguous** — "light" could mean visible, UV, or IR. `displayName` clarifies in the UI.
470+
2. **Two readings share the same physical type** — v1 schema forbids duplicate `sensorType`. Use
471+
`"raw"` for the second with a descriptive `displayName` (e.g. LTR-329: `"light"` for ambient,
472+
`"raw"` with `displayName: "Infrared"` for IR).
486473

487-
Real examples:
474+
Examples:
488475
- **LTR-390** (UV + light): `[{"displayName": "Ambient Light", "sensorType": "light"}, {"displayName": "UV Count", "sensorType": "raw"}]`
489476
- **LTR-329** (visible + IR): `[{"displayName": "Ambient Light", "sensorType": "light"}, {"displayName": "Infrared", "sensorType": "raw"}]`
490477
- **INA219** (no ambiguity): `["voltage", "current"]`
491478

492-
When using `"raw"` as a stand-in, the corresponding driver must implement `getEventRaw()` to
493-
return that second reading.
479+
When using `"raw"` as a stand-in, the driver must implement `getEventRaw()` for that reading.
480+
3. **Non-standard units** — if the sensor reports in a non-SI unit (or doesn't match Adafruit_Sensor type SI unit) then use the appropriate unitless type or raw with a descriptive `displayName` including units.
481+
4. **Clarity compared to the auto UI labels or between subcomponents** — compare other components using the same types for reference.
494482

495483
### 5d. Add product image
496484

@@ -518,14 +506,15 @@ Fix any compilation errors. Common issues:
518506

519507
---
520508

521-
## Step 7 — Format with clang-format
509+
## Step 7 — Format with clang-format, ensure passes doxygen and other CI checks
522510

523511
```bash
524512
clang-format -i src/components/i2c/drivers/WipperSnapper_I2C_Driver_<SENSOR>.h
525513
```
526514

527-
Also run on any other modified files. The repo's `.clang-format` config will be used
528-
automatically.
515+
Run on all modified files. The repo's `.clang-format` should be applied.
516+
517+
Ensure all doxygen style is consistent with existing drivers.
529518

530519
---
531520

@@ -559,15 +548,19 @@ Two separate PRs are needed:
559548

560549
## Worked Example: TMP119
561550

562-
The TMP119 is a high-accuracy temperature sensor from Texas Instruments, a variant of the TMP117
563-
with a different chip ID (0x2117 vs 0x0117).
551+
TMP119: TI high-accuracy temperature sensor, TMP117 variant (chip ID 0x2117 vs 0x0117).
564552

565553
### Step 0 — Research
566554

567-
- **Search:** `gh search repos "Adafruit TMP119" --owner adafruit` finds only the PCB repo, no
568-
standalone Arduino library. But checking the `Adafruit_TMP117` library reveals
569-
`Adafruit_TMP119.h` and `.cpp` inside it — the TMP119 class inherits from TMP117.
555+
- **Search:** Web search "adafruit TMP119" → product page https://www.adafruit.com/product/6482
556+
which links to the learn guide https://learn.adafruit.com/adafruit-tmp119-high-precision-temperature-sensor
557+
which shows the Arduino library is `Adafruit_TMP117`. Alternatively,
558+
`gh search repos "Adafruit TMP119" --owner adafruit` only finds the PCB repo — no dedicated
559+
library. Trying `TMP11` or checking `Adafruit_TMP117` repo contents reveals
560+
`Adafruit_TMP119.h/.cpp` — TMP119 inherits from TMP117.
570561
- **Library:** `Adafruit_TMP117` (contains `Adafruit_TMP119` class)
562+
- **Product URL:** `https://www.adafruit.com/product/6482`
563+
- **Docs URL:** `https://learn.adafruit.com/adafruit-tmp119-high-precision-temperature-sensor`
571564
- **I2C addresses:** 0x48, 0x49, 0x4A, 0x4B (same as TMP117, datasheet Table 7-1)
572565
- **Measures:** Temperature only → subcomponents: `ambient-temp`, `ambient-temp-fahrenheit`
573566
- **Closest driver:** `WipperSnapper_I2C_Driver_TMP117.h`
@@ -673,18 +666,15 @@ WipperSnapper_I2C_Driver_TMP119 *_tmp119 = nullptr;
673666
{
674667
"displayName": "TMP119",
675668
"vendor": "Texas Instruments",
676-
"productURL": "https://www.adafruit.com/product/6201",
677-
"documentationURL": "https://learn.adafruit.com/adafruit-tmp117-high-accuracy-i2c-temperature-monitor",
669+
"productURL": "https://www.adafruit.com/product/6482",
670+
"documentationURL": "https://learn.adafruit.com/adafruit-tmp119-high-precision-temperature-sensor",
678671
"published": false,
679672
"i2cAddresses": ["0x48", "0x49", "0x4A", "0x4B"],
680673
"subcomponents": ["ambient-temp", "ambient-temp-fahrenheit"]
681674
}
682675
```
683676

684-
Simple string subcomponents are fine here — "ambient-temp" is unambiguous for a temperature-only
685-
sensor. No need for the object format.
686-
687-
Image: grab from Adafruit product API, resize to 400x300, compress.
677+
Simple strings — unambiguous for a temperature-only sensor. Image: product API, 400x300, compress.
688678

689679
### Files changed
690680

0 commit comments

Comments
 (0)