Skip to content

SkatingApp Protocol

SkatingApp allows third parties to integrate via the UDP live stream known as the app interface. You can activate the UDP live stream via the options menu. To do this, go to Edit->Options-> General. Here you can set the App Protocol port to any free UDP port (number value). The app interface is active when this field is set, and deactivated when this field is unset.

Print Management Screen

Structure and protocol

The app interface broadcasts the live running clock and registered passages in real-time on the network. Each message contains fields that are delimited by the pipe (|) character. The first field in each message declares the type of message. There are two types of messages:

  • Running Time: This message is identified by the letter R in the first field. This message contains the actual running time of the clock and is sent every 10th of a second when there is an active race. When there is no active race, this message is sent every second.
  • Passing This message is identified by the letter P in the first field. The message contains the data that was calculated when the skater passed the finish line or a transponder loop.

Running Time Message

The runnning time message contains information on the clock state and race state of the active pair. This message is sent for each skater that is currently racing or each team that is racing. The running time has the following structure:

`R|CompetitionId|CompetitionNumber|ClockId|ActiveCompetitors[]|StartNumber|CNR|Time:0.0##|Speed:0.0##|Status|LastPassage:0.0##|NextPassage:0.0##|LastLoop|Distance:0.0##|Intermediate:0.0##|Armband`

#Example Message 500m race (1 pair of skaters):
R|2132|6|2019|37,15|6|37|0.000|0.0|0|0.0|10.0||0.0|0.0|0
R|2132|6|2019|37,15|6|15|0.000|0.0|0|0.0|10.0||0.0|0.0|1
R|2132|6|2019|37,15|6|37|0.000|0.0|0|0.0|10.0||0.0|0.0|0
R|2132|6|2019|37,15|6|15|0.000|0.0|0|0.0|10.0||0.0|0.0|1
R|2132|6|2019|37,15|6|37|0.048|0.0|1|0.0|10.0||0.0|0.0|0
R|2132|6|2019|37,15|6|15|0.048|0.0|1|0.0|10.0||0.0|0.0|1
R|2132|6|2019|37,15|6|37|0.160|0.0|1|0.0|10.0||0.0|0.0|0
R|2132|6|2019|37,15|6|15|0.160|0.0|1|0.0|10.0||0.0|0.0|1
R|2132|6|2019|37,15|6|37|0.268|0.0|1|0.0|10.0||0.0|0.0|0
R|2132|6|2019|37,15|6|15|0.268|0.0|1|0.0|10.0||0.0|0.0|1
R|2132|6|2019|37,15|6|37|0.375|0.0|1|0.0|10.0||0.0|0.0|0
R|2132|6|2019|37,15|6|15|0.375|0.0|1|0.0|10.0||0.0|0.0|1
R|2132|6|2019|37,15|6|37|0.487|0.0|1|0.0|10.0||0.0|0.0|0
R|2132|6|2019|37,15|6|15|0.487|0.0|1|0.0|10.0||0.0|0.0|1
R|2132|6|2019|37,15|6|37|0.595|0.0|1|0.0|10.0||0.0|0.0|0
R|2132|6|2019|37,15|6|15|0.595|0.0|1|0.0|10.0||0.0|0.0|1
R|2132|6|2019|37,15|6|37|0.708|0.0|1|0.0|10.0||0.0|0.0|0
R|2132|6|2019|37,15|6|15|0.708|0.0|1|0.0|10.0||0.0|0.0|1

Message field information

The list below explains the meaning of each field in the running time message.

  • CompetitionId: The SQL Id of the current competition(distance).
  • CompetitionNumber: The distance/schedule number of the current competition(distance).
  • ClockId: The SQL Id of the clock performing the measurements.
  • ActiveCOmpetitors: An array of integers containing the CNR/Bib numbers of the skaters.
  • StartNumber: The start number of the active pair that is racing.
  • CNR: The CNR/Bib number of the active skater in the pair.
  • Time: The current running time of the clock, formatted as 0.0##.
  • Speed: The current speed. Only available when using transponders and multiple loops, formatted as 0.0##.
  • Status: The current status of the race (0=Start, 1=Running, 2=Finish).
  • LastPassage: The time of the last passing, formatted as 0.0##.
  • NextPassage: An estimate of the next passing, formatted as 0.0##.
  • LastLoop: The last loop crossed, when using transponders.
  • Distance: The distance raced until this message, formatted as fractional laps, 1=1(lap)=400 meters.
  • Intermediate: The intermediate time on the last passing of a transponder loop formatted as 0.0##.
  • Armband: The armband color of the skater [0=white, 1=red, 2=yellow, 3=blue, 4=purple, 5=green]

