Make gpios work with arduino based boards

This commit is contained in:
Simon Stürz 2022-01-05 10:36:49 +01:00 committed by Michael Zanetti
parent 42f2e9a3d7
commit 509b649fbe
7 changed files with 1315 additions and 50 deletions

View File

@ -31,6 +31,7 @@
#include "integrationpluginowlet.h"
#include "plugininfo.h"
#include "owletclient.h"
#include "owletserialclient.h"
#include "hardwaremanager.h"
#include "platform/platformzeroconfcontroller.h"
@ -56,16 +57,44 @@ void IntegrationPluginOwlet::init()
m_owletSerialPortParamTypeMap.insert(arduinoMiniProThingClassId, arduinoMiniProThingSerialPortParamTypeId);
m_zeroConfBrowser = hardwareManager()->zeroConfController()->createServiceBrowser("_nymea-owlet._tcp");
// Pin params of thins
m_owletSerialPinParamTypeMap.insert(digitalOutputSerialThingClassId, digitalOutputSerialThingPinParamTypeId);
m_owletSerialPinParamTypeMap.insert(digitalInputSerialThingClassId, digitalInputSerialThingPinParamTypeId);
m_owletSerialPinParamTypeMap.insert(analogOutputSerialThingClassId, analogOutputSerialThingPinParamTypeId);
m_owletSerialPinParamTypeMap.insert(analogInputSerialThingClassId, analogInputSerialThingPinParamTypeId);
m_owletSerialPinParamTypeMap.insert(servoSerialThingClassId, servoSerialThingPinParamTypeId);
// Arduino Mini Pro mapping
m_arduinoMiniProPinMapping.insert(arduinoMiniProSettingsPin2ParamTypeId, 2);
m_arduinoMiniProPinMapping.insert(arduinoMiniProSettingsPin3ParamTypeId, 3);
m_arduinoMiniProPinMapping.insert(arduinoMiniProSettingsPin4ParamTypeId, 4);
m_arduinoMiniProPinMapping.insert(arduinoMiniProSettingsPin5ParamTypeId, 5);
m_arduinoMiniProPinMapping.insert(arduinoMiniProSettingsPin6ParamTypeId, 6);
m_arduinoMiniProPinMapping.insert(arduinoMiniProSettingsPin7ParamTypeId, 7);
m_arduinoMiniProPinMapping.insert(arduinoMiniProSettingsPin8ParamTypeId, 8);
m_arduinoMiniProPinMapping.insert(arduinoMiniProSettingsPin9ParamTypeId, 9);
m_arduinoMiniProPinMapping.insert(arduinoMiniProSettingsPin10ParamTypeId, 10);
m_arduinoMiniProPinMapping.insert(arduinoMiniProSettingsPin11ParamTypeId, 11);
m_arduinoMiniProPinMapping.insert(arduinoMiniProSettingsPin12ParamTypeId, 12);
m_arduinoMiniProPinMapping.insert(arduinoMiniProSettingsPin13ParamTypeId, 13);
m_arduinoMiniProPinMapping.insert(arduinoMiniProSettingsPinA1ParamTypeId, 15);
m_arduinoMiniProPinMapping.insert(arduinoMiniProSettingsPinA2ParamTypeId, 16);
m_arduinoMiniProPinMapping.insert(arduinoMiniProSettingsPinA3ParamTypeId, 17);
m_arduinoMiniProPinMapping.insert(arduinoMiniProSettingsPinA4ParamTypeId, 18);
m_arduinoMiniProPinMapping.insert(arduinoMiniProSettingsPinA5ParamTypeId, 19);
m_arduinoMiniProPinMapping.insert(arduinoMiniProSettingsPinA6ParamTypeId, 20);
m_arduinoMiniProPinMapping.insert(arduinoMiniProSettingsPinA7ParamTypeId, 21);
}
void IntegrationPluginOwlet::discoverThings(ThingDiscoveryInfo *info)
{
if (info->thingClassId() == arduinoMiniProThingClassId) {
// Discover serial ports for arduino bords
foreach(QSerialPortInfo port, QSerialPortInfo::availablePorts()) {
foreach(const QSerialPortInfo &port, QSerialPortInfo::availablePorts()) {
qCDebug(dcOwlet()) << "Found serial port" << port.systemLocation();
QString description = port.systemLocation() + " " + port.manufacturer() + " " + port.description();
ThingDescriptor thingDescriptor(info->thingClassId(), "Owlet Arduino Pro Mini", description);
ThingDescriptor thingDescriptor(info->thingClassId(), "Arduino Pro Mini Owlet", description);
ParamList parameters;
foreach (Thing *existingThing, myThings()) {
@ -99,38 +128,326 @@ void IntegrationPluginOwlet::discoverThings(ThingDiscoveryInfo *info)
void IntegrationPluginOwlet::setupThing(ThingSetupInfo *info)
{
Thing *thing = info->thing();
OwletTransport *transport = nullptr;
if (thing->thingClassId() == arduinoMiniProThingClassId) {
QString serialPort = thing->paramValue(arduinoMiniProThingSerialPortParamTypeId).toString();
qCDebug(dcOwlet()) << "Setup arduino mini pro owlet on" << serialPort;
OwletTransport *transport = new OwletSerialTransport(serialPort, 115200, this);
OwletSerialClient *client = new OwletSerialClient(transport, transport);
// During setup
connect(client, &OwletSerialClient::connected, info, [=](){
qCDebug(dcOwlet()) << "Connected to serial owlet" << client->firmwareVersion();
thing->setStateValue("connected", true);
});
connect(client, &OwletSerialClient::error, info, [=](){
//info->finish(Thing::ThingErrorHardwareFailure);
transport->deleteLater();
});
// Runtime
connect(client, &OwletSerialClient::connected, thing, [=](){
thing->setStateValue("connected", true);
thing->setStateValue(arduinoMiniProCurrentVersionStateTypeId, client->firmwareVersion());
foreach (Thing *childThing, myThings().filterByParentId(thing->id())) {
childThing->setStateValue("connected", true);
}
});
connect(client, &OwletSerialClient::disconnected, thing, [=](){
thing->setStateValue("connected", false);
foreach (Thing *childThing, myThings().filterByParentId(thing->id())) {
childThing->setStateValue("connected", false);
}
});
connect(thing, &Thing::settingChanged, thing, [=](const ParamTypeId &paramTypeId, const QVariant &value){
qCDebug(dcOwlet()) << "Arduino Mini Pro settings changed" << paramTypeId << value;
quint8 pinId = m_arduinoMiniProPinMapping.value(paramTypeId);
OwletSerialClient::PinMode pinMode = getPinModeFromSettingsValue(value.toString());
// Check if we have a thing for this pin and if we need to remove it before setting up
Thing *existingThing = nullptr;
foreach (Thing *childThing, myThings().filterByParentId(thing->id())) {
int existingPinId = childThing->paramValue(m_owletSerialPinParamTypeMap.value(childThing->thingClassId())).toUInt();
if (existingPinId == pinId) {
qCDebug(dcOwlet()) << "Found already configured thing for pin" << pinId;
existingThing = childThing;
break;
}
}
if (existingThing) {
if ((pinMode == OwletSerialClient::PinModeDigitalOutput && existingThing->thingClassId() == digitalOutputSerialThingClassId) ||
(pinMode == OwletSerialClient::PinModeDigitalInput && existingThing->thingClassId() == digitalInputSerialThingClassId) ||
(pinMode == OwletSerialClient::PinModeAnalogOutput && existingThing->thingClassId() == analogOutputSerialThingClassId) ||
(pinMode == OwletSerialClient::PinModeAnalogInput && existingThing->thingClassId() == analogInputSerialThingClassId) ||
(pinMode == OwletSerialClient::PinModeServo && existingThing->thingClassId() == servoSerialThingClassId)) {
qCDebug(dcOwlet()) << "Thing for pin" << pinId << "is already configured as" << pinMode;
return;
} else {
qCDebug(dcOwlet()) << "Have thing for pin" << pinId << "but should be configured as" << pinMode;
qCDebug(dcOwlet()) << "Remove existing thing before setup a new one";
emit autoThingDisappeared(existingThing->id());
}
}
setupArduinoChildThing(client, pinId, pinMode);
});
m_serialClients.insert(thing, client);
info->finish(Thing::ThingErrorNoError);
client->transport()->connectTransport();
return;
}
if (thing->thingClassId() == digitalOutputSerialThingClassId) {
info->finish(Thing::ThingErrorNoError);
// Update states
Thing *parent = myThings().findById(thing->parentId());
bool parentConnected = false;
if (parent)
parentConnected = parent->stateValue("connected").toBool();
thing->setStateValue("connected", parentConnected);
OwletSerialClient *client = m_serialClients.value(parent);
quint8 pinId = thing->paramValue(digitalOutputSerialThingPinParamTypeId).toUInt();
OwletSerialClient::PinMode pinMode = OwletSerialClient::PinModeDigitalOutput;
connect(client, &OwletSerialClient::connected, thing, [=](){
configurePin(client, pinId, pinMode);
});
if (parentConnected) {
configurePin(client, pinId, pinMode);
}
}
if (thing->thingClassId() == digitalInputSerialThingClassId) {
info->finish(Thing::ThingErrorNoError);
// Update states
Thing *parent = myThings().findById(thing->parentId());
bool parentConnected = false;
if (parent)
parentConnected = parent->stateValue("connected").toBool();
thing->setStateValue("connected", parentConnected);
quint8 pinId = thing->paramValue(digitalInputSerialThingPinParamTypeId).toUInt();
OwletSerialClient::PinMode pinMode = OwletSerialClient::PinModeDigitalInput;
OwletSerialClient *client = m_serialClients.value(parent);
connect(client, &OwletSerialClient::connected, thing, [=](){
thing->setStateValue("connected", true);
configurePin(client, pinId, pinMode);
// Read current value
quint8 pinId = thing->paramValue(digitalInputSerialThingPinParamTypeId).toUInt();
OwletSerialClientReply *reply = client->readDigitalValue(pinId);
connect(reply, &OwletSerialClientReply::finished, this, [=](){
if (reply->status() != OwletSerialClient::StatusSuccess) {
qCWarning(dcOwlet()) << "Failed to read digital pin value from" << thing << reply->status();
return;
}
if (reply->responsePayload().count() < 2) {
qCWarning(dcOwlet()) << "Invalid response payload size from request" << pinId;
return;
}
OwletSerialClient::GPIOError configurationError = static_cast<OwletSerialClient::GPIOError>(reply->responsePayload().at(0));
quint8 value = static_cast<quint8>(reply->responsePayload().at(1));
if (configurationError != OwletSerialClient::GPIOErrorNoError) {
qCWarning(dcOwlet()) << "Configure pin request finished with error" << configurationError;
return;
}
thing->setStateValue(digitalInputSerialPowerStateTypeId, value != 0x00);
});
});
connect(client, &OwletSerialClient::pinValueChanged, this, [=](quint8 id, bool power){
if (id != pinId)
return;
thing->setStateValue(digitalInputSerialPowerStateTypeId, power);
});
if (parentConnected) {
configurePin(client, pinId, pinMode);
}
return;
}
if (thing->thingClassId() == analogInputSerialThingClassId) {
info->finish(Thing::ThingErrorNoError);
// Update states
Thing *parent = myThings().findById(thing->parentId());
bool parentConnected = false;
if (parent)
parentConnected = parent->stateValue("connected").toBool();
thing->setStateValue("connected", parentConnected);
OwletSerialClient *client = m_serialClients.value(parent);
QTimer *refreshTimer = new QTimer(thing);
refreshTimer->setSingleShot(false);
refreshTimer->setInterval(thing->setting(analogInputSerialSettingsRefreshRateParamTypeId).toUInt());
connect(refreshTimer, &QTimer::timeout, this, [=](){
// Read current value
quint8 pinId = thing->paramValue(analogInputSerialThingPinParamTypeId).toUInt();
OwletSerialClientReply *reply = client->readAnalogValue(pinId);
connect(reply, &OwletSerialClientReply::finished, this, [=](){
if (reply->status() != OwletSerialClient::StatusSuccess) {
qCWarning(dcOwlet()) << "Failed to read analog pin value from" << thing << reply->status();
return;
}
if (reply->responsePayload().count() < 3) {
qCWarning(dcOwlet()) << "Invalid response payload size from request" << pinId;
return;
}
QDataStream stream(reply->responsePayload());
quint8 errorCode;
stream >> errorCode;
OwletSerialClient::GPIOError configurationError = static_cast<OwletSerialClient::GPIOError>(errorCode);
if (configurationError != OwletSerialClient::GPIOErrorNoError) {
qCWarning(dcOwlet()) << "Configure pin request finished with error" << configurationError;
return;
}
quint32 value;
stream >> value;
qCDebug(dcOwlet()) << "Analog value of" << thing << value;
thing->setStateValue(analogInputSerialAnalogValueStateTypeId, value);
});
});
quint8 pinId = thing->paramValue(analogInputSerialThingPinParamTypeId).toUInt();
OwletSerialClient::PinMode pinMode = OwletSerialClient::PinModeAnalogInput;
connect(client, &OwletSerialClient::connected, thing, [=](){
qCDebug(dcOwlet()) << "Starting refresh timer for" << thing;
configurePin(client, pinId, pinMode);
refreshTimer->start();
});
connect(client, &OwletSerialClient::disconnected, thing, [=](){
qCDebug(dcOwlet()) << "Stopping refresh timer for" << thing;
refreshTimer->stop();
});
// Start reading if already connected
if (parentConnected) {
qCDebug(dcOwlet()) << "Starting refresh timer for" << thing;
configurePin(client, pinId, pinMode);
refreshTimer->start();
}
connect(thing, &Thing::settingChanged, refreshTimer, [=](const ParamTypeId &paramTypeId, const QVariant &value){
if (paramTypeId == analogInputSerialSettingsRefreshRateParamTypeId) {
refreshTimer->setInterval(value.toUInt());
refreshTimer->start();
}
});
return;
}
if (thing->thingClassId() == analogOutputSerialThingClassId) {
info->finish(Thing::ThingErrorNoError);
// Update states
Thing *parent = myThings().findById(thing->parentId());
bool parentConnected = false;
if (parent)
parentConnected = parent->stateValue("connected").toBool();
thing->setStateValue("connected", parentConnected);
OwletSerialClient *client = m_serialClients.value(parent);
quint8 pinId = thing->paramValue(analogInputSerialThingPinParamTypeId).toUInt();
OwletSerialClient::PinMode pinMode = OwletSerialClient::PinModeAnalogInput;
connect(client, &OwletSerialClient::connected, thing, [=](){
configurePin(client, pinId, pinMode);
});
// Start reading if already connected
if (parentConnected) {
configurePin(client, pinId, pinMode);
}
return;
}
if (thing->thingClassId() == servoSerialThingClassId) {
info->finish(Thing::ThingErrorNoError);
// Update states
Thing *parent = myThings().findById(thing->parentId());
bool parentConnected = false;
if (parent)
parentConnected = parent->stateValue("connected").toBool();
thing->setStateValue("connected", parentConnected);
OwletSerialClient *client = m_serialClients.value(parent);
quint8 pinId = thing->paramValue(servoSerialThingPinParamTypeId).toUInt();
OwletSerialClient::PinMode pinMode = OwletSerialClient::PinModeServo;
connect(client, &OwletSerialClient::connected, thing, [=](){
configurePin(client, pinId, pinMode);
});
// Start reading if already connected
if (parentConnected) {
configurePin(client, pinId, pinMode);
}
return;
}
QHostAddress ip;
int port = 5555;
if (thing->thingClassId() != arduinoMiniProThingClassId) {
foreach (const ZeroConfServiceEntry &entry, m_zeroConfBrowser->serviceEntries()) {
if (entry.txt("id") == info->thing()->paramValue(m_owletIdParamTypeMap.value(info->thing()->thingClassId()))) {
ip = entry.hostAddress();
port = entry.port();
break;
}
foreach (const ZeroConfServiceEntry &entry, m_zeroConfBrowser->serviceEntries()) {
if (entry.txt("id") == info->thing()->paramValue(m_owletIdParamTypeMap.value(info->thing()->thingClassId()))) {
ip = entry.hostAddress();
port = entry.port();
break;
}
// Try cached ip
if (ip.isNull()) {
pluginStorage()->beginGroup(thing->id().toString());
ip = QHostAddress(pluginStorage()->value("cachedIP").toString());
pluginStorage()->endGroup();
}
if (ip.isNull()) {
qCWarning(dcOwlet()) << "Can't find owlet in the local network.";
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
transport = new OwletTcpTransport(ip, port, this);
} else if (thing->thingClassId() == arduinoMiniProThingClassId) {
QString serialPort = thing->paramValue(arduinoMiniProThingSerialPortParamTypeId).toString();
qCDebug(dcOwlet()) << "Setup arduino mini pro owlet on" << serialPort;
transport = new OwletSerialTransport(serialPort, 115200, this);
}
// Try cached ip
if (ip.isNull()) {
pluginStorage()->beginGroup(thing->id().toString());
ip = QHostAddress(pluginStorage()->value("cachedIP").toString());
pluginStorage()->endGroup();
}
if (ip.isNull()) {
qCWarning(dcOwlet()) << "Can't find owlet in the local network.";
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
OwletTransport *transport = new OwletTcpTransport(ip, port, this);
OwletClient *client = new OwletClient(transport, this);
connect(client, &OwletClient::connected, info, [=](){
@ -169,12 +486,9 @@ void IntegrationPluginOwlet::setupThing(ThingSetupInfo *info)
connect(client, &OwletClient::connected, thing, [=](){
thing->setStateValue("connected", true);
// FIXME: find a better way
if (thing->thingClassId() != arduinoMiniProThingClassId) {
pluginStorage()->beginGroup(thing->id().toString());
pluginStorage()->setValue("cachedIP", ip.toString());
pluginStorage()->endGroup();
}
pluginStorage()->beginGroup(thing->id().toString());
pluginStorage()->setValue("cachedIP", ip.toString());
pluginStorage()->endGroup();
qCDebug(dcOwlet()) << "Sending get platform information request...";
int id = client->sendCommand("Platform.GetInformation");
@ -286,6 +600,184 @@ void IntegrationPluginOwlet::executeAction(ThingActionInfo *info)
return;
}
if (info->thing()->thingClassId() == arduinoMiniProThingClassId) {
OwletSerialClient *client = m_serialClients.value(info->thing());
if (!client) {
qCWarning(dcOwlet()) << "Could not execute action. There is no client available for this thing";
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
if (!client->ready()) {
qCWarning(dcOwlet()) << "Could not execute action. The serial client is not ready or connected.";
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
if (info->action().actionTypeId() == arduinoMiniProPerformUpdateActionTypeId) {
qCDebug(dcOwlet()) << "Perform firmware update on" << info->thing();
//if (client->firmwareVersion() != )
// TODO: run upgrade process using avrdude
info->finish(Thing::ThingErrorNoError);
return;
}
}
if (info->thing()->thingClassId() == analogOutputSerialThingClassId) {
OwletSerialClient *client = m_serialClients.value(myThings().findById(info->thing()->parentId()));
if (!client) {
qCWarning(dcOwlet()) << "Could not execute action. There is no client available for this thing";
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
if (!client->ready()) {
qCWarning(dcOwlet()) << "Could not execute action. The serial client is not ready or connected.";
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
if (info->action().actionTypeId() == analogOutputSerialDutyCycleActionTypeId) {
quint8 dutyCycle = info->action().paramValue(analogOutputSerialDutyCycleActionDutyCycleParamTypeId).toUInt();
qCDebug(dcOwlet()) << "Set PWM duty cycle of" << info->thing() << "to" << dutyCycle;
quint8 pinId = info->thing()->paramValue(analogOutputSerialThingPinParamTypeId).toUInt();
OwletSerialClientReply *reply = client->writeAnalogValue(pinId, dutyCycle);
connect(reply, &OwletSerialClientReply::finished, this, [=](){
if (reply->status() != OwletSerialClient::StatusSuccess) {
qCWarning(dcOwlet()) << "Failed to set power on pin" << pinId << dutyCycle << reply->status();
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
if (reply->responsePayload().count() < 1) {
qCWarning(dcOwlet()) << "Failed to set power on pin" << pinId << dutyCycle << "Invalid response payload size from request";
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
OwletSerialClient::GPIOError gpioError = static_cast<OwletSerialClient::GPIOError>(reply->responsePayload().at(0));
if (gpioError != OwletSerialClient::GPIOErrorNoError) {
qCWarning(dcOwlet()) << "Set power on pin" << pinId << dutyCycle << "request finished with error" << gpioError;
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
qCDebug(dcOwlet()) << "Set PWM duty cycle finished successfully" << pinId << dutyCycle;
info->thing()->setStateValue(analogOutputSerialDutyCycleStateTypeId, dutyCycle);
info->finish(Thing::ThingErrorNoError);
return;
});
return;
}
}
if (info->thing()->thingClassId() == digitalOutputSerialThingClassId) {
OwletSerialClient *client = m_serialClients.value(myThings().findById(info->thing()->parentId()));
if (!client) {
qCWarning(dcOwlet()) << "Could not execute action. There is no client available for this thing";
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
if (!client->ready()) {
qCWarning(dcOwlet()) << "Could not execute action. The serial client is not ready or connected.";
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
if (info->action().actionTypeId() == digitalOutputSerialPowerActionTypeId) {
quint8 pinId = info->thing()->paramValue(digitalOutputSerialThingPinParamTypeId).toUInt();
bool power = info->action().paramValue(digitalOutputSerialPowerActionPowerParamTypeId).toBool();
OwletSerialClientReply *reply = client->writeDigitalValue(pinId, power);
connect(reply, &OwletSerialClientReply::finished, this, [=](){
if (reply->status() != OwletSerialClient::StatusSuccess) {
qCWarning(dcOwlet()) << "Failed to set power on pin" << pinId << power << reply->status();
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
if (reply->responsePayload().count() < 1) {
qCWarning(dcOwlet()) << "Failed to set power on pin" << pinId << power << "Invalid response payload size from request";
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
OwletSerialClient::GPIOError gpioError = static_cast<OwletSerialClient::GPIOError>(reply->responsePayload().at(0));
if (gpioError != OwletSerialClient::GPIOErrorNoError) {
qCWarning(dcOwlet()) << "Set power on pin" << pinId << power << "request finished with error" << gpioError;
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
qCDebug(dcOwlet()) << "Set power finished successfully" << pinId << power;
info->thing()->setStateValue(digitalOutputSerialPowerStateTypeId, power);
info->finish(Thing::ThingErrorNoError);
return;
});
return;
}
info->finish(Thing::ThingErrorUnsupportedFeature);
return;
}
if (info->thing()->thingClassId() == servoSerialThingClassId) {
OwletSerialClient *client = m_serialClients.value(myThings().findById(info->thing()->parentId()));
if (!client) {
qCWarning(dcOwlet()) << "Could not execute action. There is no client available for this thing";
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
if (!client->ready()) {
qCWarning(dcOwlet()) << "Could not execute action. The serial client is not ready or connected.";
info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
if (info->action().actionTypeId() == servoSerialAngleActionTypeId) {
quint8 pinId = info->thing()->paramValue(servoSerialThingPinParamTypeId).toUInt();
quint8 angle = info->action().paramValue(servoSerialAngleActionAngleParamTypeId).toUInt();
OwletSerialClientReply *reply = client->writeServoValue(pinId, angle);
connect(reply, &OwletSerialClientReply::finished, this, [=](){
if (reply->status() != OwletSerialClient::StatusSuccess) {
qCWarning(dcOwlet()) << "Failed to set servo angle on pin" << pinId << angle << reply->status();
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
if (reply->responsePayload().count() < 1) {
qCWarning(dcOwlet()) << "Failed to set servo angle on pin" << pinId << angle << "Invalid response payload size from request";
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
OwletSerialClient::GPIOError gpioError = static_cast<OwletSerialClient::GPIOError>(reply->responsePayload().at(0));
if (gpioError != OwletSerialClient::GPIOErrorNoError) {
qCWarning(dcOwlet()) << "Set angle on servo pin" << pinId << angle << "request finished with error" << gpioError;
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
qCDebug(dcOwlet()) << "Set servo angle finished successfully" << pinId << angle;
info->thing()->setStateValue(servoSerialAngleStateTypeId, angle);
info->finish(Thing::ThingErrorNoError);
return;
});
return;
}
info->finish(Thing::ThingErrorUnsupportedFeature);
return;
}
Q_ASSERT_X(false, "IntegrationPluginOwlet", "Not implemented");
@ -294,5 +786,132 @@ void IntegrationPluginOwlet::executeAction(ThingActionInfo *info)
void IntegrationPluginOwlet::thingRemoved(Thing *thing)
{
Q_UNUSED(thing)
if (thing->thingClassId() == arduinoMiniProThingClassId && m_serialClients.contains(thing)) {
m_serialClients.take(thing)->deleteLater();
}
}
OwletSerialClient::PinMode IntegrationPluginOwlet::getPinModeFromSettingsValue(const QString &settingsValue)
{
if (settingsValue == "Output") {
return OwletSerialClient::PinModeDigitalOutput;
} else if (settingsValue == "Input") {
return OwletSerialClient::PinModeDigitalInput;
} else if (settingsValue == "PWM") {
return OwletSerialClient::PinModeAnalogOutput;
} else if (settingsValue == "Analog Input") {
return OwletSerialClient::PinModeAnalogInput;
} else if (settingsValue == "Servo") {
return OwletSerialClient::PinModeServo;
} else {
return OwletSerialClient::PinModeUnconfigured;
}
}
void IntegrationPluginOwlet::setupArduinoChildThing(OwletSerialClient *client, quint8 pinId, OwletSerialClient::PinMode pinMode)
{
Thing *parentThing = m_serialClients.key(client);
if (!parentThing) {
qCWarning(dcOwlet()) << "Could not setup child thing because the parent thing seems not to be available";
return;
}
// Note: pin has no thing here any more, we can set up a new one if required
OwletSerialClientReply *reply = client->configurePin(pinId, pinMode);
connect(reply, &OwletSerialClientReply::finished, this, [=](){
if (reply->status() != OwletSerialClient::StatusSuccess) {
qCWarning(dcOwlet()) << "Failed to configure pin" << pinId << pinMode << reply->status();
return;
}
if (reply->responsePayload().count() < 1) {
qCWarning(dcOwlet()) << "Invalid configure pin response payload size from request" << pinId << pinMode;
return;
}
OwletSerialClient::GPIOError configurationError = static_cast<OwletSerialClient::GPIOError>(reply->responsePayload().at(0));
if (configurationError != OwletSerialClient::GPIOErrorNoError) {
qCWarning(dcOwlet()) << "Configure pin request finished with error" << configurationError;
return;
}
qCDebug(dcOwlet()) << "Configure pin request finished successfully" << pinId << pinMode;
// Setup child devices
switch (pinMode) {
case OwletSerialClient::PinModeDigitalOutput: {
qCDebug(dcOwlet()) << "Setting up digital output on serial owlet for pin" << pinId;
ThingDescriptor descriptor(digitalOutputSerialThingClassId, thingClass(digitalOutputSerialThingClassId).displayName() + " (" + QString::number(pinId) + ")", QString(), parentThing->id());
ParamList params;
params.append(Param(digitalOutputSerialThingPinParamTypeId, pinId));
descriptor.setParams(params);
emit autoThingsAppeared(ThingDescriptors() << descriptor);
break;
}
case OwletSerialClient::PinModeDigitalInput: {
qCDebug(dcOwlet()) << "Setting up digital input on serial owlet for pin" << pinId;
ThingDescriptor descriptor(digitalInputSerialThingClassId, thingClass(digitalInputSerialThingClassId).displayName() + " (" + QString::number(pinId) + ")", QString(), parentThing->id());
ParamList params;
params.append(Param(digitalInputSerialThingPinParamTypeId, pinId));
descriptor.setParams(params);
emit autoThingsAppeared(ThingDescriptors() << descriptor);
break;
}
case OwletSerialClient::PinModeAnalogOutput: {
qCDebug(dcOwlet()) << "Setting up digital output on serial owlet for pin" << pinId;
ThingDescriptor descriptor(analogOutputSerialThingClassId, thingClass(analogOutputSerialThingClassId).displayName() + " (" + QString::number(pinId) + ")", QString(), parentThing->id());
ParamList params;
params.append(Param(analogOutputSerialThingPinParamTypeId, pinId));
descriptor.setParams(params);
emit autoThingsAppeared(ThingDescriptors() << descriptor);
break;
}
case OwletSerialClient::PinModeAnalogInput: {
qCDebug(dcOwlet()) << "Setting up analog input on serial owlet for pin" << pinId;
ThingDescriptor descriptor(analogInputSerialThingClassId, thingClass(analogInputSerialThingClassId).displayName() + " (" + QString::number(pinId) + ")", QString(), parentThing->id());
ParamList params;
params.append(Param(analogInputSerialThingPinParamTypeId, pinId));
descriptor.setParams(params);
emit autoThingsAppeared(ThingDescriptors() << descriptor);
break;
}
case OwletSerialClient::PinModeServo: {
qCDebug(dcOwlet()) << "Setting up servo on serial owlet for pin" << pinId;
ThingDescriptor descriptor(servoSerialThingClassId, thingClass(servoSerialThingClassId).displayName() + " (" + QString::number(pinId) + ")", QString(), parentThing->id());
ParamList params;
params.append(Param(servoSerialThingPinParamTypeId, pinId));
descriptor.setParams(params);
emit autoThingsAppeared(ThingDescriptors() << descriptor);
break;
}
case OwletSerialClient::PinModeUnconfigured:
break;
}
});
}
void IntegrationPluginOwlet::configurePin(OwletSerialClient *client, quint8 pinId, OwletSerialClient::PinMode pinMode)
{
OwletSerialClientReply *reply = client->configurePin(pinId, pinMode);
connect(reply, &OwletSerialClientReply::finished, this, [=](){
if (reply->status() != OwletSerialClient::StatusSuccess) {
qCWarning(dcOwlet()) << "Failed to set pin mode on pin" << pinId << pinMode << reply->status();
return;
}
if (reply->responsePayload().count() < 1) {
qCWarning(dcOwlet()) << "Failed to set pin mode on pin" << pinId << pinMode << "Invalid response payload size from request";
return;
}
OwletSerialClient::GPIOError gpioError = static_cast<OwletSerialClient::GPIOError>(reply->responsePayload().at(0));
if (gpioError != OwletSerialClient::GPIOErrorNoError) {
qCWarning(dcOwlet()) << "Set pin mode on pin" << pinId << pinMode << "request finished with error" << gpioError;
return;
}
qCDebug(dcOwlet()) << "Set pin mode finished successfully" << pinId << pinMode;
});
}

View File

@ -34,6 +34,8 @@
#include "integrations/integrationplugin.h"
#include "extern-plugininfo.h"
#include "owletserialclient.h"
class ZeroConfServiceBrowser;
class OwletClient;
@ -57,8 +59,19 @@ private:
ZeroConfServiceBrowser *m_zeroConfBrowser = nullptr;
QHash<Thing *, OwletClient *> m_clients;
QHash<Thing *, OwletSerialClient *> m_serialClients;
QHash<ThingClassId, ParamTypeId> m_owletIdParamTypeMap;
// Serial owlets
QHash<ThingClassId, ParamTypeId> m_owletSerialPortParamTypeMap;
QHash<ThingClassId, ParamTypeId> m_owletSerialPinParamTypeMap;
QHash<ParamTypeId, quint8> m_arduinoMiniProPinMapping;
OwletSerialClient::PinMode getPinModeFromSettingsValue(const QString &settingsValue);
void setupArduinoChildThing(OwletSerialClient *client, quint8 pinId, OwletSerialClient::PinMode pinMode);
void configurePin(OwletSerialClient *client, quint8 pinId, OwletSerialClient::PinMode pinMode);
};
#endif // INTEGRATIONPLUGINOWLET_H

View File

@ -225,7 +225,7 @@
"name": "arduinoMiniPro",
"displayName": "Arduino Pro Mini Owlet",
"createMethods": ["user", "discovery"],
"interfaces": ["gateway", "connectable"],
"interfaces": ["gateway", "update", "connectable"],
"paramTypes": [
{
"id": "589715a6-aaf7-4ffb-9c34-015ac22d527e",
@ -252,8 +252,7 @@
"allowedValues": [
"None",
"Input",
"Output",
"Interrupt"
"Output"
],
"defaultValue": "None"
},
@ -267,7 +266,7 @@
"Input",
"Output",
"PWM",
"Interrupt"
"Servo"
],
"defaultValue": "None"
},
@ -292,7 +291,8 @@
"None",
"Input",
"Output",
"PWM"
"PWM",
"Servo"
],
"defaultValue": "None"
},
@ -305,7 +305,8 @@
"None",
"Input",
"Output",
"PWM"
"PWM",
"Servo"
],
"defaultValue": "None"
},
@ -342,7 +343,8 @@
"None",
"Input",
"Output",
"PWM"
"PWM",
"Servo"
],
"defaultValue": "None"
},
@ -355,7 +357,8 @@
"None",
"Input",
"Output",
"PWM"
"PWM",
"Servo"
],
"defaultValue": "None"
},
@ -368,7 +371,8 @@
"None",
"Input",
"Output",
"PWM"
"PWM",
"Servo"
],
"defaultValue": "None"
},
@ -497,6 +501,228 @@
"type": "bool",
"defaultValue": false,
"cached": false
},
{
"id": "f7e86ecd-761b-412e-b5de-a2547f883f76",
"name": "currentVersion",
"displayName": "Firmware version",
"displayNameEvent": "Firmware version changed",
"type": "QString",
"defaultValue": ""
},
{
"id": "43afe87d-3465-4ced-be0f-170f972afea3",
"name": "updateStatus",
"displayName": "Update status",
"displayNameEvent": "Update status changed",
"type": "QString",
"possibleValues": ["idle", "available", "updating"],
"defaultValue": "idle"
}
],
"actionTypes": [
{
"id": "8cfbb619-de54-441a-8761-d29c6f0c659a",
"name": "performUpdate",
"displayName": "Update firmware"
}
]
},
{
"id": "43aca5c1-3972-49ae-b21f-4d1486942e1f",
"displayName": "Digital GPIO output on owlet",
"name": "digitalOutputSerial",
"createMethods": ["auto"],
"interfaces": [ "power", "connectable" ],
"paramTypes": [
{
"id": "6e17a423-89c0-430b-ac78-1af60a63ce50",
"name": "pin",
"displayName": "Pin number",
"type": "uint",
"defaultValue": 1
}
],
"stateTypes": [
{
"id": "3da148b9-fe85-4bf1-9005-082b3cf0c014",
"name": "connected",
"displayName": "Connected",
"displayNameEvent": "Connected changed",
"defaultValue": false,
"type": "bool"
},
{
"id": "2f7dd16b-c1ee-4b2b-859e-e3eacc679400",
"name": "power",
"displayName": "Power",
"type": "bool",
"defaultValue": false,
"writable": true,
"displayNameEvent": "Power changed",
"displayNameAction": "Set power",
"ioType": "digitalOutput"
}
]
},
{
"id": "884cb983-3099-4355-a2e6-693f5c1b85e7",
"displayName": "Digital GPIO input on owlet",
"name": "digitalInputSerial",
"createMethods": ["auto"],
"interfaces": [ "connectable" ],
"paramTypes": [
{
"id": "6e872f08-9110-425c-9e17-e24d83452b7d",
"name": "pin",
"displayName": "Pin number",
"type": "uint",
"defaultValue": 1
}
],
"stateTypes": [
{
"id": "112dad0b-9c94-4310-8962-f81e1050c713",
"name": "connected",
"displayName": "Connected",
"displayNameEvent": "Connected changed",
"defaultValue": false,
"type": "bool"
},
{
"id": "9fd72a32-a266-4474-b5b1-e6da9835da6d",
"name": "power",
"displayName": "Powered",
"type": "bool",
"defaultValue": false,
"displayNameEvent": "Powered changed",
"ioType": "digitalInput"
}
]
},
{
"id": "c6f3cbd9-1863-430a-9fc2-19f7b57b47d7",
"displayName": "Analog input GPIO on owlet",
"name": "analogInputSerial",
"createMethods": ["auto"],
"interfaces": [ "connectable" ],
"paramTypes": [
{
"id": "55b142ab-ae9a-41cd-a23a-5a78ec141013",
"name": "pin",
"displayName": "Pin number",
"type": "uint",
"defaultValue": 1
}
],
"settingsTypes": [
{
"id": "cfdc6cd0-02ae-403c-aab6-8e86306405af",
"name": "refreshRate",
"displayName": "Refresh rate",
"type": "uint",
"unit": "MilliSeconds",
"minValue": 200,
"defaultValue": 500
}
],
"stateTypes": [
{
"id": "b8fdd65e-dd33-451e-b02f-d7cf73a2e4e2",
"name": "connected",
"displayName": "Connected",
"displayNameEvent": "Connected changed",
"defaultValue": false,
"type": "bool"
},
{
"id": "5b7b50c8-a3aa-494d-a454-96c38f257836",
"name": "analogValue",
"displayName": "Analog value",
"displayNameEvent": "Analog value changed",
"type": "uint",
"defaultValue": 0,
"minValue": 0,
"maxValue": 1023,
"ioType": "analogInput"
}
]
},
{
"id": "ee228a56-9cf2-4401-84c2-8e25f288f136",
"displayName": "PWM on owlet",
"name": "analogOutputSerial",
"createMethods": ["auto"],
"interfaces": [ "connectable" ],
"paramTypes": [
{
"id": "e2da646f-003f-495b-8250-28067732973a",
"name": "pin",
"displayName": "Pin number",
"type": "uint",
"defaultValue": 1
}
],
"stateTypes": [
{
"id": "d08ed1bd-0ddf-4bad-9689-cc7730a54b05",
"name": "connected",
"displayName": "Connected",
"displayNameEvent": "Connected changed",
"defaultValue": false,
"type": "bool"
},
{
"id": "8c8065f7-b335-41bf-a015-fd2cd7e68e81",
"name": "dutyCycle",
"displayName": "Duty cycle",
"displayNameEvent": "Duty cycle changed",
"displayNameAction": "Set duty cycle",
"type": "uint",
"minValue": 0,
"maxValue": 255,
"defaultValue": 0,
"writable": true,
"ioType": "analogOutput"
}
]
},
{
"id": "66d9725a-b096-4300-b0e1-adc043ee12c3",
"displayName": "Servo on owlet",
"name": "servoSerial",
"createMethods": ["auto"],
"interfaces": [ "connectable" ],
"paramTypes": [
{
"id": "4596b43d-93e9-46e4-8831-59f96f998606",
"name": "pin",
"displayName": "Pin number",
"type": "uint",
"defaultValue": 1
}
],
"stateTypes": [
{
"id": "0ae0d6a4-8eb1-4d4f-b093-59ce42355ad7",
"name": "connected",
"displayName": "Connected",
"displayNameEvent": "Connected changed",
"defaultValue": false,
"type": "bool"
},
{
"id": "eee4e07a-1cb1-44d1-98d5-d66d2e63f704",
"name": "angle",
"displayName": "Angle",
"displayNameEvent": "Angle changed",
"displayNameAction": "Set angle",
"type": "uint",
"minValue": 0,
"maxValue": 180,
"defaultValue": 0,
"writable": true,
"ioType": "analogOutput"
}
]
}

View File

@ -5,6 +5,7 @@ QT += network serialport
SOURCES += \
integrationpluginowlet.cpp \
owletclient.cpp \
owletserialclient.cpp \
owletserialtransport.cpp \
owlettcptransport.cpp \
owlettransport.cpp
@ -12,6 +13,7 @@ SOURCES += \
HEADERS += \
integrationpluginowlet.h \
owletclient.h \
owletserialclient.h \
owletserialtransport.h \
owlettcptransport.h \
owlettransport.h

249
owlet/owletserialclient.cpp Normal file
View File

@ -0,0 +1,249 @@
#include "owletserialclient.h"
#include "extern-plugininfo.h"
#include <QDataStream>
OwletSerialClient::OwletSerialClient(OwletTransport *transport, QObject *parent) :
QObject(parent),
m_transport(transport)
{
connect(m_transport, &OwletTransport::dataReceived, this, &OwletSerialClient::dataReceived);
connect(m_transport, &OwletTransport::error, this, &OwletSerialClient::error);
connect(m_transport, &OwletTransport::connectedChanged, this, [=](bool transportConnected){
if (!transportConnected) {
m_ready = false;
emit disconnected();
// Clean up queue
qDeleteAll(m_pendingRequests);
m_pendingRequests.clear();
}
});
}
OwletTransport *OwletSerialClient::transport() const
{
return m_transport;
}
bool OwletSerialClient::ready() const
{
return m_ready;
}
OwletSerialClientReply *OwletSerialClient::getFirmwareVersion()
{
qCDebug(dcOwlet()) << "Request owlet firmware version";
QByteArray requestData;
QDataStream stream(&requestData, QIODevice::WriteOnly);
stream << static_cast<quint8>(OwletSerialClient::CommandGetFirmwareVersion);
stream << m_requestId++;
OwletSerialClientReply *reply = createReply(requestData);
m_pendingRequests.enqueue(reply);
sendNextRequest();
return reply;
}
OwletSerialClientReply *OwletSerialClient::configurePin(quint8 pinId, PinMode pinMode)
{
qCDebug(dcOwlet()) << "Configure pin" << pinId << pinMode;
QByteArray requestData;
QDataStream stream(&requestData, QIODevice::WriteOnly);
stream << static_cast<quint8>(OwletSerialClient::CommandConfigurePin);
stream << m_requestId++;
stream << pinId << static_cast<quint8>(pinMode);
OwletSerialClientReply *reply = createReply(requestData);
m_pendingRequests.enqueue(reply);
sendNextRequest();
return reply;
}
OwletSerialClientReply *OwletSerialClient::writeDigitalValue(quint8 pinId, bool power)
{
qCDebug(dcOwlet()) << "Setting gpio output power of pin" << pinId << power;
QByteArray requestData;
QDataStream stream(&requestData, QIODevice::WriteOnly);
stream << static_cast<quint8>(OwletSerialClient::CommandWriteDigitalPin);
stream << m_requestId++;
stream << pinId << static_cast<quint8>(power ? 0x01 : 0x00);
OwletSerialClientReply *reply = createReply(requestData);
m_pendingRequests.enqueue(reply);
sendNextRequest();
return reply;
}
OwletSerialClientReply *OwletSerialClient::readDigitalValue(quint8 pinId)
{
qCDebug(dcOwlet()) << "Reading digital gpio value of pin" << pinId;
QByteArray requestData;
QDataStream stream(&requestData, QIODevice::WriteOnly);
stream << static_cast<quint8>(OwletSerialClient::CommandReadDigitalPin);
stream << m_requestId++;
stream << pinId;
OwletSerialClientReply *reply = createReply(requestData);
m_pendingRequests.enqueue(reply);
sendNextRequest();
return reply;
}
OwletSerialClientReply *OwletSerialClient::writeAnalogValue(quint8 pinId, quint8 dutyCycle)
{
qCDebug(dcOwlet()) << "Write analog gpio value of pin" << pinId << dutyCycle;
QByteArray requestData;
QDataStream stream(&requestData, QIODevice::WriteOnly);
stream << static_cast<quint8>(OwletSerialClient::CommandWriteAnalogPin);
stream << m_requestId++;
stream << pinId;
stream << dutyCycle;
OwletSerialClientReply *reply = createReply(requestData);
m_pendingRequests.enqueue(reply);
sendNextRequest();
return reply;
}
OwletSerialClientReply *OwletSerialClient::readAnalogValue(quint8 pinId)
{
qCDebug(dcOwlet()) << "Reading analog gpio value of pin" << pinId;
QByteArray requestData;
QDataStream stream(&requestData, QIODevice::WriteOnly);
stream << static_cast<quint8>(OwletSerialClient::CommandReadAnalogPin);
stream << m_requestId++;
stream << pinId;
OwletSerialClientReply *reply = createReply(requestData);
m_pendingRequests.enqueue(reply);
sendNextRequest();
return reply;
}
OwletSerialClientReply *OwletSerialClient::writeServoValue(quint8 pinId, quint8 angle)
{
qCDebug(dcOwlet()) << "Setting servo angle of pin" << pinId << angle;
QByteArray requestData;
QDataStream stream(&requestData, QIODevice::WriteOnly);
stream << static_cast<quint8>(OwletSerialClient::CommandWriteServoPin);
stream << m_requestId++;
stream << pinId;
stream << angle;
OwletSerialClientReply *reply = createReply(requestData);
m_pendingRequests.enqueue(reply);
sendNextRequest();
return reply;
}
QString OwletSerialClient::firmwareVersion() const
{
return m_firmwareVersion;
}
void OwletSerialClient::dataReceived(const QByteArray &data)
{
QDataStream stream(data);
quint8 commandId; quint8 requestId;
stream >> commandId >> requestId;
// Check if command or notification
if (commandId < 0xF0) {
quint8 statusId;
stream >> statusId;
OwletSerialClient::Command command = static_cast<OwletSerialClient::Command>(commandId);
OwletSerialClient::Status status = static_cast<OwletSerialClient::Status>(statusId);
if (m_currentReply) {
if (m_currentReply->command() == command && m_currentReply->requestId() == requestId) {
m_currentReply->m_timer.stop();
m_currentReply->m_status = status;
m_currentReply->m_responsePayload = data.right(data.length() - 3);
if (status != OwletSerialClient::StatusSuccess) {
qCWarning(dcOwlet()) << "Request finished with error" << command << "ID:" << m_currentReply->requestId() << status;
} else {
qCDebug(dcOwlet()) << "Request finished" << command << "ID:" << m_currentReply->requestId() << status << "Payload:" << m_currentReply->responsePayload().toHex();
}
emit m_currentReply->finished();
}
} else {
qCWarning(dcOwlet()) << "Received unhandled command response data" << data.toHex();
}
} else {
OwletSerialClient::Notification notification = static_cast<OwletSerialClient::Notification>(commandId);
QByteArray notificationPayload = data.right(data.length() - 2);
qCDebug(dcOwlet()) << "Notification received" << notification << "ID:" << requestId << notificationPayload.toHex();
switch (notification) {
case OwletSerialClient::NotificationReady: {
OwletSerialClientReply *reply = getFirmwareVersion();
connect(reply, &OwletSerialClientReply::finished, this, [this, reply](){
if (reply->status() != OwletSerialClient::StatusSuccess) {
qCWarning(dcOwlet()) << "Failed to get firmware version" << reply->status();
emit error();
return;
}
if (reply->responsePayload().count() != 3) {
qCWarning(dcOwlet()) << "Invalid firmware version payload size";
emit error();
return;
}
quint8 major = reply->responsePayload().at(0);
quint8 minor = reply->responsePayload().at(1);
quint8 patch = reply->responsePayload().at(2);
m_firmwareVersion = QString("%1.%2.%3").arg(major).arg(minor).arg(patch);
qCDebug(dcOwlet()) << "Connected successfully to firmware" << m_firmwareVersion;
m_ready = true;
emit connected();
});
break;
}
case OwletSerialClient::NotificationGpioPinChanged: {
quint8 pinId; quint8 powerValue;
stream >> pinId >> powerValue;
bool power = powerValue != 0x00;
qCDebug(dcOwlet()) << "Pin value changed" << pinId << power;
emit pinValueChanged(pinId, power);
break;
}
case OwletSerialClient::NotificationDebugMessage:
qCDebug(dcOwlet()) << "Firmware debug:" << QString::fromUtf8(data.right(data.length() - 2));
break;
default:
qCWarning(dcOwlet()) << "Unhandled notification received" << data.toHex();
break;
}
}
}
OwletSerialClientReply *OwletSerialClient::createReply(const QByteArray &requestData)
{
OwletSerialClientReply *reply = new OwletSerialClientReply(requestData, this);
connect(reply, &OwletSerialClientReply::finished, reply, [=](){
reply->deleteLater();
if (reply == m_currentReply) {
m_currentReply = nullptr;
}
sendNextRequest();
});
return reply;
}
void OwletSerialClient::sendNextRequest()
{
if (m_currentReply)
return;
if (m_pendingRequests.isEmpty())
return;
m_currentReply = m_pendingRequests.dequeue();
qCDebug(dcOwlet()) << "Sending request" << m_currentReply->command() << "ID:" << m_currentReply->requestId() << "Payload:" << m_currentReply->requestData().right(m_currentReply->requestData().length() - 2).toHex();
m_transport->sendData(m_currentReply->requestData());
m_currentReply->m_timer.start();
}

150
owlet/owletserialclient.h Normal file
View File

@ -0,0 +1,150 @@
#ifndef OWLETSERIALCLIENT_H
#define OWLETSERIALCLIENT_H
#include <QTimer>
#include <QObject>
#include <QQueue>
#include "owlettransport.h"
class OwletSerialClientReply;
class OwletSerialClient : public QObject
{
Q_OBJECT
public:
enum Command {
CommandGetFirmwareVersion = 0x00,
CommandConfigurePin = 0x01,
CommandWriteDigitalPin = 0x02,
CommandReadDigitalPin = 0x03,
CommandWriteAnalogPin = 0x04,
CommandReadAnalogPin = 0x05,
CommandWriteServoPin = 0x06
};
Q_ENUM(Command)
enum Notification {
NotificationReady = 0xf0,
NotificationGpioPinChanged = 0xf1,
NotificationDebugMessage = 0xff
};
Q_ENUM(Notification)
enum Status {
StatusSuccess = 0x00,
StatusInvalidProtocol = 0x01,
StatusInvalidCommand = 0x02,
StatusInvalidPlayload = 0x03,
StatusTimeout = 0xfe,
StatusUnknownError = 0xff
};
Q_ENUM(Status)
enum GPIOError {
GPIOErrorNoError = 0x00,
GPIOErrorUnconfigured = 0x01,
GPIOErrorUnsupported = 0x02,
GPIOErrorConfigurationMismatch = 0x03,
GPIOErrorInvalidParameter = 0x04,
GPIOErrorInvalidPin = 0x05
};
Q_ENUM(GPIOError)
enum PinMode {
PinModeUnconfigured = 0x00,
PinModeDigitalInput = 0x01,
PinModeDigitalOutput = 0x02,
PinModeAnalogInput = 0x03,
PinModeAnalogOutput = 0x04,
PinModeServo = 0x05
};
Q_ENUM(PinMode)
explicit OwletSerialClient(OwletTransport *transport, QObject *parent = nullptr);
OwletTransport *transport() const;
bool ready() const;
OwletSerialClientReply *getFirmwareVersion();
OwletSerialClientReply *configurePin(quint8 pinId, PinMode pinMode);
OwletSerialClientReply *writeDigitalValue(quint8 pinId, bool power);
OwletSerialClientReply *readDigitalValue(quint8 pinId);
OwletSerialClientReply *writeAnalogValue(quint8 pinId, quint8 dutyCycle);
OwletSerialClientReply *readAnalogValue(quint8 pinId);
OwletSerialClientReply *writeServoValue(quint8 pinId, quint8 angle);
QString firmwareVersion() const;
signals:
void connected();
void disconnected();
void error();
void pinValueChanged(quint8 pinId, bool power);
private slots:
void dataReceived(const QByteArray &data);
private:
OwletTransport *m_transport = nullptr;
bool m_ready = false;
quint8 m_requestId = 0;
OwletSerialClientReply *m_currentReply = nullptr;
QQueue<OwletSerialClientReply *> m_pendingRequests;
QString m_firmwareVersion;
OwletSerialClientReply *createReply(const QByteArray &requestData);
void sendNextRequest();
};
class OwletSerialClientReply : public QObject
{
Q_OBJECT
friend class OwletSerialClient;
public:
QByteArray requestData() const { return m_requestData; };
OwletSerialClient::Command command() const { return m_command; };
quint8 requestId() const { return m_requestId; };
OwletSerialClient::Status status() const { return m_status; };
QByteArray responsePayload() const { return m_responsePayload; };
signals:
void finished();
private:
explicit OwletSerialClientReply(const QByteArray &requestData, QObject *parent = nullptr) :
QObject(parent),
m_requestData(requestData)
{
Q_ASSERT(m_requestData.length() >= 2);
m_command = static_cast<OwletSerialClient::Command>(m_requestData.at(0));
m_requestId = static_cast<quint8>(m_requestData.at(1));
m_timer.setInterval(1000);
m_timer.setSingleShot(true);
connect(&m_timer, &QTimer::timeout, this, [=](){
m_status = OwletSerialClient::StatusTimeout;
emit finished();
});
}
QTimer m_timer;
QByteArray m_requestData;
OwletSerialClient::Command m_command;
quint8 m_requestId = 0;
// Response
OwletSerialClient::Status m_status = OwletSerialClient::StatusUnknownError;
QByteArray m_responsePayload;
};
#endif // OWLETSERIALCLIENT_H

View File

@ -27,12 +27,13 @@ OwletSerialTransport::OwletSerialTransport(const QString &serialPortName, uint b
connect(m_serialPort, &QSerialPort::errorOccurred, this, &OwletSerialTransport::onError);
#endif
m_reconnectTimer = new QTimer(this);
m_reconnectTimer->setInterval(5000);
m_reconnectTimer->setSingleShot(false);
connect(m_reconnectTimer, &QTimer::timeout, this, [=](){
if (m_serialPort->isOpen()) {
// Clear any buffer content
m_serialPort->clear();
m_reconnectTimer->stop();
return;
} else {
@ -48,6 +49,8 @@ bool OwletSerialTransport::connected() const
void OwletSerialTransport::sendData(const QByteArray &data)
{
qCDebug(dcOwlet()) << "UART -->" << data.toHex();
// Stream bytes using SLIP
QByteArray message;
QDataStream stream(&message, QIODevice::WriteOnly);
@ -71,7 +74,8 @@ void OwletSerialTransport::sendData(const QByteArray &data)
}
stream << static_cast<quint8>(SlipProtocolEnd);
qCDebug(dcOwlet()) << "UART -->" << qUtf8Printable(data) << message.toHex();
//qCDebug(dcOwlet()) << "UART -->" << message.toHex();
m_serialPort->write(message);
m_serialPort->flush();
}
@ -102,6 +106,7 @@ void OwletSerialTransport::connectTransport()
if (!m_serialPort->open(QIODevice::ReadWrite)) {
qCWarning(dcOwlet()) << "Could not open serial port on" << m_serialPortName << m_serialPort->errorString();
m_reconnectTimer->start();
emit error();
return;
}
@ -120,7 +125,7 @@ void OwletSerialTransport::disconnectTransport()
void OwletSerialTransport::onReadyRead()
{
QByteArray data = m_serialPort->readAll();
qCDebug(dcOwlet()) << "UART <-- raw:" << data.toHex() << qUtf8Printable(data);
//qCDebug(dcOwlet()) << "UART <--" << data.toHex();
for (int i = 0; i < data.length(); i++) {
quint8 receivedByte = data.at(i);
@ -144,8 +149,8 @@ void OwletSerialTransport::onReadyRead()
switch (receivedByte) {
case SlipProtocolEnd:
// We are done with this package, process it and reset the buffer
if (!m_buffer.isEmpty() && m_buffer.length() >= 3) {
qCDebug(dcOwlet()) << "UART <--" << m_buffer.toHex() << qUtf8Printable(m_buffer);
if (!m_buffer.isEmpty() && m_buffer.length() >= 2) {
qCDebug(dcOwlet()) << "UART <--" << m_buffer.toHex();
emit dataReceived(m_buffer);
}
m_buffer.clear();
@ -168,6 +173,7 @@ void OwletSerialTransport::onError(QSerialPort::SerialPortError serialPortError)
if (serialPortError != QSerialPort::NoError && serialPortError != QSerialPort::OpenError && m_serialPort->isOpen()) {
qCWarning(dcOwlet()) << "Serial port error occured" << serialPortError << m_serialPort->errorString();
emit error();
m_reconnectTimer->start();
if (m_serialPort->isOpen()) {
m_serialPort->close();