Migration Lambda
Overview
The Migration Lambda provides three core operations for managing IoT device metadata between different systems for Sensors and Gateways which are present in the InstalllerAppDeviceDetails table:
enrichMetadataFromLoRaWAN: Backfills missing device metadata from LoRaWAN tables to InstallerAppDeviceDetails
renameIoTCoreNames: Updates IoT Core device/gateway names & description and stores metadata back to InstallerAppDeviceDetails
migrateMetadata: Migrates device metadata from InstallerAppDeviceDetails to LoRaWAN Devices/Gateways tables
Note: Recommended execution order is enrichMetadataFromLoRaWAN → renameIoTCoreNames → migrateMetadata to ensure all required metadata is available for subsequent operations.
Flow Diagram
NOTE – Enrichment Pre-Step
A new, silent pre-step (enrichMetadataFromLoRaWAN) now runs before the two operations shown above.
Its only purpose is to back-fill any missing values for Vendor, Model, Floor, or FullEUI in InstallerAppDeviceDetails.
API Reference
Request Parameters:
{
"enrichMetadataFromLoRaWAN": boolean, // Enable metadata enrichment operation
"renameIoTCoreNames": boolean, // Enable IoT Core renaming operation
"migrateMetadata": boolean, // Enable metadata migration operation
"site": string // Site filter specification
}Important Constraints:
Only one operation can run at a time
The Lambda returns an error if multiple flags are set to
true.
Site Filtering
The `site` parameter supports flexible filtering options:
Value | Description | Example |
|---|---|---|
All | Process all sites | {"migrateMetadata": true, "site": "All"} |
Single site | Process specific site | {"migrateMetadata": true, "site": "TEST"} |
Multiple sites | Comma-separated list | {"migrateMetadata": true, "site": "Site0,Site1"} |
Features:
Case-insensitive matching (e.g., "test" matches "TEST")
Whitespace handling (e.g., "Site0, Site1" works correctly)
Operation 0: enrichMetadataFromLoRaWAN
Purpose
Backfills missing device metadata (Vendor, Model, Floor, FullEUI) in InstallerAppDeviceDetails by looking up corresponding records in LoRaWAN Devices/Gateways tables.
When This Operation is Needed
Devices were created in InstallerApp before being provisioned in LoRaWAN
Frontend didn't populate all required metadata fields
Missing attributes prevent rename/migrate operations from succeeding
Decision Logic
The system determines the action for each device based on field completeness and EnrichmentStatus:
Decision | Condition | Action |
|---|---|---|
| Any of Vendor/Model/Floor/FullEUI is empty AND | Process device for first time |
| Any of Vendor/Model/Floor/FullEUI is empty AND | Retry previously failed device |
| All fields populated OR previous enrichment succeeded | Skip (counted in "previously_enriched") |
skip_other | Exception occurred during decision logic | Skip due to error |
Processing Flow
Scan InstallerAppDeviceDetails for records with missing metadata
Match by EntityId: Use last 5 characters to find corresponding DevEui/GatewayEui in LoRaWAN tables
Selective Update: Only populate empty fields, preserve existing data
Status Tracking: Update EnrichmentStatus, EnrichmentTimestamp, EnrichmentError
Status Values
Status | Description |
|---|---|
| Successfully populated missing fields |
| Retry succeeded after previous failure |
| No matching record found in LoRaWAN tables |
Operation 1: renameIoTCoreNames
Purpose
Rename IoT Core devices/gateways based on standardized naming conventions and update InstallerAppDeviceDetails with IoT metadata.
Decision Logic
The system determines the action for each device based on `IotRenameStatus`:
Decision | Condition | Action |
|---|---|---|
rename | `IotRenameStatus` is empty/null | Process device for first time |
retry | `IotRenameStatus` = "FAILED" | Retry previously failed device |
rerename | `IotRenameTimestamp` < `UpdatedAt` | Re-process updated device |
already_renamed | Device previously processed successfully | Skip (counted in "previously_renamed" ) |
Naming Conventions
Sensors
Format: `{Site}-{Model} {last5CharsOfEUI}`
Example:
Site: "TEST", Model: "EMS1", EUI: "c01122334455600" → "TEST-EMS1 56000"
Gateways
Format: `{Site}-{last5CharsOfEUI}`
Example:
Site: "TEST", EUI: "8153f95c7c1452211" → "TEST-52211"
IoT Core Description
The system generates a JSON description stored in both IoT Core and InstallerAppDeviceDetails:
{
"Location": "Column location",
"Floor": "P1",
"Site": "TEST",
"Vendor": "Elsys",
"Model": "EMS1"
}Status Tracking
Updates InstallerAppDeviceDetails with the following attributes:
Attribute | Description | Values |
|---|---|---|
IotRenameStatus | Processing status | IOT_RENAMED, IOT_RENAMED_AFTER_FAILED, IOT_RENAMED_AFTER_SOURCE_UPDATE, FAILED |
IotRenameTimestamp | Processing timestamp | ISO 8601 UTC format |
IotRenameError | Error details | Error message (cleared on success) |
WirelessIotName | Generated device name | As per naming conventions |
WirelessIotId | IoT Core device ID | AWS IoT Core identifier |
WirelessIoTDesc | JSON description | Structured metadata |
Error Conditions
Error Type | Condition | Status | Action |
|---|---|---|---|
Description too long | JSON description > 2048 characters | FAILED | Log error, store in IotRenameError |
Device not found | Device/Gateway not in IoT Core | FAILED | Log error, store in IotRenameError |
API Exception | Any other AWS API error | FAILED | Log error, store in IotRenameError |
Operation 2: migrateMetadata
Purpose
Copy device metadata from InstallerAppDeviceDetails to LoRaWAN Devices/Gateways tables.
Decision Logic
The system determines the action for each device based on `MigrationStatus`:
Decision | Condition | Action |
|---|---|---|
migrate | `MigrationStatus` is empty/null | Process device for first time |
retry | `MigrationStatus` = "FAILED" | Retry previously failed device |
remigrate | `MigrationTimestamp` < `UpdatedAt` | Re-process updated device |
already_migrated | Device previously processed successfully | Skip (counted in "previously_migrated" ) |
Pulse Counter Processing
Overview
The migration system handles sensors with pulse counters by creating child records in the LoRaWAN Devices table. Each pulse counter creates a separate record with a unique EUI suffix.
Processing Flow
Check HasPulseCounters: If
True, process pulse countersCreate Child Records: For each
DeviceType{i}that exists:
Generate child EUI:
{parent_eui}_P{i}Parse
PulseScaler{i}into value and unitCreate record with all pulse counter attributes
Set Parent DeviceType: Set parent record's
DeviceType = "LWM"Cleanup Orphans: Remove any existing
_P{i}records that no longer have correspondingDeviceType{i}
Pulse Counter Child Record Creation
When a sensor has HasPulseCounters = True, the system creates up to 3 child records:
Child Record EUI Format:
{parent_eui}_P1- First pulse counter{parent_eui}_P2- Second pulse counter{parent_eui}_P3- Third pulse counter
Example:
Parent EUI:
0004a30b012e2891Child EUIs:
0004a30b012e2891_P10004a30b012e2891_P20004a30b012e2891_P3
Pulse Scaler Parsing Logic
The system parses pulse scaler values from the format "{value} {unit}":
Source Field Examples:
PulseScaler1:10.0 kWPulseScaler2:1.0 galPulseScaler3:Empty value
Parsing Results:
PulseScaler1: Value =
10.0, Unit =kWPulseScaler2: Value =
1.0, Unit =galPulseScaler3: Value =
null, Unit =null
Attribute | Source Field | Example Value |
|---|---|---|
DevEui |
|
|
DeviceType |
|
|
Vendor |
|
|
Model |
|
|
InstallationTimestamp |
|
|
MigrationTimestamp | Current timestamp |
|
PulseScalarValue | Parsed from |
|
PulseScalarUnit | Parsed from |
|
Example Migration Result
Before Migration (InstallerAppDeviceDetails):
{
"EntityId": "0004a30b012e2891",
"FullEUI": "0004a30b012e2891",
"HasPulseCounters": true,
"DeviceType1": "Pulse Electricity Meter",
"DeviceType2": "Pulse Water Meter",
"PulseScaler1": "10.0 kW",
"PulseScaler2": "1.0 gal",
"Vendor": "Synetica",
"Model": "ENL-STS-P"
}After Migration (LoRaWAN Devices Table):
Parent Record:
{
"DevEui": "0004a30b012e2891",
"DeviceType": "LWM",
"Site": "TEST",
"SensorLocation": "Column test",
"InstallationTimestamp": "2025-08-19T23:45:47Z"
}Child Record 1:
{
"DevEui": "0004a30b012e2891_P1",
"DeviceType": "Pulse Electricity Meter",
"Vendor": "Synetica",
"Model": "ENL-STS-P",
"PulseScalarValue": "10.0",
"PulseScalarUnit": "kW",
"InstallationTimestamp": "2025-08-19T23:45:47Z"
}Child Record 2:
{
"DevEui": "0004a30b012e2891_P2",
"DeviceType": "Pulse Water Meter",
"Vendor": "Synetica",
"Model": "ENL-STS-P",
"PulseScalarValue": "1.0",
"PulseScalarUnit": "gal",
"InstallationTimestamp": "2025-08-19T23:45:47Z"
}Pulse Counter Cleanup and Deletion
The migration system includes automatic cleanup functionality to handle pulse counter record lifecycle:
Automatic Cleanup Scenarios
1. DeviceType Removal Cleanup
When a sensor has HasPulseCounters = true but a specific DeviceType{i} is empty/null:
System automatically deletes the corresponding
{parent_eui}_P{i}record from LoRaWAN Devices tableUses conditional delete to ensure the record existed before deletion
Logs cleanup actions for audit purposes
2. HasPulseCounters Toggle Cleanup
When a sensor's HasPulseCounters is changed from true to false:
System removes all potential orphaned
{parent_eui}_P1,{parent_eui}_P2,{parent_eui}_P3recordsEnsures data consistency between InstallerAppDeviceDetails and LoRaWAN Devices table
Handles cases where pulse counter configuration is removed
Benefits of Automatic Cleanup
Data Consistency: Prevents orphaned records in LoRaWAN tables
Configuration Changes: Handles device reconfiguration automatically
Audit Trail: All cleanup actions are logged for compliance
Error Resilience: Graceful handling of missing records
Target Tables & Field Mapping
LoRaWAN Devices Table (Sensors)
Source Field | Target Field |
|---|---|
SecondaryId | Site |
LocationType + LocationDesc | SensorLocation |
PulseScaler1 | PulseScalarValue |
PulseScaler1 | PulseScalarUnit |
DirectionalDegrees | DirectionalDegreesScalar |
WirelessIotName | WirelessIotName |
WirelessIotId | WirelessIotId |
WirelessIoTDesc | WirelessIoTDesc |
MigrationTimestamp | MigrationTimestamp |
DeviceType | DeviceType |
CreatedAt | InstallationTimestamp |
LoRaWAN Gateways Table (Gateways)
Source Field | Target Field |
|---|---|
SecondaryId | Site |
LocationType + LocationDesc | Location |
WirelessIotName | WirelessIotName |
WirelessIotId | WirelessIotId |
WirelessIoTDesc | WirelessIoTDesc |
MigrationTimestamp | MigrationTimestamp |
CreatedAt | InstallationTimestamp |
Status Tracking
Updates InstallerAppDeviceDetails with:
Attribute |
|---|