Undefined Reference Problem

I’m having a problem with the web dev giving me an ‘Undefined Reference’ error. The exact error is:
dotstarball_v2.ino:143:0: undefined reference to “ParseNew(char (&) [1500])” . If I comment out the call to ParseNew(jsonString);, it compiles. I’m curious where my mistake is here? Is it a problem with pointers? (I’m getting the concept but my syntax/execution is lacking.)

#include "CmdProcessor.h"

void CmdSubscribeHandler(const char *event, const char *data) 
{
    char jsonString[1500];
    strcpy(jsonString, data);
    Log.warn("DSBcmd message received at %s.", Time.timeStr().c_str());
    Log.trace("...Payload: %s", jsonString);
    ParseNew(jsonString);
    ProcessResets();
}

In CmdProcessor.h:

void ParseNew(char (&NewCmd)[1500]);

in CmdProcessor.cpp:

void ParseNewCmd(char (&NewCmd)[1500]) 
{
    StaticJsonBuffer<2000> JsonBuffer;
    JsonObject& root = JsonBuffer.parseObject(NewCmd);
    
    if (!root.success()) {
        Log.error("JSON parseObject() failed. Attempted to parse: ");
        Log.error(NewCmd);
        return;
    }
    
    int payloadCount = 0;
    for (auto id : root) {
        payloadCount++;
        
        char myKey[30];
        strcpy(myKey, id.key);
        //sprintf(msg,"(%i) New cmd targets device/group: %s", payloadCount, myKey);
        Log.warn("(%i) New cmd targets device/group: %s", payloadCount, myKey);

        if (AppliesToMe(myKey)) {
            //JsonObject& obj = id.value.as<JsonObject&>();
            ParseInner(id.value.as<JsonObject&>());
        } // if (AppliesToMe)
    } //for (auto id : root)
} //ParseNew

I would declare the function in both the .h and .cpp file as:

void ParseNew(char *NewCmd);

Also, beware of this:

StaticJsonBuffer<2000> JsonBuffer;

That allocates 2000 bytes on the stack, which is only 6K. You run the risk of overflowing the stack doing that. In most cases you can allocate that object as a global variable. There’s over 60K of free heap, so allocating 2K out of that is generally safe.

1 Like

@rickkas7 Thanks for the delcaration tip. I made the changes to the .cpp and .h but now I’m getting this compiler error: dotstarball_v2.ino:143:0: undefined reference to “ParseNew(char*)”

Do I have to change the function call from the .ino?

Regarding declaring the JsonBuffer in the global area, I had the same concern and tried declaring that as global. However, after running into problems I stumbled on this issue which basically says the buffer has to be declared in a local scope. I wan’t quite aware of heap vs stack and so perhaps now I will check out one of the alternative parsers such as the one you created. I just wasn’t trying to “rock the boat” when I had functional code.

You seem to have a prototype void ParseNew(char *NewCmd); but only an implementation for void ParseNewCmd(char *NewCmd).
If there is no implementation for the function you have prototyped the linker would rightfully throw that error.

I’m also slightly puzzled by this snippet

void CmdSubscribeHandler(const char *event, const char *data) 
{
    char jsonString[1500];
    strcpy(jsonString, data);
    ...
}

Do you expect the string to grow or why are you declaring it that big?
If the subscription will be triggered by a Particle.publish() the maximum payload will be 255 byte - so your buffer is almost six times that, why?
If your buffer isn’t meant to hold a growing string, I’d declare it as char jsonString[strlen(data)+1]; not to waste precious space on the stack - you already sacrifice 2KB for the nested function call, the extra 1.5K don’t seem to be a good idea.

1 Like

@ScruffR

You seem to have a prototype void ParseNew(char *NewCmd); but only an implementation for void ParseNewCmd(char *NewCmd).

Thank you for catching that. I just had a "palm to the forehead" moment. I miss-typed the function name when I was editing the variable declaration. It compiles now.

