Skip to main content

Migrating from v1 to v3 Blueprints

This guide will help you migrate your existing v1 blueprints to the new v3 features.

Key Improvements in v3

  • Hardware Independence: Use capability requirements instead of hardcoded product names for portable blueprints
  • Access Levels: Control who can read telemetry, execute commands, or modify configuration
  • Device Profiles: Implement standardized interfaces for consistent device behavior across the ecosystem
  • UCUM-Compliant Units: Standardized units of measurement with automatic scaling support
  • Declarative Configuration: Define configuration in manifest instead of Lua code
  • Enhanced Metadata: Support for troubleshooting info in alerts, colors for enums
  • Command Response Definitions: Document expected responses directly in manifest
  • Native JSON Support: Built-in handling of complex JSON data structures
  • String Error Messages: Human-readable errors replace numeric error codes

Step-by-Step Migration

Step 1: Update Blueprint Specification

Change blueprint_spec of your manifest.yaml:

Manifest v1
blueprint_spec: device/1.0
Manifest v3
blueprint_spec: device/3.0

Step 2: Update Blueprint Metadata

  1. Remove vendor and verification_level fields. Both of these data are now managed as metadata in the marketplace.
  2. Add category field for UI grouping. See avialable categories at manifest reference.
Manifest v1
vendor: Enapter
verification_level: verified
Manifest v3
category: electrolysers

Step 3: Replace communication_module

The communication_module section has been replaced with a more flexible runtime section.

  1. Replace the communication_module section with runtime section.
  2. Remove product field.
  3. Set type of the runtime. In case of Lua blueprint it would be lua, in case of standalone blueprints it would be embedded.
  4. In case of Lua blueprint:
    1. Rename lua section to options.
    2. Replace lua_file, if any, with options.file.
    3. Rename dependencies to luarocks.
    4. Rename allow_dev_dependencies to allow_dev_luarocks.
Manifest v1
communication_module:
product: ENP-RL6
lua:
file: firmware.lua
Manifest v3
runtime:
type: lua
options:
file: firmware.lua

Step 4: Declare Implemented Profiles

v3 introduces device profiles—standardized interfaces for common device types. When a blueprint implements a profile, it must provide all required telemetry, commands, and properties defined by that profile.

Add implements field to declare profiles:

implements:
- sensor.ambient_temperature

telemetry:
ambient_temperature:
type: float
display_name: Ambient Temperature
unit: Cel

If your telemetry name differs from profile, specify the mapping:

implements:
- sensor.solar_irradiance

telemetry:
irradiance:
type: float
display_name: Solar Irradiance
unit: W/m2
implements: sensor.solar_irradiance.solar_irradiance

See all available profiles on GitHub.

Step 5: Update Colors for Enum Values

v3 uses standardized color palette for enum values. Replace hex colors with named colors (e.g., green, red, yellow, blue). Colors are displayed in device status badges and telemetry dashboards.

Manifest v1
telemetry:
status:
type: string
enum:
ok:
display_name: OK
color: "#ffeedd"
Manifest v3
telemetry:
status:
type: string
enum:
ok:
display_name: OK
color: green

We are strongly recommend to use the following colors for the device status. With this approach users will be able to quickly understand the device status of the different implementations.

UsageColor nameStatuses example
normal operations, idlegreen-lighteridle, OK, powered, operating, online
active actiongreensteady, charging
pre- or post- actiongreen-dark, green-darker, cyan-dark, cyan-darkerrump up, rump down
warningyellowwarning, overheated, low level
errorrederror, gas leakage, fault
fatal errorred-darkerfatal error, sensor damaged
maintenancepink-darkermaintenance mode, service mode

Step 6: Update Units to UCUM Format

v3 requires UCUM-compliant units for telemetry and properties. Replace arbitrary unit strings with UCUM standard codes.

Manifest v1
telemetry:
power:
type: float
unit: watt
Manifest v3
telemetry:
power:
type: float
unit: W

Add auto_scale for automatic unit conversion in UI:

telemetry:
power:
type: float
unit: W
auto_scale: [W, kW, MW]

UI will display 500 W, 5.2 kW, or 1.5 MW depending on value magnitude.

Examples of common UCUM units:

UnitUCUMUnitUCUMUnitUCUM
AmpereALiterLSeconds
BarbarMinuteminVoltV
Bar gaugebargNormal literNlVolt-ampereVA
CelsiusCelPercent%Volt-ampere reactiveVAR
HourhPercent LEL%LELWattW
KilowattkWRevolutions per minuterpmWatt-hourWh

Step 7: Add Access Levels

