System Architecture
ESP RainMaker Neo is an IoT platform you deploy into your own AWS account: firmware on the device, a serverless backend built from AWS managed services, and clients that talk to both. This page is the shape of the whole system, for someone evaluating Neo or trying to work out which part of it a problem belongs to.
Neo has no servers you operate and no shared multi-tenant service in the middle. Every deployment is a set of CloudFormation stacks in one AWS account, in one region.
The four planes
Device plane. The Neo firmware SDK, one C source tree that builds for both ESP-IDF and POSIX targets. A Node holds an X.509 certificate, connects to AWS IoT Core over mutual TLS, and owns its own state: it evaluates Schedules from its real-time clock, watches Trigger conditions, and reports Parameter values into its shadows.
Cloud plane. AWS IoT Core is the message broker and the shadow store. Around it sits a serverless backend: API Gateway in front of Go functions on Lambda, DynamoDB for state, S3 for firmware images and device files, and IoT rules that route device messages — sometimes straight to a Lambda, sometimes straight into DynamoDB with no compute in the path at all.
Client plane. Phone apps, web dashboards, and voice assistant integrations. Clients use two separate credentials: a signed HTTPS call to the REST API for anything transactional, and a scoped MQTT connection to IoT Core for live state and control. Neither the phone apps nor the web clients live in the Neo repositories — they are the existing ESP RainMaker apps and SDKs, talking Neo's protocols. Users of the system interact with their nodes through these clients.
Admin plane. A dashboard and its APIs, backed by a separate identity pool and a separate IAM role. Admins interact with the system through this interface. Admins register Nodes in bulk, run OTA jobs, and query the fleet. They deliberately hold fewer AWS permissions than you might expect — see Security.
What runs in your AWS account
Neo deploys as a small number of CDK stacks, split by lifecycle rather than by feature:
- An identity pair of stacks creates the two Cognito user pools — one for end users, one for admins — and their app clients.
- A base stack holds everything long-lived: the Cognito Identity Pool and the IAM roles it hands out, the API Gateway REST API and its Cognito authorizer, DynamoDB tables, S3 buckets, the default IoT policy attached to every Node, IoT role aliases, and the AWS IoT Fleet Indexing configuration.
- A core stack holds the compute: the Lambda functions and their API Gateway wiring, grouped per feature area — users, groups, nodes, node admin, node services, notifications, files, voice assistant integrations.
- An optional admin dashboard stack hosts the operator UI.
Splitting base from core is what makes the backend safely re-deployable: you can ship new function code without touching the resources that hold your data or your customers' identities.
Two AWS services show up in less obvious roles. ECS Fargate runs bulk Node registration, because a job covering thousands of devices does not fit inside a Lambda timeout. SQS is an optional shape for the device-to-cloud request path: the same handler can either be invoked directly by an IoT rule, one invocation per message, or fed from a queue that absorbs bursts. Which mode is active can be flipped at runtime by a super admin, and neither the firmware nor the wire protocol changes between them.
How a parameter change travels
The path from a user tapping a toggle to that Node's state being visible to every other client:
- The client (say a phone app) exchanges its Cognito ID token for temporary AWS credentials, then asks the backend for a second, narrower set of credentials scoped to the MQTT topics of the Groups it can reach.
- Using those credentials, the client publishes the new value to the Node's parameter topic, addressed by Device ID and Parameter ID.
- The Node would have already subscribed to the relevant topics. The Node's IoT policy permits it to subscribe only to its own topics, so no other Node can receive it.
- AWS IoT Core delivers the client's message to the Node's topic.
- The Node's write callback runs, the firmware applies the value, and the Node reports the resulting state into its group-scoped shadow.
- Any client reads current state from that shadow. Nothing in the loop polls the device.
- If the changed Parameter is marked
indexed, the Node also updates itsiparamsshadow. An IoT rule mirrors that into DynamoDB — no Lambda involved — so a dashboard can query 10,000 Nodes without 10,000 shadow reads.
Step 2 is a plain MQTT publish, not a write to the shadow's desired section. That single design choice explains most of Neo's runtime behaviour: control is fast and stateless, and it is also fire-and-forget, so a Node that is offline at that instant never receives the change. Node–Cloud Communication covers the consequences.
What Neo does not include
Being explicit about the edges saves evaluation time:
- No hosted service. There is no Espressif-operated Neo cloud to sign up for. You deploy it, you own the AWS bill, and you own the data.
- No store-and-forward for control. A parameter set to a disconnected Node is not queued for later delivery. Features that need delivery guarantees have to be built on top.
- No always-on connectivity index. AWS IoT's own connectivity indexing is deliberately left off, because "the MQTT socket is open" is not the same as "the Node has finished subscribing and is ready for commands". Neo derives online state from what the Node itself reports instead.
For the individual Lambda functions, DynamoDB tables, and IoT rules behind these services, see cloud backend architecture.
Related
- Cloud backend architecture — the detailed version, with named resources
- Node–Cloud Communication — the device plane in detail
- Security — identity, authorization, and least privilege across the planes
- Deployment — what actually gets created in your AWS account, and how