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).
data.precisionnumberµs, ≥ 0Optional. How precisely the device's clock is synchronised to the network time; every scheduler keeps it as a margin. Without it, the value from the studio's Settings applies (default 1 µs).
data.maxGclEntriesinteger≥ 1Optional. How many gate control list entries the device holds per port. Every schedule is checked against it, and the local scheduler keeps within it where it can. Without it, the value from the studio's Settings applies (default: no limit).
data.macstringxx:xx:xx:xx:xx:xxOptional, end stations only. The device's unicast MAC address, for every port without its own. Streams to this device can use it as their destination address (stream.useListenerMac).
data.portMacs{ p0: string, … }per portOptional, end stations only. The MAC of individual ports. A stream that uses the listener's MAC is sent to the MAC of the port it arrives on (the last link of its route); ports without one use data.mac.
data.processingDelaynumberµs, ≥ 0Optional. Bridge delay: how long the device needs between receiving a frame completely and sending it on, when it forwards. Every scheduler counts it. Without it, the value from the studio's Settings applies (default 1 µs).
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.
data.lengthnumberm, ≥ 0Optional. Cable length; the propagation delay, length / 200 m/µs, counts in every scheduler. Without it, 100 m (0.5 µs).
data.sharedBg{ burst, rate }bytes, kbit/sOptional. Other traffic on this link in the class of streams without windows (local scheduler with “Schedule all streams”): how many bytes can arrive at once, and how many kbit/s on average. Without it, the values from Settings apply.
  • 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.
withinPeriodbooleanOptional, default false. With true, every frame is sent and delivered within one period, counted from the start of the cycle: the k-th frame of the cycle in the k-th period, clock margins of talker and listener included. For control loops that run in step with the network cycle. Without it, a frame may still be on its way when the next one is sent.
macstringxx:xx:xx:xx:xx:xxThe stream's destination address. Switches recognise the stream by it and its VLAN. New streams get their own address from 91:E0:F0:00:00:01 on (the range reserved for streams); without it, the studio assigns the next free one.
useListenerMacbooleanOptional, default false. With true, the listener's MAC is the destination address instead of mac: the MAC of the listener's port the route ends on (data.portMacs), else the device MAC (data.mac).
vlaninteger1 – 4094The stream's VLAN ID. By default all streams are in VLAN 1 (Settings: another shared VLAN, or an own VLAN per stream from 100 on); without it, the studio uses that setting.

How latency and jitter are measured

The studio checks every schedule with the same definitions, whichever scheduler computed it, and every scheduler plans with the same delays. Frames are store-and-forward: a device sends a frame on only after it has arrived completely, that is after the end of its previous window plus the cable's propagation delay (length / 200 m/µs). On top of that it needs its bridge delay (processingDelay) and keeps its clock margin (precision); both are 1 µs by default.

es0 → sw02.4 µssw0 → sw12.4 µssw1 → es12.4 µsclock margins of es0 and es1latency 14.7 µs
Stream S1 of the example, first frame. Each blue bar is a transmission window: 300 bytes at 1 Gbit/s take 2.4 µs. The thin teal bar is the cable: 500 ns for 100 m. The dashed step is the bridge delay plus the forwarding device's clock margin (2 µs here). Latency runs to the last bit arriving at es1, plus the clock margins of es0 and es1.
Latency
For each frame instance: end of its window on the last hop, plus that cable's propagation delay, minus the start of its window on the first hop, plus the clock margins of talker and listener. 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.
Egress queue
All scheduled streams share one queue per port, so frames of different streams must never wait in it at the same time. A forwarded frame counts as waiting from its complete arrival minus the device's clock margin until the start of its next window plus that margin; a frame the device sends itself, at the instant its window opens. A frame also has to leave before the next frame of its own stream arrives.
Within the period (optional)
By default a frame may still be on its way when the next one is sent, as long as it meets its latency bound. A stream with withinPeriod set is sent and delivered within one period instead: the k-th frame of the cycle between the start of the k-th period and the start of the next, clock margins of talker and listener included. Use it for control loops that run in step with the network cycle.

05

Scheduler request

