Форматирование IP-адреса через семейство printf(...) аналогично Serial Object
Я пытаюсь использовать sprintf с IP-адресом, и я не уверен, почему Serial.print() может преобразовать IP-адрес в разумное удобочитаемое представление. Я имею в виду, что, предположительно, он использует перегруженную функцию, которая определяет, к какому типу относится параметр. Я хочу как-то использовать эту функциональность в printf или sprintf.
Что я знаю:
IPAddress ip = WiFi.localIP();
Serial.println(ip);
отлично работает и выводит "192.168.4.1".
char *buf = malloc(128);
sprintf(buf,"%s",String(ip).c_str());
генерирует buf как "17082560", что не является неправильным, но это не обычный формат для IP-адреса.
Я мог бы написать функцию для вывода ее так, как хочу, но я подумал, что должен быть какой-то относительно простой способ использовать функциональность Serial.print(), но каким-то образом направить ее на вызов sprintf() . Или любой другой простой способ обеспечить форматированный IP-вывод будет оценен по достоинству.
@Octopus, 👍2
3 ответа
Лучший ответ:
В то время как ответ KIIV решает вашу проблему, он не затрагивает некоторые очень интересные части вашего первоначального вопроса:
Я не уверен, почему Serial.print() способен преобразовать IPAddress в разумное удобочитаемое представление
Это работает благодаря нескольким абстрактным классам в библиотеке Arduino
под названием Print
и Printable
. Объект печати - это все, что может
выводить текст, например последовательный порт, сетевое соединение или ЖК-дисплей.
Печатаемый объект - это все, что может быть преобразовано в текстовое
представление и отправлено через объект печати.
Из 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);
// ...
};
Другими словами, для того, чтобы создать отпечаток, вам нужно только реализовать
write(uint8_t)
, который должен выводить один символ. Затем вы получаете
все методы print()
и println()
.
Из Printable.h:
class Printable
{
public:
virtual size_t printTo(Print& p) const = 0;
};
Это означает, что вы можете сделать любой объект доступным для печати, реализовав
printTo (Печать и печать)
.
Из IPAddress.h:
class IPAddress : public Printable {
// ...
};
Это означает, что объекты IPAddress доступны для печати: они знают, как напечатать
себя в любой печати. Вы можете найти реализацию
IPAddress::printTo(Print&)
в конце
IPAddress.cpp.
Таким образом, чтобы ответить на ваш вопрос, Serial.print()
на самом деле не знает, как
создать текстовое представление IPAddress , но IPAddress знает
, как отправить себя через Serial
или любой другой объект печати.
Я хочу как-то использовать эту функциональность [...] должен быть какой -то относительно простой способ использовать функциональность Serial.print() предоставляет, но каким-то образом направляет его на вызов sprintf() .
Эта функциональность не обеспечивается Serial.print()
: дело в том
, что IP-адрес доступен для печати. Чтобы использовать это, вы должны
создать объект печати. Вы не можете использовать sprintf()
для этого, но вы можете
создать объект печати, который ведет себя как sprintf()
и записывает в строку C
:
// Запись в строку C (массив char), а-ля sprintf().
// Предупреждение: проверка переполнения буфера отсутствует.
class StringPrinter : public Print
{
public:
StringPrinter(char *buffer) : buf(buffer), pos(0) {}
virtual size_t write(uint8_t c)
{
buf[pos++] = c; // добавить символ в строку
buf[pos] = 0; // нулевой перминатор
return 1; // записан один символ
}
private:
char *buf;
size_t pos;
};
который можно было бы использовать следующим образом:
char buf[16];
StringPrinter(buf).print(ip);
Для простоты я бы вообще предпочел прямой
решение sprintf()
. Однако, если вам не хватает flash, приведенный выше код
, скорее всего, будет меньше, так как sprintf()
велик, и вы, скорее
всего, уже используете Print
в своей программе.
Короче говоря: hex(17082560) = 0x0104A8C0
, где: 0x01 = 1
, 0x04 = 4
, 0xA8 = 168
и 0xC0 = 192
. Видите узор? Он печатается как один uint32_t
.
Конечно, конечность меняется на обратную, так как платформа имеет малый конечный код, но IP-адреса находятся в сетевом порядке байтов (большой конечный код).
И поскольку вы не поняли этого ответа, существует также целый код:
uint32_t ip = (uint32_t) WiFi.localIP();
sprintf(buf, "%u.%u.%u.%u", ip & 0xFF, (ip>>8) & 0xFF, (ip>>16) & 0xFF, (ip>>24) & 0xFF);
Может быть, даже лучше:
IPAddress ip = WiFi.localIP();
sprintf(buf, "%u.%u.%u.%u", ip[0], ip[1], ip[2], ip[3]);
да, как я уже сказал, "в этом нет ничего плохого, но это не обычный формат для IP-адреса". это не ответ на мой вопрос. Хотя спасибо., @Octopus
@Octopus: Я надеялся, что ты поймешь это сам. В любом случае код добавлен (непроверенный), @KIIV
прямо сейчас. сейчас у меня один из тех моментов "почему я об этом не подумал". идеально!, @Octopus
Существует также однострочное решение, как вы пожелаете. Вместо String(ip)
используйте ip.toString()
.
IPAddress ip = WiFi.localIP();
char *buf = malloc(128);
sprintf(buf, "%s", ip.toString().c_str());
Фон: Функция Serial.println(ip);
вызывает Print::print(const Printable& x)
, которая, в свою очередь, вызывает IPAddress::printTo(Print& p)
.
size_t Print::print(const Printable& x) {
return x.printTo(*this);
}
В качестве альтернативы String IPAddress::toString()
создает нужную строку и резервирует только необходимый объем памяти (код из ESP8266 2.7.4).
String IPAddress::toString() const
{
StreamString sstr;
#if LWIP_IPV6
if (isV6())
sstr.reserve(40); // 8 коротких x 4 символа каждый + 7 двоеточий + nullterm
else
#endif
sstr.reserve(16); // 4 байта с 3 символами max + 3 точки + nullterm или '(IP unset)'
printTo(sstr);
return sstr;
}
Потрясающая информация об абстрактных классах. Это как раз то, что мне до смерти хотелось узнать., @Octopus