Docs / Custom CAN Profiles / Format Guide
Custom CAN Profiles

Format Guide

A custom CAN profile teaches your KrispyGauge to read data from any CAN bus, whether that's an ECU, a PDM, a sensor module or a vehicle's own network. You don't need a firmware update. A profile is a small JSON text file. You can download one from our profile library, build one with the profile builder (which can also import a DBC file), or write your own using this guide.

schema 1 Up to 32 channels Listen only

Not sure where to start?

Copy this prompt into your AI assistant (ChatGPT, Claude, Gemini or similar). It will ask for your vehicle, your ECU and any CAN data you already have, then write the profile for you. Paste the result into the profile builder to check it.

You are helping me write a custom CAN profile for my KrispyGauge, a digital gauge that reads values from a CAN bus. A profile is a JSON file that tells the gauge where each value sits in the CAN frames and how to convert it.

Before writing anything, ask me:
1. The make, model and year of my vehicle, and the ECU or device I'm reading from (for example a Haltech Elite 2500, a Link G4X, an EGT module, or the car's own network).
2. Whether I already have CAN data for it: the ECU maker's CAN protocol document, a DBC file, an export from the ECU's tuning software, or a log from a CAN tool. Ask me to paste or upload whatever I have.
3. The bus speed, if I know it (125, 250, 500 or 1000 kbit/s).
4. Which values I want on the gauge (at most 32), such as RPM, coolant, oil pressure, boost, battery voltage.

Only use CAN IDs, byte positions and scaling from my documents or from official published documentation for my exact ECU. If you are not sure of a value, say so and ask me — never guess or invent a CAN ID or bit position. Tell me which channels you are unsure about.

