2

I am trying to use an sprintf with an IP address and I am not sure why Serial.print() is able to convert an IP address to a reasonable humanly readable representation.

I mean, presumably it uses an overloaded function that detects what type the parameter is. I want to somehow leverage that functionality with printf or sprintf.

What I know:

IPAddress ip = WiFi.localIP();
Serial.println(ip);

works great and outputs "192.168.4.1".

char *buf = malloc(128);
sprintf(buf,"%s",String(ip).c_str());

generates buf as "17082560" which isn't wrong, but it's not the usual format for an IP address.

I could write a function to output it the way I want, but I figured there must be some relatively simple way to utilize the functionality that Serial.print() provides but somehow channel it to an sprintf() call. Or any other simple way to provide a formatted IP output would be appreciated.

dda
1,5951 gold badge12 silver badges17 bronze badges
asked Oct 25, 2016 at 20:01

4 Answers 4

4

Whereas KIIV's answer solves your problem, it does not address some very interesting parts of your original question:

I am not sure why Serial.print() is able to convert IPAddress to a reasonable human readable representation

This works thanks to a couple of abstract classes in the Arduino library called Print and Printable. A Print object is anything that can output text, like a serial port, a network connection or an LCD. A Printable object is anything that can be converted to a text representation and sent through a Print object.

From Print.h:

class Print
{
 // ...
 virtual size_t write(uint8_t) = 0;
 // ...
 size_t print(const String &);
 size_t print(const char[]);
 size_t print(unsigned int, int = DEC);
 size_t print(const Printable&);
 // ...
 size_t println(const String &s);
 // ...
};

In other words, in order to create a Print, you only have to implement write(uint8_t), which should output a single character. Then you get all the print() and println() methods.

From Printable.h:

class Printable
{
 public:
 virtual size_t printTo(Print& p) const = 0;
};

This means you can make any object Printable by implementing printTo(Print&).

From IPAddress.h:

class IPAddress : public Printable {
 // ...
};

This means IPAddress objects are Printable: they know how to print themselves into any Print. You can find the implementation of IPAddress::printTo(Print&) at the end of IPAddress.cpp.

Thus, to answer your question, Serial.print() does not really know how to create a textual representation of the IPAddress, but IPAddress knows how to send itself through Serial, or any other Print object.

I want to somehow leverage that functionality [...] there must be some relatively simple way to utilize the functionality that Serial.print() provides but somehow channel it to an sprintf() call.

The functionality is not provided by Serial.print(): it's the fact that the IPAddress is Printable. In order to leverage that, you have to create a Print object. You cannot use sprintf() for that, but you can create a Print object which behaves like sprintf() and writes into a C string:

// Write into a C string (a char array), a la sprintf().
// Warning: there is no check for buffer overflow.
class StringPrinter : public Print
{
public:
 StringPrinter(char *buffer) : buf(buffer), pos(0) {}
 virtual size_t write(uint8_t c)
 {
 buf[pos++] = c; // add the character to the string
 buf[pos] = 0; // null perminator
 return 1; // one character written
 }
private:
 char *buf;
 size_t pos;
};

which could be used like this:

char buf[16];
StringPrinter(buf).print(ip);

For the sake of simplicity, I would generally prefer the straight sprintf() solution. However, if you are short on flash, the code above is likely to be smaller, as sprintf() is large and you are very likely to be already using Print in your program.

answered Oct 26, 2016 at 10:13
1
  • Awesome information about the abstract classes. This is kind of what I was dying to know. Commented Oct 27, 2016 at 22:38
4

In short: hex(17082560) = 0x0104A8C0 where: 0x01 = 1, 0x04 = 4, 0xA8 = 168 and 0xC0 = 192. See the pattern? It's printed as a single uint32_t.

Of course endianness is reversed as platform is little endian but IP Adresses are in network byte order (big endian)

And because you didn't understand this answer, there is also whole code:

uint32_t ip = (uint32_t) WiFi.localIP();
sprintf(buf, "%u.%u.%u.%u", ip & 0xFF, (ip>>8) & 0xFF, (ip>>16) & 0xFF, (ip>>24) & 0xFF);

Maybe even better:

IPAddress ip = WiFi.localIP();
sprintf(buf, "%u.%u.%u.%u", ip[0], ip[1], ip[2], ip[3]);
answered Oct 25, 2016 at 20:15
3
  • yes, as I said, "which isn't wrong, but its not the usual format for an IP address". this doesn't answer my question. Thanks, though. Commented Oct 25, 2016 at 20:16
  • @Octopus: I hoped you'll figure this out by yourself. Anyway code added (untested) Commented Oct 25, 2016 at 20:31
  • right on. now i'm having one of those "why didn't i think of that" moments. perfect! Commented Oct 25, 2016 at 22:01
2

There is also a one-line solution just as you wish. Instead of String(ip) use ip.toString().

IPAddress ip = WiFi.localIP();
char *buf = malloc(128);
sprintf(buf, "%s", ip.toString().c_str());

Background: The function Serial.println(ip); calls Print::print(const Printable& x), which in turn calls IPAddress::printTo(Print& p).

size_t Print::print(const Printable& x) {
 return x.printTo(*this);
}

Alternatively, String IPAddress::toString() creates the desired string and reserves just the needed amount of memory (code from ESP8266 2.7.4)

String IPAddress::toString() const
{
 StreamString sstr;
#if LWIP_IPV6
 if (isV6())
 sstr.reserve(40); // 8 shorts x 4 chars each + 7 colons + nullterm
 else
#endif
 sstr.reserve(16); // 4 bytes with 3 chars max + 3 dots + nullterm, or '(IP unset)'
 printTo(sstr);
 return sstr;
}
answered Dec 27, 2020 at 13:31
1
"%u.%u.%u.%u", ip & 0xFF

instead of, should be

... "%u.%u.%u.%u", (ip>>0) & 0xFF,...
Rohit Gupta
6122 gold badges5 silver badges18 bronze badges
answered Nov 29, 2024 at 22:07

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.