---json
{
"name": "Writing profiles"
}
---
====== 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.
{{ :axiom:device_library:devlib-advanced-json.png?nolink&800 |}}
==Start from a working profile==
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. |
==Three common mistakes==
* **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 [[axiom:device_library:test_and_tune|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.
==Custom decoding code==
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.