Jump to content

noobs guide to real time telemetry from DCS


steve2112

Recommended Posts

i've finally got something working after a day and a bit of frustration, so i thought i would share for other people interested in getting real time telemetry data form DCS.

 

many many many thanks to jboecker at DCS-BIOS for all his patience and help with my stupid question.

 

so basically, you can talk to DCS as a programmer using a file called Exports.lua in your DCS/Scripts folder here C:\Users\me\Saved Games\DCS\Scripts or here C:\Users\me\Saved Games\DCS.openbeta\Scripts if you are on 1.5

 

so here's my Exports.lua

 

local log_file = nil

package.path  = package.path..";.\\LuaSocket\\?.lua"
package.cpath = package.cpath..";.\\LuaSocket\\?.dll"
socket = require("socket")
host = host or "localhost"
port = port or 27015

function LuaExportStart()
   log_file = io.open("C:/Users/me/Saved Games/DCS/Logs/Export.log", "w")
end

function LuaExportBeforeNextFrame()
end
function LuaExportAfterNextFrame()
end

function LuaExportStop()

if log_file then
	log_file:write("Closing log file.")
	log_file:close()
	log_file = nil
end	

if connectSoc then
	socket.try(connectSoc:send("quit")) -- to close the listener socket
	connectSoc:close()
end

end

function LuaExportActivityNextEvent(t)

if connectSoc==nil then
	log_file:write("try to open socket\n")
	connectSoc = socket.try(socket.connect(host, port)) -- connect to the listener socket
	connectSoc:setoption("tcp-nodelay",true) -- set immediate transmission mode
	
	if connectSoc then
		log_file:write("socket opened ok\n")
		socket.try(connectSoc:send(string.format("Hello from DCS\n")))
	else
		log_file:write("socket open failed\n")
		return t+0.5
	end
end

local altBar = LoGetAltitudeAboveSeaLevel()
local altRad = LoGetAltitudeAboveGroundLevel()
local pitch, bank, yaw = LoGetADIPitchBankYaw()
local tas = LoGetTrueAirSpeed()


socket.try(connectSoc:send(string.format(
		"altBar=%.2f, pitch=%.2f, bank=%.2f tas=%.2f\n", 
		altBar, 57.3*pitch, 57.3*bank, tas)))

local o = LoGetWorldObjects()
for k,v in pairs(o) do

	-- if v.UnitName=="steve" then
	socket.try(connectSoc:send(string.format(
		"name=%s UnitName=%s LatLong=(%f,%f)\n", 
		v.Name, v.UnitName, v.LatLongAlt.Lat, v.LatLongAlt.Long)))
	-- end

end

return t+0.5
end

 

 

so all you need to do on the DCS side is put that in the Scripts folder and run DCS

 

then the bit that took me a while to work out. That export script is basically a client application, it attempts to connect to a TCP server on port 27015. if you don't know what this sentence means, you need to do a lot of reading on networking and TCPIP.

 

so you need to write a server to listen to port 27015. and here it is (on windows)

 

 

// astest.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#undef UNICODE

#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>

// Need to link with Ws2_32.lib
#pragma comment (lib, "Ws2_32.lib")
// #pragma comment (lib, "Mswsock.lib")

#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "27015"

