Concepts

Anatomy of a Komod

A Komod is a .NET class that derives from KomodBase<TSettings> and is marked with the [Komod] attribute. At load time KOFA reads the ports and settings from it by reflection, so you register nothing.

[Komod("acme.demo.thermostat",
    DisplayName = "Termosztát",
    Description = "Hiszterézises termosztát: a hőmérséklet alapján kapcsolja a fűtést.",
    Category = KomodCategory.Controller)]
public sealed class ThermostatKomod : KomodBase<ThermostatSettings>
{
    // The input must be linked in the graph. If no message arrives for 5 seconds, OnInputTimeout runs (failsafe).
    [Input("temperature", DisplayName = "Hőmérséklet", Deadline = "00:00:05")]
    public required IInput<Scalar> Temperature { get; init; }

    [Output("heater", DisplayName = "Fűtés")]
    public required IOutput<BoolValue> Heater { get; init; }

    private bool _heating;

    protected override async Task ExecuteAsync(CancellationToken cancellationToken)
    {
        Context.ReportStatus(KomodHealth.Warning, "Nincs adat");
        SetHeater(false);

        await foreach (var message in Temperature.ReadAllAsync(cancellationToken))
        {
            var settings = Settings;
            var celsius = message.Value.Value;

            if (celsius < settings.TargetCelsius - settings.HysteresisCelsius)
                SetHeater(true);
            else if (celsius > settings.TargetCelsius + settings.HysteresisCelsius)
                SetHeater(false);

            Context.ReportStatus(KomodHealth.Ok, $"{celsius:0.0} °C → cél {settings.TargetCelsius:0.#} °C, fűtés {(_heating ? "BE" : "KI")}");
        }
    }

    // The deadline elapsed: with no measurement the safe state is "off".
    protected override void OnInputTimeout(IInputPort port)
    {
        SetHeater(false);
        Context.ReportStatus(KomodHealth.Warning, "Nincs hőmérséklet adat, a fűtés kikapcsolt");
    }

    protected override Task<bool> OnSettingsChangedAsync(ThermostatSettings oldSettings, ThermostatSettings newSettings) =>
        Task.FromResult(true);

    private void SetHeater(bool on)
    {
        _heating = on;
        Heater.Publish(new BoolValue(on));
    }
}

The building blocks

Element What it is for
[Komod("id", …)] Id (lowercase, stable), display name, description, category, vehicle type. All properties are in the attributes reference.
KomodBase<TSettings> The base class. TSettings is a record (with default values, constructible with new()). For a Komod without settings: KomodBase.
[Input] / [Output] Typed ports as IInput<T> / IOutput<T> properties. Ports.
Context The instance's services: id, logging, clock, storage folder, hardware, status reporting. See the API reference.
[KomodCommand] A method callable from the browser. Commands.

Instances

A Komod is a type; its instances run in the graph, each with its own settings and state. You can have several instances of the same Komod (two thermometers), unless MaxInstances limits it. The instance id is Context.InstanceId, its name in the graph is InstanceLabel.

Categories and vehicle types

Category (Source, Sensor, Controller, Actuator, Vision, Ai, Visualization, Converter, Service, System, Connectivity, Other) groups the Komod in the graph palette and in the Komod store. VehicleTypes (Car, Sailboat, Any) tells which vehicle it is for.

Several Komods in one package

A package may contain several Komod classes (for example a sensor and its converter). The package id and version are shared; the Komod ids are separate.

Do and don't

  • Do not block in ExecuteAsync (Thread.Sleep, .Result): wait asynchronously and watch the CancellationToken.
  • Do not keep state in static fields: the package can be reloaded (hot reload) and several instances of a type can run.
  • Put your own data types into your Komod package, see Own types.