Tie points to locator serial numbers and store receiver telemetry

Adds devices/<serial>/log as a job-anchored MQTT ingest path: a locator
identifies itself by serial in the topic (auto-registered on first
sight, org resolved from the job in the payload) rather than by a
pre-provisioned broker credential. Device.serialNumber is now globally
unique so the bare topic segment is enough to resolve identity.
Locator lookup/auto-register logic is shared (LocatorRegistryService)
between this and the existing devices/<mqttUsername>/points path.

New /sim page (own header, outside the main app nav) simulates a
transmitter: pick an open job or create one, set a serial number and
telemetry defaults, and send points one at a time or on an interval
along a simulated walking path. It calls a new authenticated backend
endpoint (POST /orgs/:orgId/sim/publish) that publishes onto the real
broker rather than writing the DB directly, so the simulator exercises
the actual ingest pipeline end-to-end.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
ulhub
2026-07-14 19:41:58 +00:00
parent b7479fa68f
commit e91c91e037
16 changed files with 672 additions and 50 deletions

View File

@@ -27,10 +27,10 @@ export class DevicesService {
}
if (dto.serialNumber) {
const existing = await this.prisma.device.findUnique({
where: { orgId_serialNumber: { orgId, serialNumber: dto.serialNumber } },
where: { serialNumber: dto.serialNumber },
});
if (existing) {
throw new ConflictException('A device with this serial number already exists in this organization');
throw new ConflictException('A device with this serial number already exists');
}
}
const device = await this.prisma.device.create({
@@ -46,7 +46,8 @@ export class DevicesService {
note: 'Broker credentials must be created separately (mosquitto_passwd) until dynamic broker auth lands.',
}
: {
note: `Relayed locator: points published by a gateway should carry "serial": "${device.serialNumber}".`,
logTopic: device.serialNumber ? `devices/${device.serialNumber}/log` : undefined,
note: `Relayed locator: points published by a gateway should carry "serial": "${device.serialNumber}", or a direct-connect locator can publish single readings to devices/${device.serialNumber}/log.`,
},
};
}