int _tmain(int argc, _TCHAR* argv[])
{
   WSADATA wsaData;
   int iResult;

   SOCKET ListenSocket = INVALID_SOCKET;
   SOCKET ClientSocket = INVALID_SOCKET;

   struct addrinfo *result = NULL;
   struct addrinfo hints;

   int iSendResult;
   char recvbuf[DEFAULT_BUFLEN];
   int recvbuflen = DEFAULT_BUFLEN;
   
for(;;) {
	// Initialize Winsock
	iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
	if (iResult != 0) {
		printf("WSAStartup failed with error: %d\n", iResult);
		return 1;
	}

	ZeroMemory(&hints, sizeof(hints));
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;
	hints.ai_flags = AI_PASSIVE;

	// Resolve the server address and port
	iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
	if ( iResult != 0 ) {
		printf("getaddrinfo failed with error: %d\n", iResult);
		WSACleanup();
		return 1;
	}
	printf("getaddrinfo OK\n");

	// Create a SOCKET for connecting to server
	ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
	if (ListenSocket == INVALID_SOCKET) {
		printf("socket failed with error: %ld\n", WSAGetLastError());
		freeaddrinfo(result);
		WSACleanup();
		return 1;
	}
	printf("socket OK\n");

	// Setup the TCP listening socket
	iResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen);
	if (iResult == SOCKET_ERROR) {
		printf("bind failed with error: %d\n", WSAGetLastError());
		freeaddrinfo(result);
		closesocket(ListenSocket);
		WSACleanup();
		return 1;
	}
	printf("bind OK\n");

	freeaddrinfo(result);

	iResult = listen(ListenSocket, SOMAXCONN);
	if (iResult == SOCKET_ERROR) {
		printf("listen failed with error: %d\n", WSAGetLastError());
		closesocket(ListenSocket);
		WSACleanup();
		return 1;
	}
	printf("listen OK\n");

	// Accept a client socket
	ClientSocket = accept(ListenSocket, NULL, NULL);
	if (ClientSocket == INVALID_SOCKET) {
		printf("accept failed with error: %d\n", WSAGetLastError());
		closesocket(ListenSocket);
		WSACleanup();
		return 1;
	}
	printf("accept OK\n");

	// No longer need server socket
	closesocket(ListenSocket);

	// Receive until the peer shuts down the connection
	do {

		iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
		if (iResult > 0) {
			//printf("Bytes received: %d\n", iResult);

			char* end = strchr(recvbuf, 10);
			if(end && end<&recvbuf[recvbuflen])
				*end=0;
			printf("%s\n", recvbuf);
		}
		else if (iResult == 0)
			printf("Connection closing...\n");
		else  {
			printf("recv failed with error: %d\n", WSAGetLastError());
			//closesocket(ClientSocket);
			//WSACleanup();
			break;
		}

	} while (iResult > 0);
	
	// cleanup
	printf("closesocket OK\n");
	iResult = shutdown(ClientSocket, SD_SEND);
	if (iResult == SOCKET_ERROR) {
		printf("shutdown failed with error: %d\n", WSAGetLastError());
		closesocket(ClientSocket);
		WSACleanup();
		return 1;
	}

	// cleanup
	closesocket(ClientSocket);
	WSACleanup();
}

   return 0;
}

 

 

so to use this code, get MSVC, start a new console app, and in your c++ source file, just paste all that, compile and run.

 

now as you fly around in DCS, you should see data in the console window.

 

the real trick here is the sync'ing of the network connections. i was having a really hard time to get the two things to start up reliably. so in the Export.lua file you will notice that i check to see if the connection is open on every call to LuaExportActivityNextEvent. if it isn't, i attempt to open it, and if ok, i start sending data.

 

similarly on the windows server side, it needs to keep retrying to open a connection, so i took the example from here

https://msdn.microsoft.com/en-us/library/windows/desktop/ms737593(v=vs.85).aspx

and put the whole thing in a forever loop so it keeps retrying to connect.

 

this way i was able to get the thing to run regardless of the startup sequence. (although sometime it doesn''t start and then i try again and it works, so still looking into that)

 

this should work for any plane by the way, not just a10c

 

there's probably a better way and i'd be happy to know about it, but at least i can say, this works.


Edited by steve2112
  • Like 1

My kit: i7-4790K@4GHz / 8GB - GTX 980ti + rift CV1 - X52 pro - Multi Keyboard Remapper - 2DOF motion sim (in development)

Link to comment
Share on other sites

ok, i've just found a better way by borrowing some ideas from DCS-BIOS, multicast!

 

the nice thing is there's no connections to make and there no significant load on DCS i think.

 

anyhow, its super simple, here's the exports.lua

 

 


package.path  = package.path..";.\\LuaSocket\\?.lua"
package.cpath = package.cpath..";.\\LuaSocket\\?.dll"
 
socket = require("socket")
ipaddr = "239.255.50.10"
port = 5010

-- Lua Export Functions
function LuaExportStart()

