Global variable not updating after parting out app in to functions?

Hi everyone,
This is actually being run on a particle but I suspect my issue is c++ related and not hardware related. I have a working copy of this script below, in the second one I have parted my logic out in to a couple of functions and now for whatever reason my MON DynamicJsonDocument is not updating when set_mon_values() is called. When the app makes a request to the /levelup route I can confirm the server is responding with the correct JSON, however when the app then tries to pass those values back to the server (seed and level) they are both set to zero.
I think I am just missing something fundamental to c++ here.

working:

// This #include statement was automatically added by the Particle IDE.
#include <ArduinoJson.h>

// This #include statement was automatically added by the Particle IDE.
#include <HttpClient.h>


// This #include statement was automatically added by the Particle IDE.
#include <Adafruit_mfGFX.h>
#include "Arduino_ST7789.h"
#include <SPI.h>


#define TFT_MOSI       13
#define TFT_MISO       A4
#define TFT_CLK        12
#define TFT_CS         A2
#define TFT_RST        A0                                            
#define TFT_DC         A1

Arduino_ST7789 tft = Arduino_ST7789(TFT_DC, TFT_RST, TFT_CS);


float p = 3.1415926;
int HTTP_PORT = 3000;
String HTTP_METHOD = "POST";
char HOST_NAME[] = "192.168.0.116";
String PATH_NAME = "";
int counter = 0;
unsigned int nextTime = 0;    // Next time to contact the server
HttpClient http;

// Cardiomon values
DynamicJsonDocument MON(8192);
int CELL_SIZE = 5;
// int seed = 0;
// int level = 0;
// int c1[3] = {0, 0, 0};
// int c2[3] = {0, 0, 0};
// int c3[3] = {0, 0, 0};
// int cells[] = {};
// int x_off = 0;

// Pass 8-bit (each) R,G,B, get back 16-bit packed color
uint16_t get_color(uint8_t r, uint8_t g, uint8_t b) {
  return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}

void set_mon_values(char* res) {
    DynamicJsonDocument doc(8192);
    deserializeJson(doc, res);
    MON = doc;
}

void draw_mon() {
    tft.fillScreen(BLACK);
    int y = 200;
    int x = 0;
    int x_off = MON["x_limit"];
    uint16_t c1 = get_color((int)MON["c1"][0], (int)MON["c1"][1], (int)MON["c1"][2]);
    uint16_t c2 = get_color((int)MON["c2"][0], (int)MON["c2"][1], (int)MON["c2"][2]);
    uint16_t c3 = get_color((int)MON["c3"][0], (int)MON["c3"][1], (int)MON["c3"][2]);
    int start_x = 67 - floor(x_off / 2) * CELL_SIZE;
    Serial.println(x_off);
    for(int i = 0; i < MON["cells"].size(); i++) {
        if ((i != 0) && (i % x_off == 0)) {
            y -= CELL_SIZE;
            x = 0;
            Serial.println("");
        }
        int cell = MON["cells"][i];
        int dx = start_x + (x * CELL_SIZE);
        Serial.print(cell);
        if (cell != 0) {
            uint16_t color = cell == 1 ? c1 : cell == 2 ? c3 : c2;
            tft.fillRect(dx, y, CELL_SIZE, CELL_SIZE, color);
        }
        x += 1;
    }
}

// Headers currently need to be set at init, useful for API keys etc.
http_header_t headers[] = {
     { "Content-Type", "application/json" },
     { "Accept" , "application/json" },
    // { "Accept" , "*/*"},
    { NULL, NULL } // NOTE: Always terminate headers will NULL
};

http_request_t request;
http_response_t response;





void setup(void) {
  Serial.begin(9600);
  tft.init(135,240);
  Serial.println(F("Screen Initialized"));
  Serial.println(F("Color got."));
  Serial.println(F("Screen filled."));
  tft.fillScreen(BLACK);
//   if(client.connect(HOST_NAME, HTTP_PORT)) {
//     // if connected:
//     Serial.println("Connected to server");
//   }
}

void loop() {
  // put your main code here, to run repeatedly:
    // uint16_t color = get_color(counter % 255, (counter + 75) % 255, (counter + 150) % 255); 
    // tft.fillRect(counter % 50,counter % 50, counter % 20, counter % 20, color);
    counter += 1;
        if (nextTime > millis()) {
        return;
    }

    Serial.println();
    Serial.println("Application>\tStart of Loop.");
    // Request path and body can be set at runtime or at setup.
    request.hostname = HOST_NAME;
    request.port = HTTP_PORT;
    request.path = "/levelup";

    // The library also supports sending a body with your request:
    //request.body = "{\"key\":\"value\"}";
    int seed = rand() % 100000;
    int level = (rand() % 4) + 1;
    String preJson = "{\"seed\":\"%d\",\"level\":\"%d\"}";
    char bod[100];
    sprintf(bod, preJson, seed, level);
    request.body = bod;
    

    // Get request
    http.post(request, response, headers);
    Serial.print("Application>\tResponse status: ");
    Serial.println(response.status);

    Serial.print("Application>\tHTTP Response Body: ");
    Serial.println(response.body);
    int n = response.body.length();
    char char_array[n + 1];
    strcpy(char_array, response.body.c_str());
    set_mon_values(char_array);
    draw_mon();
    nextTime = millis() + 1000;
}

