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:
- 01Project file.autotsn.jsonThe network as you drew it: devices, ports, links and streams with their routes.
- 02Scheduler requestHTTP POST, JSONThe same network, in the shape the scheduling service reads.
- 03Scheduler responseJSONA transmission window for every frame on every link it crosses.
- 04Gate control liststaprio · YANG · JSONThe windows as IEEE 802.1Qbv gate schedules, per egress port.
Units and conventions
| Frame size | bytes | One Ethernet frame of a stream, as it is scheduled. |
|---|---|---|
| Period, latency, jitter of a stream | µs | Whole microseconds in the scheduler request. |
| Every time in a schedule or gate control list | ns | Measured from the start of the cycle. |
| Line rate of a link | Mbit/s | The studio offers 100 Mbit/s, 1 Gbit/s, 2.5 Gbit/s, 10 Gbit/s. |
- Devices are referred to by their name (
id) everywhere: in links, in streams and in routes. Names are unique. - Ports are named
p0top15. In the scheduler request and the gate control lists a port is writtendevice-port, e.g.sw0-p1. - Links are full duplex. A link direction is written
(u,v): frames leavingutowardsv. 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
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.
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:
| Field | Type | Unit / values | Meaning |
|---|---|---|---|
| format | string | "autotsn-studio/1" | Format identifier and version of the file. |
| name | string | Name of the network, as shown in the studio's top bar. | |
| nodes | Device[] | Every switch and end station. | |
| edges | Link[] | Every cable between two ports. | |
| streams | Stream[] | Every time-triggered stream, with its route. | |
| gcl | object | null | The 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.
| Field | Type | Unit / values | Meaning |
|---|---|---|---|
| id | string | [A-Za-z0-9_.-]+ | The device name. Unique in the network; every other reference to the device uses it. |
| typefixed | string | "device" | Always "device". |
| position | { x, y } | canvas px | Where the device sits on the canvas. Layout only; schedulers ignore it. |
| data.kind | string | see device kinds | What the device is. Decides whether it is a bridge (switch) or an end station. |
| data.ports | integer | 1 – 16 | Number of Ethernet ports. The ports are named p0, p1, … p(n−1). |
| data.precision | number | µs, ≥ 0 | Optional. 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.maxGclEntries | integer | ≥ 1 | Optional. 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.mac | string | xx:xx:xx:xx:xx:xx | Optional, 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 port | Optional, 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.processingDelay | number | µs, ≥ 0 | Optional. 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). |
| kind | Shown as | Role | Default ports | Name prefix |
|---|---|---|---|---|
| switch | Switch | bridge | 4 | sw |
| station | End station | end station | 2 | es |
| hpc | HPC | end station | 4 | HPC- |
| zcu | Zone controller | end station | 4 | ZCU- |
| camera | Camera | end station | 2 | CAM- |
| radio | 5G / telematics | end station | 2 | TCU- |
| drive | Drive ECU | end station | 2 | ECU- |
| sensor | Sensor / actuator | end station | 2 | SA- |
| infotainment | Infotainment | end station | 2 | IVI- |
| cockpit | Cockpit | end station | 2 | CKP- |
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.
| Field | Type | Unit / values | Meaning |
|---|---|---|---|
| id | string | Unique, opaque identifier of the link. | |
| typefixed | string | "link" | Always "link". |
| source | string | device id | One end of the cable. |
| sourceHandle | string | p0 … p15 | The port used at the source device. |
| target | string | device id | The other end of the cable. |
| targetHandle | string | p0 … p15 | The port used at the target device. |
| data.bandwidth | number | Mbit/s | Line 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.length | number | m, ≥ 0 | Optional. 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/s | Optional. 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.
| Field | Type | Unit / values | Meaning |
|---|---|---|---|
| id | string | S1, S2, … | Unique name of the stream. |
| talker | string | device id | The device that sends the frames. |
| listener | string | device id | The device that receives them. One listener per stream. |
| size | integer | bytes, 1 – 9000 | Length of the one frame the talker sends every period. |
| period | number | µs, > 0 | The talker sends one frame every period. |
| latency | number | µs, > 0 | Upper bound for the end-to-end latency of every frame (definition below). |
| jitter | number | µs, ≥ 0 | Upper bound for the jitter (definition below). 0 asks for strictly periodic transmission. |
| route | string[] | device ids | The 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. |
| withinPeriod | boolean | Optional, 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. | |
| mac | string | xx:xx:xx:xx:xx:xx | The 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. |
| useListenerMac | boolean | Optional, 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). | |
| vlan | integer | 1 – 4094 | The 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.
- 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
withinPeriodset 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.
POST https://api.autotsn.de/schedulerContent-Type: application/json{ "graph": { "nodes": [ … ], "edges": [ … ] }, "streams": [ … ], "userpass": "…", "numofqueues": 0, "numofwindows": 0 }
| Field | Type | Unit / values | Meaning |
|---|---|---|---|
| graph | object | The topology: nodes and edges, below. | |
| streams | object[] | One entry per stream, below. | |
| userpass | string | Access code from the studio's Settings. Your own scheduler may ignore it. | |
| numofqueuesfixed | integer | 0 | Reserved. |
| numofwindowsfixed | integer | 0 | Reserved. |
graph.nodes[]: one per device
| Field | Type | Unit / values | Meaning |
|---|---|---|---|
| label | string | Device id (node.id). | |
| typefixed | string | "PC" | Sent for switches and end stations alike. It does not change the schedule. |
| precision | number | µs | How 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. |
| maxGclEntries | integer | ≥ 1 | Only 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. |
| processingDelay | number | µs | Bridge 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
| Field | Type | Unit / values | Meaning |
|---|---|---|---|
| from | string | Link source (edge.source). | |
| to | string | Link target (edge.target). | |
| fromPort | string | <device>-<port> | Port at the source, e.g. sw0-p1. |
| toPort | string | <device>-<port> | Port at the target, e.g. sw1-p3. |
| bandwidth | number | Mbit/s | Line rate (edge.data.bandwidth). |
| length | number | m | Cable 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. |
| listOfPeriodsfixed | array | [] | Reserved. |
streams[]: one per stream
| Field | Type | Unit / values | Meaning |
|---|---|---|---|
| name | string | Stream id. Also sent as Topic. | |
| Topic | string | Same as name. | |
| talkerNode | string | Talker device id. | |
| listenerNode | string | Listener device id. | |
| messageSize | integer | bytes | Frame size plus the frame overhead from Settings (default 0). |
| period | integer | µs | Period, rounded to whole µs. |
| maxLatency | integer | µs | Latency bound, rounded down. |
| maxJitter | integer | µs | Jitter bound, rounded down. |
| routes | string[][] | A list holding exactly one route: the stream's device list. | |
| routesWithPortsfixed | string[][] | Same as routes. Device ids only, despite the name. | |
| priorityfixed | integer | 0 | Reserved. Traffic classes are chosen in step 4. |
| withinPeriod | boolean | Only 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.
| Field | Type | Unit / values | Meaning |
|---|---|---|---|
| status | string | "feasible" | "infeasible" | Whether a schedule was found. |
| message | string | Optional. Shown to the user when the status is infeasible. | |
| timeline | Group[] | The schedule, when feasible: one group per link direction. | |
| timeline[].group | string | "(u,v)" | The link direction: frames leave device u through the port that connects it to v. |
| timeline[].data[].label | string | Stream id. | |
| timeline[].data[].data[].timeRange | [number, number] | ns | Start and end of one transmission window, from the start of the cycle. One window per frame instance. |
| …data[].edge | string | "(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.
{"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.
| tas | Window computed by the scheduler. Opens the time-aware traffic classes (PCP 7 by default). |
|---|---|
| auto | Best-effort window added by Autofill. Opens every other traffic class. |
| guard | Guard band added by Autofill before a time-aware window. All gates closed. |
| user | Window 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.
# sw0 → sw1 · cycle 1000000 ns · 9 entriestc 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.
| Field | Type | Unit / values | Meaning |
|---|---|---|---|
| streams[].stream | string | Stream id. | |
| streams[].talker | string | device id | The device that sends the stream and has to tag its frames. |
| streams[].class | string | "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[].pcp | integer | 0–7 | The 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, .vlan | string, integer | With 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.
| Field | Type | Unit / values | Meaning |
|---|---|---|---|
| forwarding[].bridge | string | device id | The 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.
import json, mathmodel = 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] = mbpsfor 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.
# 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, mathfrom http.server import BaseHTTPRequestHandler, HTTPServerdef schedule(req):rate = {}for e in req["graph"]["edges"]:rate[e["from"], e["to"]] = rate[e["to"], e["from"]] = e["bandwidth"]groups, t = {}, 0for 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]) # nsgroups.setdefault(f"({u},{v})", []).append({"label": s["name"], "data": [{"timeRange": [t, t + tx]}]})t += txreturn {"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 tohttp://127.0.0.1:8000and 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_OPTIONSabove) 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.
- Project filenodes, edges, streamsautotsn-project.schema.json
- Scheduler requestwhat the studio sendsautotsn-scheduler-request.schema.json
- Scheduler responsewhat a scheduler answersautotsn-scheduler-response.schema.json
Questions about the model, or a format your hardware needs?
We build exporters and integrations for TSN tool chains.