conn = socket.udp()
conn:settimeout(0)

end

function LuaExportStop()

socket.try(conn:close())
end

function LuaExportBeforeNextFrame()
end

function LuaExportAfterNextFrame()

socket.try(conn:sendto("hello from DCS\0", ipaddr , port))

local altBar = LoGetAltitudeAboveSeaLevel()
local pitch, bank, yaw = LoGetADIPitchBankYaw()
local tas = LoGetTrueAirSpeed()

socket.try(conn:sendto(string.format(
		"altBar=%.2f, pitch=%.2f, bank=%.2f tas=%.2f\n", 
		altBar, 57.3*pitch, 57.3*bank, tas),
		ipaddr , port))

local o = LoGetWorldObjects()
for k,v in pairs(o) do

	-- if v.UnitName=="steve" then
	socket.try(conn:sendto(string.format(
		"name=%s UnitName=%s LatLong=(%f,%f)\n", 
		v.Name, v.UnitName, v.LatLongAlt.Lat, v.LatLongAlt.Long), 
ipaddr , port))
	-- end

end


end

 

and the c++ program

 

// DCS_airshow.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <winsock2.h>
#include <Ws2tcpip.h>
#include <stdio.h>

// Link with ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")

int _tmain(int argc, _TCHAR* argv[])
{

   char* multicast_ip = "239.255.50.10";
unsigned short multicast_port = 5010;
SOCKADDR_IN multicast_addr;
WSADATA wsaData;
int hr;
BOOL bOptVal = TRUE;
ip_mreq mreq;
int max_length = 16;

WSAStartup(MAKEWORD(2,0), &wsaData);

SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sock == INVALID_SOCKET) {
       wprintf(L"socket failed with error %d\n", WSAGetLastError());
       return 1;
   }

// construct bind structure
memset(&multicast_addr, 0, sizeof(multicast_addr));
multicast_addr.sin_family = AF_INET;
multicast_addr.sin_addr.s_addr = htonl(INADDR_ANY);
multicast_addr.sin_port = htons(multicast_port);

hr = bind(sock, (struct sockaddr *) &multicast_addr, sizeof(multicast_addr));
if (hr != 0) {
       wprintf(L"bind failed with error %d\n", WSAGetLastError());
       return 1;
   }
/* Specify the multicast group */
mreq.imr_multiaddr.s_addr = inet_addr(multicast_ip);
/* Accept multicast from any interface */
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
/* Join the multicast address */
hr = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char FAR *) &mreq, sizeof(mreq));
if (hr != 0) {
       wprintf(L"setsockopt failed with error %d\n", WSAGetLastError());
       return 1;
   }
int optval = 8;
hr = setsockopt(sock,IPPROTO_IP,IP_MULTICAST_TTL, (char*)&optval,sizeof(int));
if (hr != 0) {
       wprintf(L"setsockopt failed with error %d\n", WSAGetLastError());
       return 1;
   }
hr = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&bOptVal, sizeof(bOptVal));
if (hr != 0) {
       wprintf(L"setsockopt failed with error %d\n", WSAGetLastError());
       return 1;
   }

int timeout = 1000; // 1 sec
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *) &timeout, sizeof(timeout));

int cnt=0;
while (true)
{
	unsigned char buffer[4096] = { 0 };

	int n = recvfrom(sock, (char*)buffer, 4096, 0, NULL, 0);
	if (n == SOCKET_ERROR) {
		//wprintf(L"%d recvfrom failed with error %d\n", cnt++, WSAGetLastError());
		continue;
	}

	printf("%s\n", buffer);
}
WSACleanup();
   return 0;
}

 

works great


Edited by steve2112

My kit: i7-4790K@4GHz / 8GB - GTX 980ti + rift CV1 - X52 pro - Multi Keyboard Remapper - 2DOF motion sim (in development)

Link to comment
Share on other sites

Science Bitch

Greek/German origin.

Flying sims since 1984.

Using computers since 1977.

Favored FS's:F/A18 Interceptor, F19 Stealth Fighter, Gunnship, F16 Combat Pilot, Flight of the Intruder, A320, Falcon 4.0, MSFS 2004-X, DCS

