Production B2B system · Full-stack ownership
MowFleet Control Center: operating an autonomous mower fleet
MowFleet runs fleets of autonomous Husqvarna mowers, and somebody has to answer the customer's simplest question: did the robots actually mow my lawn? I designed and built MowFleet Control Center — a Next.js dashboard on top of scheduled AWS ingestion, normalized DynamoDB data and S3-managed maps — to turn raw fleet telemetry into coverage, utilization, errors and reports people can act on. I still support it today.
The constraint that shapes everything
Nobody works on MCC full-time — not me, not anyone at MowFleet. Every technical choice answers to that: managed services over clever infrastructure, boring failure modes over elegant abstractions, sync jobs that recover on their own. My part covered requirements discovery, architecture, implementation and rollout, plus the support calls whenever a vendor API shifts under the product.
Architecture: keep the vendor at arm's length
Husqvarna OAuth and a partly reverse-engineered Fleet Services surface feed scheduled Lambda jobs. The jobs normalize external responses into DynamoDB tables, while GeoJSON zone data is stored in S3. The dashboard reads MowFleet-owned contracts instead of vendor response objects, limiting the impact of upstream changes. The architecture diagram is generated from Mermaid source stored with the implementation.
What operators get
Fleet overview
Utilization summary, mower counts, error summaries, zone coverage and cumulative savings for energy, CO2 and labor.
Mowers and errors
A working view of individual machines, current issues and historical error details, grouped through Husqvarna access groups.
Activity and zones
Mowing sessions become heatmaps and zone coverage views, so customers can see where autonomous mowers actually worked.
Maps and reporting
Admins can manage GeoJSON map data and generate PDF reports from the same chart data used by the live dashboard.
Operator workflows
Operators need to correlate mower state, zone-level activity and customer reporting data. The screens are intentionally information-dense and organized around investigations such as a missed coverage target, an inactive machine or a discrepancy between utilization and recorded mowing sessions.
Two kinds of reads
Interactive authentication and selected live reads go through Husqvarna directly. Historical activity, utilization and error data are collected by scheduled jobs and served from MowFleet-owned AWS stores. This separates user-facing query latency from the availability and response shape of the vendor API.
- 01 Husqvarna OAuth
- 02 Fleet Services API
- 03 Scheduled Lambda sync
- 04 DynamoDB + S3
- 05 MCC API endpoints
- 06 Dashboard + reports
One pair of hands, the whole path
There was no frontend team, backend team or ops team to split this across — the same delivery scope ran from product discovery through vendor ingestion to the PDF a customer receives. Concretely:
- Requirements were derived from fleet operations, reporting obligations, access-group permissions and field-support workflows.
- Implementation covered the Next.js application, API surface, scheduled ingestion, storage, OAuth, maps, charts and report generation.
- Production support has included report discrepancies, missing utilization data, EPOS compatibility and data requirements for a future mobile client.
- Because no engineer operates MCC continuously, the system favors managed AWS services, explicit data boundaries and recoverable sync jobs.
Design decisions
Use access groups as the tenancy boundary
Dashboard queries and backend endpoints consistently filter by Husqvarna access group, separating root administration from customer-scoped views.
Normalize vendor data before reads
Historical metrics are not calculated from the vendor API on every request. Scheduled Lambdas transform responses into DynamoDB access patterns that the dashboard can query predictably.
Keep OAuth exchange server-side
Next.js API routes handle the OAuth redirect and refresh-token exchange, so application secrets do not move into browser code.
Treat maps as operational data
Zone maps are GeoJSON files in S3, not hardcoded frontend assets. That keeps customer map data updateable without rebuilding the dashboard.
Reuse dashboard data in reports
PDFs are generated client-side with react-pdf. Charts are rendered, captured as images and reused inside reports so exported documents match the dashboard view.
Isolate vendor API volatility
The reverse-engineered Husqvarna integration is confined to the sync and API layer. Fleet Services changes therefore do not require every chart and page to understand upstream response shapes.
Design for unattended operation
Serverless deployment, managed storage and scheduled ingestion minimize the number of services that require continuous operator attention.
Actual tickets from production
Support here is concrete: something observable stops matching reality and I go find out why. A sample of real cases — vendor API changes eating statistics, robot compatibility checks, and visibility features Husqvarna quietly removed from their own product:
- Investigating why Utilization Summary can stop showing activity while Zones Coverage still proves that robots were mowing.
- Explaining utilization calculations when customer reports reveal domain rules such as parts of a site being mowed twice per week.
- Checking support for Husqvarna 580 EPOS and 540 EPOS robots used in a MowFleet hybrid setup.
- Scoping whether MCC can replace visibility that Husqvarna removed from Automower Connect for customer staff: charging, no loop signal and cutting-height warnings.
- Helping future app work align with the existing backend data flow and MCC data model.
What I'd tackle next
Most of the remaining risk lives at the vendor boundary and inside the biggest sync jobs, so that's where the next effort goes:
- Make sync jobs more chunked and parallel so large fleets do not run into long Lambda execution windows.
- Redesign the oldest DynamoDB access patterns around accessGroupId where it would reduce query cost and filtering.
- Add first-class alerting for operational thresholds such as no loop signal, low cutting height and stalled activity.
- Build better contract tests around Husqvarna response shapes, because the external API is the riskiest moving part.