not working:

// This #include statement was automatically added by the Particle IDE.
#include <ArduinoJson.h>

// This #include statement was automatically added by the Particle IDE.
#include <HttpClient.h>


// This #include statement was automatically added by the Particle IDE.
#include <Adafruit_mfGFX.h>
#include "Arduino_ST7789.h"
#include <SPI.h>


#define TFT_MOSI       13
#define TFT_MISO       A4
#define TFT_CLK        12
#define TFT_CS         A2
#define TFT_RST        A0                                            
#define TFT_DC         A1

Arduino_ST7789 tft = Arduino_ST7789(TFT_DC, TFT_RST, TFT_CS);



http_request_t request;
http_response_t response;
float p = 3.1415926;
int HTTP_PORT = 3000;
String HTTP_METHOD = "POST";
char HOST_NAME[] = "192.168.0.116";
String PATH_NAME = "";
int init = 0;
unsigned int nextTime = 0;    // Next time to contact the server
HttpClient http;
// Http headers
http_header_t headers[] = {
     { "Content-Type", "application/json" },
     { "Accept" , "application/json" },
    { NULL, NULL }
};

// Cardiomon values
DynamicJsonDocument MON(8192);
int CELL_SIZE = 5;

void set_mon_values(char* res) {
    DynamicJsonDocument doc(8192);
    deserializeJson(doc, res);
    MON = doc;
}

// http methods
void mon_request(String path) {
    request.hostname = HOST_NAME;
    request.port = HTTP_PORT;
    request.path = path;
    if (path == "/levelup") {
        int seed = MON["seed"];
        int level = MON["level"];
        String preJson = "{\"seed\":\"%d\",\"level\":\"%d\"}";
        char bod[100];
        sprintf(bod, preJson, seed, level);
        request.body = bod;
    } else {
        request.body = "";
    }
    http.post(request, response, headers);
    Serial.print("Application>\tResponse status: ");
    Serial.println(response.status);
    Serial.print("Application>\tHTTP Response Body: ");
    Serial.println(response.body);
    int n = response.body.length();
    char char_array[n + 1];
    strcpy(char_array, response.body.c_str());
    set_mon_values(char_array);
}

// Pass 8-bit (each) R,G,B, get back 16-bit packed color
uint16_t get_color(uint8_t r, uint8_t g, uint8_t b) {
  return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}


void draw_mon() {
    tft.fillScreen(BLACK);
    int y = 200;
    int x = 0;
    int x_off = MON["x_limit"];
    Serial.println(x_off);
    uint16_t c1 = get_color((int)MON["c1"][0], (int)MON["c1"][1], (int)MON["c1"][2]);
    uint16_t c2 = get_color((int)MON["c2"][0], (int)MON["c2"][1], (int)MON["c2"][2]);
    uint16_t c3 = get_color((int)MON["c3"][0], (int)MON["c3"][1], (int)MON["c3"][2]);
    int start_x = 67 - floor(x_off / 2) * CELL_SIZE;
    for(int i = 0; i < MON["cells"].size(); i++) {
        if ((i != 0) && (i % x_off == 0)) {
            y -= CELL_SIZE;
            x = 0;
        }
        int cell = MON["cells"][i];
        int dx = start_x + (x * CELL_SIZE);
        Serial.print(cell);
        if (cell != 0) {
            uint16_t color = cell == 1 ? c1 : cell == 2 ? c3 : c2;
            tft.fillRect(dx, y, CELL_SIZE, CELL_SIZE, color);
        }
        x += 1;
    }
}

void setup(void) {
  Serial.begin(9600);
  tft.init(135,240);
  tft.fillScreen(BLACK);
}

void loop() {
    if (nextTime > millis()) {
        return;
    }
        
    if (init == 0) {
        mon_request("/newmon");
        init = 1;
    }
    draw_mon();
    if ((int)MON["level"] == 5) {
        mon_request("/newmon");
    } else {
        mon_request("/levelup");
    }
    nextTime = millis() + 1000;
}

You variables aren't global.

really global means outside of any function

Your working code has the variable inside loop

Your not working code has the variable inside a function.
This means this variable is only known inside this function

put the definition of the variables here

#define TFT_CS         A2
#define TFT_RST        A0                                            
#define TFT_DC         A1

