The AutoTSN network model

How AutoTSN Studio describes a Time-Sensitive Network and its streams, what it sends to a scheduler and what it expects back. It is all plain JSON: take your network to your own scheduler, or plug your scheduler into the studio.

Open AutoTSN Studio JSON Schemasformat autotsn-studio/1

01

Overview

A network in AutoTSN is a set of devices with numbered ports, links between two ports, and streams: periodic frames from one talker to one listener along a fixed route. A scheduler turns that into a transmission window for every frame on every link it crosses, and the studio turns the windows into IEEE 802.1Qbv gate control lists. Four JSON documents carry the model through those steps:

  1. 01Project file.autotsn.jsonThe network as you drew it: devices, ports, links and streams with their routes.
  2. 02Scheduler requestHTTP POST, JSONThe same network, in the shape the scheduling service reads.
  3. 03Scheduler responseJSONA transmission window for every frame on every link it crosses.
  4. 04Gate control liststaprio · YANG · JSONThe windows as IEEE 802.1Qbv gate schedules, per egress port.

Units and conventions

Frame sizebytes
Period, latency, jitter of a streamµs
Every time in a schedule or gate control listns
Line rate of a linkMbit/s
  • Devices are referred to by their name (id) everywhere: in links, in streams and in routes. Names are unique.
  • Ports are named p0 to p15. In the scheduler request and the gate control lists a port is written device-port, e.g. sw0-p1.
  • Links are full duplex. A link direction is written (u,v): frames leaving u towards v. Both directions are scheduled independently.
  • A schedule repeats every cycle. The cycle is the least common multiple of all stream periods (the hyperperiod), or a multiple of it if a scheduler plans a longer horizon.

02

A worked example

Two end stations and a camera on two switches. The camera link runs at 100 Mbit/s, the others at 1 Gbit/s. Stream S1 is a 300-byte control frame every 500 µs; S2 a 1000-byte camera frame every millisecond. The documents below are generated by the studio's own code from this network, so they are exactly what you would get.

Two-switch line

p0p31 Gbit/sp1p0100 Mbit/sp1p31 Gbit/sp1p01 Gbit/ses0CAM-0sw0sw1es1

Point at a device, a link or a stream to find it in the document, or point at the document to find it here.

What the studio saves: Project → Save project file. Open it in the studio with Project → Open project file.

{
"format": "autotsn-studio/1",
"name": "Two-switch line",
"nodes": [
{
"id": "es0",
"type": "device",
"position": { "x": 0, "y": 160 },
"data": { "kind": "station", "ports": 2 }
},
{
"id": "CAM-0",
"type": "device",
"position": { "x": 220, "y": 0 },
"data": { "kind": "camera", "ports": 2 }
},
{
"id": "sw0",
"type": "device",
"position": { "x": 220, "y": 160 },
"data": { "kind": "switch", "ports": 4 }
},
{
"id": "sw1",
"type": "device",
"position": { "x": 440, "y": 160 },
"data": { "kind": "switch", "ports": 4 }
},
{
"id": "es1",
"type": "device",
"position": { "x": 660, "y": 160 },
"data": { "kind": "station", "ports": 2 }
}
],
"edges": [
{
"id": "l1",
"type": "link",
"source": "es0",
"target": "sw0",
"sourceHandle": "p0",
"targetHandle": "p3",
"data": { "bandwidth": 1000 }
},
{
"id": "l2",
"type": "link",
"source": "CAM-0",
"target": "sw0",
"sourceHandle": "p1",
"targetHandle": "p0",
"data": { "bandwidth": 100 }
},
{
"id": "l3",
"type": "link",
"source": "sw0",
"target": "sw1",
"sourceHandle": "p1",
"targetHandle": "p3",
"data": { "bandwidth": 1000 }
},
{
"id": "l4",
"type": "link",
"source": "sw1",
"target": "es1",
"sourceHandle": "p1",
"targetHandle": "p0",
"data": { "bandwidth": 1000 }
}
],
"streams": [
{
"id": "S1",
"talker": "es0",
"listener": "es1",
"size": 300,
"period": 500,
"latency": 100,
"jitter": 5,
"route": ["es0", "sw0", "sw1", "es1"]
},
{
"id": "S2",
"talker": "CAM-0",
"listener": "es1",
"size": 1000,
"period": 1000,
"latency": 200,
"jitter": 10,
"route": ["CAM-0", "sw0", "sw1", "es1"]
}
],
"gcl": null
}

03

