Selected work

Case study / Reliability / state / testing

Smart Light Controller

What does reliable control look like when the time source can fail?

A .NET reference controller that makes scheduling and failure recovery predictable through small, testable boundaries.

Project type
software
Contribution
Implemented controller, HTTP paths, and deterministic tests
Status
v1.0.0 released
Context
Independent C#/.NET 10 reference project
Updated
Illustrated controller behaviour: schedule controls simulated output, a brief time-source fault retains it, the failure threshold turns it off, and a successful read restores scheduling
Controller behaviour illustration with simulated output.

Key decision: Put ITimeProvider and ILightOutput at the controller boundary. Keep LightSchedule free of I/O and mutable state. Supply HttpTimeProvider and ConsoleLightOutput through dependency injection.

Start here

Overview

The engineering problem

A scheduled light needs an unambiguous answer at each window boundary, including midnight. It also needs a defined response when its external time source fails, without treating a brief outage as a reason to immediately change the output.

Contribution

I separated the pure scheduling policy from the stateful controller, connected an HTTP time adapter and simulated output, and wired a cancellable hosted worker to apply the schedule.

What exists now

The released controller applies daily schedules to a simulated light, enters safe mode when time readings fail, and returns to scheduled control after recovery. Local tests exercise policy, adapter, and HTTP paths. No physical light is connected.

Inspect the work

Engineering detail

Constraints

  • One configured daily active window controls one simulated output. Start is inclusive, end is exclusive, and equal boundaries mean the light stays off.
  • The runtime reads local time with its UTC offset from an HTTPS provider. A failed read cannot establish the current schedule state.
  • Repeated provider failures must turn the light off at a configured threshold. A later successful read must restore scheduled operation.
  • Configuration errors must fail at startup, and normal automated tests must run without an external service or wall-clock dependence.

Architecture at a glance

The .NET hosted worker checks once after startup and then polls with PeriodicTimer. LightController reads through ITimeProvider, evaluates the pure LightSchedule policy, and changes ILightOutput only when the requested state differs. It tracks consecutive time-provider failures. The threshold forces the output off, and a successful read clears safe mode and reapplies the schedule. Validated options configure the loop, schedule, and HTTP provider.

Smart Light Controller control path

Validated configuration supplies the worker and controller. The worker invokes the controller, which reads time through an HTTP adapter, evaluates the schedule, and drives a simulated output. Consecutive provider failures move the controller into safe mode. The next successful read restores scheduled operation.

Text explanation

  • Configuration: SmartLight options set the schedule, polling interval, failure threshold, time-zone path, HTTPS base URI, and timeout. Invalid values fail at startup.
  • Worker: A BackgroundService runs one check after startup, then invokes the controller on each cancellable PeriodicTimer tick.
  • Controller: LightController coordinates time reads, schedule decisions, output transitions, and the consecutive-failure count.
  • Output: ILightOutput is implemented by a state-holding console simulation. No physical device is controlled.
  • Schedule: LightSchedule is a pure policy for same-day and overnight windows with inclusive start and exclusive end.
  • Safe mode: The controller retains output below the failure threshold, forces it off at the threshold, and leaves safe mode after a successful read.
  • Time provider: ITimeProvider is implemented by HttpTimeProvider, which makes cancellable HTTP requests and wraps expected transport or data failures.
  • Time service: The configured external HTTPS service returns an offset-bearing local datetime for the requested time-zone path.

Connections: Configuration to Worker (polling interval); Worker to Controller (check on each tick); Controller to Schedule (evaluate local time); Controller to Output (change when needed); Controller to Safe mode (track failures and recovery); Controller to Time provider (request current time); Time provider to Time service (HTTPS request).

Annotations: External HTTPS dependency at the time-service node.

Key decisions

Decision 01

Where should time reads and light output meet the control logic?

Direct HTTP calls and output writes inside schedule calculations would make behaviour depend on network and device side effects.

Selected
Put ITimeProvider and ILightOutput at the controller boundary. Keep LightSchedule free of I/O and mutable state. Supply HttpTimeProvider and ConsoleLightOutput through dependency injection.
Alternatives
Read system or network time and write output directly inside the schedule policy
Trade-off
The interfaces and adapters add a little structure to a single-output program.
Consequence
Scheduling is testable with supplied times, and a future time source or physical output adapter can be substituted without rewriting the policy.

Decision 02

When should an unavailable time source turn the light off?

One failed poll gives no trustworthy current time, but changing an enabled output immediately would react to a transient outage. Keeping the previous state forever would leave an unknown schedule state in place.

Selected
Count consecutive TimeProviderException failures. Preserve the last output below the validated threshold, force it off at the threshold, and clear the count and reapply the schedule on the next successful read.
Alternatives
Turn off after every failed read; Retain the last output indefinitely
Trade-off
The output may remain on briefly while time is unavailable below the threshold. The threshold and polling interval define that exposure.
Consequence
Safe-mode entry is a distinct logged transition, later failed polls do not repeat the output change, and recovery follows the configured schedule immediately.

Decision 03

How can the tests exercise failure and recovery without an external clock?

Wall-clock waits and real HTTP calls would make boundary and outage scenarios slow or unpredictable.

Selected
Supply fixed or mutable times and in-memory outputs to controller tests. Use an in-process HttpMessageHandler for HTTP provider integration tests. Keep acceptance scenarios at the controller boundary.
Alternatives
Run tests against the live time service or a manually started mock server
Trade-off
These suites validate controller behaviour and HTTP parsing, but they do not measure a deployed provider or physical device.
Consequence
Normal tests run locally and deterministically while covering schedule edges, transport failures, safe-mode shutdown, and recovery.

Reliability and quality

Schedule and state checks
xUnit tests cover same-day and overnight boundaries, equal start and end, failure counting, idempotent output transitions, cancellation, safe mode, recovery, and options validation.
HTTP boundary checks
Integration tests replace HttpMessageHandler in-process to exercise time-zone URI construction, offset parsing, HTTP errors, malformed responses, timeout, cancellation, and transient recovery.
Acceptance scenarios
In-memory scenarios follow an overnight window across boundaries and exercise one failed read, threshold shutdown, and restored operation after recovery.
Delivery and security gates
CI uses locked restore, format verification, a Release build, three test suites, coverage artifact collection, and a transitive runtime dependency audit. CodeQL analyzes C# on pushes, pull requests, and a weekly schedule. Dependabot proposes NuGet and Actions updates monthly. No coverage threshold is enforced.

Trace the control path

What next

A device adapter and operational measurements would be the next evidence needed for physical use. A local or NTP-backed time source could also reduce dependence on the configured HTTP service.

Scope of the evidence

Limitations

  • The supplied ILightOutput adapter only stores state for a console simulation. There is no physical-device adapter or evidence of hardware behaviour.
  • Runtime accuracy and availability depend on the configured external HTTPS time provider. The tests replace its transport and do not establish live-service reliability.
  • The scheduling model covers one daily window for one output. It has no calendars, exceptions, or multiple-device coordination.
  • This is a single-process reference implementation, not a deployed home-automation system. The release contains source and no device package.