tin  1.5.9
product_overview

Purpose

tin is a C++ framework for developing deterministic real-time C++ software for bare-metal, arm, and x86 architectures. It gives teams a bounded runtime substrate, explicit behavior models, portable I/O contracts, platform boundaries, and adapter contracts.

The core idea is simple: raw system inputs should become typed local events, and typed local events should drive explicit behavior. Queues, channels, timers, tasks, adapters, and state machines should be visible enough to review and bounded enough to deploy on constrained targets.

Use tin when a system needs:

  • deterministic event flow and transition behavior;
  • fixed-capacity runtime infrastructure;
  • host-testable embedded behavior;
  • clear separation between behavior, I/O, platform code, and domain code;
  • product documentation and review artifacts that map to actual code structure.

Package Shape

tin is organized around small public surfaces. tsm models behavior, runtime moves events and schedules bounded work, tio describes portable I/O contracts, thal describes target adapter contracts, and tin brings those pieces together.

Use tin.md for the main API entry points, architecture.md for dependency rules, and developer_onboarding.md when contributing changes.

Top-Down Motivation

A product team may start with behavior:

Build a surgical-assist arm controller that can boot safely, run self-tests, coordinate console readiness, enter teleoperation, pause into safe hold, recover, and fault predictably.

The same shape applies outside robotics: a trading engine can model market-data ingest, risk checks, order routing, throttling, recovery, and fault behavior as explicit states and typed events while keeping queues, timers, and dispatch bounded.

At this level, the important questions are behavioral:

  • Which states can the product be in?
  • Which events are legal in each state?
  • What happens on fault?
  • Can the behavior be replayed and reviewed?
  • Can the same behavior run in simulation and on target hardware?

tin keeps the layers separated:

  • application and domain layers own product-specific models, simulation, replay, lifecycle composition, middleware integration, or market connectivity.
  • tsm owns behavior: booting, self-test, ready, teleoperation, safe hold, and fault handling.
  • runtime owns bounded event queues, actors, typed ports, timers, tasks, channels, and deterministic dispatch.
  • tio owns portable device-facing contracts for sensors, pedals, encoders, drives, and other adapter boundaries.
  • thal owns target profiles and platform bindings for Linux simulation, RTOS targets, and board-specific adapters.

The product benefit is a reviewable behavior core that is not tangled into middleware callbacks, driver code, or ad hoc timers.

At the behavior layer, that can start as a normal tsm definition:

struct Supervisor {
struct Booting {};
struct SelfTest {};
struct Ready {};
struct Teleoperating {};
struct SafeHold {};
struct Faulted {};
struct BootComplete {};
struct SelfTestPassed {};
struct ConsoleLinked {};
struct FootPedalPressed {};
struct FootPedalReleased {};
struct Fault {};
tsm::T<Booting, BootComplete, SelfTest>,
tsm::T<SelfTest, SelfTestPassed, Ready>,
tsm::T<Ready, ConsoleLinked, Teleoperating>,
tsm::T<Teleoperating, FootPedalReleased, SafeHold>,
tsm::T<SafeHold, FootPedalPressed, Teleoperating>,
tsm::T<Teleoperating, Fault, Faulted>,
tsm::T<SafeHold, Fault, Faulted>>;
};
consteval auto transitions(TransitionEntries...)
Definition: tsm.h:2448
transition_table< TransitionEntries... > Ts
Definition: tsm.h:2445

Bottom-Up Motivation

A platform team may start with infrastructure:

We have interrupts, drivers, periodic ticks, CAN frames, UART messages, task wakeups, and fault signals. Every product reimplements queues, callbacks, timers, and glue differently.

At this level, the important questions are mechanical:

  • Is memory bounded?
  • What happens when a queue is full?
  • Can an interrupt path enqueue work safely?
  • Can task wakeups and sleeps use integer ticks?
  • Can the same pipeline run in host tests?
  • Can raw packets and samples become typed product events?

tin gives that infrastructure a deterministic shape. A driver can publish into a fixed-capacity channel, a task can translate samples into typed events, and a tsm machine can handle those events as explicit behavior. The low-level mechanics stay bounded while the product logic stays readable.

At the runtime layer, that can start as a bounded channel and typed event translation:

#include <cstdint>
#include "tin/runtime.h"
struct SensorSample {
std::uint16_t millivolts{};
};
void adc_ready_from_isr(SensorSample sample) {
(void)samples.try_send_from_isr(sample);
}
void drain_samples(auto& runtime) {
SensorSample sample{};
while (samples.try_receive(sample)) {
if (sample.millivolts > 3000) {
(void)runtime.send_event(PowerDrive::FaultDetected{});
} else {
(void)runtime.send_event(PowerDrive::InternalReady{});
}
}
}
Definition: sync.h:507
bool try_send_from_isr(T const &value)
Definition: sync.h:539
bool try_receive(T &value)
Definition: sync.h:549
Tin runtime-kernel facade.

The result is a product line where each surface can document its own API and examples while sharing deterministic runtime infrastructure underneath.