Difference between revisions of "Telemetry interface"
(→Using the data: Info about the lap counter updated) |
(Version 3 of the shared memory structure STelemetry) |
||
Line 1: | Line 1: | ||
− | Maniaplanet 4 and Trackmania | + | Maniaplanet 4, Trackmania Turbo and Trackmania 2020 provide '''telemetry''' data via a shared memory interface. |
==Interface== | ==Interface== | ||
Line 17: | Line 17: | ||
* Vehicle state - Current information about the vehicle itself | * Vehicle state - Current information about the vehicle itself | ||
* Device state - Information for virtual chairs | * Device state - Information for virtual chairs | ||
+ | * Player state - Information about the current player (TM2020) | ||
The complete data structure can be taken from the header file maniaplanet_telemetry.h: | The complete data structure can be taken from the header file maniaplanet_telemetry.h: | ||
Line 28: | Line 29: | ||
enum { | enum { | ||
− | ECurVersion = | + | ECurVersion = 3, |
}; | }; | ||
Line 60: | Line 61: | ||
struct SGameState { | struct SGameState { | ||
EGameState State; | EGameState State; | ||
− | char GameplayVariant[64]; // | + | char GameplayVariant[64]; // environment name 'stadium' 'canyon', .... |
char MapId[64]; | char MapId[64]; | ||
char MapName[256]; | char MapName[256]; | ||
Line 71: | Line 72: | ||
Nat32 NbCheckpoints; | Nat32 NbCheckpoints; | ||
Nat32 CheckpointTimes[125]; | Nat32 CheckpointTimes[125]; | ||
− | Nat32 NbCheckpointsPerLap; | + | Nat32 NbCheckpointsPerLap; |
− | Nat32 | + | Nat32 NbLapsPerRace; |
− | char __future__[ | + | Nat32 Timestamp; |
+ | Nat32 StartTimestamp; // timestamp when the State will change to 'Running', or has changed when after the racestart. | ||
+ | char __future__[16]; | ||
}; | }; | ||
struct SObjectState { | struct SObjectState { | ||
Line 111: | Line 114: | ||
Bool IsLightsOn; | Bool IsLightsOn; | ||
Bool IsFlying; // long time since touching ground. | Bool IsFlying; // long time since touching ground. | ||
+ | Bool IsOnIce; | ||
+ | |||
+ | Nat32 Handicap; // bit mask: [reserved..] [NoGrip] [NoSteering] [NoBrakes] [EngineForcedOn] [EngineForcedOff] | ||
+ | float BoostRatio; // 1 thrusters starting/full .... 0 -> finished | ||
− | char __future__[ | + | char __future__[20]; |
}; | }; | ||
struct SDeviceState { // VrChair state. | struct SDeviceState { // VrChair state. | ||
Line 120: | Line 127: | ||
char __future__[32]; | char __future__[32]; | ||
+ | }; | ||
+ | |||
+ | struct SPlayerState { | ||
+ | Bool IsLocalPlayer; // Is the locally controlled player, or else it is a a remote player we're spectating, or a replay. | ||
+ | char Trigram[4]; // 'TMN' | ||
+ | char DossardNumber[4]; // '01' | ||
+ | float Hue; | ||
+ | char UserName[256]; | ||
+ | char __future__[28]; | ||
}; | }; | ||
Line 130: | Line 146: | ||
SVehicleState Vehicle; | SVehicleState Vehicle; | ||
SDeviceState Device; | SDeviceState Device; | ||
+ | SPlayerState Player; | ||
}; | }; | ||
} | } | ||
− | #endif // _MANIAPLANET_TELEMETRY_H</syntaxhighlight> | + | |
+ | // ----------------------------------------------- | ||
+ | // Changelog: | ||
+ | // Version 3 is a superset of Version 2. | ||
+ | // New fields are: | ||
+ | // Race.Timestamp, Race.StartTimestamp | ||
+ | // Vehicle.IsOnIce, Vehicle.Handicap, Vehicle.BoostRatio | ||
+ | // Player.* | ||
+ | |||
+ | #endif // _MANIAPLANET_TELEMETRY_H | ||
+ | |||
+ | </syntaxhighlight> | ||
==Using the data== | ==Using the data== |
Revision as of 17:13, 12 June 2022
Maniaplanet 4, Trackmania Turbo and Trackmania 2020 provide telemetry data via a shared memory interface.
Interface
The data is written continuously to a non-persisted memory mapped file in virtual memory. External applications have direct access to it. The name of the file mapping object is ManiaPlanet_Telemetry.
The interface is active immediately after the start of the game. If Maniaplanet and Trackmania Turbo are running at the same time (or multiple instances of the same game), it is not possible to determine from which instance the data originates.
It is not known when or how often the data is updated.
An export of the telemetry data by UDP streaming is not available. The data can only be received on the PC on which the game is running.
The following information is provided:
- Game state - Information about the current state of the game
- Race state - Information about the current state of the race
- Object state - State of the vehicle within the virtual world
- Vehicle state - Current information about the vehicle itself
- Device state - Information for virtual chairs
- Player state - Information about the current player (TM2020)
The complete data structure can be taken from the header file maniaplanet_telemetry.h:
#ifndef _MANIAPLANET_TELEMETRY_H
#define _MANIAPLANET_TELEMETRY_H
#pragma once
namespace NManiaPlanet {
enum {
ECurVersion = 3,
};
typedef unsigned int Nat32;
typedef unsigned int Bool;
struct Vec3 {
float x,y,z;
};
struct Quat {
float w,x,y,z;
};
struct STelemetry {
struct SHeader {
char Magic[32]; // "ManiaPlanet_Telemetry"
Nat32 Version;
Nat32 Size; // == sizeof(STelemetry)
};
enum EGameState {
EState_Starting = 0,
EState_Menus,
EState_Running,
EState_Paused,
};
enum ERaceState {
ERaceState_BeforeState = 0,
ERaceState_Running,
ERaceState_Finished,
};
struct SGameState {
EGameState State;
char GameplayVariant[64]; // environment name 'stadium' 'canyon', ....
char MapId[64];
char MapName[256];
char __future__[128];
};
struct SRaceState {
ERaceState State;
Nat32 Time;
Nat32 NbRespawns;
Nat32 NbCheckpoints;
Nat32 CheckpointTimes[125];
Nat32 NbCheckpointsPerLap;
Nat32 NbLapsPerRace;
Nat32 Timestamp;
Nat32 StartTimestamp; // timestamp when the State will change to 'Running', or has changed when after the racestart.
char __future__[16];
};
struct SObjectState {
Nat32 Timestamp;
Nat32 DiscontinuityCount; // the number changes everytime the object is moved not continuously (== teleported).
Quat Rotation;
Vec3 Translation; // +x is "left", +y is "up", +z is "front"
Vec3 Velocity; // (world velocity)
Nat32 LatestStableGroundContactTime;
char __future__[32];
};
struct SVehicleState {
Nat32 Timestamp;
float InputSteer;
float InputGasPedal;
Bool InputIsBraking;
Bool InputIsHorn;
float EngineRpm; // 1500 -> 10000
int EngineCurGear;
float EngineTurboRatio; // 1 turbo starting/full .... 0 -> finished
Bool EngineFreeWheeling;
Bool WheelsIsGroundContact[4];
Bool WheelsIsSliping[4];
float WheelsDamperLen[4];
float WheelsDamperRangeMin;
float WheelsDamperRangeMax;
float RumbleIntensity;
Nat32 SpeedMeter; // unsigned km/h
Bool IsInWater;
Bool IsSparkling;
Bool IsLightTrails;
Bool IsLightsOn;
Bool IsFlying; // long time since touching ground.
Bool IsOnIce;
Nat32 Handicap; // bit mask: [reserved..] [NoGrip] [NoSteering] [NoBrakes] [EngineForcedOn] [EngineForcedOff]
float BoostRatio; // 1 thrusters starting/full .... 0 -> finished
char __future__[20];
};
struct SDeviceState { // VrChair state.
Vec3 Euler; // yaw, pitch, roll (order: pitch, roll, yaw)
float CenteredYaw; // yaw accumulated + recentered to apply onto the device
float CenteredAltitude; // Altitude accumulated + recentered
char __future__[32];
};
struct SPlayerState {
Bool IsLocalPlayer; // Is the locally controlled player, or else it is a a remote player we're spectating, or a replay.
char Trigram[4]; // 'TMN'
char DossardNumber[4]; // '01'
float Hue;
char UserName[256];
char __future__[28];
};
SHeader Header;
Nat32 UpdateNumber;
SGameState Game;
SRaceState Race;
SObjectState Object;
SVehicleState Vehicle;
SDeviceState Device;
SPlayerState Player;
};
}
// -----------------------------------------------
// Changelog:
// Version 3 is a superset of Version 2.
// New fields are:
// Race.Timestamp, Race.StartTimestamp
// Vehicle.IsOnIce, Vehicle.Handicap, Vehicle.BoostRatio
// Player.*
#endif // _MANIAPLANET_TELEMETRY_H
Using the data
Nadeo has provided an example program (including source code) in the closed Maniaplanet 4 Beta forums. The tool displays all of the provided telemetry data items live in a window.
To process the data, we need to know when and how the individual data records are provided. The following C++ source code allows you to create a simple console application that will output some of the telemetry data live:
// maniaplanet_telemetry.cpp : TM² - TrackMania Telemetry Monitor
//
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include "maniaplanet_telemetry.h"
bool done = false;
BOOL CtrlHandler(DWORD fdwCtrlType)
{
done = true;
return TRUE;
}
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hMapFile = NULL;
void* pBufView = NULL;
using namespace NManiaPlanet;
const volatile STelemetry* Shared = NULL;
Nat32 UpdateNumber = 0;
// Set console title and control handler
SetConsoleTitle(TEXT("TM² - TrackMania Telemetry Monitor"));
SetConsoleCtrlHandler((PHANDLER_ROUTINE)CtrlHandler, TRUE);
// Main loop
while (!done)
{
// Get access to the telemetry data
if (Shared == NULL)
{
if (hMapFile == NULL)
hMapFile = OpenFileMapping(FILE_MAP_READ, FALSE, TEXT("ManiaPlanet_Telemetry"));
if (hMapFile != NULL)
{
if (pBufView == NULL)
pBufView = (void*)MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, 4096);
}
// Show the wait status in the console title
static bool show_waiting_title = true;
if (show_waiting_title && pBufView == NULL)
{
show_waiting_title = false;
SetConsoleTitle(TEXT("TM² - Waiting for the game..."));
}
Shared = (const STelemetry*)pBufView;
}
else
{
STelemetry S;
for (;;)
{
Nat32 Before = Shared->UpdateNumber;
memcpy(&S, (const STelemetry*)Shared, sizeof(S));
Nat32 After = Shared->UpdateNumber;
if (Before == After)
break;
else
continue; // reading while the game is changing the values.. retry.
}
// Update the console title and output the header
static bool write_header = true;
if (write_header)
{
write_header = false;
SetConsoleTitle(TEXT("TM² - TrackMania Telemetry Monitor"));
puts("UpdateNumber,GameState,GameplayVariant,MapName,MapId,RaceState,RaceTime,"
"NbRespawns,NbCheckpoints,CheckpointTimes,Timestamp,SpeedMeter,InputSteer,InputGasPedal,"
"InputIsBraking,EngineRpm,EngineCurGear,EngineTurboRatio,RumbleIntensity");
}
// Check for updated telemetry data
if (S.UpdateNumber != UpdateNumber)
{
UpdateNumber = S.UpdateNumber;
// Output update number
printf("%u,", S.UpdateNumber);
// Output game data
switch (S.Game.State)
{
case STelemetry::EState_Starting:
printf("Starting,");
break;
case STelemetry::EState_Menus:
printf("Menus");
break;
case STelemetry::EState_Running:
printf("Running");
break;
case STelemetry::EState_Paused:
printf("Paused");
break;
}
printf(",%s,%s,%s,", S.Game.GameplayVariant, S.Game.MapName, S.Game.MapId);
// Output race data
switch (S.Race.State)
{
case STelemetry::ERaceState_BeforeState:
printf("BeforeStart");
break;
case STelemetry::ERaceState_Running:
printf("Running");
break;
case STelemetry::ERaceState_Finished:
printf("Finished");
break;
}
printf(",%d,%u,%u,%u,", S.Race.Time, S.Race.NbRespawns, S.Race.NbCheckpoints,
S.Race.NbCheckpoints == 0 ? 0 : S.Race.CheckpointTimes[S.Race.NbCheckpoints - 1]);
// Output some vehicle data
printf("%u,%u,%f,%f,%s,%f,%d,%f,%f", S.Vehicle.Timestamp, S.Vehicle.SpeedMeter, S.Vehicle.InputSteer,
S.Vehicle.InputGasPedal, S.Vehicle.InputIsBraking ? "True" : "False", S.Vehicle.EngineRpm,
S.Vehicle.EngineCurGear, S.Vehicle.EngineTurboRatio, S.Vehicle.RumbleIntensity);
printf("\n");
}
}
// Suspend the busy wait loop
Sleep(5);
}
// Cleanup
if (pBufView)
UnmapViewOfFile(pBufView);
if (hMapFile)
CloseHandle(hMapFile);
return 0;
}
UpdateNumer is used to synchronize the data between the game and the receiver. The variable is incremented once before the values are changed and once thereafter.
For timing information, the object state and vehicle state structures each contain a Timestamp variable. The resolution of the included time is 10 milliseconds.
A few notes about the race data:
- A new race starts when the race state changes from BeforeState to Running.
- A race ends when the race state changes from Running to Finished or BeforeState. In order to distinguish between finish and restart, it's necessary to check whether the checkpoint count has increased.
- It is not guaranteed that the checkpoint time has been updated at the same time as the checkpoint number is raised.
- The current lap of a multi-lap race can be determined by dividing NbCheckpoints by NbCheckpointsPerLap.
- The provided telemetry data records are not cleared after a race ends.