Topology

The project file is the complete model. It is what Project → Save project file writes and Project → Open project file reads. Its top level:

Project file, top level
formatstring"autotsn-studio/1"Format identifier and version of the file.
namestringName of the network, as shown in the studio's top bar.
nodesDevice[]Every switch and end station.
edgesLink[]Every cable between two ports.
streamsStream[]Every time-triggered stream, with its route.
gclobject | nullThe gate control lists as edited in step 4, or null. Studio-internal; for tooling use the JSON export of the gate control lists instead.

Devices

Every entry of nodes is a device. Its kind decides its role: bridges forward frames; end stations talk and listen. End stations may still appear in the middle of a route, for daisy-chained devices with a built-in switch.

Device
idstring[A-Za-z0-9_.-]+The device name. Unique in the network; every other reference to the device uses it.
typefixedstring"device"Always "device".
position{ x, y }canvas pxWhere the device sits on the canvas. Layout only; schedulers ignore it.
data.kindstringsee device kindsWhat the device is. Decides whether it is a bridge (switch) or an end station.
data.portsinteger1 – 16Number of Ethernet ports. The ports are named p0, p1, … p(n−1).
Device kinds
kindShown asRoleDefault portsName prefix
switch Switchbridge4sw
station End stationend station2es
hpc HPCend station4HPC-
zcu Zone controllerend station4ZCU-
camera Cameraend station2CAM-
radio 5G / telematicsend station2TCU-
drive Drive ECUend station2ECU-
sensor Sensor / actuatorend station2SA-
infotainment Infotainmentend station2IVI-
cockpit Cockpitend station2CKP-

Links

Every entry of edges is one cable between a port of one device and a port of another. source and target only record how the cable was drawn; frames use it in both directions.

Link
idstringUnique, opaque identifier of the link.
typefixedstring"link"Always "link".
sourcestringdevice idOne end of the cable.
sourceHandlestringp0 … p15The port used at the source device.
targetstringdevice idThe other end of the cable.
targetHandlestringp0 … p15The port used at the target device.
data.bandwidthnumberMbit/sLine rate of the cable, the same in both directions. The studio offers 100 Mbit/s, 1 Gbit/s, 2.5 Gbit/s, 10 Gbit/s.
  • A port takes exactly one cable.
  • Two devices are connected by at most one cable, and no device is linked to itself.
  • A port index is always lower than the device's port count.

04

Streams

A stream is one frame of size bytes that the talker sends every period, to exactly one listener, along route. The route is part of the model: a scheduler places frames on it and does not choose paths. When you add a stream, the studio lists the simple paths between talker and listener, shortest first.

Stream
idstringS1, S2, …Unique name of the stream.
talkerstringdevice idThe device that sends the frames.
listenerstringdevice idThe device that receives them. One listener per stream.
sizeintegerbytes, 1 – 9000Length of the one frame the talker sends every period.
periodnumberµs, > 0The talker sends one frame every period.
latencynumberµs, > 0Upper bound for the end-to-end latency of every frame (definition below).
jitternumberµs, ≥ 0Upper bound for the jitter (definition below). 0 asks for strictly periodic transmission.
routestring[]device idsThe path, from talker to listener. Consecutive devices must be linked. The route is part of the model: schedulers place frames on it and do not route.

How latency and jitter are measured

The studio checks every schedule with the same definitions, whichever engine computed it. Frames are store-and-forward: a bridge starts sending a frame only after it has received all of it. Propagation delay on the cable is not modelled.

es0 → sw02.4 µssw0 → sw12.4 µssw1 → es12.4 µslatency 9.2 µs
Stream S1 of the example, first frame. Each bar is a transmission window: 300 bytes at 1 Gbit/s take 2.4 µs. The dashed steps are the gap the bridge needs before it forwards (1 µs in the local engine). Latency runs from the first bit leaving es0 to the last bit arriving at es1.
Latency
For each frame instance: end of its window on the last hop minus start of its window on the first hop. The stream meets its bound if the largest value over all instances in the cycle is at most latency.
Jitter
How far consecutive frames drift from the period: the largest |startk − startk−1 − period|, taken on the first hop and on the last hop. 0 means every frame is sent and delivered at exactly the same offset in its period.

05

Scheduler request

When you press Run scheduler with the AutoTSN server selected, the studio sends one HTTP request. The endpoint and the access code come from Settings (the gear icon in the studio). The body is the project, reshaped: the format dates from AutoTSN v1, so some fields are fixed values kept for compatibility.