When you press Run scheduler with the server option selected, the studio sends one HTTP request. The address and the access code come from Settings, under Scheduling server (the gear icon in the studio): the AutoTSN server by default, or your own scheduler. The body is the project, reshaped: the format dates from AutoTSN v1, and fields marked fixed always carry the value shown.

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. It does not change the schedule.
precisionnumberµsHow precisely the device's clock is synchronised: node.data.precision, else the value from Settings (default 1). The scheduler keeps this much margin when a frame passes from one device to the next, and reserves it at talker and listener within the latency bound.
maxGclEntriesinteger≥ 1Only sent when there is a limit: node.data.maxGclEntries, else the value from Settings. Gate control list entries per port the device holds. Information for your own scheduler; the AutoTSN server does not use it yet, the studio checks every result against it.
processingDelaynumberµsBridge delay: node.data.processingDelay, else the value from Settings (default 1). When the device forwards a frame, the next window opens at least this long after the frame has arrived completely, on top of the clock margin. Optional for your own scheduler; treat a missing value as 0.

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).
lengthnumbermCable length: edge.data.length, else 100. The scheduler adds the propagation delay, length / 200 m/µs, between hops and within the latency bound: 0.5 µs for 100 m.
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 down.
maxJitterintegerµsJitter bound, rounded down.
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.
withinPeriodbooleanOnly sent when true (stream.withinPeriod): the stream's frames must be sent and delivered within their period. Your own scheduler may treat a missing field as false.

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 (the common one of all ports, or a port's own), and flags 0x2 for hardware offload. With forwarding entries: bridge vlan and bridge fdb commands per switch.

NETCONF / YANG.xml

ieee802-dot1q-sched gate parameter tables: admin-control-list, admin-cycle-time as a fraction of a second, admin-base-time in seconds and nanoseconds. With forwarding entries, wrapped in a NETCONF <config> together with ieee802-dot1q-bridge filtering-database entries and VLAN membership per switch.

JSON.json

Every entry with start, duration, gate mask, kind and stream, per port, the base time (baseTimeNs: a number, or a decimal string when it is too large to be exact), plus the priority (PCP), address and VLAN each stream's talker must use, and the forwarding entries. The easiest one to post-process.

Linux taprio, port sw0-p1 of the worked example
# sw0 → sw1 · cycle 1000000 ns · 9 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 4900 \
sched-entry S 0x80 2400 \
sched-entry S 0x7f 482600 \
sched-entry S 0x00 15000 \
sched-entry S 0x80 2400 \
sched-entry S 0x7f 469000 \
sched-entry S 0x00 15000 \
sched-entry S 0x80 8000 \
sched-entry S 0x00 700 \
flags 0x2

Frame priority per stream

A gate list only does what it says if every talker tags its frames with the right priority. The JSON export therefore lists, for every stream, the PCP its talker must use; the taprio script and the YANG XML carry the same table as a comment. The studio configures gates, not talkers: setting the PCP on each sending device is up to you.

Gate lists as JSON: streams
streams[].streamstringStream id.
streams[].talkerstringdevice idThe device that sends the stream and has to tag its frames.
streams[].classstring"scheduled" | "shared"scheduled: the stream has transmission windows of its own. shared: it found no room and travels in the gaps between the windows (local scheduler with “Schedule all streams”), with worst-case bounds instead of exact times.
streams[].pcpinteger0–7The priority the talker must put on the frames. Scheduled streams use the highest time-aware class (7 by default), shared streams the highest class that is not time-aware (6 by default).
streams[].mac, .vlanstring, integerWith forwarding entries: the destination address and VLAN ID the talker sends with.

Normally every stream is scheduled. With “Schedule all streams”, the local scheduler can also produce shared streams: streams that found no room for windows of their own. They are sent one class lower, in the gaps that Autofill opens, and the studio computes guaranteed worst-case bounds for their latency and jitter. Those bounds assume that nothing else uses the shared or the time-aware class on that link; traffic that does, such as time sync frames, is declared in the studio's settings or per link (data.sharedBg).

Forwarding entries

Gate lists only work if every frame takes the route it was scheduled on. In a network with loops, Ethernet would choose the path itself (address learning and spanning tree), so the studio also gives every switch one static entry per stream: frames with this destination address and VLAN leave on this port. A stream is identified by its mac and vlan: by default each stream gets its own address from 91:E0:F0:00:00:01 on and VLAN 1 (Settings can choose another VLAN for all streams, or an own VLAN per stream), or it uses the listener's MAC: the MAC of the listener's port it arrives on (data.portMacs), else the device MAC. Every export includes the entries and the VLANs each port must carry, unless they are switched off in the export dialog. In a network with loops, the stream VLANs have to be taken out of spanning-tree control on the switches; the static entries keep the routes loop-free.

Gate lists as JSON: forwarding
forwarding[].bridgestringdevice idThe switch.
forwarding[].entries[]{ mac, vlan, port, portNumber, streams }One static entry per destination address and VLAN: frames with them leave on this port. port is the interface name (as in the gate lists), portNumber the port number (p0 = 1); streams lists the streams it carries.
forwarding[].vlans[]{ port, portNumber, vids }The VLANs each port of the switch must be a member of (tagged): every port a stream arrives on or leaves on.

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 address under Scheduling server in the studio's Settings. The server option in step 3 then reads Your server, and every run goes from the browser straight to your scheduler, not through ours. The studio shows the result in the Gantt chart, checks it frame by frame, 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, open Settings, set the address to http://127.0.0.1:8000 and press Test connection: it sends a network with one stream and tells you whether the request got through, the answer has the right format, and the schedule passes the check. Then select Your server in step 3.
  • 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.
  • The check uses the timing model above: cable delay, bridge delay and clock margins between hops, the egress queue, and the latency and jitter definitions. A schedule that leaves one of them out is shown with the problems found.

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