Guide / Tutorial TARGET SCRIPTING TUTORIAL: Read status.json & process Flags keyvalue

Greetings Commanders,

If you use TARGET Script to enhance the functionality of your Thrustmaster HOTAS, sooner or later you may want to add some form of state tracking.

The best way that I know of for your HOTAS to "know" what's happening within the game is via reading the status.json file within the journal folder and decoding the various key/values within.
One of those keys is 'Flags'. 'Flags' is a binary coded integer which represents 32 different states of various ship systems.
(Refer to section 14 in latest Journal Docs for more information - http://hosting.zaonce.net/community/journal/v37/Journal_Manual_v37.pdf)

This post will give a working example of;
1 - How to read status.json
2 - How to grab the 'Flags' key value
3 - How to process 'Flags' and assign each of the 32 bits to individual Flag variables to use in your scripts
4 - A sample function to show how to use the variables exposed by #3
5 - A simple Mapkey to show the above in action

Even though this example only uses 4-5 of the Flag variables, for sake of completeness, I have included every Flag mask and every individual Flag variable for every bit represented in the Flags key value. (you're welcome!)

Flag masks are courtesy of @Robert Maynard (thanks Robert...now you know what I did with them!)

Disclaimer: No warranties are implied or granted with this code...it is offered as-is, where-is.

Step 1: Create a new text document in the same place as your current TARGET Script...call it ED_StatusJson.tmh

Step 2: Cut and paste the following into it...

Code:
// File: ED_StatusJson.tmh
// Author: CMDR Clicker

//------------------//
// GLOBAL VARIABLES //
//------------------//

    char    myJson;                // Char array to store status.json

//------------------//
//  Flag Variables  //
//------------------//

    // Refer to section 13 in latest Journal Docs for more information - http://hosting.zaonce.net/community/journal/v34/Journal_Manual_v34.pdf
    // Use these after calling fnProcessFlags() in your functions...
    // They're global so can be accessed anywhere in your script

    int fDocked;
    int fLanded;
    int fLandingGear;
    int fShieldsUp;
    int fSupercruise;
    int fFAOff;
    int fHardpoints;
    int fInWing;
    int fLights;
    int fCargoScoop;
    int fSilentRunning;
    int fFuelScoop;
    int fSRVHandbrake;
    int fSRVTurretView;
    int fSRVTurretRetracted;
    int fSRVDriveAssist;
    int fMassLocked;
    int fFSDCharging;
    int fFSDCooldown;
    int fLowFuel;
    int fOverHeating;
    int fLatLong;
    int fDanger;
    int fInterdicted;
    int fMainShip;
    int fFighter;
    int fSRV;
    int fHUDAnalysis;
    int fNightVision;
    int fAltFromAvgRad;
    int fsdJump;
    int fSRVHiBeam;

// End of Global Variable declarations
// ===============================================================

    // FUNCTION:    Read status.json into 'myJson'
    // Requires:    'StatusFile' alias must be set correctly

    int fnReadStatusJson() {

        // Set the 'alias' below to point to the Elite Dangerous journal folder / status.json file
        // The below path is usually where the Elite Dangerous journal file can be found.
        // Just replace '<username>' with your windows username.

        alias StatusFile            = "C:\\Users\\<username>\\Saved Games\\Frontier Developments\\Elite Dangerous\\status.json";

        int     fp;                                                    // General purpose file handle
        int     buf = 512;                                        // Buffer variable. status.json can get lengthy, so assign a buffer of at least 512
        int     myResult;                                        // General pupose 'result' variable

        Dim(&myJson, buf);                                       // Dimension our character array to size of buffer
        myJson[0] = 0;                                                // Best always to zero out the array before use

        fp = fopen(&StatusFile, "r");                                // Open status.json for reading
        if (fp) {                                                                  // If we succeed in opening...
            myResult = fread(&myJson, 1, buf, fp);          // ...read the entire file into our character array (myJson)...
            fclose(fp);                                                        // ... and close the file
            if (myResult == 0) {                                         // 'myResult' should contain the number of characters read...
                printf("File Read Error: status.json\x0a");   // ...so, if we read zero, we had an issue
            }
            else {                                  
                strdel(&myJson, myResult-1, buf);             // If no issues with read, remove any trailing garbage
            }
        }
        else {
            printf("Status.json access error.\x0a");            // File not found OR locked by Elite Dangerous
        }
    }

// END of fnReadStatusJson()
// ===============================================================


    // FUNCTION: Read and process "Flags" value from "status.json"

    int fnProcessFlags() {

        // Declare all required local variables

        int Flags = 0;                                            // contains the Flags key value integer.

        int EndValue = 0;                                        // Catch when we get to a space or comma
                                              
        int myResult;                                            // Temporary general purpose variable to capture results of file and string operations
        int chrpnt;                                                // Pointer to current character in string

        char thischr;                                            // Contains current character read during string search
        Dim(&thischr, 2);              
        thischr[0] = 0;                          

        char keyvalue;                                            // Contains our Key Value when complete
        Dim(&keyvalue, 16);                                        // arbitrary size. Currently Flags integer value does not exceed 11 digits
        keyvalue[0] = 0;

        // Flags Value MASKS //

        int ED_Docked               =    0x0000000000000001;
        int ED_Landed               =    0x0000000000000002;
        int ED_LandingGearDown      =    0x0000000000000004;
        int ED_ShieldsUp            =    0x0000000000000008;

        int ED_Supercruise          =    0x0000000000000010;
        int ED_FlightAssistOff      =    0x0000000000000020;
        int ED_HardpointsDeployed   =    0x0000000000000040;
        int ED_InWing               =    0x0000000000000080;

        int ED_LightsOn             =    0x0000000000000100;
        int ED_CargoScoopDeployed   =    0x0000000000000200;
        int ED_SilentRunning        =    0x0000000000000400;
        int ED_ScoopingFuel         =    0x0000000000000800;

        int ED_SRVHandbrake         =    0x0000000000001000;
        int ED_SRVTurret            =    0x0000000000002000;
        int ED_SRVTurretRetracted   =    0x0000000000004000;
        int ED_SRVDriveAssist       =    0x0000000000008000;

        int ED_FSDMassLocked        =    0x0000000000010000;
        int ED_FSDCharging          =    0x0000000000020000;
        int ED_FSDCooldown          =    0x0000000000040000;
        int ED_LowFuel              =    0x0000000000080000;

        int ED_OverHeating          =    0x0000000000100000;
        int ED_HasLatLong           =    0x0000000000200000;
        int ED_IsInDanger           =    0x0000000000400000;
        int ED_BeingInterdicted     =    0x0000000000800000;

        int ED_InMainShip           =    0x0000000001000000;
        int ED_InFighter            =    0x0000000002000000;
        int ED_InSRV                =    0x0000000004000000;
        int ED_HudInAnalysisMode    =    0x0000000008000000;

        int ED_NightVision            =    0x0000000010000000;
        int ED_AltFromAvgRad        =    0x0000000020000000;
        int ED_FSDJump                =    0x0000000040000000;
        int ED_SRVHiBeam            =    0x0000000080000000;

        // -- End of Variable declarations --

        fnReadStatusJson();                                                // Get latest status.json

        int Start = 64;                                                    // Flags key value always starts at char# 64 in status.json
                                                                        // Flags key value always ends in a space if zero, else comma character

        chrpnt = 0;                                                        // Reset character pointer

        while (EndValue == 0) {                                            // Start loop...
            strsub(&thischr, &myJson, Start+chrpnt, Start+chrpnt);        // Grab next character in 'myJson' and put it into 'thischr'
      
            myResult = strcmp(&thischr, " ");
            if (myResult == 0) EndValue = 1;                            // End loop when we find a space

            myResult = strcmp(&thischr, ",");
            if (myResult == 0) EndValue = 1;                            // End loop when we find a comma

            if (EndValue == 0) strcat(&keyvalue, &thischr);                // Otherwise, update keyvalue...

            chrpnt = chrpnt + 1;                                        // ...Update character pointer, and loop
        }

        Flags = ieval(&keyvalue);                                        // Convert string to integer when we finished the loop

        printf("Flags Integer    =    %d\x0a", Flags);                    // print the Flags integer value to the console

        // TARGET uses 32 bit signed precision for its integers.
        // Flags values larger than 2147483648 wrap around to negative
        // number and means that bit 31 (fSRVHiBeam) is set.
 
        if (Flags != 0) {                                                // If Flags == 0, it means the game has not started yet...

            // ... otherwise, test for bit 31 first, like so...

            if (Flags < 0) {
                fSRVHiBeam            = 1;
            }
            else {
                fSRVHiBeam            = 0;
            }

        // ...then assign the rest of the 'Flags' values using 'bitwise and' between Flags and a mask for each variable, like so...

            fsdJump                = ((Flags & ED_FSDJump) > 0);
            fAltFromAvgRad        = ((Flags & ED_AltFromAvgRad) > 0);
            fNightVision        = ((Flags & ED_NightVision) > 0);
            fHUDAnalysis        = ((Flags & ED_HudInAnalysisMode) > 0);
            fSRV                = ((Flags & ED_InSRV) > 0);
            fFighter            = ((Flags & ED_InFighter) > 0);
            fMainShip            = ((Flags & ED_InMainShip) > 0);
            fInterdicted        = ((Flags & ED_BeingInterdicted) > 0);
            fDanger                = ((Flags & ED_IsInDanger) > 0);
            fLatLong            = ((Flags & ED_HasLatLong) > 0);
            fOverHeating        = ((Flags & ED_OverHeating) > 0);
            fLowFuel            = ((Flags & ED_LowFuel) > 0);
            fFSDCooldown        = ((Flags & ED_FSDCooldown) > 0);
            fFSDCharging        = ((Flags & ED_FSDCharging) > 0);
            fMassLocked            = ((Flags & ED_FSDMassLocked) > 0);
            fSRVDriveAssist        = ((Flags & ED_SRVDriveAssist) > 0);
            fSRVTurretRetracted    = ((Flags & ED_SRVTurretRetracted) > 0);
            fSRVTurretView        = ((Flags & ED_SRVTurret) > 0);
            fSRVHandbrake        = ((Flags & ED_SRVHandbrake) > 0);
            fFuelScoop            = ((Flags & ED_ScoopingFuel) > 0);
            fSilentRunning        = ((Flags & ED_SilentRunning) > 0);
            fCargoScoop            = ((Flags & ED_CargoScoopDeployed) > 0);
            fLights                = ((Flags & ED_LightsOn) > 0);
            fInWing                = ((Flags & ED_InWing) > 0);
            fHardpoints            = ((Flags & ED_HardpointsDeployed) > 0);
            fFAOff                = ((Flags & ED_FlightAssistOff) > 0);
            fSupercruise        = ((Flags & ED_Supercruise) > 0);
            fShieldsUp            = ((Flags & ED_ShieldsUp) > 0);
            fLandingGear        = ((Flags & ED_LandingGearDown) > 0);
            fLanded                = ((Flags & ED_Landed) > 0);
            fDocked                = ((Flags & ED_Docked) > 0);

        //    Print any Flags which are set to 1

            printf("\x0a\ Decoded Flags");
            printf("\x0a===============\x0a");
            if (fSRVHiBeam)             printf("SRVHiBeam\x0a");
            if (fsdJump)                 printf("Hyperjump\x0a");
            if (fAltFromAvgRad)         printf("Radius-ALT\x0a");
            if (fNightVision)             printf("Night Vision\x0a");
            if (fHUDAnalysis)             printf("AnalysisHUD\x0a");
            if (fSRV)                     printf("In SRV\x0a");
            if (fFighter)                 printf("In Fighter\x0a");
            if (fMainShip)                 printf("In MainShip\x0a");
            if (fInterdicted)             printf("Being Interdicted\x0a");
            if (fDanger)                 printf("!!! DANGER !!!\x0a");
            if (fLatLong)                 printf("Lat\\Lon available\x0a");
            if (fOverHeating)             printf("Over Heating\x0a");
            if (fLowFuel)                 printf("Low Fuel\x0a");
            if (fFSDCooldown)             printf("FSD Cooldown\x0a");
            if (fFSDCharging)             printf("FSD Charging\x0a");
            if (fMassLocked)             printf("FSD Masslocked\x0a");
            if (fSRVDriveAssist)         printf("SRV Drive Assist\x0a");
            if (fSRVTurretRetracted)     printf("SRV Turret Retracted\x0a");
            if (fSRVTurretView)         printf("SRV Turret View\x0a");
            if (fSRVHandbrake)             printf("SRV Handbrake\x0a");
            if (fFuelScoop)             printf("Fuel Scoop Deployed\x0a");
            if (fSilentRunning)         printf("Silent Running\x0a");
            if (fCargoScoop)             printf("Cargo Scoop Deployed\x0a");
            if (fLights)                 printf("Lights On\x0a");
            if (fInWing)                 printf("In Wing\x0a");
            if (fHardpoints)             printf("Hardpoints Deployed\x0a");
            if (fFAOff)                 printf("Flight Assist OFF\x0a");
            if (fSupercruise)             printf("Supercruise Engaged\x0a");
            if (fShieldsUp)             printf("Shields Up\x0a");
            if (fLandingGear)             printf("Landing Gear Deployed\x0a");
            if (fLanded)                 printf("Landed on planet\x0a");
            if (fDocked)                 printf("Docked\x0a");
            printf("===============\x0a");
        }
    }

// END of fnProcessFlags()
// ===============================================================

// TOGGLE:        Toggles Landing Gear
// Argument:    x ... 1 = Deploy, 0 = Retract

    int tgLandingGear(int x) {

        fnProcessFlags();                                                // Get latest Flags Value first

        if (!fSupercruise & !fSRV & !fLanded & !fDocked) {                // Only attempt to toggle landing gear if we're not in Supercruise, in SRV, Landed or Docked
            if (fLandingGear != x) {                                    // Only DEPLOY or RETRACT if landing gear status does NOT match the action we asked for
                                                                        // ie. DEPLOY only when RETRACTED and vice-versa
                ActKey(PULSE+KEYON+'L');                                // Change 'L' to whatever you have bound for "Landing Gear" in game.
                                                      
                if (!fLandingGear) {                                    // Print the action to console...
                    printf("\x0a\Landing Gear        DEPLOYED    >>\x0a");
                }
                else {
                    printf("\x0a\Landing Gear        RETRACTED    <<\x0a");
                }
            }
            else {
                printf("\x0a\Landing Gear already matches state, nothing to do.\x0a");
            }
        }
        else {                                                            // ... or, print the reason no action taken
            if (fSupercruise) {
                printf("\x0a\Cannot deploy landing gear whilst in supercruise\x0a\x0a");
            }
            if (fSRV) {
                printf("\x0a\Cannot toggle landing gear whilst in SRV\x0a\x0a");
            }
            if (fLanded | fDocked) {
                printf("\x0a\Cannot toggle landing gear when landed\x0a\x0a");
            }
        }
    }

// ===============================================================

a) - Find the line which contains 'alias StatusFile' and replace <username> with your Windows username and make sure the file path is correct.
If you are unsure of your username, first, find status.json on disk, then click the address bar...it should display the entire path to use.

b) - I use 'L' in game to toggle landing gear. If you use something different, find the following line inside the tgLandingGear() function...
Code:
    ActKey(PULSE+KEYON+'L');