Write the profile to these rules (full guide: https://krispygauge.com/docs/custom-can — JSON Schema: https://krispygauge.com/customCan/custom-can-profile.schema.json):
- One JSON object: "schema": 1, "name" (up to 47 characters), optional "author" (up to 31) and "notes", "bus": {"bitrate": 125000, 250000, 500000 or 1000000}, and "channels": a list of 1 to 32 channels.
- Each channel needs: "key" (unique, lower-case a-z, 0-9 and _ only, up to 23 characters), "name" (what the gauge shows, up to 15 characters), "id" (the CAN ID as a hex string like "0x360"), "order" ("motorola" for big-endian or "intel" for little-endian), "lsb_byte" (the byte holding the value's LEAST significant bit, 0-7, byte 0 is the first data byte), "lsb_bit" (0-7, 0 is the lowest bit) and "length" (1-32 bits).
- A 16-bit big-endian value in bytes n and n+1 has "lsb_byte": n+1, "lsb_bit": 0. DBC start bits for Motorola signals count from the MOST significant bit, so convert them; don't copy them across.
- Bits must fit in an 8-byte frame. IDs above 0x7FF need "extended": true. Add "signed": true for values that can be negative.
- Conversion: displayed value = raw × "scale" + "offset" ("scale" can't be 0). Or use "formula" (up to 31 characters, x is the raw value; + - * / % ^, brackets, abs sqrt ln log exp min(a,b) max(a,b)) instead of scale and offset.
- Always set "min" and "max" (max greater than min) for a sensible display range. Optional: "decimals" 0-3, "timeout_ms" (default 1000).
- Units matter: use exactly "rpm", "km/h", "°C", "kPa" (absolute pressure), "psi" or "bar" (gauge pressure) so the gauge converts them to my display settings. Convert to those base units: temperatures in °C (not °F or kelvin), speed in km/h, and oil/fuel/boost pressure as gauge pressure, not absolute. Other units such as "V", "%", "°" or "λ" are shown as received. Units are up to 7 characters.
- Multiplexed frames: add "mux": {"lsb_byte", "lsb_bit", "length", "equals"} to each channel that only applies when the selector has that value.

When you're done, give me the complete profile as one valid JSON code block, with no comments inside it, and a short list of any channels you weren't sure about. Remind me to paste it into the KrispyGauge profile builder at https://krispygauge.com/customCan to check it, and to compare the gauge's live values with my ECU software before I rely on them.

AI assistants make mistakes, especially with CAN IDs and bit positions. The best results come from giving it your ECU's CAN document or DBC file. Always compare the gauge's live values with your ECU software before you rely on them.

Each value the profile describes is called a channel. Once a profile is installed, its channels appear in the gauge's metric list as Custom 1, Custom 2 and so on, shown under the names you gave them. You can put them on any page, gauge face or alert, just like built-in metrics.

Your gauge only ever listens. A custom profile can't make the gauge transmit anything on your vehicle's bus.

  1. Installing a profile
  2. A complete example
  3. The file
  4. Channels
  5. Units
  6. Naming recommendations
  7. Keep keys stable
  8. Formulas
  9. Multiplexed frames
  10. Limits
  11. Checking your file
  12. Troubleshooting
  13. Trust and safety

Installing a profile

  1. Connect to your gauge's WiFi and open its web page.
  2. Go to Advanced → ECU / Protocol and select Custom CAN.
  3. Under Upload a profile, choose your .json file.
  4. The page checks the file and shows a preview of every channel: name, CAN ID and bit position. If anything is wrong, it tells you which channel and why.
  5. Click Install on gauge.
  6. If the profile uses a different bus speed from the one the gauge is running, you'll be asked to reboot. Say yes. The new speed is applied at start-up.

After that, the live table shows every channel's current value. It's the quickest way to check a profile is right before you build pages around it.

You can Download JSON to back up the installed profile, or Remove it. Installing a new profile replaces the old one.


A complete example

You can also load this into the builder with Load example.

{
  "schema": 1,
  "name": "My ECU - engine data",
  "author": "Your name",
  "notes": "Connect to the ECU's CAN 1 at 1 Mbit/s.",
  "bus": { "bitrate": 1000000 },
  "channels": [
    {
      "key": "rpm",
      "name": "RPM",
      "unit": "rpm",
      "id": "0x360",
      "order": "motorola",
      "lsb_byte": 1,
      "lsb_bit": 0,
      "length": 16
    },
    {
      "key": "coolant",
      "name": "Coolant",
      "unit": "°C",
      "id": "0x3E0",
      "order": "motorola",
      "lsb_byte": 1,
      "lsb_bit": 0,
      "length": 16,
      "scale": 0.1,
      "offset": -273.15,
      "min": -40,
      "max": 140
    },
    {
      "key": "battery",
      "name": "Battery",
      "unit": "V",
      "id": "0x372",
      "order": "motorola",
      "lsb_byte": 1,
      "lsb_bit": 0,
      "length": 16,
      "scale": 0.1,
      "min": 8,
      "max": 16,
      "decimals": 1
    }
  ]
}

The file

A profile is one JSON object with these fields:

FieldRequiredWhat it is
schemayesAlways 1.
nameyesThe profile's name, shown on the gauge's web page. Up to 47 characters.
authornoWho wrote it. Up to 31 characters.
notesnoAnything a reader of the file should know: which bus, which connector, which pins. It isn't stored on the gauge.
bus.bitrateyesThe bus speed in bits per second: 125000, 250000, 500000 or 1000000. It must match the bus you're connected to.
channelsyesA list of 1 to 32 channels.

The gauge ignores fields it doesn't recognise. You can add your own (for example "source": "...") and files written for newer gauges still load on older ones.


Channels

Each channel describes one value: which CAN frame it's in, where its bits sit in that frame, and how to turn those bits into a number.

FieldRequiredWhat it is
keyyesA short ID for this channel. Lower-case letters, digits and _, up to 23 characters, unique in the file. See Keep keys stable.
nameyesWhat the gauge shows. Up to 15 characters, so keep it short.
unitnoThe unit, such as rpm, °C, kPa, V or %. Up to 7 characters. It matters more than it looks: see Units.
idyesThe CAN frame ID. Hex as a string ("0x360") or a plain number (864).
extendednotrue for 29-bit (extended) IDs. Default false.
orderyesByte order: "motorola" (big-endian) or "intel" (little-endian).
lsb_byteyesThe byte that holds the value's least significant bit. Byte 0 is the first data byte.
lsb_bityesWhich bit of that byte is the least significant bit, 0–7, where 0 is the lowest bit.
lengthyesHow many bits the value uses, 1–32.
signednotrue if the value can be negative (two's complement). Default false.
scalenoMultiply the raw number by this. Default 1. Can't be 0.
offsetnoThen add this. Default 0.
min, maxnoThe display range for dials and bars. If you leave them out, the gauge uses the full range the value can produce, which is often far wider than you want. Set them.
decimalsnoDecimal places shown, 0–3. Default 0.
timeout_msnoIf the frame doesn't arrive within this many milliseconds, the channel shows "no data" instead of a stale value. Default 1000. The gauge also treats anything older than 2 seconds as stale, so values above 2000 act as 2000.
formulanoReplaces scale and offset with a calculation. See Formulas.
muxnoFor multiplexed frames. See Multiplexed frames.

How the value is worked out

displayed value = raw × scale + offset

For example, a coolant temperature sent as tenths of a kelvin uses "scale": 0.1, "offset": -273.15. A raw value of 3631 then becomes 3631 × 0.1 − 273.15 = 90 °C.

Finding the bits

Most ECU documentation and DBC files tell you the start byte, length and byte order. The builder's bit map shows exactly which bits a channel reads, and its Try it box decodes a frame you paste in. Some common cases:

A 16-bit big-endian value in bytes 0 and 1 (very common, e.g. Haltech, Link, MoTeC). The low byte is byte 1:

"order": "motorola", "lsb_byte": 1, "lsb_bit": 0, "length": 16

In general, a big-endian value in bytes n and n+1 uses lsb_byte: n+1.

A 16-bit little-endian value in bytes 2 and 3. The low byte comes first:

"order": "intel", "lsb_byte": 2, "lsb_bit": 0, "length": 16

A single bit or a small field. A 4-bit value in the top half of byte 7:

"order": "intel", "lsb_byte": 7, "lsb_bit": 4, "length": 4

Coming from a DBC file? DBC "start bits" for Motorola signals count from the most significant bit, so they aren't the same as lsb_byte/lsb_bit. Don't copy the start bit across: use Import DBC in the profile builder, which converts it for you, along with the scale, offset, range and units.


Units — get these right

The unit field decides how the gauge treats a channel. Use exactly the spellings below and the channel behaves like the gauge's built-in metric of that kind. Use anything else and the value is shown exactly as received.

The name is just a label, so "Engine RPM" and "RPM" behave the same. The unit is what counts.

Write the unit asThe gauge treats it asWhat you get
rpmEngine speedFollows your Max RPM and Redline settings and uses the same dial ticks as the built-in RPM, so tachometers and redline zones look right.
km/hRoad speedConverted by the Speed setting (km/h, mph, m/s, knots) and follows Max Speed.
°CTemperatureConverted by the Temperature setting (°C / °F).
kPaPressure, absoluteConverted by the Pressure setting. Use for MAP, baro and anything else measured against a vacuum.
psiPressure, gaugeConverted by the Pressure setting. Use for oil, fuel, coolant and boost pressure measured against atmosphere.
barPressure, gaugeAs psi.
anything else (V, %, °, A, L, λ…)Shown as receivedNo conversion.

Always write the profile in these base units, whatever you prefer to see. Each driver then chooses their own display units in the gauge's settings, and one profile works for everyone.

  • Temperatures in °C, not °F. A channel in °F is shown as received and won't follow the Temperature setting. If the ECU sends Fahrenheit, convert it with a formula: "formula": "(x-32)/1.8", "unit": "°C".
  • Speeds in km/h, not mph. If the ECU sends mph, use "scale" × 1.609344.
  • Pressures: mind absolute vs gauge. If the ECU sends oil pressure as absolute kPa, subtract atmosphere and convert to psi. Then 0 means "no pressure" and the Pressure setting's kPa(a) mode adds atmosphere back correctly:
    "unit": "psi", "scale": 0.01450377, "offset": -14.69232
    (That is (raw × 0.1 − 101.3) kPa × 0.1450377 for an ECU sending tenths of a kPa absolute.)
  • Use °C with the degree sign, exactly as shown. C, degC or deg C won't be recognised.

Naming recommendations

Names are labels, but consistent names make pages easier to build and profiles easier to share. Up to 15 characters; longer names are shortened.

MeasurementSuggested nameUnit
Engine speed RPM rpm
Road speed Speed km/h
Coolant temperature Coolant °C
Intake air temperature Intake Air °C
Oil temperature Oil Temp °C
Oil pressure Oil Press psi
Fuel pressure Fuel Press psi
Manifold pressure MAP kPa
Boost Boost psi
Throttle position TPS %
Battery voltage Battery V
Lambda Lambda (none)
AFR AFR (none)
Ignition timing Ign Angle °
ECU temperature ECU Temp °C
Gear Gear (none)

Keep keys stable

When you upload a new version of a profile, each key stays on the same Custom slot it had before. A key that was Custom 3 is still Custom 3, so the user's pages and alerts keep showing the same thing.

  • Don't rename keys between versions of a profile. Change name as much as you like.
  • Removing a channel frees its slot. A new key added later may take it.

Formulas

Sometimes raw × scale + offset isn't enough. A formula replaces it with a calculation, where x is the raw value:

"formula": "(x*0.1-40)*9/5+32"
  • Up to 31 characters.
  • Operators: + - * / % ^ and brackets.
  • Functions: abs() sqrt() ln() log() exp() min(a,b) max(a,b).
  • When a formula is present, scale and offset are ignored.
  • A formula that doesn't make sense (a divide by zero, the square root of a negative) shows "no data" rather than a wrong number.
  • The gauge checks every formula when you install the profile. A typo stops the upload rather than failing silently.

Multiplexed frames

Some devices send different data in the same frame ID, using one byte (the selector) to say which set it is. EGT modules and some dash streams work this way. Give each channel a mux saying which selector value it belongs to:

{ "key": "egt_1", "name": "EGT 1", "unit": "°C",
  "id": "0x700", "order": "motorola", "lsb_byte": 2, "lsb_bit": 0, "length": 16,
  "mux": { "lsb_byte": 0, "lsb_bit": 0, "length": 8, "equals": 1 } },
{ "key": "egt_2", "name": "EGT 2", "unit": "°C",
  "id": "0x700", "order": "motorola", "lsb_byte": 2, "lsb_bit": 0, "length": 16,
  "mux": { "lsb_byte": 0, "lsb_bit": 0, "length": 8, "equals": 2 } }

The selector uses the same byte order as the channel. The channel only updates when the selector matches.


Limits

Channels per profile32
Profiles on the gauge1 (installing replaces)
Channel name15 characters
Unit7 characters
Key23 characters: a-z, 0-9, _
Profile name47 characters
Formula31 characters
Value width1–32 bits
Frame sizeClassic CAN, up to 8 bytes (no CAN-FD)
PrecisionValues are stored as single-precision numbers, so raw values above about 16 million lose their last digits.

The installed profile is stored alongside your Personalisation images and uses 4 KB of that space. While a profile is installed, you can store 15 images instead of 16.


Checking your file

The profile builder runs these same checks as you type. The gauge rejects a file, and tells you which channel is at fault, if:

  • it isn't valid JSON, or is over 64 KB
  • schema isn't 1
  • there are no channels, or more than 32
  • a key is missing, repeated, too long, or uses characters other than a-z 0-9 _
  • a channel's bits run past the end of an 8-byte frame
  • length isn't 1–32
  • scale is 0, or a number isn't a real number
  • max isn't greater than min
  • decimals isn't 0–3
  • an id is above 0x7FF without "extended": true, or above 0x1FFFFFFF
  • bitrate isn't one of the four supported speeds
  • a formula doesn't parse

It accepts the file but warns you when a name or unit has been shortened, or the file uses values (text for raw values), which isn't supported yet.

A frame that arrives shorter than a channel needs isn't an error. That channel just shows "no data" until a full frame arrives.

Writing profiles with an AI assistant: start with the prompt at the top of this page, and give it your ECU's CAN document or DBC file. It can also use the JSON Schema. Paste the result into the builder to check it, then install it and compare the live table with your ECU software before relying on it.


Troubleshooting

What you seeLikely cause
Every channel shows "—"Wrong bus speed (check bitrate and reboot when asked), wrong wiring or termination, or the gauge isn't set to Custom CAN.
One channel shows "—"Wrong id, extended set wrongly, or the frame is shorter than the channel needs.
A number that jumps around wildlyWrong order, or lsb_byte is off by one.
Roughly right but 10× or 100× outWrong scale.
Negative numbers show as huge positivesAdd "signed": true.
A temperature reads −273The ECU is sending 0 for it. Usually no sensor is fitted (0 kelvin is −273.15 °C).
Temperature or speed won't follow the Units settingThe unit isn't exactly °C / km/h. See Units.
The tachometer ignores Max RPM / RedlineThe unit isn't exactly rpm.

Trust and safety

  • Profiles from the community haven't been checked against a real vehicle unless we say so. The gauge shows every profile as community / unverified. Profiles in the KrispyDigital library are marked as tested when we've tested them on a vehicle.
  • Always compare a new profile's live values against your ECU's own software before trusting it for warnings or alerts.
  • A profile is data only. It can't run code, and it can't make the gauge transmit.