The second hardware revision is where a companion app stops being simple. Version one is in customers' hands, version two adds a sensor and a faster radio, and both have to work with the same app in the same store listing. By the third generation you are maintaining code paths for hardware you no longer manufacture, testing against units you have to keep in a drawer, and answering support tickets from someone who bought a device four years ago and just reinstalled the app.

Backward compatibility is not an engineering nicety. It is a commercial position: every unit you have ever sold is a promise, and a customer whose device stops working because you shipped an app update writes the review that costs you the next hundred sales. The good news is that almost all of the cost is avoidable if the compatibility strategy is designed into the first version rather than retrofitted into the third.

Version the protocol, not just the firmware

The mistake that creates most of the pain is having the app identify hardware by model name or firmware build number and branch on it with a chain of conditionals. That produces code no one can safely change, and it breaks the moment marketing renames a product.

The alternative is a protocol version plus a capability set: on connect, the device reports an integer protocol version and a structured description of what it can do, and the app branches on capabilities, never on model names. A concrete shape for a BLE device:

  • Device Information Service — manufacturer, model, hardware revision, firmware revision, serial number. Standard characteristics, so any diagnostic tool can read them.
  • Protocol version characteristic — a single integer incremented only when the wire format changes in a way that is not backward compatible.
  • Capability bitfield or feature list — one bit or one entry per optional capability: has a barometer, supports high-rate streaming, supports encrypted pairing, supports a factory reset command.

With that in place, adding a sensor in generation three does not touch generation one's code path at all. The app asks what the device can do and hides the UI for anything it cannot. Keeping this table straight over years is a discipline problem as much as a coding one, and it belongs with your broader firmware version management practice.

Negotiation, not assumption

Capability negotiation should be two-way. The device announces what it supports; the app announces which protocol versions it understands. Both sides then operate at the highest version they share. This is what lets an old app keep working with a new device — the new device drops back to the older format instead of sending packets the old app cannot parse. Without it, every device shipment silently breaks every app version older than that week's release.

Feature flags per hardware revision

Once capabilities are reported, the app needs somewhere to decide what to do with them. Hard-coding those decisions in a release means every capability change requires an app store submission and a two-to-seven-day wait for review.

A remote configuration layer solves this. The app fetches a small document keyed by hardware revision and firmware version, describing which features to show, which are in beta, and which are known broken on a specific revision — a same-day switch when a field problem appears on one production lot, with no build required. Cache it locally and always compile in a safe default set, so a device that has never reached the internet still functions.

The costs here are real and ongoing. Every additional supported revision multiplies your test matrix, and the maintenance bill is a line item most founders under-budget — the full picture is in what app maintenance actually costs after launch.

The support cost of a long tail

Supporting old units is not free, and it helps to see where the money goes:

CostWhat drives itTypical mitigation
Regression testingEvery release must be verified against every supported revision, by hand or on a device farmAutomated hardware-in-the-loop rigs with one unit of each generation
Retained hardwareYou need working samples of every generation, plus spares as they failBuy back or retain 5 to 10 units per generation at end of production
Code complexityBranching logic slows every new feature and invites bugs in old pathsCapability abstraction layer, so branches exist in one file
Support loadAgents must identify which generation a caller has before troubleshootingApp surfaces model and firmware on a diagnostics screen
Cloud schema dragOld devices send an older payload the backend must keep parsingNormalize at ingest into one internal schema

A reasonable planning number for a consumer hardware product is that each additional supported generation adds roughly 10 to 20 percent to app engineering effort per release. Three generations is manageable. Six, without a capability abstraction, is a rewrite waiting to happen — and often the honest answer is that it is time to think about what belongs in the next product rather than the next patch.

Forced updates: when they are justified

A forced update — the app refuses to run until a newer build is installed, or the device refuses to operate until firmware is updated — is a blunt instrument. Users hate it, and it fails badly when someone needs the device right now on a weak connection. It is justified in a narrow set of cases:

  • Security. A vulnerability that exposes user data or lets a third party control the device.
  • Safety. A firmware defect with a physical consequence — thermal, battery, or a device that actuates something.
  • Backend cutover. An API version being retired where the old app will simply stop working anyway; better a clear prompt than a silent failure.
  • Data corruption. A bug that damages stored user data the longer it runs.

Everything else should be a soft prompt: recommend the update, explain the benefit, and let it be dismissed. Where you do force it, the update path must be robust enough to survive a failed attempt without leaving the device unusable, which is why a dual-bank OTA design with rollback matters far more than the update policy itself. Never make the app the only route to a working device: a unit that cannot be used at all until an update completes will generate returns.

Store churn you do not control

Apple and Google move the floor under you on their own schedule: both regularly require builds to target a recent SDK, drop older OS versions from their tooling, and change permission rules — Bluetooth and location prompts have been reworked several times, and each change touches pairing flows. Your minimum supported OS will rise whether you like it or not, roughly every couple of years. Note who that strands: users on old phones are disproportionately the users with old hardware you were carefully still supporting. And store review can reject an update for a policy that did not exist when you last shipped, so never plan a hardware launch that depends on an app store submission clearing in under a week.

Sunsetting a generation without bricking it

Eventually you stop supporting a generation. Doing it well is mostly communication and graceful degradation:

  1. Announce a window. Six to twelve months of notice in the app, by email, and on the product page, stating exactly what will stop and what will keep working.
  2. Freeze, then retire. Stop adding features, keep shipping security fixes, and only then end updates entirely.
  3. Preserve local function. A sensor that still records and still pairs locally when the cloud goes away is a working product; one that becomes a paperweight is a support crisis.
  4. Give people their data — bulk export before the shutdown date, not after.
  5. Offer a path forward. A trade-in credit converts anger into a sale, and it belongs in the product's service and support plan from the beginning.

Note that the same discipline applies when the hardware forces your hand rather than the software — a part going obsolete can end a generation on someone else's schedule, which is why planning for component end-of-life and planning app compatibility are really one exercise. And because every generation change touches the very first thing a user does, revisit the pairing and onboarding flow each time, since it must now correctly recognize three different devices.

Projects House builds connected products where the hardware, firmware, and app are designed as one system, including the versioning scheme that keeps generation one alive while generation three ships. If you are planning a product family rather than a single unit, describe it through our contact form.