Server script API
A server script is one call of defineScript({ … }) in TypeScript or JavaScript. The editor completes these names and
checks the types before it saves. For how to create and run a script, see Server scripts.
defineScript
defineScript({
tags: ["Line1.P1", { path: "Line1.Breaker", on: "any" }],
every: "10s",
at: "0 6 * * *",
alarms: { tags: ["Boiler?.*"], minPriority: "high" },
when: { expr: "ctx.value('Line1.P1') > 80", forMs: 2000 },
timezone: "Europe/Istanbul",
onStart(ctx) {},
onChange(ctx, path, ev) {},
onTimer(ctx) {},
onSchedule(ctx, ev) {},
onAlarm(ctx, alarm) {},
onWhen(ctx, ev) {},
onStop(ctx) {},
});
A script calls defineScript once. Several triggers can be combined; the handlers run one after another, never at the
same time. Every handler may be async; an error or a rejected promise counts as an error of the script.
Triggers
| Option | Form | Calls |
|---|---|---|
tags | List of paths; an entry may be { path, on, deadband } with on = "value" (default), "quality" or "any" | onChange |
every | Duration: fixed delay after the previous run ends. { rate: "10s" }: fixed rate; a tick while the previous run is busy is skipped. { delay: "10s" } is the same as the plain form. At least 50 ms. | onTimer |
at | Cron with 5 fields (minute hour day month weekday), an ISO date and time that runs once (2026-12-31T23:59:00, optionally with Z or +03:00), or a list of them | onSchedule |
alarms | { tags, states, minPriority }: path patterns with * and ? (empty: all alarms); states "came", "went", "acked" (empty: all three); minPriority "low", "medium", "high", "critical" or 0–3 | onAlarm |
when | One or a list of { expr, forMs, edge, name }: expr as a text or a function of ctx; forMs how long the new state must last; edge "rising" (default), "falling" or "both" | onWhen |
timezone | Time zone for at; the Time zone (cron) setting of the script takes precedence; empty: the server's local time | — |
Durations are written as "500ms", "10s", "1m", "1h" or a number of milliseconds.
A when condition is evaluated only when one of the tags changes (and when forMs has passed): list the signals the
expression reads in tags. When the first values have arrived (at the latest after 5 seconds), the starting state is set
without an edge, so a restart does not trigger a false edge.
Handlers and events
| Handler | Called | Event |
|---|---|---|
onStart(ctx) | When the script starts | — |
onChange(ctx, path, ev) | A signal in tags changes | ev.prev, ev.cur: { value, quality, ts } or null; ev.initial: the first value after the start; ev.qualityOnly: only the quality changed; ev.missed: changes merged while the script was busy |
onTimer(ctx) | every elapses | — |
onSchedule(ctx, ev) | at is due | ev.at: the entry that fired; ev.missed: a run missed while the script was stopped, made up once at the start |
onAlarm(ctx, alarm) | An alarm comes, goes or is acknowledged | id, defId, path, state, priority, priorityLevel, text, ts, value, user (who acknowledged), area, alarmClass, level, description |
onWhen(ctx, ev) | A when condition has an edge | index, name, edge, value, ts |
onStop(ctx) | The script is saved again, made inactive or the service stops properly | — ; it may take at most the handler time limit and 5 seconds |
ev.initial is true once for each signal after onStart, with the value from the last-value store or the first live
value. Compare prev and cur only after it, to avoid false edges after a restart. onChange is not called when the same
value arrives again (with on: "value").
At most 50 alarm events per second (200 in a burst) are delivered to a script and at most 256 wait; more are skipped and
counted in Skipped. Keep the alarm filter narrow.
ctx
| Member | Returns | Meaning |
|---|---|---|
ctx.value(path) | Value or undefined | Last value of a signal; reading it also subscribes to it |
ctx.quality(path) | Text | "good", "uncertain", "bad", "not_connected", "overflow", "substituted", or "unknown" when the signal is not available |
ctx.ts(path) | Milliseconds | Time of the last change |
ctx.setTag(path, value) | — | Writes a number, an on/off value or a text; see Writing. Throws an error when it is refused |
ctx.log(...), ctx.warn(...), ctx.error(...) | — | Write to the script log; console.log / warn / error do the same |
ctx.now() | Milliseconds | The current time |
ctx.state.get(key), ctx.state.set(key, value) | Value | Persistent state of the script, kept across restarts; at most 1000 keys and 64 KB per value |
ctx.lib(name) | Object | A shared library |
ctx.dryRun | On/off | True during a trial run, when nothing is written |
A script writes at most 200 log lines per second.
Writing
| Target | Rule |
|---|---|
| Internal memory signal | Always allowed |
| Signal of a driver (field point) | Only with Can command field points on and the path matching one of the Command target patterns; within the Command rate limit (per minute). It is a command: written to the command log as script:<name> with the trigger (onChange:<path>, every, at, alarm:<path>, when, onStop) and the version |
| Signal point without a driver | Only with Can publish computed values on and the path matching one of the Publish target patterns: the value is published as the value of that point. Not a command, no command log |
System.*, Alarms.*, Session.*, computed internal signals | Refused |
Patterns use * (any sequence) and ? (one character), for example Line1.* or Boiler?.Sp.
The patterns decide which points a script may command: keep command targets as narrow as possible. Responsibility
zones do not apply to scripts, and a script cannot write to the command point of an electrical device. A write that
YTCC refuses before sending (permission, pattern, rate limit, electrical device) throws an error and counts in
Rejected. ctx.setTag does not wait for the driver: a command that the driver refuses or that times out does not
throw; it is in the command log.
Published values:
- are converted to the data type of the point (an on/off point takes
true, a non-zero number or the text"true"/"1"; integers are rounded and must fit; lists, objects andnullare refused); - are sent at most 10 times per second per point; faster changes are merged and the last value always arrives;
- have the quality of the script: when the script stops, fails to load or is quarantined, the points become Bad · Not connected within 6 seconds;
- do not trigger the script's own
onChange(no echo loop); - are limited to the number of
tagsplus 10 000 points per script.
If two scripts publish the same point, the last one wins; the editor warns about overlapping patterns.
defineLibrary
defineLibrary("common", {
average(...xs: number[]) { return xs.reduce((a, b) => a + b, 0) / Math.max(1, xs.length); },
});
The first argument must be the name the library is saved under. A library has no process, permissions or log of its own:
it runs in the script that uses it, with that script's permissions. Write the name as a fixed text in ctx.lib("common")
so that YTCC knows which scripts to reload when the library changes.
Status signals
Every script has the signals System.Scripts.<name>.*, usable on screens, in alarms and in the history:
| Signal | Meaning |
|---|---|
Running, Quarantined | The script is running; it is quarantined |
Version | Running version |
Runs, Errors | Number of handler runs and errors |
LastRun, LastError | Time of the last run; text of the last error |
AvgMs | Average run time |
Commands, Rejected | Commands sent; writes refused |
Published | Values actually published |
Skipped | Ticks or alarm events skipped because the script was busy or a limit was reached |
Limits
| Limit | Default | Range | When exceeded |
|---|---|---|---|
| Memory limit (MB) | 64 | 8–1024 | The script is stopped and restarted |
| Handler time limit (ms) | 1000 | 10–60000 | The call is cut off and counts as an error |
| Command rate limit (per minute) | 60 | 1–6000 | The command is refused |
A script has no access to files, the network or other programs. If its process fails five times within five minutes,
the script is quarantined: it waits, and a system alarm script_<name> with priority high is raised. Saving the script
again ends the quarantine.