Hobby-freelance project · since 2023
MowFleet Control Center: operating an autonomous mower fleet
MowFleet runs fleets of autonomous Husqvarna mowers, and its customers ask a simple question: did the robots mow the site? I designed and built MowFleet Control Center (MCC), a Next.js dashboard backed by a serverless AWS pipeline. It turns Husqvarna fleet data into coverage, utilization, errors, live warnings and PDF reports. I started it in 2023 and still maintain it.
Built to run unattended
Nobody works on MCC full-time, me included. That drove most choices: managed AWS services, few moving parts, and sync jobs that simply retry on the next schedule. My part covered requirements, architecture, implementation and rollout, plus support when the vendor API changes.
- 5 min
- activity and live sync
- 1 hour
- access-group sync
- 2 hours
- utilization + errors
- 24 hours
- season summary
- Designed and built the dashboard, backend services, data model and deployment, starting in 2023.
- Periodic contract engagement covering production support and targeted feature work.
- MowFleet Control Center is an operations dashboard for autonomous mower fleets.
- Next.js 16 dashboard with React 19, Tailwind 4, daisyUI 5, SWR, Chart.js, Google Maps and react-pdf, hosted on Vercel.
- Serverless Framework v4 backend on AWS Lambda (Node 24, arm64), API Gateway, DynamoDB and S3 in eu-central-1.
- Husqvarna OAuth and Fleet Services APIs are the source of users, access groups, mower state, utilization and errors.
- The integration includes a partly reverse-engineered Husqvarna web and Fleet Services surface.
- Scheduled Lambdas sync activity and live snapshots every 5 minutes, access groups hourly, utilization and errors every 2 hours, and season summaries daily.
- Most support work is triggered by vendor API changes or new operational requirements.
- The application covers fleet analytics, zones, maps, mower state, live warnings, activity history, settings and PDF reports.
Architecture
Husqvarna OAuth and a partly reverse-engineered Fleet Services API feed scheduled Lambda jobs, which normalize the responses into DynamoDB; GeoJSON zones and season summaries live in S3. Historical charts read MowFleet's own API. Live mower status is still read straight from Husqvarna in the browser.
What operators get
Fleet overview
Utilization summary, mower counts, error summaries, zone coverage, cumulative energy and CO2 savings, and monthly labor savings.
Live warnings
A live panel, refreshed every minute, flags mowers with no loop signal, a cutting height under 45 mm, mowers that stopped reporting and charging that did not verify: visibility Husqvarna removed from Automower Connect.
Mowers and errors
Individual machines, their 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 the mowers worked.
Maps and reporting
Admins manage GeoJSON map data and generate PDF reports from the same chart data the dashboard shows.
Operator workflows
Operators need to correlate mower state, zone-level activity and customer reporting data. The screens are dense on purpose and organized around investigations such as a missed coverage target, an inactive machine or a gap between utilization and recorded mowing sessions.
Two kinds of reads
Sign-in and live mower status go through Husqvarna directly. Historical activity, utilization and error data come from scheduled jobs and MowFleet's own AWS stores, so the historical charts make no per-mower vendor calls. Sign-in checks, cached for five minutes, and live status still depend on Husqvarna.
From external fleet data to customer-facing operations
- 01 Husqvarna OAuth per-user sign-in; a server route exchanges the code vendor
- 02 Fleet Services web API live mower inventory and status, read in the browser vendor
- 03 Scheduled Lambda sync activity, live snapshots, utilization and errors on timers MowFleet
- 04 DynamoDB + S3 normalized history, GeoJSON zones and season summaries MowFleet
- 05 MCC API endpoints the query shapes the dashboard asks for MowFleet
- 06 Dashboard + reports operator screens and the customer-facing PDF MowFleet
Scope
I built every layer myself, from requirements to vendor ingestion to the PDF the customer receives:
- Requirements came 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, live warnings and data requirements for a future mobile client.
Design decisions
Use access groups as the tenancy boundary
Dashboard queries and backend endpoints filter by Husqvarna access group, separating root administration from customer-scoped views.
Normalize vendor data before reads
Scheduled Lambdas copy vendor data into DynamoDB tables shaped for the dashboard’s queries, so historical charts never call Husqvarna per request.
Keep the OAuth exchange server-side
A Next.js API route exchanges the OAuth code on the server, so the app secret never reaches the browser.
Treat maps as operational data
Zone maps are GeoJSON files in S3, not frontend assets, so customer maps change 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.
Know where the vendor still leaks in
Live mower status is still read straight from Husqvarna’s Fleet Services web API in the browser, and every backend request checks the user against Husqvarna, cached for five minutes. Those are the two places an upstream change can still reach the screen.
Design for unattended operation
Serverless deployment, managed storage and scheduled ingestion keep the number of things that need watching small. A failed sync is retried on the next schedule.
Support cases
Most support starts when something on screen stops matching what happened in the field. Typical cases:
- 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 zones mowed only every other week.
- Checking support for Husqvarna 580 EPOS and 540 EPOS robots used in a MowFleet hybrid setup.
- Building live warnings to replace visibility Husqvarna removed from Automower Connect for customer staff: charging, no loop signal and cutting height.
- 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 sits at the vendor boundary and in the largest sync jobs:
- Chunk and parallelize the sync jobs so large fleets stay well inside Lambda limits, and isolate accounts so one failing account does not abort a run.
- Redesign the oldest DynamoDB access patterns around accessGroupId where it would reduce query cost and filtering.
- Push alerts by email or SMS on top of the live warnings.
- Move the remaining live Husqvarna reads behind the backend, so the browser only talks to MowFleet’s API.
- Add contract tests around Husqvarna response shapes, because the external API is the riskiest moving part.