Форматирование IP-адреса через семейство printf(...) аналогично Serial Object

printf

Я пытаюсь использовать 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-вывод будет оценен по достоинству.

, 👍2


3 ответа


Лучший ответ:

2

В то время как ответ 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 в своей программе.

,

Потрясающая информация об абстрактных классах. Это как раз то, что мне до смерти хотелось узнать., @Octopus


3

Короче говоря: 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


2

Существует также однострочное решение, как вы пожелаете. Вместо 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;
}
,