Jump to content

How to read DCS Bios read export messages from C#


jj_bravo

Recommended Posts

I'm trying to read DCS Bios messages in C#.

I've looked at this, but I'm not quite getting how to decode the message:

https://github.com/dcs-bios/dcs-bios/blob/master/Scripts/DCS-BIOS/doc/developerguide.adoc#the-dcs-bios-export-protocol

 

I want to read different gauge data and lights states.

 

This is an example of the data I'm getting back from DCS and I'm not quite sure what to do next:

Address : 0 Value : 16710 Address : 2 Value : 12589 Address : 4 Value : 17208 Address : 6 Value : 26719 Address : 8 Value : 29295 Address : 10 Value : 25966 Address : 12 Value : 116 Address : 14 Value : 8224 Address : 65534 Value : 1
Address : 4 Value : 17208 Address : 65534 Value : 2
Address : 6 Value : 26719 Address : 65534 Value : 3
Address : 8 Value : 29295 Address : 65534 Value : 4
Address : 10 Value : 25966 Address : 65534 Value : 5
Address : 12 Value : 116 Address : 65534 Value : 6
Address : 14 Value : 8224 Address : 65534 Value : 7
Address : 0 Value : 16710 Address : 65534 Value : 8
Address : 2 Value : 12589 Address : 65534 Value : 9
Address : 4 Value : 17208 Address : 65534 Value : 10
Address : 6 Value : 26719 Address : 65534 Value : 11
Address : 8 Value : 29295 Address : 65534 Value : 12
Address : 10 Value : 25966 Address : 65534 Value : 13
Address : 12 Value : 116 Address : 65534 Value : 14
Address : 14 Value : 8224 Address : 65534 Value : 15
Address : 0 Value : 16710 Address : 65534 Value : 16
Address : 2 Value : 12589 Address : 65534 Value : 17
Address : 4 Value : 17208 Address : 65534 Value : 18
Address : 6 Value : 26719 Address : 65534 Value : 19
Address : 8 Value : 29295 Address : 65534 Value : 20

 

Any help would be appreciated.

Thanks!

Link to comment
Share on other sites

Your primary resource should be the documentation here: https://github.com/dcs-bios/dcs-bios/blob/master/Scripts/DCS-BIOS/doc/developerguide.adoc.

 

I've pasted my javascript code to parse the DCS-BIOS stream, it won't give you all the answers but it will give you a good example, and should be straightforward to port to C#. This is pulled from a NodeRed environment, so there is a lot going on outside of these functions, but they are the important parts. You can also study the DCS-BIOS arduino code available on github.

 

/**
* Parse the DCS BIOS export message from the UDP client.
* Apply updates to the retained buffer in flow context.
* Transform msg.payload into an array of modified addresses.
*/
function applyUpdates(msg, flow) {
// the original message payload, binary buffer from dcs-bios
let buf = msg.payload;
// msg.payload transforms into an array of updated addresses
msg.payload = flow.get("updateAddresses") || [];
msg.payload.length = 0;

/**
 * Get (or create) the stored output buffer that we will apply updates to.
 * RawData is stored in binary format.
 */
let rawData = flow.get("rawData") || Buffer.alloc(65536);

// check for valid message format, the first 4 bytes are always 0x55
if (buf.length < 4 ||
	buf.readUInt32LE(0) !== 0x55555555)
{
	node.error("Malformed DCS-BIOS message received!", msg);
	return;
}

let offset = 4;

// Go through each update block in the message.
while (buf.length - offset > 0) {
	let startAddress = buf.readUInt16LE(offset);
	offset += 2;
	
	let dataLenB = buf.readUInt16LE(offset);
	offset += 2;
	
	buf.copy(rawData, startAddress, offset, offset + dataLenB);
	offset += dataLenB;
	
	msg.payload.push({ startAddress, dataLenB });
}

flow.set("rawData", rawData);
flow.set("updateAddresses", msg.payload);

return msg;
}

