How can I handle a function that reads and returns fixed length char arrays?

Hi all, I’ve got a function that’s going to be used in my Spark program which retrieves Twitter tweets which are sitting on my local server (so I have control over formatting of the text). My intention is that the function be able to take a global variable and update it with the latest Tweet. If for some reason there is no new Tweet because the connection is unavailable, it should return the previous Tweet instead (in other words, return the same variable that was passed into the function). Here’s my code:

char latesttweet(char lasttweet)
{
    if (TCPClient.available())
    {
        char t[140];
        int bytesAvail = 140;
        int ap = 0;
        while(bytesAvail>0)
        {
            t[ap] = TCPClient.read();
            ap++;
            bytesAvail--;
        }
        panicstatus = 0;
        return *t;
    }
    if (!TCPClient.connected())
    {
        panicstatus = 2;
        TCPClient.stop();
        for(;;);
        return lasttweet;

In the main function, I create a fixed length variable and call the function as shown below:

char tweet[140];
tweet = latesttweet(tweet);

At least, that’s what I would like to do ideally – take a predefined 140-character array, pass it into the function, and return a new 140-character array if there’s something to read in on the server. If there’s nothing there/no connection, just return the same 140-character array that was passed into the function.

Some here may find it obvious that this doesn’t work, but I’m not sure why it doesn’t work. The compiler errors I get are pretty cryptic (basically implying that I need to dereference something in the function call). However, every time I try dereferencing something, it gives me more problems than solutions.

I’ve read about C++ functions like strcpy and memcpy, but I don’t know if those are implemented in Spark – I don’t see them in the documentation at least.

Any thoughts on what I should do here?

Thanks!

-Dan

@dbsoundman, could you also post the error messages. They might be cryptic at first, but they usually do help :wink:

There are some bits in your code that seem somehow odd.

for example will never reach the return lasttweet; statement, since the program will loop indefinetly in the for(;;);

I guess the deref message comes from the fact that this

actually passes a char* into a function that only expects one char and then tries to force a char into a place where a char* should live (since the tweet without the [x] is actually a char*).
If you want your function to receive and return a string of char you'd have to change your function into this

 char* latesttweet(char* lasttweet)

and would have to

return t;

Otherwise you are dereferencing the char* and only return the one character t[0].

But it's not good to return a pointer to a local variable anyway. As soon as you leave the function that created it, the memory where your local var was stored, can be used for something else, corrupting your return value. Variables that you "create" inside a function and want to stay alive after you return from your function, should be allocated on the heap.
This said, why do you not use the String class as return value? This does the allocating/freeing for you.

And as for strcpy and memcpy they are available, but String might still be a bit easier to use and understand.

In this, this and this thread you can read some more about pointers, local variables and strings, if you like :wink:

1 Like

BTW:

Why do you not actually manipulate your global variable within your latesttweet() function?

If you declare it int latesttweet(char* tweet) you can pass in any array holding a tweet and manipulate it in place and as a return value you could indicate if it's a new one, still the same or an error occured by choosing appropriate int values.

Thanks for the advice. I think I may try going with the String route, as that seems to be much easier to manipulate. Passing in as a pointer to an array is a good back up plan.

OK, so I tried declaring

String tweet

But the compiler didn’t like it.

Here’s the function as it stands:

String latesttweet(String &lasttweet)
{
    if (TCPClient.available())
    {
        String t;
        t = TCPClient.read();
        panicstatus = 1;
        return t;
    }
    if (!TCPClient.connected())
    {
        panicstatus = 3;
        TCPClient.stop();
        return lasttweet;
    }
}

And in void loop{} I’m calling it as such:

tweet = latesttweet(tweet);

Here’s the complier error (note that the function shown above starts at line 166 in my code):

In file included from ../inc/spark_wiring.h:30:0,
from ../inc/application.h:29,
from SparkTime/SparkTime.h:4,
from SparkTime/SparkTime.cpp:31:
../../core-common-lib/SPARK_Firmware_Driver/inc/config.h:12:2: warning: #warning "Defaulting to Release Build" [-Wcpp]
#warning "Defaulting to Release Build"
^
In file included from ../inc/spark_wiring.h:30:0,
from ../inc/application.h:29,
from clocktweetcontrol.cpp:2:
../../core-common-lib/SPARK_Firmware_Driver/inc/config.h:12:2: warning: #warning "Defaulting to Release Build" [-Wcpp]
#warning "Defaulting to Release Build"
^
clocktweetcontrol.cpp: In function 'String latesttweet(String&)':
clocktweetcontrol.cpp:181:1: warning: control reaches end of non-void function [-Wreturn-type]
t = TCPClient.read();
^
clocktweetcontrol.o: In function `latesttweet(String&)':
/spark/compile_server/shared/workspace/worker_2/core-firmware/build/clocktweetcontrol.cpp:171: undefined reference to `String::operator=(StringSumHelper&&)'
/spark/compile_server/shared/workspace/worker_2/core-firmware/build/clocktweetcontrol.cpp:173: undefined reference to `String::String(String&&)'
clocktweetcontrol.o: In function `loop':
/spark/compile_server/shared/workspace/worker_2/core-firmware/build/clocktweetcontrol.cpp:63: undefined reference to `String::operator=(String&&)'
collect2: error: ld returned 1 exit status
make: *** [2f00e11fe31c05f5a3add1803a9da55260c2580343751205b8712168dcb3.elf] Error 1

What exactly am I missing here?

Thanks,
Dan

First I think you should drop the ampersand & in

And the

tells you, that your function is not returning a value, when neither of the two if-statements is true.

1 Like

Thanks. Not sure why I used that & in the first place…

I removed it, and created a general “else” condition. The Spark Build compiler seems to be down right now but I’ll see what it does later this weekend…

Still getting errors without the &. I don’t get the error until I actually try to call the function in void loop, which I do as I said in my previous post.

Here’s the current error set when I have the function call uncommented:

In file included from ../inc/spark_wiring.h:30:0,
from ../inc/application.h:29,
from SparkTime/SparkTime.h:4,
from SparkTime/SparkTime.cpp:31:
../../core-common-lib/SPARK_Firmware_Driver/inc/config.h:12:2: warning: #warning "Defaulting to Release Build" [-Wcpp]
#warning "Defaulting to Release Build"
^
In file included from ../inc/spark_wiring.h:30:0,
from ../inc/application.h:29,
from clocktweetcontrol.cpp:2:
../../core-common-lib/SPARK_Firmware_Driver/inc/config.h:12:2: warning: #warning "Defaulting to Release Build" [-Wcpp]
#warning "Defaulting to Release Build"
^
clocktweetcontrol.o: In function `latesttweet(String)':
/spark/compile_server/shared/workspace/worker_1/core-firmware/build/clocktweetcontrol.cpp:169: undefined reference to `String::operator=(StringSumHelper&&)'
/spark/compile_server/shared/workspace/worker_1/core-firmware/build/clocktweetcontrol.cpp:171: undefined reference to `String::String(String&&)'
/spark/compile_server/shared/workspace/worker_1/core-firmware/build/clocktweetcontrol.cpp:182: undefined reference to `String::String(String&&)'
clocktweetcontrol.o: In function `loop':
/spark/compile_server/shared/workspace/worker_1/core-firmware/build/clocktweetcontrol.cpp:70: undefined reference to `String::operator=(String&&)'
collect2: error: ld returned 1 exit status
make: *** [38a7361a2e8c51dd8e75eae86f14530011bd46047ce5ce30c016df018780.elf] Error 1

Line 169 has “t = TCPClient.read();”, where t is a variable defined as a String in line 168. Lines 171 and 182 are where I attempt to return a string from the function: in 171 I have “return t”, and in 182 I have “return lasttweet”. In both cases, I’m using the “return” statement to attempt to pass a string out as the result of the function – is this a problem? I couldn’t find a clear answer on how that’s typically done anywhere else.

The undefined reference messages occure when you try to call a method of a class that does not actually exist.

So you can’t assign a String the way you try to do it (line 169), since there is no assignment operator = defined that would take the address of a pointer pointing to a String :wink:

And the other message tells you that there is no String constructor that takes a parameter of this type.

I have come across a thread dealing with some difficulties with String, but I can’t remember where. I’ll try to dig it up, or some one else might be of more assistance for the time being.

I’ll come back to this ASAP.

Edit: One thing I already can say about TCPClient.read() is that there is no overload that will return a String, so you can’t do t = TCPClient.read() while t is a String.

Have a look here and here that might clear up some things how to use String.
There are some experimental portions within the implementation of String when #defining __GXX_EXPERIMENTAL_CXX0X__ but I think for your use you’d be best off with a char[] and int TCPClient.read(uint8_t *buffer, size_t size) instead of int TCPClient.read().

Have a try with this

int gettweet(char* tweetbuffer, size_t tweetlen = 140)
{
    if (tcpClient.connected())
    {
        return tcpClient.read((uint8_t*) tweetbuffer, tweetlen);
          // possible values >0 ... read bytes      (tweet updated)
          //                 -1 ... nothing to read (tweet unchanged)
    }
    else
    {
        tcpClient.stop();
        return 0;                   
          // the tweet is unchanged, client stopped
    }
    
    return -1;
          // something went wrong, we should never end up here
}

That looks like a great idea! I would be returning a pointer to the array, correct?

I’m still brushing up on all of this stuff so thanks for bearing with me, it’s been a couple years since my college C++ courses.

I’m a little confused as to what this statement returns…

return TCPClient.read((uint8_t*) tweetbuffer, tweetlen);

Is it an array? I can’t find anything in the docs that specifies that the client.read() function can deal with two arguments like this. I’m just not quite sure what type of variable I need to pass into and out of the gettweet function; I assumed I just needed a char* array, but I’m not quite sure.

Thanks for the help!
-Dan

Using @ScruffR example, you would pass a pointer to an allocated buffer, and it will populate that buffer inside the function. The caller would do something like:

char my_tweetbuf[141]; // Add additional buffer space for the null terminator
int tweet_length;

tweet_length = gettweet(my_tweetbuf, 140);
my_tweetbuf[tweet_length] = '\0'; // Force a null terminator

Now my_tweetbuf will contain the string of the received message of length tweet_length.

Thanks. I tried your suggestion, but I’m still getting really weird compiler errors…

clocktweetcontrol.cpp: In function 'void loop()':
clocktweetcontrol.cpp:72:46: error: 'latesttweet' was not declared in this scope
Serial.println(rtc.minute(unix));
^

I created the variables you showed above, except my_tweetbuf is actually called tweet in my code. FWIW, here’s my void loop:

void loop()
{
    if(!Spark.connected() && discon == false) // used for debugging loss of cloud connection
    {
        Serial.println("Disconnected at unix timestamp ");
        Serial.println(unix);
        discon = true;
    }
    if(Spark.connected() && discon == true)
    {
        Serial.println("Reconnected at unix timestamp ");
        Serial.println(unix);
        discon = false;
    }
    unix = updateunix();
    if(unix > stamp+9) // debug output of timestamp
    {
        Serial.println("unix timestamp is ");
        Serial.println(unix);
        stamp = unix;
    }
    //statusLED(); // FUNCTION CALL
    clockcontrol();
    minute = rtc.minute(unix); // debug output of minute
    if(minute > minbuf)
    {
        Serial.println("unix minute is ");
        Serial.println(rtc.minute(unix));
        minbuf = minute;
    }
    if(unix-lastrun>299) // if current timestamp is 300+ seconds ago (5 minutes)
    {
        Serial.println("unix minus lastrun is ");
        Serial.println(unix-lastrun);
        Serial.println("about to enter sendgetrequest from loop");
        sendgetrequest();
        tweet_length = latesttweet(tweet, 140);
        tweet[tweet_length] = '\0';
        lastrun = unix; // once it's all done, set the last time twitter feed was checked
    }
}

What I find exceptionally weird about that error message is it seems to be pointing me to the line where I call the function, yet the ^ indicator shows a command given in a totally unrelated line of code above. I hope it’s something dumb like a missed parenthesis…

@dbsoundman, as mentioned in my previous post

there is an overload for TCPClient.read that does take a pointer to a buffer where to store the read data and how many bytes to get at the most. And it returns the number of actually acquired bytes.
The inline comment of my code snippet should have been clear about that.
(The cool thing about the Core firmware is, that you can just go to the OPEN SOURCE section and browse through all the implementations and even find things that are not yet documented completely)

The acquired string is directly placed into the place referenced by the passed in pointer, so there is no need to return it. The return type of this function is int so what you get back is an integer that you can use to decide, what to do with the buffer content.

Since I've set the default value for tweetlen = 140, you should even be able to omit that second parameter, if you'd pass in 140 anyway - as @wtfuzz suggested in his post, that accuratly explains one possible use.
I'd only add a switch (tweet_length) case: ... construct that should take care of the non-success return values 0 and -1;

And since I'm not sure how your tweets are terminated and how you use them, I don't know if the '\0' terminator is required - but it's always safe to use it :wink:

Your error message itself is quite clear - forget about the carret ^ for now :wink:

The compiler does not know a latesttweet function.
Either because you have taken my snippet and have not changed the function name from gettweet to latesttweet or you have declared latesttweet somewhere after its first use in your code.

Try to move the declaration before setup() { } and loop() { } or place a - so called - function prototype int latesttweet(char*, size_t = 140); there (with this prototype you'd have to omit the default value declaration = 140 in the actual implementation of the function, otherwise the compiler will become a complainer :wink: ).

Prototypes "tell" the compiler how a call to a function has to look and that an implementation for such a function will follow somewhere later on.

Just for clarification:

When you have a look at the function signature

you have all your answers right there.
In goes a pointer to a char(array) char* and a number size_t and out comes a number int.

And since the same variables go into TCPClient.read, where tweetbuffer gets cast into (uint8_t), and its return value gets directly returned, you can easily guess what the signature of this overload of read looks like.

I have had the same problem, and it was because C++ doesn’t really have what’s call a fixed-length array or list like other languages (That’s a vector in C++). When a C++ array is passed to a function, it became dynamic pointer of memory. When I did something like this:

char arr[] = {'g', 'o', 'd', 'd', 'e', 's', 's'};

void printArrayLength(char anyArray) {
    // do something, i.e. get the array's length
    int myLength = sizeof(anyArray)/sizeof(anyArray[0]);
    Serial.println(myLength);
}

printArrayLength(arr);

That didn’t work, because arr[] became arr* already, and your static, fixed-length array became a whimsical bytes in memory. I ended up printing out the size of those bytes.

What I did was to make my function a template function that’ll just take any pointer to an array of variable length N.

#include <array>

char arr[] = {'g', 'o', 'd', 'd', 'e', 's', 's'};

template<std::size_t N>
void printArrayLengh(const char (&anyArray)[N]) {
    // Now you can do anything with anyArray and its length N like ordinary arrays, since it's been dereferenced.
    Serial.println(N);
    
}

printArrayLength(arr);

Not sure if this is what you are experiencing.

1 Like

I think there is a misconception going on.

When you write

void printArrayLength(char anyArray)

you don't really do what you are saying. Even if you call your parameter anyArray it won't become an array. The type you declared is char and this is what you get - a one byte char. So I would even doubt, that the compiler wouldn't complain about `sizeof(anyArray[0])' since anyArray just isn't an array.
That's not a C++ thing, that is how C and C++ work.

If you want to pass an array into a function, you'd have to declare it

void printArrayLength(char* anyArray)

but you'll still not get what you want :wink:
Since you are passing in a pointer to anyArray you'll not be happy with the result of sizeof(annyArray). You will be given the size of the pointer (4byte on the Core).
Because of this standard C/C++ behaviour you'll see a lot of std functions that take a pointer to an array plus its size as a second parameter.

1 Like

@ScruffR is exactly right here! The only other point that needs to be made is that char arrays in C/C++ are terminated by a zero ( ‘\0’ ) at the end to avoid having all the string handling functions need to get passed the current length. So in the above example with char arr[] = {'g', 'o', 'd', 'd', 'e', 's', 's'}; if you had written the more usual form of char arr[] = "goddess"; the array would be 8 locations including the trailing zero byte terminator and Serial.println() etc. would all just work. It is like you are going out of your way to do something unusual so you can write more code when normal C/C++ would have just worked fine.

1 Like

Thanks @bko, I was so occupied by the pointer, that I completely missed the string side of things :wink:

1 Like