HTTP
POST https://api.autotsn.de/scheduler
Content-Type: application/json
{ "graph": { "nodes": [ … ], "edges": [ … ] }, "streams": [ … ], "userpass": "…", "numofqueues": 0, "numofwindows": 0 }
Request, top level
graphobjectThe topology: nodes and edges, below.
streamsobject[]One entry per stream, below.
userpassstringAccess code from the studio's Settings. Your own scheduler may ignore it.
numofqueuesfixedinteger0Reserved.
numofwindowsfixedinteger0Reserved.

graph.nodes[]: one per device

Request node
labelstringDevice id (node.id).
typefixedstring"PC"Sent for switches and end stations alike.
precisionfixedinteger1Reserved.

graph.edges[]: one per link

Request edge
fromstringLink source (edge.source).
tostringLink target (edge.target).
fromPortstring<device>-<port>Port at the source, e.g. sw0-p1.
toPortstring<device>-<port>Port at the target, e.g. sw1-p3.
bandwidthnumberMbit/sLine rate (edge.data.bandwidth).
lengthfixednumber100Reserved. The studio does not model propagation delay.
listOfPeriodsfixedarray[]Reserved.

streams[]: one per stream

Request stream
namestringStream id. Also sent as Topic.
TopicstringSame as name.
talkerNodestringTalker device id.
listenerNodestringListener device id.
messageSizeintegerbytesFrame size plus the frame overhead from Settings (default 0).
periodintegerµsPeriod, rounded to whole µs.
maxLatencyintegerµsLatency bound, rounded.
maxJitterintegerµsJitter bound, rounded.
routesstring[][]A list holding exactly one route: the stream's device list.
routesWithPortsfixedstring[][]Same as routes. Device ids only, despite the name.
priorityfixedinteger0Reserved. Traffic classes are chosen in step 4.

For example, stream S1 of the worked example becomes messageSize 300, period 500, maxLatency 100, maxJitter 5 and routes [["es0", "sw0", "sw1", "es1"]].

06

Scheduler response

The scheduler answers with JSON. Only the fields below are read; anything else is ignored. HTTP 401 or 403 means the access code was refused, 503 that the service is busy; any other error status is shown as a failed run.

Response
statusstring"feasible" | "infeasible"Whether a schedule was found.
messagestringOptional. Shown to the user when the status is infeasible.
timelineGroup[]The schedule, when feasible: one group per link direction.
timeline[].groupstring"(u,v)"The link direction: frames leave device u through the port that connects it to v.
timeline[].data[].labelstringStream id.
timeline[].data[].data[].timeRange[number, number]nsStart and end of one transmission window, from the start of the cycle. One window per frame instance.
…data[].edgestring"(u,v)"Optional. Overrides the group for this window.
  • A window is the time the frame is on the wire on that link direction: from its first to its last bit, so its length is the transmission time at the link's line rate.
  • Give one window per frame instance in the cycle: a stream with a 500 µs period in a 1 ms cycle has two windows on each of its links.
  • Windows on the same link direction must not overlap.
  • The cycle the studio uses is the smallest multiple of the hyperperiod that covers every window.
No schedule exists
{
"status": "infeasible",
"message": "No feasible schedule exists for these streams."
}

07

Gate control lists

From the windows, the studio builds one gate control list per egress port that carries scheduled frames. Each entry opens a set of gates (traffic classes, PCP 0–7) for a time interval. Scheduled windows open the time-aware traffic classes (PCP 7 by default, changeable in step 4); Autofill gives the remaining time to the other classes and puts a guard band, with all gates closed, before every time-aware window so no best-effort frame is still on the wire when it opens.

Kinds of gate entries
tasWindow computed by the scheduler. Opens the time-aware traffic classes (PCP 7 by default).
autoBest-effort window added by Autofill. Opens every other traffic class.
guardGuard band added by Autofill before a time-aware window. All gates closed.
userWindow added or edited by hand.

The gate mask has bit n set when the gate of traffic class n is open: 0x80 opens only PCP 7, 0x7f opens PCP 0–6, 0x00 closes everything. Traffic classes map one-to-one to PCP values.

Export formats

Linux taprio.sh

One tc qdisc command per egress port. Interval per entry in ns, gate mask in hex, base time, and flags 0x2 for hardware offload.

NETCONF / YANG.xml

ieee802-dot1q-sched gate parameter tables: admin-control-list, admin-cycle-time as a fraction of a second, admin-base-time.

