Writing device profiles (Advanced JSON)
A device profile is a short piece of JSON that tells Axiom how to find, identify, set up and read a device. This page explains the format and the Advanced JSON editor, so you can write a profile for a device that isn't in the catalogue.
The easiest way to write a profile is to copy one for a similar device. Open a built-in profile from the Devices tab (Definition, then Customise this profile), or add one from the Catalogue. Then edit it in Advanced JSON.
The Advanced JSON tab
- Profiles (left) lists every profile in your draft, with its address, origin (new, replaces built-in, …), polling interval and name. Search by name or address.
- The editor (right) shows the selected profile as JSON. The label above it tells you whether you are editing your local draft or looking at a read-only built-in definition.
- The Descriptor guide (below) is a quick reference to the fields and command syntax.
| Button | What it does |
|---|---|
| New profile | Starts a new, empty profile in your draft. You'll be asked for a type name. |
| Edit package | Opens the whole library (every profile) as one JSON document. |
| Save to draft | Saves the editor's JSON to your draft. Unsaved edits are not written or exported. |
| Discard edits | Throws away unsaved changes in the editor. |
| Remove custom | Removes the selected profile from your draft. |
| Import JSON | Loads a library or single-profile .json file from your computer. A whole library replaces your draft (you'll be asked first). A single profile is added to it. |
| Import from URL | Loads a shared profile or library from an https:// address. The server must allow browser access (CORS). |
| Export draft / Export selected profile | Saves your draft, or one profile, as a .json file. |
| Clear custom draft | Empties your draft. |
Below the editor, the page shows the draft's size and any Check messages. Checks are advice: they point out likely mistakes but don't stop you writing. The only verdict that counts is what Axiom reports after you install and restart: the rejected count and first error on the Install tab.
The library file
A library is one JSON object. Its devTypes object holds one entry per device type, keyed by the type's name:
{
"devTypes": {
"AHT20": { ... },
"VL53L1X": { "disable": true }
}
}
- A key that doesn't match a built-in profile adds a new type.
- A key that matches a built-in profile customises it: your version is tried first, with the built-in as a fallback.
- “disable”: true disables that type. If the entry also contains a full profile, it is kept but ignored until you enable it again.
Type names can use 1–48 letters, digits, underscores and hyphens (e.g. LTR-390).
A complete example: AHT20
This is the catalogue profile for the AHT20 temperature and humidity sensor:
{
"_notes": "Temperature and humidity sensor. Data format: Status(8bit) + Humidity(20bit) + Temperature(20bit).",
"addresses": "0x38",
"deviceType": "AHT20",
"detectionValues": "",
"initValues": "0xbe0800=",
"pollInfo": {
"c": "=r6&0xac3300=",
"i": 500,
"s": 1
},
"devInfoJson": {
"name": "Temperature & Humidity Sensor",
"desc": "",
"manu": "Asair",
"type": "AHT20",
"clas": ["TEMP", "RH"],
"resp": {
"b": 6,
"a": [
{ "n": "status", "t": "B", "u": "", "f": "02x", "o": "uint8", "vs": false },
{ "n": "humidity", "t": ">I", "u": "%", "r": [0, 100],
"m": "0xfffff000", "s": 12, "d": 10485.76, "f": "3.1f", "o": "float" },
{ "n": "temperature", "at": 2, "t": ">I", "u": "°C", "r": [-40, 80],
"m": "0x000fffff", "d": 5242.88, "a": -50, "f": "3.2f", "o": "float" }
]
}
}
}
In words:
- Look for it at address 0x38. There is no identification check (detectionValues is empty), so anything at 0x38 is treated as an AHT20.
- When it's found, write 0xbe 0x08 0x00 to initialise it.
- Every 500 ms, read the 6-byte result of the previous measurement, then write 0xac 0x33 0x00 to start the next one. Keep 1 sample.
- Decode the 6 bytes as a status byte, a 20-bit humidity value and a 20-bit temperature value.
Profile fields
| Field | Required | Meaning |
|---|---|---|
| deviceType | No | The type name. If present, it must equal the key. |
| addresses | Yes, for an I²C device | Where to look: one address, a list or an inclusive range, e.g. “0x38”, “0x29,0x30”, “0x10-0x18”. |
| deviceTypeId | Robotical modules only | The ID that Robotical's own add-on modules report, instead of an address, e.g. “0x0084”. |
| detectionValues | No | How to recognise the device (see below). Empty means “anything at this address”. |
| initValues | No | Commands sent once when the device is found. |
| pollInfo | No | What to read and how often. Leave it out for an actuator that isn't read. |
| pollInfo.c | The poll command. | |
| pollInfo.i | Polling interval in milliseconds (e.g. 500 = twice a second). | |
| pollInfo.s | Number of samples Axiom keeps between deliveries. | |
| devInfoJson | Yes | Name, maker, classes and how to decode the data (see below). |
| disable | No | true disables this device type. |
scanPriority and fields starting with _ (such as _notes) are accepted but not used. Profiles in a library are always scanned at the highest priority.
Command syntax
detectionValues, initValues and pollInfo.c use the same syntax. A command is a list of steps separated by & or ;. Each step is bytes to write=what happens next. The bytes before the = are written to the device as hex (the 0x is optional). What comes after the = depends on the field:
| After = | In initValues and pollInfo.c | In detectionValues |
|---|---|---|
| rN | Read N bytes | — |
| pN | Pause N milliseconds | Pause N milliseconds |
| 0x… or hex | Ignored | The value expected back. Separate alternatives with commas. |
| 0b… | A byte count (don't use) | A bit pattern to compare. x marks a bit to ignore. |
| nothing | Write only | Write only |
Examples:
| Command | Meaning |
|---|---|
| 0x01=r2 | Write 0x01 (select a register), then read 2 bytes. |
| 0xbe0800= | Write the three bytes 0xbe 0x08 0x00. |
| 0x0001=p25 | Write 0x00 0x01, then wait 25 ms. |
| 0x0f=0x69,0x6a,0x6c | (detection) Write 0x0f and expect 0x69, 0x6a or 0x6c back. |
| 0x00=0b000000xx | (detection) Write 0x00 and expect six zero bits followed by two bits that are ignored. |
- Pauses need an =. Write =p20. A bare p20 is sent to the device as bytes.
- To write data, put it before the =. In initValues, 0x10=0x60 writes only 0x10 and ignores 0x60. To write both bytes, use 0x1060.
- An empty detectionValues claims anything at the address. Your profile is tried before the built-in ones, so it can take over a device a built-in profile would have recognised. If the datasheet has an ID or “who am I” register, check it.
You can test a detection command on a connected device before installing it. See Try a detection command in Testing and tuning devices.
Describing the data (devInfoJson)
| Field | Meaning |
|---|---|
| name, desc, manu | Friendly name, description and maker, shown in apps. |
| type | Type label (normally the same as the key). |
| clas | Classes used for searching and grouping, e.g. [“TEMP”, “RH”]. |
| resp.b | The number of bytes one poll reads. Axiom sizes its buffer from this, so it must match the reads in pollInfo.c. |
| resp.a | The list of values (attributes) to decode from those bytes. |
Each attribute in resp.a describes one value:
| Key | Meaning | Example |
|---|---|---|
| n | Name | “temperature” |
| t | How the bytes are read, in Python struct style: B is an unsigned byte, h a signed 16-bit value, H an unsigned 16-bit value, I an unsigned 32-bit value, and so on. Put < in front for little-endian byte order, or > for big-endian. | “>I” |
| at | Byte position to start from. Otherwise each value follows the previous one. | 2 |
| m | Bit mask applied to the raw value | “0x000fffff” |
| s | Shift right by this many bits (left if negative) | 12 |
| sb, ss | Sign bit position, and the value to subtract when it is set | 11, 4096 |
| d | Divide by this | 5242.88 |
| a | Then add this | -50 |
| u | Units | “%”, “°C” |
| r | Expected range [min, max] | [-40, 80] |
| f | Display format, printf-style | “3.2f” |
| o | Output type | “float”, “uint8” |
| vs | false hides the value from graphs | false |
For the AHT20 temperature, the steps are: read 4 bytes starting at byte 2, keep the low 20 bits, divide by 5242.88, then subtract 50.
Some built-in profiles include a small decoding program for data that can't be described with the keys above. For safety, live readings in the Device Library ignore decoding code supplied in a profile. Only reviewed decoders for a few built-in devices are used. Describe your device's data with the attribute keys wherever you can.
Limits
- The connected Axiom reports how many custom profiles it can hold and how big the library can be. The Install tab shows both, e.g. 1 of 19 profiles · 1.6 KiB of 64 KiB.
- Addresses Axiom uses for its own hardware (including 0x25, 0x36 and 0x70–0x77) are reserved. The battery fuel gauge profile (MAX17048) can't be replaced or disabled.
- A profile you add needs an address (or a Robotical deviceTypeId). A library can't create devices that are wired directly to Axiom.