Reference implementation of the running time message in C#

The following code can be used as a starting point for decoding/encoding the running time message. This code is taken from SkatingApp and used in receiving and transmitting the running time message.

public enum ScgRunningTimeStatus
{
    Start,
    Running,
    Finish
}

public class RunningTimeMessage : IMessage
{
    public int CompetitionId { get; set; }
    public int CompetitionNumber { get; set; }
    public int ClockId { get; set; }
    public int[] ActiveCompetitors { get; set; }
    public int StartNumber { get; set; }
    public int Cnr { get; set; }
    public decimal Time { get; set; }
    public decimal Speed { get; set; }
    public ScgRunningTimeStatus Status { get; set; }
    public decimal LastPassage { get; set; }
    public decimal NextPassage { get; set; }
    public string LastLoop { get; set; }
    public decimal Distance { get; set; }
    public decimal Intermediate { get; set; }
    public int Armband { get; set; }

    public decimal CurrentLap => Math.Floor(Distance);

    public string ToDataString()
    {
        return string.Format(CultureInfo.InvariantCulture,
            "R|{0}|{1}|{2}|{3}|{4}|{5}|{6:0.000}|{7:0.0##}|{8}|{9:0.0##}|{10:0.0##}|{11}|{12:0.0##}|{13:0.0##}|{14}",
            CompetitionId, CompetitionNumber, ClockId, string.Join(",", ActiveCompetitors), StartNumber, Cnr, Time,
            Speed, (int) Status, LastPassage, NextPassage, LastLoop, Distance, Intermediate, Armband);
    }

    public static RunningTimeMessage CreateMessage(string data)
    {
        var fields = data.Split('|');
        if (fields.Length != 16) return null;
        var message = new RunningTimeMessage();
        for (var i = 0; i < fields.Length; i++)
            switch (i)
            {
                case 1:
                    message.CompetitionId = Convert.ToInt32(fields[i], CultureInfo.InvariantCulture);
                    break;
                case 2:
                    message.CompetitionNumber = Convert.ToInt32(fields[i], CultureInfo.InvariantCulture);
                    break;
                case 3:
                    message.ClockId = Convert.ToInt32(fields[i], CultureInfo.InvariantCulture);
                    break;
                case 4:
                    try
                    {
                        message.ActiveCompetitors = fields[i].Split(',').Select(x => Convert.ToInt32(x)).ToArray();
                    }
                    catch (Exception)
                    {
                    }

                    break;
                case 5:
                    message.StartNumber = Convert.ToInt32(fields[i], CultureInfo.InvariantCulture);
                    break;
                case 6:
                    message.Cnr = Convert.ToInt32(fields[i], CultureInfo.InvariantCulture);
                    break;
                case 7:
                    message.Time = Convert.ToDecimal(fields[i], CultureInfo.InvariantCulture);
                    break;
                case 8:
                    message.Speed = Convert.ToDecimal(fields[i], CultureInfo.InvariantCulture);
                    break;
                case 9:
                    ScgRunningTimeStatus status;
                    Enum.TryParse(fields[i], out status);
                    message.Status = status;
                    break;
                case 10:
                    message.LastPassage = Convert.ToDecimal(fields[i], CultureInfo.InvariantCulture);
                    break;
                case 11:
                    message.NextPassage = Convert.ToDecimal(fields[i], CultureInfo.InvariantCulture);
                    break;
                case 12:
                    message.LastLoop = fields[i];
                    break;
                case 13:
                    message.Distance = Convert.ToDecimal(fields[i], CultureInfo.InvariantCulture);
                    break;
                case 14:
                    message.Intermediate = Convert.ToDecimal(fields[i], CultureInfo.InvariantCulture);
                    break;
                case 15:
                    message.Armband = Convert.ToInt32(fields[i], CultureInfo.InvariantCulture);
                    break;
            }
        return message;
    }
}