...and replace 'L' with what you have bound in game.
Example if you have bound 'Joy 23', the statement will be...
Code:
    ActKey(PULSE+KEYON+DX23);

Step 3: - Open your current TARGET Script (usually "main.tmc") and
a) - directly underneath any "include" statements near the top of your script, add...
Code:
    include "ED_StatusJson.tmh"

b) - 'MapKey' any unused switch or button as follows...
(I'm using the Flaps switch on my WARTHOG Throttle for this example)

Code:
    MapKey(&Throttle, FLAPD, EXEC("tgLandingGear(1);"));    // DEPLOY Landing Gear
    MapKeyR(&Throttle, FLAPD, EXEC("tgLandingGear(0);"));    // RETRACT Landing Gear

Step 4: Save, Compile, Run ... fix any errors encountered, then repeat this step until no errors.
Step 5: Fire up Elite and Test

There are comments within the code to explain what each section or statement is doing.
That said, if you have any questions or suggestions etc...do let me know.

Have fun.

Cheers
Clicker

p.s. @johntarr ... you asked for it!
 
Last edited:
For anyone interested...

In my "production script" I check for "Flags" not equal to "0" which signifies that the game has started.
At that point I set a loop to read status.json twice per second.
This way I don't need to specifically call the Read and Process json routines each time I want to check a key value within status.json.

Surprisingly this has not had any impact on the script responsiveness.
I run an 8 year old i7-4770k on stock speeds, so if your rig is newer/faster than this, this method is quite a bit cleaner (IMHO).

To kick off a background loop, use the following in your code somewwhere...
Code:
ActKey(KEYON+REXEC(3, 500, "fnReadStatusJson();", RNOSTOP));    // 3=internal handle...can be anything so long as you've not used elsewhere. 500=milliseconds for the loop

Cheers
Clicker

p.s.
My "production script" can be found in my signature below!
The above code can be found within my "fnsStartCheck()" function, inside the "ED_StateTracker.tmh" file
 
I finally got around to it @Clicker :)