I mean for this parsing program to receive packets via UDP. The buffer is large enough to take a full UDP packet with a default network MTU of 1500. I am only testing using publish/subscribe. The ArduioJson documentation has warnings to make sure the buffer size is large enough for some duplication overhead. So while a UDP packet may only allow something like 1472 bytes worth of usable payload, I made the buffer 2000 (no scientific calculation, just a guess). Regarding the duplication of the string, I couldn't figure out how to pass the const char *data into the ArduinoJson. It kept throwing errors about cannot convert const *char to *char or something to that effect. Unfortunately, the efficiency of my code is limited by my bad understanding of C/C++.

By the way, if you’re parsing large JSON objects you may want to use my library instead. It’s optimized for large JSON objects and is based on jsmn, but with a much easier to use API.

1 Like

By the way, if you’re parsing large JSON objects you may want to use my library instead. It’s optimized for large JSON objects and is based on jsmn, but with a much easier to use API.

I was just thinking the same thing. OK... biting the bullet. I'll learn your library!

@rickkas7 I started to change my CmdProcessor over to your library and I’m hitting a barrier. I think perhaps I should explain what I’m trying to do instead of presenting bits and pieces. I have a large 3D-printed ball with DotStar (Sk9822 chipset) LED affixed to the outside in columns of 23 pixels. There are 24 columns for a total of 552 LEDs. I want this LED ball to be part of a swarm of balls. And I want to be able to address the swarm, sub-groups of ball within the swarm, or individual balls. To communicate with the swarm, I will have a “master” device sending out the animation commands (or perhaps a PC.) The master will communicate to the swarm on a local wifi subnet/VLAN via UDP. Hopefully the private network will minimize extraneous traffic so that the UDP packets are received with minimal loss. In order to address the swarm, I came up with a custom command format and will send the commands wrapped in JSON. It mostly works, and I keep building upon the project when I have time. This is a sample of the JSON I’m sending:

{ 
    "A--": {  
        "Q":[  
            {"S": { 
                "D":false 
                } 
            } 

            , {"M": { 
                "M":11 
                ,"U":30000 
                ,"T":24 
                , "O":["C23"] 
                ,"C":[0,255] 
                } 
            }  
        ]  
    } , {
    "B--": {   
        {"M": { 
            "M":1 
            ,"C":[255] 
            ,"U":2000 
            ,"T":1 
            } 
        } 
    } 
} 

I hope the formatting is OK to visualize (copy and paste for this sucked). In this command scheme the root elements address the swarm with a 3-character key. In the example, I am address swarm “A–” and swarm “B–”. (Each dash is sub-grouping of devices within the swarm. the “-” is a wildcard.) Each swarm receives a command “element”. Swarm A receives a “Q” command (a sequence of other elements) containing “S” (settings update) and “M” (mode change) commands. Swarm B receives a single “M” (mode change) command. Each command contains a bunch of parameters governing the duration (“U”), number of steps in the animation (“T”), etc.

I explain all this because the very first thing I want to do is parse the above sample and iterate through the root keys to figure out if the command applies to the device’s swarm ID. After that, I need to iterate through the sequence to get the individual commands to build the “Sequence” object in memory. Using your library, it seems that I can only iterate through the keys if the values are not Objects or Arrays. If that is the case, how do you recommend trying to parse the above example?

There’s no wrapper for that. However, you can use getKeyValueTokenByIndex to get the jsmntok_t * for the key, which can get you the key name, and also the jsmntok_t * for the value, which will be an object or array.

If it’s an array, you can pass that jsmntok_t * to things like getValueByIndex and if it’s an object, any of the calls to iterate an object.

@rickkas7 I re-wrote my “ParseNew” function to use your library. I am not getting a correct value back when using the getTokenValue. I opened an issue on your git repository… I think that would be the most appropriate place to put it. Can you take a look and see where I went wrong? Thanks,

I updated the Github issue. I think it’s a data problem, but I also included a test case in the parser unit test and it appears to work.

@rickkas7. Thanks! I got it all working. I do like your approach for keeping the object in memory but using an array of tokens for mapping the key pairs within the original object.

1 Like