---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.