Link to comment
Share on other sites

Thank you sir :-) Answered some of my questions I never got round to asking.

 

Thanks for taking the time to put this up.

-16AGR- 16th Air Guards Regiment is always looking for pilots - http://www.16agr.com

 

EWRS - Early Warning Radar Script

 

Specs:

 

 

Gigabyte Sniper Z5-S

Intel i5-4670k 3.4GHz OC'd 3.9GHz w/ Thermaltake 120mm Water 3.0 Pro Liquid CPU Cooler

16GB RAM

Gigabyte GTX 1080

TM Hotas Warthog: SN: 06976

Saitek Pro Flight Combat Rudder Pedals

TrackIR5 with TrackClipPro & Oculus Rift

2x 28" 4k UHD Monitors (3840x2160 each) + 1280x1024

 

 

Link to comment
Share on other sites

Can you explain what you mean by saying that? It seems very disrespectful saying that to someone who has helped out the DCS community.

 

Oh, well it is not. Deffinetly not.

 

Have you ever heard of a TV series called Breaking bad?

 

That is a line from the series that gets used allot in various ermmmm variations.

 

The original is

 

 

[ame]

[/ame]

 

 

 

 

It is by no means disrespectful, on the contrary.

 

I urge you to watch the series if you have not allready done so.

 

and on a sidenote..... i am a bit rough on the edges at times but definitely not disrespectful.

 

Thanks for allowing me to clear this up.

 

Fly safe.


Edited by ineth

Greek/German origin.

Flying sims since 1984.

Using computers since 1977.

Favored FS's:F/A18 Interceptor, F19 Stealth Fighter, Gunnship, F16 Combat Pilot, Flight of the Intruder, A320, Falcon 4.0, MSFS 2004-X, DCS

Link to comment
Share on other sites

Great! Was looking for a heartbeat function for an external application, this was the first step in the right direction (it works).

 

Is there a Lo* function that just returns the number of players? Or players units as alternative? Iterating through all units is a little overkill for just the # of players.


Edited by Coug4r

-

If man were meant to fly he'd be filled with helium.

Link to comment
Share on other sites

Just a heads-up: to play nice with other applications that use Export.lua, you should store a reference to any existing callbacks before you redefine them and make sure to call them from your own callbacks.

 

See the Lua code for Helios or DCS-BIOS for an example.

 

For the same reason, you must avoid global variables as much as possible. In DCS-BIOS, I put everything into a single global table named BIOS or into file-local variables.

 

This doesn't really matter as long as you only use it internally, but once you publish anything, you will have users that want to use it concurrently with other programs such as Helios or TARS.

Link to comment
Share on other sites

Ian;2636740']Just a heads-up: to play nice with other applications that use Export.lua, you should store a reference to any existing callbacks before you redefine them and make sure to call them from your own callbacks.

 

See the Lua code for Helios or DCS-BIOS for an example.

 

For the same reason, you must avoid global variables as much as possible. In DCS-BIOS, I put everything into a single global table named BIOS or into file-local variables.

 

This doesn't really matter as long as you only use it internally, but once you publish anything, you will have users that want to use it concurrently with other programs such as Helios or TARS.

 

thanks for pointing that out, i did notice that code in DCS-BIOS and like you said, my code is just an example, and i wanted simplicity most of all to demonstrate the point, but the right way to do it is what you suggest.

 

thx

My kit: i7-4790K@4GHz / 8GB - GTX 980ti + rift CV1 - X52 pro - Multi Keyboard Remapper - 2DOF motion sim (in development)

Link to comment
Share on other sites

  • 5 years later...

Is this code still valid  for the actual version of DCS ?

Where can I find info about what values can be obtained beside these ones ? I would be intereseted in g-forces, impacts, ... if for a haptic vest
 

local altBar = LoGetAltitudeAboveSeaLevel()
local pitch, bank, yaw = LoGetADIPitchBankYaw()
local tas = LoGetTrueAirSpeed()

Edited by cercata
Link to comment
Share on other sites

  • 3 months later...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...