Got the code implemented and working through rewriting some of my functions. So far so good! Compiles and works so far, so I am a happy chappy. Thanks for sharing this in an easy to consume format. Now I am trying to think of how else I can utilise this input data to improve the rest of the script! :p

A quick query, could the status.json let the script know when the game stops? I had a user request on my script a while back for the LEDs on the HOTAS to turn off when script stops. Was thinking if script can get a report the game has stopped, it could trigger LED function to 0 in script to disable, and even stop running script automatically on game close perhaps. Not sure if that might be possible?

Also, regarding "ActKey(KEYON+REXEC(3, 500, "fnReadStatusJson();", RNOSTOP));"

When I added it to try it out, I found it returned a lot of errors 'cant find file' I think was the error, when the script was starting up. Not sure if I might be putting in wrong place or I might just need to disable output during startup. Is that expected? If I recall the errors stopped when game started, but I might be wrong. Maybe I just need to move it or test some more lol

Cheers,
AD
 
Hi @Aussiedroid ,

You won't regret adding this functionality to your script.

Re Game stop, yes, I do this in my production script.
When 'flags == 0' it means the game is not running.
So the test for game start can also be used to detect when the game stops.
Exit to menu is NOT stopping the game....beware!

re your error messages...

first question...are you using the tutorial code or code from my production script.
next question...can you reproduce the error and quote it here?

