问题描述
我在 Arduino 环境中使用了一个日志库,该库主要依赖于预处理器魔法 - 为了不将任何代码编译成不符合设置日志级别的二进制文件,并且具有可用的文件名、函数名和行号事不宜迟。
输出通常使用 Serial.printf
完成。所以像
LOG_N("%02X/%02X @%d/%d? (heap=%d)\n",sv,fc,ad,ln,ESP.getFreeHeap() - lastHeap);
扩展为
if (MBUlogLvl >= level) Serial.printf("[N] %lu| %-20s [%4d] %s: %02X/%02X @%d/%d? (heap=%d)\n",millis(),file_name(__FILE__),__LINE__,__func__,ESP.getFreeHeap() - lastHeap);
(涉及几个中间预处理器宏扩展)
Serial.printf
在内部是 LOGDEVICE->printf
,在其他地方有 Print *LOGDEVICE = &Serial;
。
虽然只要我能够使用 Serial
,它就可以正常工作,但它不适用于 MCU 位于现场某处而无法访问 Serial
输出的情况。我确实有另一个帮助程序类设置 Telnet 服务器,其中日志输出分发给所有连接的客户端。
不过,我无法将两者连接起来。我似乎无法在编译或运行时成功地将 Serial.printf
切换到所需的 TelnetLog.printf
。
到目前为止我尝试过:
-
#define LOGSTMT TelnetLog.printf
并使用LOGSTMT
而不是LOGDEVICE->printf
。这里的问题是需要在日志库头文件中进行修改以影响代码的所有部分(该库也用于其他外部提供的库)。我可以修改日志库,但只能修改一次,以免重复破坏其他库的代码。 - using
LOGDEVICE = &tlog;
-tlog
是类TelnetLog
的一个实例,它派生自Print
。虽然这在原则上是有效的,但由于LOGDEVICE
是指向Print
的指针,因此未使用TelnetLog.printf
,而是使用Print.write
- 进而TelnetLog.write
函数用于输出,因为Print.write
是虚拟的。这对于提供多个 TCP 客户端来说是非常低效的,因为每个字符都将单独发送。 - 使用指向
printf
或Serial
的相应TelnetLog
的函数指针。我根本没有设法编译一些东西。
这是我的示例代码。为了便于阅读,我在这里使用了 AltLog
类而不是 TelnetLog
。 AltLog
只是将输出加倍到串行:
#include <Arduino.h>
#include <typeinfo>
class AltLog : public Print {
public:
size_t write(uint8_t c) {
Serial.printf("/1: %c",c);
Serial.printf("/2: %c",c);
return 5;
}
template <typename... Args>
size_t printf(const char *format,Args&&... args) {
size_t len = 0;
char fmt[strlen(format) + 4];
strcpy(fmt,"/1: ");
strcat(fmt,format);
len = Serial.printf(fmt,std::forward<Args>(args) ...);
strcpy(fmt,"/2: ");
strcat(fmt,std::forward<Args>(args) ...);
return len;
}
};
AltLog a;
Print *device = &Serial;
void setup() {
Serial.begin(115200);
Serial.println("");
Serial.println("_OK_");
delay(5000);
Serial.printf("%s\n",typeid(*device).name());
device->printf("Serial.%c\n",'S');
device = &a;
Serial.printf("%s\n",typeid(*device).name());
dynamic_cast<AltLog *>(device)->printf("Alternate.%c\n",'A');
device->printf("Alternate.%c\n",'A');
}
void loop() {
}
这确实有效,但它不是解决方案,因为它需要 dynamic_cast
我在日志库中没有。输出如下:
_OK_
14HardwareSerial
Serial.S
6AltLog
/1: Alternate.A
/2: Alternate.A
/1: A/2: A/1: l/2: l/1: t/2: t/1: e/2: e/1: r/2: r/1: n/2: n/1: a/2: a/1: t/2: t/1: e/2: e/1: ./2: ./1: A/2: A/1:
/2:
有什么建议吗?
解决方法
在我的特殊情况下,我找到了一个解决方案(不过,一般问题仍然存在)。
Arduino/ESP32 内核的 Print
类具有另一个虚拟函数 size_t write(const uint8_t *buffer,size_t size)
。由于这不是纯虚拟的,我没有注意到。
Print::printf()
正在使用这个 write
变体来输出数据。 write
的默认实现是使用 write
的单字符变体(需要在派生类中实现的纯虚函数)。因此,对 device->printf()
的调用将使用 Print::printf()
,依次使用 Print::write()
和最后 AltLog::write()
。 AltLog::printf()
根本没有发挥作用。
实现AltLog::write(buffer,size)
函数解决了我的困境,现在可以将完整的缓冲区作为一个发送。
#include <Arduino.h>
class AltLog : public Print {
public:
size_t write(uint8_t c) {
Serial.printf("/1: %c",c);
Serial.printf("/2: %c",c);
return 5;
}
size_t write(const uint8_t *buffer,size_t size) {
Serial.print("/1: ");
Serial.write(buffer,size);
Serial.print("/2: ");
Serial.write(buffer,size);
return 4 + size;
}
};
AltLog a;
Print *device = &Serial;
void setup() {
Serial.begin(115200);
Serial.println("");
Serial.println("_OK_");
delay(5000);
device->printf("Serial.%c\n",'S');
device = &a;
device->printf("Alternate.%c\n",'A');
}
void loop() {
}