KloradDocs

Interaction and actuation

Action, Entitlement, Behaviour, Animation. How people change a twin, and how a twin commands a device, in one direction only.

A twin people can only look at is a dashboard. Interaction is how they change it, and for a Digital Twin, how a change reaches the physical thing. Klorad models it as a chain of four parts that runs in one direction.

  1. ActionWhat can be done to an object, and by whom it is asked.
  2. EntitlementAccess decides: this actor, this World, this object.
  3. BehaviourThe predefined change, applied atomically.
  4. AnimationShows the change. Changes nothing.

For a Digital Twin: Command → Actuator → device → reading → the twin's state changes.

The chain runs left to right. A Behaviour has no way to request an Action.
PartWhat it isWho defines it
ActionSomething that can be done to an object: "Dim", "Open", "Mark emptied". It says which objects offer it (appliesTo), how to check its parameters (validate), which Behaviour follows, and for a twin, which command to send.You, as an ActionDefinition
EntitlementThe decision whether this actor may take this Action on this object in this World. World only asks; Access answers.@klorad/access (createAccessPolicy), or allowEveryone for a single user tool
BehaviourThe predefined change that follows an allowed Action: it patches the object and may ask for an Animation.You, as a BehaviourDefinition
AnimationA request to renderers to show the change (pulse or highlight). It changes nothing.The Behaviour, through animate()

One way only

A Behaviour receives update and animate, and nothing that could request an Action. That is deliberate: chains of Actions triggering Actions are how automation loops and privilege escalation happen. If something should follow an Action, a person or a rule outside the chain requests it, and the Entitlement checks it again.

A Behaviour's changes are collected and applied together after it returns. If it throws, or produces an invalid change, nothing changes and the outcome is failed.

Outcomes

interaction.perform(request) resolves to an ActionResult. Every outcome, refusals included, is also one action event on the scene, so an audit log sees what was denied as well as what was done.

OutcomeMeaning
doneThe Behaviour ran.
sentA twin's command was accepted by its device, and the Behaviour ran. The twin's physical state follows when the device reports it.
deniedThe Entitlement refused. reason says why.
invalidUnknown Action or object, the Action does not apply to the object, or its parameters failed validate.
failedThe actuator refused or failed, or the Behaviour threw. World state is unchanged.

Commanding a Digital Twin

An Action with a command applies to twins. When it is allowed, Klorad builds a Command (with the twin's entity, the actor and the instant) and sends it through the Actuator registered for the twin's binding.source, before the Behaviour runs. If the device refuses, the outcome is failed and nothing changes.

The twin's physical state is never written by the Action. It changes when the device reports its new state as a reading, the same path every reading takes. Typically the Behaviour records the request (requestedAt) and the reading confirms it.

interaction.ts
import { allowEveryone, createAccessPolicy } from "@klorad/access";
import { connectSource, fixtureDevice } from "@klorad/api/integration";
import { createInteraction, createScene } from "@klorad/api/world";

const scene = createScene({ coordinateSystem: { origin: { lat: 40.62637, lon: 22.94838 } } });
scene.add({ id: "lamp", name: "Street lamp", position: { east: -8, north: 4 }, properties: { level: 100 } });
scene.add({
  id: "barrier",
  name: "Entrance barrier",
  position: { east: 6, north: -2 },
  correspondence: "twin",
  binding: { source: "plc", entity: "barrier-1" },
});

// The physical side: a simulated PLC that reports the barrier's state and accepts commands.
const plc = fixtureDevice({
  id: "plc",
  entities: { "barrier-1": { state: "closed" } },
  respond: (command, now) => (now.state === command.name ? `already ${now.state}` : { state: command.name }),
});
connectSource(scene, plc);

const policy = createAccessPolicy({
  scene,
  roles: { operator: "*", visitor: [] },
  worlds: [{ id: "site", name: "Site", scope: { all: true }, capabilities: { actions: "*" }, principals: [{ kind: "role", id: "operator" }] }],
});

const interaction = createInteraction(scene, {
  entitlement: policy,
  actuators: [plc],
  behaviours: [
    { id: "dim", run: ({ update, animate }) => (update({ properties: { level: 30 } }), animate({ kind: "pulse" })) },
    { id: "requested", run: ({ update, now }) => update({ properties: { requestedAt: now } }) },
  ],
  actions: [
    { id: "dim", label: "Dim", appliesTo: (o) => o.id === "lamp", behaviour: "dim" },
    { id: "open", label: "Open", appliesTo: (o) => o.correspondence === "twin", behaviour: "requested", command: () => ({ name: "open" }) },
  ],
});

const operator = { id: "eleni", roles: ["operator"] };
const visitor = { id: "guest", roles: ["visitor"] };

async function demo() {
  await interaction.perform({ action: "dim", objectId: "lamp", actor: visitor, world: "site" }); // { outcome: "denied", reason: ... }
  await interaction.perform({ action: "dim", objectId: "lamp", actor: operator, world: "site" }); // { outcome: "done" }
  await interaction.perform({ action: "open", objectId: "barrier", actor: operator, world: "site" }); // { outcome: "sent", command }
  // About 600 ms later the PLC reports state "open": scene.latest("barrier", "state")?.value === "open"
  return interaction.available("lamp", operator, "site").map((a) => a.id); // ["dim"]
}

// For a demo or a single-user tool, allowEveryone says so in its name.
const playground = createInteraction(createScene({ coordinateSystem: { origin: { lat: 0, lon: 0 } } }), { entitlement: allowEveryone });

export { demo, playground };

In React, wrap your tree in <InteractionProvider interaction={interaction}>; useActions lists what an actor may do on an object and useAction performs one and tracks its state. See @klorad/react.

Run it liveSandbox: ActionsAction, Entitlement, Behaviour, Animation: an operator may act and a visitor may not; a twin's command goes out and its state follows the device.

Something wrong or unclear? Tell your contact at Prieston Technologies.