Pulsen A.I — An Azure IoT Cloud Platform for Solar, Battery Storage and Microgrid Operations
HOME - case studies - pulsen ai ems azure iot cloud platform
Pulsen A.I — An Azure IoT Cloud Platform for Solar, Battery Storage and Microgrid Operations

Client
Solpulsen (Sweden)
Industry
Energy & Renewables / Smart Microgrids
Region
Northern Europe
Project Type
Industrial IoT Cloud Platform
Delivery Model
Three verified phases — V1 telemetry, V2A heartbeat, V2B commands
Platform
Microsoft Azure (IoT Hub, Event Hubs, Container Apps, PostgreSQL)
Client Overview
Solpulsen is a Swedish energy company building and operating smart microgrids — solar PV generation, battery energy storage systems (BESS), grid connections and controllable loads, combined at a customer site and coordinated so the site draws less from the grid, stores cheap energy and rides through price peaks.
Each site is run by an EMS controller: an industrial edge device supplied by a specialist manufacturer that speaks Modbus RTU and Modbus TCP to the inverters, battery racks, meters and generators installed on site. The controller is very good at local control. What it does not do is give Solpulsen a company-wide view of every site, a historical record they own, or a way to act on a site from anywhere but the site itself.
Solpulsen's commercial product, Pulsen A.I, depends on exactly that: continuous visibility across a growing fleet of installations, and eventually the ability to dispatch energy remotely. They engaged TTPL to design and build the cloud platform that turns a set of independent controllers into one operable fleet.
Challenges & Problem Statement
Solpulsen's growth plan created pressure the existing setup could not absorb:
- No fleet-level visibility. Site data lived on the site controller. Answering "how did all our installations perform last month" meant visiting each one — a cost that scales linearly with every new installation sold.
- The data was not theirs to keep. Operational history sat inside third-party hardware in a vendor format. Without an independent, durable record, Solpulsen could not build reporting, billing or performance analytics on its own terms.
- Reading is not enough — energy businesses need to act. The commercial value of a microgrid depends on dispatching storage at the right moment. That requires sending commands from the cloud to the hardware, with proof of what actually happened.
- Safety and liability could not be hand-waved. A command that silently fails on live energy equipment is not a bug report, it is an incident. Every command needed an evidence trail that stands up after the fact.
- A hardware vendor Solpulsen does not control. The controller protocol, its release schedule and its firmware behaviour all belong to a third party. The cloud platform had to be robust to a contract that could change without notice.
Project Overview
TTPL designed and delivered the complete Azure cloud platform behind Pulsen A.I. Controllers publish MQTT over TLS to Azure IoT Hub, which authenticates each device with its own revocable identity and routes messages by type onto Azure Event Hubs. A dedicated .NET 10 Worker Service running 24/7 on Azure Container Apps consumes that stream and writes to PostgreSQL twice in a single pass — once as the verbatim raw frame for audit and replay, once as typed, indexed time-series rows for dashboards. A separate ASP.NET Core REST API container serves web and mobile clients and hosts the cloud-to-device command service.
Delivery ran in three verified phases: V1 telemetry ingestion, V2A heartbeat and cloud-link monitoring, and V2B downlink commands — a full issue, validate, dispatch, acknowledge, complete lifecycle with timeout handling and an append-only audit log. V2B was proven end-to-end against the live controller in the field, with physical actuation deliberately left disabled until the hardware command contract is formally signed off.
Approach
TTPL treated this as an integration and reliability problem first, and a feature-delivery problem second. The controller protocol was the fixed point everything else had to accommodate, so the work began by writing the integration contract down — envelope shape, message types, identifiers, required fields — and building a device simulator against it before any cloud infrastructure existed. The simulator speaks raw MQTT rather than using the Azure SDK, precisely so that it exercises the same contract the real hardware implements, including a mock command responder. That decision paid for itself repeatedly: the full pipeline could be built, broken and rebuilt without waiting on field hardware or vendor availability.
The second principle was that correctness should come from idempotency, not from optimistic delivery guarantees. Rather than pursuing exactly-once semantics that no layer of the stack actually offers, every write deduplicates on the vendor message identifier, so redelivery after a crash is a no-op by construction. The same reasoning shaped storage: raw JSONB is kept forever because it is the contract with the vendor and the replay source if a mapping bug surfaces months later, while typed tables exist because dashboards need indexed numeric columns rather than JSON scans.
The third was separation by scaling axis. Ingestion and the read API were built as independent deployable services sharing one database through two schemas, so device volume and user traffic never compete and a failure in either cannot take down the other.
Phasing was explicit and gated. V1 shipped and was verified before V2A started; V2A before V2B. Each phase ended with evidence — row counts, dead-letter counts, timed round trips — rather than a status update. For commands, the safety posture was decided before the code: the message path would be proven in production using mock commands only, with physical actuation disabled by default and enabled per command, never as a blanket switch, once the manufacturer delivers command codes, parameter ranges, safety limits and readback rules.
Solution Delivered
Buffered Ingestion Path — IoT Hub to Event Hubs to PostgreSQL
Controllers publish MQTT over TLS 1.2 to Azure IoT Hub, which terminates TLS, authenticates each device against its own revocable identity and routes messages onto Event Hubs by message type. The event stream sits deliberately between the broker and the database: every accepted message is durably retained for 72 hours whether or not anything downstream is reading it. A worker redeploy, a crash or a brief database outage costs nothing — the worker resumes from its last recorded position and catches up.
Dual-Write Storage — Verbatim Audit Frame plus Typed Time Series
Each frame is written twice in one pass. The raw payload goes to a JSONB table exactly as received, with stream partition, sequence and enqueue metadata — the audit record and the source of truth for any future replay. In parallel, the envelope method selects a parser that writes typed rows: per-phase voltages and currents, run status and power limits, and per-device EMS telemetry covering PV, battery, grid, load and generator power with state of charge. Unknown message methods are stored raw with a warning rather than rejected, so a new vendor message type can never break ingestion.
Idempotent Writes and Dead-Letter Isolation
Every insert deduplicates on the vendor message identifier using a unique constraint with on-conflict-do-nothing, which makes at-least-once redelivery safe by construction instead of by hope. Frames that fail to parse are diverted to a dead-letter table with the payload and the error text, and processing continues — a poison message from a firmware change can never block the stream for the whole fleet.
Two Identifiers, Real Per-Device Time Series
Every stored row carries both the authenticated controller identity stamped by the broker and the vendor position identifier that names the specific device behind that controller. One site controller reporting an inverter, a battery rack and a meter therefore lands as three distinct, independently queryable series, and a device cannot spoof another device’s identity because the controller identity is assigned at authentication time rather than taken from the payload.
Independent Ingestion and Read Services on One Database
Ingestion runs as a headless worker container with no HTTP surface; the REST API runs as a separate container with external ingress. They share a single PostgreSQL server through two schemas — one created and owned by the worker, one managed by Entity Framework for the tenant, site, controller and device hierarchy plus identity and command tables. One source of truth, two independently deployable and independently scalable services.
Heartbeat and Cloud-Link Monitoring
The platform tracks last-seen time and cloud-link status per controller against a configurable heartbeat interval, detects missed heartbeats and records offline and recovery events. Because device clocks proved unreliable in the field, liveness is judged on broker-side sequence and enqueue time rather than on any timestamp the device reports — a design rule that came directly from an observed multi-hour clock skew on live hardware.
Audited Cloud-to-Device Command Lifecycle
Commands move through issue, validate, dispatch, acknowledge and complete as explicit states. A configuration-driven registry whitelists every command code and validates each parameter by type, range, enum and required-ness before anything leaves the cloud. The command is dispatched as a cloud-to-device message with a time-to-live matching its expiry; the controller’s acknowledgement and final result return on a dedicated event stream with its own consumer group. A timeout monitor forces a deterministic terminal state when a reply never arrives, idempotency is enforced by request identifier, and only one command is allowed in flight per device.
Append-Only Command Audit Trail
Every command is stored as an aggregate with its exact dispatched payload, alongside an append-only event log recording each state transition with its timestamp and reason. After the fact, an operator can show precisely what was sent, when the device accepted it, what result it reported and how long each step took — the evidence trail an energy business needs when a command outcome is questioned.
Physical Actuation Behind an Explicit Safety Gate
Physical actuation shipped disabled. Only a small set of safe mock commands were enabled, which allowed the entire command path to be verified against the live controller in production with zero risk to energy equipment. Real commands are onboarded one at a time by adding a definition to the registry — code, parameters, ranges, safety limits and readback rule — and enabling that command individually, each followed by a fresh verification cycle.
Device Simulator and Test Harness
A dedicated simulator impersonates the controller end to end using raw MQTT rather than the cloud SDK, so it exercises the exact contract the real hardware implements. Scheduled jobs publish realistic run, base and per-device frames at production cadence while a parallel reader confirms arrival on the event stream, letting publish and ingest be observed side by side. A mock command controller completes the loop for command testing, and the command service itself is covered by unit tests.
Least-Privilege Credentials and a Documented Blast Radius
Every application credential is scoped to the narrowest capability that works: the ingestion worker and the API hold listen-only stream credentials that can read but cannot inject. The genuinely high-value secrets — device keys and the service credential that can send commands — were identified explicitly, documented with their blast radius, and separated from everything the applications carry day to day, with secrets held as encrypted platform secrets and a documented path to a managed vault.
Result
Pulsen A.I now runs as a continuously operating Azure platform rather than a set of isolated site controllers. Telemetry ingestion, heartbeat monitoring and downlink commands are all live, and none of them depend on a developer machine being switched on.
Telemetry (V1) was verified end to end through the deployed cloud worker, with raw frames, per-phase run data, base data and per-device EMS rows all landing correctly and zero dead-letter rows in normal operation — while a deliberately malformed test frame was correctly captured with its error rather than blocking the stream. Backlog replay from stream retention was confirmed, proving the buffering design does what it was chosen for.
Heartbeat and cloud-link monitoring (V2A) gives Solpulsen per-controller liveness with offline and recovery events, based on broker-side timing rather than untrusted device clocks.
Downlink commands (V2B) passed end-to-end verification on the live field controller. A parameterised command reached acknowledgement in roughly one second and a final completed result shortly after, for a measured issue-to-completed round trip of 1.77 seconds, with the same request identifier traceable through the dispatched payload, the acknowledgement, the result and four audit-log transitions. The phase was then formally frozen, with physical actuation left disabled pending the manufacturer's signed-off command contract.
Two field problems were found and resolved through this work that would otherwise have surfaced during commercial operation: a controller clock running eight hours ahead of UTC that caused valid commands to be rejected as expired, and a stream consumer configuration that could drop replies under load. Both were fixed and re-verified.
Client Benefits
Fleet-Wide Visibility Instead of Per-Site Visits
Every connected site reports into one platform continuously, so operational questions are answered from the cloud rather than by travelling to an installation. The cost of understanding the fleet stops scaling with the number of sites sold.
An Operational Record Solpulsen Owns
Raw frames and typed time series both live in Solpulsen’s own database. Reporting, billing, performance analysis and future analytics can be built on data the business controls, not extracted from third-party hardware in a vendor format.
No Telemetry Lost to Deployments or Outages
Seventy-two hours of durable stream retention means redeploys, worker restarts and brief database outages cause a delay rather than a gap. The worker resumes from its last checkpoint and catches up — verified in practice by replaying a backlog from retention.
Remote Dispatch with Proof, Not Assumption
Commands resolve to a deterministic terminal state with an append-only trail showing what was sent, when it was accepted, what the device reported and how long each step took — verified on live hardware at a 1.77-second round trip.
A Commercial Path That Cannot Be Rushed Into Risk
Physical actuation stays off until the manufacturer delivers command codes, ranges, safety limits and readback rules, and is then enabled one command at a time. Solpulsen proved the technology in production without ever putting live energy equipment at risk.
Resilience to a Vendor Contract Solpulsen Does Not Control
Unknown message types are stored rather than rejected, unparseable frames are isolated instead of blocking the stream, and the raw archive allows typed tables to be rebuilt if a mapping assumption turns out to be wrong. A firmware change on the vendor side degrades gracefully instead of causing an outage.
Hardware-Independent Development and Testing
The simulator reproduces the controller’s exact MQTT contract, including command responses, so new work can be built and regression-tested without field hardware, site access or vendor scheduling.
Headroom Designed In, Not Retrofitted
Scaling is a configuration change rather than a redesign: more broker units for more devices, more throughput units for more volume, and up to one worker replica per stream partition, with partition ownership coordinated automatically.
Technologies Used