Passing Message

The passing message contains information on an active passing of either a loop or the finish line. It contains the passing time and lap time and the lap number of that passing.The passing message has the following structure:

`P|CompetitionId|CompetitionNumber|ClockId|Lane|Cnr|Lap|LapTime:0.0##|PassingTime:0.000|FinalLap?|Armband`

#Example passings in a 500m race (1 pair of skaters)
P|2132|6|2019|I|37|1.0|10.750|10.758|0|0
P|2132|6|2019|O|15|1.0|10.850|10.853|0|1
P|2132|6|2019|O|37|2.0|27.170|37.922|1|0
P|2132|6|2019|I|15|2.0|27.920|38.774|1|1

Message field information

The list below explains the meaning of each field in the passing message.

  • CompetitionId: The SQL Id of the current competition(distance).
  • CompetitionNumber: The distance/schedule number of the current competition(distance).
  • ClockId: The SQL Id of the clock performing the measurements.
  • Lane: The Lane where the passing has occurred (I=Inner Lane, O=Outer Lane).
  • CNR: The CNR/Bib number of the active skater in the pair.
  • Lap: The lap in which the passing occurred.
  • LapTime: The lap time of the passing, in at least one decimal.
  • PassingTime: The time of the clock when crossing the finish line, in 3 decimals.
  • FinalLap: 1 when the passing is the final passing(finish), else 0.
  • Armband: The armband color of the skater [0=white, 1=red, 2=yellow, 3=blue, 4=purple, 5=green]

Reference implementation of the passing message in C#

The following code can be used as a starting point for decoding/encoding the passing message. This code is taken from SkatingApp and used in receiving and transmitting the passing message.

public class PassageMessage : IMessage
{
    public int CompetitionId { get; set; }
    public int CompetitionNumber { get; set; }
    public int ClockId { get; set; }
    public string Lane { get; set; }
    public int Cnr { get; set; }
    public decimal Lap { get; set; }
    public decimal LapTime { get; set; }
    public decimal Time { get; set; }
    public bool IsFinalLap { get; set; }
    public int Armband { get; set; }

    public string ToDataString()
    {
        return string.Format(CultureInfo.InvariantCulture, "P|{0}|{1}|{2}|{3}|{4}|{5:F1}|{6:F3}|{7:F3}|{8}|{9}",
            CompetitionId, CompetitionNumber, ClockId, Lane, Cnr, Lap, LapTime, Time, IsFinalLap ? 1 : 0, Armband);
    }

    public static PassageMessage CreateMessage(string data)
    {
        var fields = data.Split('|');
        if (fields.Length != 11) return null;
        var message = new PassageMessage();
        for (var i = 0; i < fields.Length; i++)
            switch (i)
            {
                case 1:
                    message.CompetitionId = Convert.ToInt32(fields[i], CultureInfo.InvariantCulture);
                    break;
                case 2:
                    message.CompetitionNumber = Convert.ToInt32(fields[i], CultureInfo.InvariantCulture);
                    break;
                case 3:
                    message.ClockId = Convert.ToInt32(fields[i], CultureInfo.InvariantCulture);
                    break;
                case 4:
                    message.Lane = fields[i];
                    break;
                case 5:
                    message.Cnr = Convert.ToInt32(fields[i], CultureInfo.InvariantCulture);
                    break;
                case 6:
                    message.Lap = Convert.ToDecimal(fields[i], CultureInfo.InvariantCulture);
                    break;
                case 7:
                    message.LapTime = Convert.ToDecimal(fields[i], CultureInfo.InvariantCulture);
                    break;
                case 8:
                    message.Time = Convert.ToDecimal(fields[i], CultureInfo.InvariantCulture);
                    break;
                case 9:
                    message.IsFinalLap = Convert.ToInt32(fields[i], CultureInfo.InvariantCulture) > 0;
                    break;
                case 10:
                    message.Armband = Convert.ToInt32(fields[i], CultureInfo.InvariantCulture);
                    break;
            }
        return message;
    }
}

Example exports of real race data

The files contain exports made with PacketSender of the App Interface data stream. You can use the logs to get a better understanding of the traffic sent on the App Interface bus. Each log file contains at least one or more complete races from start to finish.