JSON.json

Every entry with start, duration, gate mask, kind and stream, per port. The easiest one to post-process.

Linux taprio, port sw0-p1 of the worked example
# sw0 → sw1 · cycle 1000000 ns · 10 entries
tc qdisc replace dev sw0-p1 parent root handle 100 stab overhead 24 taprio \
num_tc 8 \
map 0 1 2 3 4 5 6 7 0 0 0 0 0 0 0 0 \
queues 1@0 1@1 1@2 1@3 1@4 1@5 1@6 1@7 \
base-time 0 \
sched-entry S 0x00 3400 \
sched-entry S 0x80 2400 \
sched-entry S 0x7f 60200 \
sched-entry S 0x00 15000 \
sched-entry S 0x80 8000 \
sched-entry S 0x7f 399400 \
sched-entry S 0x00 15000 \
sched-entry S 0x80 2400 \
sched-entry S 0x7f 479200 \
sched-entry S 0x00 15000 \
flags 0x2

08

Use your own scheduler

There are two ways to bring a network from AutoTSN Studio to another scheduler.

1. Read the project file

Save the project and read it in any language. Everything a scheduler needs is in edges and streams; the transmission time of a frame on a hop is ⌈size · 8000 / line rate⌉ ns.

Python 3
import json, math
model = json.load(open("two-switch-line.autotsn.json"))
# Line rate per direction in Mbit/s. Every cable is full duplex.
rate = {}
for link in model["edges"]:
a, b, mbps = link["source"], link["target"], link["data"]["bandwidth"]
rate[a, b] = rate[b, a] = mbps
for s in model["streams"]:
hops = list(zip(s["route"], s["route"][1:]))
tx_ns = [math.ceil(s["size"] * 8000 / rate[h]) for h in hops]
print(f'{s["id"]}: one frame every {s["period"]} µs over {hops}')
print(f' transmission per hop {tx_ns} ns, deadline {s["latency"]} µs')

2. Plug your scheduler into the studio

Serve the request and response described above over HTTP, and enter your URL as the endpoint in the studio's Settings. The studio then sends every run to your scheduler and shows its result in the Gantt chart, checks latency and jitter, and builds gate control lists from it. The stub below shows the protocol in about 40 lines of standard-library Python.

Python 3, stub_scheduler.py
# A stub that speaks the AutoTSN scheduler protocol. It is not a real scheduler:
# it sends every stream's first frame once, back to back, and ignores periods.
import json, math
from http.server import BaseHTTPRequestHandler, HTTPServer
def schedule(req):
rate = {}
for e in req["graph"]["edges"]:
rate[e["from"], e["to"]] = rate[e["to"], e["from"]] = e["bandwidth"]
groups, t = {}, 0
for s in req["streams"]:
route = s["routes"][0]
for u, v in zip(route, route[1:]):
tx = math.ceil(s["messageSize"] * 8000 / rate[u, v]) # ns
groups.setdefault(f"({u},{v})", []).append(
{"label": s["name"], "data": [{"timeRange": [t, t + tx]}]})
t += tx
return {"status": "feasible",
"timeline": [{"group": g, "data": d} for g, d in groups.items()]}
class Handler(BaseHTTPRequestHandler):
def cors(self):
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Access-Control-Allow-Methods", "POST, OPTIONS")
self.send_header("Access-Control-Allow-Headers", "Content-Type")
def do_OPTIONS(self):
self.send_response(204)
self.cors()
self.end_headers()
def do_POST(self):
req = json.loads(self.rfile.read(int(self.headers["Content-Length"])))
body = json.dumps(schedule(req)).encode()
self.send_response(200)
self.cors()
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(body)
HTTPServer(("127.0.0.1", 8000), Handler).serve_forever()
  • Run it with python3 stub_scheduler.py, select the AutoTSN server engine, and set the endpoint to http://127.0.0.1:8000.
  • The studio calls your scheduler from the browser, so it must answer CORS preflight requests (the do_OPTIONS above) and allow the origin of the studio.
  • Browsers may ask for your permission before a website talks to a server on your own computer or local network. Allow it for this site.
  • A scheduler on another machine should use HTTPS; browsers block plain HTTP requests from an HTTPS page.

09

JSON Schemas

Machine-readable definitions of the three documents (JSON Schema, draft 2020-12), to validate files and generate types. The project file names the format version it was written with in its format field.

Questions about the model, or a format your hardware needs?

We build exporters and integrations for TSN tool chains.

Contact us