If error = "Status.json access error." means that the script could not open the file.
If error = "File Read Error: status.json" means zero characters read...so if the script does not report "access error" as above, it means the file was empty. This happens if ED is running and was writing to the file at the exact same time you tried to read it.
In my production code, I have have a retry loop for just this reason.
Actually thanks to you I've just looked at my prod code and find it is wrong! I need to define 'ReadErrorCount' variable as a global not a local.

I wrote the tutorial as a stand-alone function which the user can call from within their functions when they needed it.
The background loop may require additional or recoded functions (it's been a while).
Dare I suggest you download my production script (in my sig) and take a look at how I implemented it there?

Always happy to help, just sing out.

Clicker
 
Last edited:
Thanks @Clicker !

Yeah it is going well so far - no regrets :) Cleans up the code a bit and provides that assurance things are aligned to game. No issues from the testing so far.

I've got your script open in another tab, so will see if I can check out the flag == 0 scenario and retrofit to be on close. Currently, due to the error I got I haven't re-enabled the looping but sounds like I will need that to do the shutdown check. I was going to loop back on that next after I test out the last function rewrite tonight. I'll let you know if I get the same message or issue occurring. I am pretty sure it was the Read Error message being returned btw.

I'm using the tutorial script, and haven't really changed any so far. I've got my alpha build up on GitHub atm as I work through the updates and Odyssey.