Arduino_ST7789 tft = Arduino_ST7789(TFT_DC, TFT_RST, TFT_CS);

int seed;  // definition of the variable outside ANY function
int level; // definition of the variable outside ANY function

http_request_t request;
http_response_t response;
float p = 3.1415926;

assigning a value stays inside your function

void mon_request(String path) {
    request.hostname = HOST_NAME;
    request.port = HTTP_PORT;
    request.path = path;
    if (path == "/levelup") {
        seed = MON["seed"];  // just assign don't define (by using a leading variable-type
        level = MON["level"];// just assign don't define (by using a leading variable-type

best regards Stefan

Hi Stefan,

Thanks for the reply, unfortunately that's not the issue -> MON is the global object I am referring to which is updated by set_mon_values, the assignment to level and seed is just to cast the same properties on the MON object as integers before they're submitted in the post request (previously they were being generated by rand() inside loop() as you pointed out). MON is instantiated outside of any function and should be updated in set_mon_values() the same way it is in the working script (as far as I'm aware).

I believe it is definitely an issue with the assignment to the MON variable as previously the x_limit property of MON was also printing fine however now it is set to 0 in perpetuity (the same as seed and level).

Cheers

I haven't followed your code completely through, but such constructs, where you have defined a local variable and are then passing a reference to it outside the function in which it is declared, can be risky. You may end up assigning to a global variable, a reference which no longer exists at the time it is to be used. In this case, char_array will not exist (or is not guaranteed to exist) when mon_request() returns.

Edit
I don't know the board you are using but these values are very large DynamicJsonDocument doc(8192) . It looks like MON contains a reference to it (doc) which will not exist when set_mon_values() returns.

Ahhhhh I think you're on the money with the first point. The second pattern was working in the previous iteration but I assume it's because it's referencing the global within the lifetime of the other variable (I can probably deserialise straight in to the global anyway no?).

I'm gonna give your first point a try later - will report back.

Edit

I haven't followed your code completely through, but such constructs, where you have defined a local variable and are then passing a reference to it outside the function in which it is declared, can be risky. You may end up assigning to a global variable, a reference which no longer exists at the time it is to be used. In this case, char_array will not exist (or is not guaranteed to exist) when mon_request() returns.

I know you said you haven't looked through my code properly but wouldn't this be effectively the same as in the first example where char_array is being passed to the same function from inside loop()?

I notice that your sketch produces a pile of debug output. Please post the debug output from Serial Monitor.

18:42:04.100 -> Application>   Response status: 200
18:42:04.100 -> Application>   HTTP Response Body: {x_limit:8,level:1,seed:452,c1:[255,228,153],c2:[153,206,255],c3:[255,187,0],cells:[0,0,2,2,2,2,0,0,0,2,1,1,1,1,2,0,0,2,2,1,1,2,2,0,2,1,1,1,1,1,1,2,0,2,1,2,2,1,2,0,2,3,2,0,0,2,3,2,0,2,3,2,2,3,2,0,0,0,2,0,0,2,0,0]}|

Device responds with

{ seed: '0', level: '0' }

Despite receiving correct values.
Recieves:

18:41:28.508 -> Application>	Response status: 200
18:41:28.508 -> Application>	HTTP Response Body: {"x_limit":8,"level":1,"seed":0,"c1":[255,0,0],"c2":[255,0,0],"c3":[102,0,0],"cells":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}
18:41:38.563 -> 0
18:41:38.563 -> Application>	Response status: 200
18:41:38.563 -> Application>	HTTP Response Body: {"x_limit":8,"level":1,"seed":0,"c1":[255,0,0],"c2":[255,0,0],"c3":[102,0,0],"cells":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}
18:41:38.563 -> 0

From the '/levelup' route in perpetuity. The zero is (int)MON["x_limit"] being logged despite it clearly being set to 8 in the response body.

So it doesn't appear to be a lifetime issue, I moved the assignment to MON to the bottom of the request function (assigning deserializing char_array before mon_request returns) but still no dice :frowning:

Seems like it was some kind of lifetime issue.

I have resorted to assigning each field of the response to its own global.

    DeserializationError err = deserializeJson(MON, char_array);
    seed = MON["seed"];
    level = MON["level"];
    x_off = MON["x_limit"];
    c1[0] = (int)MON["c1"][0]; c1[1] = (int)MON["c1"][1]; c1[2] = (int)MON["c1"][2];
    c2[0] = (int)MON["c2"][0]; c2[1] = (int)MON["c2"][1]; c2[2] = (int)MON["c2"][2];
    c3[0] = (int)MON["c3"][0]; c3[1] = (int)MON["c3"][1]; c3[2] = (int)MON["c3"][2];

    cells.clear();
    for (int i = 0; i < MON["cells"].size(); i++) {
        cells.push_back((int)MON["cells"][i]);
    }