Skip to main content

Enums and Lookup Tables

The DSL provides two mechanisms for named values: enums and lookups. They serve different purposes.

Enums​

Enums define a new Go type with a String() method. Use them when the field has a small, dense set of possible values.

Definition​

enum WindReference {
0 = "true_north"
1 = "magnetic_north"
2 = "apparent"
3 = "true_boat"
4 = "true_water"
}

Generated Go code​

type WindReference uint8

const (
WindReferenceTrueNorth WindReference = 0
WindReferenceMagneticNorth WindReference = 1
WindReferenceApparent WindReference = 2
WindReferenceTrueBoat WindReference = 3
WindReferenceTrueWater WindReference = 4
)

func (v WindReference) String() string {
switch v {
case 0: return "true_north"
case 1: return "magnetic_north"
case 2: return "apparent"
case 3: return "true_boat"
case 4: return "true_water"
default: return fmt.Sprintf("WindReference(%d)", v)
}
}

// Known values marshal as JSON strings, unknown as numbers.
func (v WindReference) MarshalJSON() ([]byte, error) { ... }
func (v *WindReference) UnmarshalJSON(data []byte) error { ... }

Usage in PGN​

pgn 130306 "Wind Data" {
wind_reference WindReference :3
}

The Go struct field becomes WindReference WindReference. In JSON, known enum values serialize as strings (the label), and unknown values serialize as numbers:

{"wind_reference": "apparent"}
{"wind_reference": 99}

UnmarshalJSON accepts both formats, so JSON round-trips are lossless.

When to use enums​

  • The value set is small (fits in the bit width)
  • Values are dense (0, 1, 2, ... with few gaps)
  • You want type safety (a distinct Go type)
  • You want String() for human-readable output

Lookup tables​

Lookups map sparse integer keys to human-readable names without creating a new type. Use them when the key space is large and sparse.

Definition​

lookup VictronRegister uint16 {
0x0100 = "Product ID"
0x0200 = "Device Mode"
0x0201 = "Device State"
0xED8D = "DC Channel 1 Voltage"
0xED8E = "DC Channel 1 Power"
0xEDD3 = "Yield Today"
}

The type after the name (uint16) specifies the key type.

Generated Go code​

var victronRegisterNames = map[uint16]string{
0x0100: "Product ID",
0x0200: "Device Mode",
0x0201: "Device State",
0xED8D: "DC Channel 1 Voltage",
0xED8E: "DC Channel 1 Power",
0xEDD3: "Yield Today",
}

// RegisterName returns the human-readable name, or empty if unknown.
func (m VictronBatteryRegister) RegisterName() string {
return victronRegisterNames[m.Register]
}

// LookupFields returns JSON field name -> resolved name for display code.
func (m VictronBatteryRegister) LookupFields() map[string]string {
return map[string]string{
"register": victronRegisterNames[m.Register],
}
}

Usage in PGN​

pgn 61184 "Victron Battery Register" {
register uint16 :16 lookup=VictronRegister
}

The Go struct field stays uint16 (no new type), but gains a RegisterName() string helper method and a LookupFields() method for display code.

Display tools like lplex use LookupFields() to wrap lookup fields as {"id": <raw>, "name": "..."} objects in JSON output. Unknown values omit the name field.

When to use lookups​

  • The key space is large or sparse (e.g., 0x0100, 0xED8D)
  • You want to keep the raw integer type for arithmetic
  • You're mapping register IDs, product codes, or other identifiers
  • Adding new entries shouldn't require a new Go constant

Comparison​

EnumLookup
Go typeNew named typeOriginal integer type
String()Yes (on the type)Name() function
Type safetyYesNo
Key spaceDense (0, 1, 2, ...)Sparse (any values)
JSON outputString (known) or number (unknown){"id": <raw>, "name": "..."} object
Use caseSmall finite setsLarge/sparse mappings