Cheers, AD
 
@Aussiedroid ,

Take a look at ED_StateTracker.tmh in my prod script if you haven't already.
This is the set of functions I rewrote specifically to suit the background loop.

Every iteration of fnsReadStatusJson also pulls out the flags key value and decodes every bit (via fnsProcessFlags) thus setting every single status bit variable.
fnsProcessFlags() is where I check if the game is running. So, in essence, once running, I'm checking the game is still running every single loop.

The rest of the script functions can then simply rely on the fact that these 32 status variables are always fresh.
(ie within a second of actual status change at worst)

Have fun.

Cheers
Clicker
 
@Clicker ,

Got it all working! Script shuts down and disables LEDs now. All the other upgrades seem to be working out after some more testing too. Thanks for your help!

Also, I think in your post #2 it should be fnsProcessFlags() instead of fnReadStatusJson().

Cheers, AD
 
Hi, yeah...I could have worded that better to avoid confusion.

I meant that with each iteration of the loop, fnsReadStatusJson() calls fnsProcessFlags(), which in turns decodes the bits.

Anyone else reading this, dont confuse references to my production script with the tutorial in the original posts.
They are two implementations of the same idea with the tutorial being somewhat simpler.

Cheers
 
Last edited:
Hi Clicker.
Im beginner with script, but i still learn more and more.
I use part of scripts from you and Aussie. ty guys. I implemented your script with json.status now, all working fine, i need check GUIFocus for my script. But im not successful :(
I need run something like this.
MapKey(&Joystick, H2U, TEMPO (
EXEC(" if (GUIFocus) ActKey(KEYON+PULSE+ UIPanelUp);"), // Interface Mode
EXEC(" if (!GUIFocus) ActKey(KEYON+PULSE+PiptoEng);"), // 1x Pip to Engine (Arrow Up)
EXEC(" if (!GUIFocus) ActKey(KEYON+PULSE+mPowerToEngine);"), // macro Power 2:4:0
500));
And im not sure how define flag, if (!GUIFocus), or (GUIFocus=0).
Can you post here some simple script with GUI reading from json.status?
Ty very much.
 
@Hafram ,

This tutorial thread focuses only on grabbing the "Flags" key value and decoding into the 32 discrete bits.
Getting and decoding the GUIFocus key value in status.json is therefore somewhat outside the bounds of this tutorial.

The good news however, is that my production script contains a function to grab the GUIFocus key value.
Click the link in my signature "Clickers WARTHOG TARGET Script" which takes you to the forum thread for my production script

Once you download my script bundle, I recommend you check out the ED_StateTracker.tmh file on how to extract GUIFocus via my "fnsProcessGuiFocus()" function.
The GUIFocus key value can have 11 different values. I have created and defined global variables for each of these at the end of my ED_GlobalVars.tmh file.
Then do a search within ED_Functions.tmh and ED_Toggles.tmh for examples of how I use this.

If you need further clarification or have questions, please post within that thread.
I'll be more than happy to elaborate further.

Cheers
Clicker
 
Last edited:
For anyone interested...

In my "production script" I check for "Flags" not equal to "0" which signifies that the game has started.
At that point I set a loop to read status.json twice per second.
This way I don't need to specifically call the Read and Process json routines each time I want to check a key value within status.json.

Hi,

A couple of points to note under Odyssey I'd like to share
  1. I no longer use "Flags != 0" (not equal to zero) as the test to reflect the game has started. This is due to a new key value introduced for Odyssey...Flags2. If you start the game 'on foot' it is possible that Flags remains zero.
  2. The Flags2 Key value only appears in status.json when Odyssey is running.
Therefore, within my production script I do the following;
  1. I use the total length of the status.json file as an indicator of game start (ie greater than 68 characters)
  2. I test if Flags2 key value pair is present. If it is, we are running Odyssey. If not, we're running Horizons.
Cheers
Clicker
 
Last edited:
I know this is an old thread but I just wanted to say thanks for the logic @Clicker .
I've used your logic as a basis in a little c# app that takes the state and sends MQTT messages to Home Assistant where automation changes the colour of an LED strip I have in the room. The room now goes red when I'm in combat, white for FSD jumps etc.
 
Back
Top Bottom