Difference between revisions of "Telemetry interface"
m (→Interface: Comments) |
(→Using the data: Info about the lap counter updated) |
||
Line 297: | Line 297: | ||
* 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. | * 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. | * 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. | * The provided telemetry data records are not cleared after a race ends. | ||
Revision as of 20:49, 16 October 2019
Maniaplanet 4 and Trackmania Turbo 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
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 = 2,
};
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]; // player model 'StadiumCar', 'CanyonCar', ....
char MapId[64];
char MapName[256];
char __future__[128];
};
struct SRaceState {
ERaceState State;
Nat32 Time;
Nat32 NbRespawns;
Nat32 NbCheckpoints;
Nat32 CheckpointTimes[125];
Nat32 NbCheckpointsPerLap; // new since Maniaplanet update 2019-10-10; not supported by Trackmania Turbo.
Nat32 NbLaps; // new since Maniaplanet update 2019-10-10; not supported by Trackmania Turbo.
char __future__[24];
};
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.
char __future__[32];
};
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];
};
SHeader Header;
Nat32 UpdateNumber;
SGameState Game;
SRaceState Race;
SObjectState Object;
SVehicleState Vehicle;
SDeviceState Device;
};
}
#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.