v3 adds access_level field to control who can read telemetry, view properties, execute commands, or modify configuration.

properties:
fw_ver:
display_name: Firmware Version
access_level: readonly
telemetry:
temperature:
display_name: Temperature
access_level: readonly
commands:
restart:
display_name: Restart Device
access_level: user
configuration:
connection:
display_name: Connection Settings
access_level: installer

Available levels (lowest to highest): readonly, user, owner, installer, vendor, system. The default value for properties and telemetry is readonly, while for commands and configuration it is user.

Step 8: Enhance Alerts Metadata

v3 adds richer alert metadata for better troubleshooting. Add troubleshooting steps, related telemetry, affected components, and possible conditions for alerts.

Manifest v1
alerts:
cannot_read_data:
severity: error
display_name: Cannot Read Data
description: Failed to read data
Manifest v3
alerts:
cannot_read_data:
severity: error
display_name: Cannot Read Data
description: Failed to read data
troubleshooting:
- Check physical connections
- Verify device is powered on
telemetry:
- packet_loss
components:
- Hardware Communication
conditions:
- Hardware failure
- Connection issue
- Wrong configuration

Step 9: Add Command Response Definitions

v3 allows documenting command responses in the manifest. Add response field to commands that return data.

commands:
get_temperature:
display_name: Get Temperature
arguments:
unit_id:
display_name: Unit ID
type: integer
response:
temperature:
display_name: Temperature
type: float

Step 10: Use JSON for Complex Command Arguments

v3 adds native JSON type support for command arguments. Use type: json with json_schema field (inline JSON or path to schema file).

commands:
write:
display_name: Write
group: modbus
arguments:
queries:
display_name: Write Queries
type: json
json_schema: |
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
}
}

Step 11: Update Error Handling in Lua Code

v3 uses string error messages instead of numeric error codes. Replace result ~= 0 checks with err ~= nil and remove err_to_str() conversions.

Lua v1
local state, result = di7.is_closed(1)
if result ~= 0 then
enapter.log('Error: ' .. di7.err_to_str(result), 'error')
end
Lua v3
local channel, err = digitalin.new('port://di-1')
if err ~= nil then
enapter.log('Error: ' .. err, 'error')
return
end

local state, err = channel:get_state()
if err ~= nil then
enapter.log('Error: ' .. err, 'error')
end

Step 12: Update to Object-Oriented Lua API

v3 replaces function-based hardware APIs with object-oriented channel APIs. Create channel objects with module.new(uri) constructors, then call methods using colon syntax.

Lua v1
-- Call module function with port number
local state, _ = di7.is_closed(1)
telemetry.di1_closed = state
Lua v3
-- Create channel object
local di1, _ = digitalin.new('port://di-1')

-- Call methods on the object via colon syntax
local state = di1:get_state()
telemetry.di1_closed = (state == digitalin.HIGH)

Common hardware module migrations (not exhaustive list):

v1 Modulev3 Module
di7digitalin
rl6relay, digitalout
ai4analogin
ao4analogout
rs232, rs485serial
cancan
modbusmodbus

See Lua API reference for more examples of available APIs.

Step 13: Use Declarative Configuration

v3 moves configuration from Lua code to manifest.

  1. Add configuration section to manifest.
  2. Remove read_configuration and write_configuration commands from manifest.
  3. Remove require('enapter.ucm.config') and config.init() from Lua code, and enapter-ucm Luarocks dependency from the manifest.
  4. Use configuration.read() to access config values.
  5. Use configuration.after_write() to handle config changes.
Manifest v3
configuration:
connection:
display_name: Connection
parameters:
connection_uri:
display_name: Connection URI
type: string
required: true
Lua v3
local conn

function enapter.main()
reconnect()
configuration.after_write('connection', function()
conn = nil -- Reset connection on config change
end)
scheduler.add(1000, reconnect)
end

function reconnect()
if conn then return end
if not configuration.is_all_required_set('connection') then return end

local cfg, err = configuration.read('connection')
if err then return end
conn, _ = modbus.new(cfg.connection_uri)
end
tip

It is a common pattern to create and use the connection_uri parameter to configure how and where device should connect to. See more about connection URIs.

See configuration documentation for more details.

Step 14: Use enapter.main() as Entrypoint

v3 automatically calls enapter.main() as the entrypoint. Remove manual main() calls from your code.

Lua v1
function main()
scheduler.add(1000, send_telemetry)
end

-- Manual call required
main()
Lua v3
function enapter.main()
scheduler.add(1000, send_telemetry)
end

-- No manual call needed
-- enapter.main() is called automatically

All Rights Reserved © 2025 Enapter AG.