/**
* Converts the array of update addresses to the corresponding set of
* output control addresses.
* Integer data updates yield the same address, while partial
* string updates must step backward until the nearest control base
* address is found.
* 
* Updated values are deserialized and written to the output
* object's val property.
* 
* The returned msg.payload is an array of outputs to trigger change events.
*/
function mapUpdatesToControls(msg, flow) {
let addressMap = flow.get("outputAddressMap");
let rawData = flow.get("rawData");
let now = Date.now();
let changedOutputs = [];

msg.payload.forEach(({ startAddress, dataLenB }) => {
	// do some asserts to check for errors, remove later if they are never encountered
	if (Math.abs(startAddress % 2) === 1 || Math.abs(dataLenB % 2) === 1 || dataLenB === 0) {
		node.error(`Unexpected update address (${startAddress}) or length (${dataLenB})`);
	}
	
	// start at the end of the update data stream to walk backwards, possibly through multiple controls
	// Note: subtract 2 instead of 1 since we want even addresses, and dataLenB is always even
	let controlAddr = startAddress + dataLenB - 2;
	
	// make sure we find all controls back to the start of the data stream
	while (controlAddr >= startAddress) {
		// if not at a base control address, step backward to find nearest control
		while (!addressMap.has(controlAddr)) {
			controlAddr -= 2;
		}
		
		let outputsAtAddress = addressMap.get(controlAddr);
		outputsAtAddress.forEach((output) => {
			
			let hasChanged = false;
			
			// if this is an integer
			switch (output.type.charAt(0)) {
				case "i": {
					output.prevVal = output.val;
					let intVal = rawData.readUInt16LE(controlAddr);
					// get the bits representing this value using mask and shift_by
					output.val = (intVal & output.mask) >> output.shift_by;
					hasChanged = (output.val !== output.prevVal);
					break;
				}
				case "s": {
					//output.prevVal = output.rawVal;
					output.prevVal = output.hexVal;
					output.val = rawData.toString("utf8", controlAddr, controlAddr + output.max_length);
					//output.asciiVal = rawData.toString("ascii", controlAddr, controlAddr + output.max_length);
					output.hexVal = rawData.toString("hex", controlAddr, controlAddr + output.max_length);
					output.rawVal = rawData.slice(controlAddr, controlAddr + output.max_length);
					//hasChanged = !output.rawVal.equals(output.prevVal);
					hasChanged = (output.hexVal !== output.prevVal);
					break;
				}
				default:
					node.warn("unknown output type: " + output.type);
			}
			
			// add the output to trigger a change event downstream, prevent duplicates
			if ((hasChanged ||
				now - output.lastUpdate >= 1000) &&
				!changedOutputs.some((o) => Object.is(o, output)))
			{
				changedOutputs.push(output);
			}
			
			// capture update time for telemetry
			output.updateDelta = now - output.lastUpdate;
			output.lastUpdate = now;
		});
		
		controlAddr -= 2;
	}
});

msg.payload = changedOutputs;
return msg;
}

/**
* Pivots the flow.outputs object to a Map indexed by output address.
* This is convenient for quickly updating based on the output data stream.
* Sets msg.payload to the new Map object, flow.outputAddressMap.
*/
function buildOutputAddressMap(msg, flow) {
let outputs = flow.get("outputs");

let addressMap = Object.values(outputs).reduce(
	(map, output) => {
		let addr = output.address;
		
		// map multiple controls per address, since many controls are bitfields
		// and share the same base address
		let outputList = map.get(addr) || [];
		outputList.push(output);
		
		map.set(addr, outputList);
		
		return map;
	}, new Map());

flow.set("outputAddressMap", addressMap);
msg.payload = addressMap;

return msg;
}

Link to comment
Share on other sites

All of the control information is available in the DCS-BIOS project. This file for the A-10 (for example):

https://github.com/dcs-bios/dcs-bios/blob/master/Scripts/DCS-BIOS/doc/json/A-10C.json

 

Basically I just read that file in and split it into input controls and output controls, the outputs are what you care about parsing. The last function in the source code I posted (buildOutputAddressMap) re-maps that structure for fast lookup by the address, which is faster and more convenient when you are parsing the output stream.

 

One thing to be aware of is DCS-BIOS maintains a buffer of all control values in Lua memory, and it transmits updates to that information in small segments. You should maintain a shadow copy of the entire buffer on your side and apply the updates to your copy as they stream in. Then, you can interpret the values from the buffer as you need them. This is necessary because in some cases you will get partial updates to string data, so you cannot form the full picture from just the network stream alone. The first function I posted (applyUpdates) updates the local buffer, and the second function (mapUpdatesToControls) maps the updated raw values from the buffer to the more "friendly" control structure that is used thereafter. This process involves some manipulation of the raw value based on the mask, shift_by, etc. in the control metadata.


Edited by y2kiah
Link to comment
Share on other sites

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

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