Как написать библиотеку, которая поддерживает как последовательную связь HW, так и SW и позволяет пользователю выбирать, какую из них использовать?
Я работаю над библиотекой для Arduino/ESP8266/Teensy, которая использует последовательный для отправки команд AT. Я хочу сделать его максимально настраиваемым, поэтому я избегал жесткого кода серийного номера и прошу ссылку в методе запуска:
void MyClass::init(HardwareSerial* hwSerialPtr)
{
_serialPtr = hwSerialPtr;
...
}
На некоторых устройствах будет полезно использовать SoftwareSerial
вместо HW, поскольку доступен только один последовательный, поэтому сохраните его для целей отладки или других функций.
Итак, мой вопрос: как я могу принять оба типа? Я читал, что как HW, так и SW serial имеют поток
в качестве базового класса, но в нем нет таких методов, как begin()
и т. Д.
Возможное решение, которое я считаю, состоит в том, чтобы иметь два указателя (один для HWSerial и один для SWSerial), затем обернуть некоторые методы, такие как begin ()
, println ()
, ...
в моем классе и, на основе которого был установлен указатель, использовать один или другой.
Что-то вроде этого:
class MyClass{
public:
void init(HardwareSerial* hwSerialPtr);
void init(SoftwareSerial* swSerialPtr);
private:
HardwareSerial* _hwSerialPtr;
SoftwareSerial* _swSerialPtr;
void begin(long baud);
void println(const String &s)
};
затем
void MyClass::begin(long baud)
{
if( _hwSerialPtr != null ){ _hwSerialPtr->begin(baudrate); }
else if( _swSerialPtr != null ){ _swSerialPtr->begin(baudrate); }
}
...etc...
но это решение кажется мне очень сложным и не оптимизированным.
Есть идеи получше?
@Noisemaker, 👍3
Обсуждение2 ответа
Хитрость здесь в том, чтобы подняться "на уровень выше". Как HardwareSerial
, так и SoftwareSerial
наследуются от класса Stream. Именно этот класс обеспечивает большую часть интерфейса, который вы фактически используете.
Таким образом, вы можете предоставить конструктору только потоковый
объект и использовать его:
class Foo {
private:
Stream *_dev;
public:
foo(Stream *dev) : _dev(dev) {}
foo(Stream &dev) : _dev(&dev) {}
void begin() {
_dev->println("I am ready");
}
};
Foo myFoo(Serial1);
SoftareSerial btSerial(11,12);
Foo myOtherFoo(btSerial);
Однако класс Stream не предоставляет
.begin()
для настройки скорости передачи в бодах. Пользователю необходимо предоставить возможность настроить порт перед его использованием.
Serial1.begin(115200);
myFoo.begin(); // --> "I am ready" on Serial 1
или:
btSerial.begin(9600);
myOtherFoo.begin(); // --> "I am ready" on Software serial pins 11/12.
Это известно как полиморфизм, когда объект рассматривается как один из родительских классов в цепочке наследования и является одним из наиболее мощных аспектов объектно-ориентированного программирования.
Отсутствие начала
в вашей библиотеке на самом деле не имеет большого значения. Это повышает гибкость (пользователю легче выбрать скорость передачи данных), и это означает, что вы не заставляете их включать программное обеспечение, если оно им не нужно.
Существуют и другие вещи, которые наследуют поток
, например сетевой клиент
. Таким образом, мгновенно ваш класс можно использовать как с сетевыми подключениями, так и просто с последовательными.
Так что придерживайтесь использования потока
. Это общепринятый способ ведения дел, и совершенно нормальный. Например, так моя библиотека ICSC обрабатывает последовательные объекты. (Раньше я использовал метод с несколькими указателями, но он действительно стал запутанным при попытке поддерживать другие платформы. В итоге я
получил HardwareSerial, SoftwareSerial
, USBSerial
, Клиент
... настоящий беспорядок. Так намного проще просто использовать поток
.)
Кстати, прежде чем вы спросите, у меня есть два конструктора с очень небольшими различиями, так что вы можете создать объект либо передавая потоковый объект по ссылке (как в примерах) или в качестве указателя (который может быть большим, если вы хотите использовать это в другом классе, который имеет указатель на объект потока).
Другой вариант - объединить указатель потока с dynamic_cast
, чтобы попытаться определить тип передаваемого объекта.
void Foo::begin(int baud) {
if (HardwareSerial *hs = dynamic_cast<HardwareSerial *>(_dev)) {
hs->begin(baud);
} else if (SoftwareSerial *ss = dynamic_cast<SoftwareSerial *>(_dev)) {
ss->begin(baud);
} else {
// Мы не знаем, что здесь делать
// значит, ничего не делать?
}
}
Таким образом, dynamic_cast
используется как оператор instanceof Java для определения типа используемого объекта.
Конечно, это означает, что вы действительно ограничиваете библиотеку работой только с этими двумя типами объектов, но по-прежнему можно передавать другие типы классов, если пользователь предварительно настроит их перед вызовом функции begin. Что приводит к путанице - какой тип нужен пользователю .begin()
и какие типы .begin()
вызываются для вас? Просто чище всегда иметь его, так как пользователь должен сам вызывать .begin ()
- тогда нет никакой двусмысленности.
Я повторяю совет Майенко: унаследуйте от Stream
и позвольте пользователю обработать
вызов begin ()
, если это вообще возможно. Однако, если вы действительно настаиваете
на вызове begin()
из своего класса, я предлагаю вам сделать свой класс
шаблоном, например:
// T - тип последовательного порта, обычно HardwareSerial или
// SoftwareSerial.
template<class T> class MyClass {
public:
MyClass(T& port) : port(port) {}
void begin(unsigned long baud) { port.begin(baud); }
private:
T& port;
};
Затем ваш пользователь создаст его как
MyClass<HardwareSerial> myObject(Serial);
MyClass<SoftwareSerial> myOtherObject(mySerial);
Конечно, это выглядит не очень красиво и может сбить с толку ваших пользователей, но у него
есть преимущество в том, что он может работать со всем, что отдаленно
похоже на HardwareSerial
, например, с AltSoftSerial
или другими альтернативными
последовательными реализациями программного обеспечения.
- Какой лучший способ объявить Serial при создании библиотеки Arduino?
- AT-команда не отвечает на последовательный монитор
- Как отправить команду AT на sim800l с помощью SoftwareSerial
- Как остановить SoftwareSerial от получения данных и повторно включить его в какой-то другой момент?
- Как эта строка кода определяет, подключен ли последовательный интерфейс?
- Ардуино для чтения с преобразователя RS232 в последовательный модуль TTL
- Не нашел датчик отпечатков пальцев :( Arduino Mega 2560 Adafruit Fingerprint Sensor
- Чтение SMS с помощью Arduino Uno и SIM800L и печать на LCD (16x2 буквенно-цифровых) с использованием последовательного соединения
и то, как используются примеры библиотеки WiFiEsp, не очень хорошо? https://github.com/bportaluri/WiFiEsp/blob/master/examples/WebClient/WebClient.ino, @Juraj
почему вы хотите заняться этим в библиотеке? и существуют альтернативные последовательные реализации программного обеспечения, @Juraj