Skip to content

Commit

Permalink
Merge pull request #79 from kivancsikert/peripherals/add-environment
Browse files Browse the repository at this point in the history
Add environmental sensor support
  • Loading branch information
lptr authored Jan 31, 2024
2 parents d6e5bd3 + 23d6b5c commit 1dcc73b
Show file tree
Hide file tree
Showing 14 changed files with 308 additions and 55 deletions.
2 changes: 2 additions & 0 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ lib_deps =
https://github.com/tzapu/WiFiManager.git#v2.0.16-rc.2
arduino-libraries/NTPClient@^3.2.1
thijse/ArduinoLog@^1.1.1
# SHT3x support
robtillaart/SHT31@~0.5.0

[debug]
build_type = debug
Expand Down
18 changes: 14 additions & 4 deletions src/devices/Device.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,6 @@ class Device : ConsoleProvider {

deviceDefinition.registerPeripheralFactories(peripheralManager);

// deviceTelemetryCollector.registerProvider("peripherals", peripheralManager);

mqttDeviceRoot->registerCommand(echoCommand);
mqttDeviceRoot->registerCommand(pingCommand);
// TODO Add reset-wifi command
Expand All @@ -241,7 +239,19 @@ class Device : ConsoleProvider {
// We want RTC to be in sync before we start setting up peripherals
kernel.getRtcInSyncState().awaitSet();

peripheralManager.createPeripherals();
auto builtInPeripheralsCofig = deviceDefinition.getBuiltInPeripherals();
Log.traceln("Loading configuration for %d built-in peripherals",
builtInPeripheralsCofig.size());
for (auto& perpheralConfig : builtInPeripheralsCofig) {
peripheralManager.createPeripheral(perpheralConfig);
}

auto& peripheralsConfig = deviceConfig.peripherals.get();
Log.infoln("Loading configuration for %d peripherals",
peripheralsConfig.size());
for (auto& perpheralConfig : peripheralsConfig) {
peripheralManager.createPeripheral(perpheralConfig.get());
}

kernel.getKernelReadyState().awaitSet();

Expand Down Expand Up @@ -277,7 +287,7 @@ class Device : ConsoleProvider {
TDeviceConfiguration& deviceConfig = deviceDefinition.config;
Kernel<TDeviceConfiguration> kernel { deviceConfig, deviceDefinition.statusLed };
shared_ptr<MqttDriver::MqttRoot> mqttDeviceRoot = kernel.mqtt.forRoot("devices/ugly-duckling/" + deviceConfig.instance.get());
PeripheralManager peripheralManager { mqttDeviceRoot, deviceConfig.peripherals };
PeripheralManager peripheralManager { mqttDeviceRoot };

TelemetryCollector deviceTelemetryCollector;
MqttTelemetryPublisher deviceTelemetryPublisher { mqttDeviceRoot, deviceTelemetryCollector };
Expand Down
20 changes: 20 additions & 0 deletions src/devices/DeviceDefinition.hpp
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
#pragma once

#include <list>

#include <devices/Peripheral.hpp>
#include <kernel/Kernel.hpp>
#include <kernel/PwmManager.hpp>
#include <kernel/drivers/BatteryDriver.hpp>
#include <kernel/drivers/LedDriver.hpp>

#include <peripherals/environment/EnvironmentSht3x.hpp>

#include <version.h>

using namespace farmhub::kernel;
using namespace farmhub::kernel::drivers;
using namespace farmhub::peripherals::environment;

namespace farmhub::devices {

Expand Down Expand Up @@ -48,6 +53,18 @@ class DeviceDefinition {
}

virtual void registerPeripheralFactories(PeripheralManager& peripheralManager) {
peripheralManager.registerFactory(sht3xFactory);
registerDeviceSpecificPeripheralFactories(peripheralManager);
}

virtual void registerDeviceSpecificPeripheralFactories(PeripheralManager& peripheralManager) {
}

/**
* @brief Returns zero or more JSON configurations for any built-in peripheral of the device.
*/
virtual std::list<String> getBuiltInPeripherals() {
return {};
}

public:
Expand All @@ -59,6 +76,9 @@ class DeviceDefinition {

public:
TDeviceConfiguration& config = configFile.config;

private:
EnvironmentSht3xFactory sht3xFactory;
};

template <typename TDeviceConfiguration>
Expand Down
91 changes: 51 additions & 40 deletions src/devices/Peripheral.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ using std::shared_ptr;
using std::unique_ptr;

using namespace farmhub::kernel;
using namespace farmhub::kernel::drivers;

namespace farmhub::devices {

Expand Down Expand Up @@ -79,35 +80,40 @@ class PeripheralCreationException
: public std::exception {
public:
PeripheralCreationException(const String& name, const String& reason)
: name(name)
, reason(reason) {
: message(String("PeripheralCreationException: Failed to create peripheral '" + name + "' because " + reason)) {
}

const char* what() const noexcept override {
return String("Failed to create peripheral '" + name + "' because " + reason).c_str();
return message.c_str();
}

const String name;
const String reason;
const String message;
};

class PeripheralFactoryBase {
public:
PeripheralFactoryBase(const String& type)
: type(type) {
PeripheralFactoryBase(const String& factoryType, const String& peripheralType)
: factoryType(factoryType)
, peripheralType(peripheralType) {
}

virtual unique_ptr<PeripheralBase> createPeripheral(const String& name, const String& jsonConfig, shared_ptr<MqttDriver::MqttRoot> mqttRoot) = 0;

const String type;
const String factoryType;
const String peripheralType;
};

template <typename TDeviceConfig, typename TConfig, typename... TDeviceConfigArgs>
class PeripheralFactory : public PeripheralFactoryBase {
public:
// By default use the factory type as the peripheral type
// TODO Use TDeviceConfigArgs&& instead
PeripheralFactory(const String& type, TDeviceConfigArgs... deviceConfigArgs)
: PeripheralFactoryBase(type)
: PeripheralFactory(type, type, std::forward<TDeviceConfigArgs>(deviceConfigArgs)...) {
}

PeripheralFactory(const String& factoryType, const String& peripheralType, TDeviceConfigArgs... deviceConfigArgs)
: PeripheralFactoryBase(factoryType, peripheralType)
, deviceConfigArgs(std::forward<TDeviceConfigArgs>(deviceConfigArgs)...) {
}

Expand Down Expand Up @@ -145,34 +151,39 @@ class PeripheralManager
: public TelemetryPublisher {
public:
PeripheralManager(
const shared_ptr<MqttDriver::MqttRoot> mqttDeviceRoot,
ArrayProperty<JsonAsString>& peripheralsConfig)
: mqttDeviceRoot(mqttDeviceRoot)
, peripheralsConfig(peripheralsConfig) {
const shared_ptr<MqttDriver::MqttRoot> mqttDeviceRoot)
: mqttDeviceRoot(mqttDeviceRoot) {
}

void registerFactory(PeripheralFactoryBase& factory) {
Log.traceln("Registering peripheral factory: %s",
factory.type.c_str());
factories.insert(std::make_pair(factory.type, std::reference_wrapper<PeripheralFactoryBase>(factory)));
}

void createPeripherals() {
Log.infoln("Loading configuration for %d peripherals",
peripheralsConfig.get().size());

for (auto& perpheralConfigJsonAsString : peripheralsConfig.get()) {
PeripheralDeviceConfiguration deviceConfig;
deviceConfig.loadFromString(perpheralConfigJsonAsString.get());
const String& name = deviceConfig.name.get();
const String& type = deviceConfig.type.get();
try {
unique_ptr<PeripheralBase> peripheral = createPeripheral(name, type, deviceConfig.params.get().get());
peripherals.push_back(move(peripheral));
} catch (const PeripheralCreationException& e) {
Log.errorln("Failed to create peripheral: %s of type %s because %s",
name.c_str(), type.c_str(), e.reason.c_str());
}
factory.factoryType.c_str());
factories.insert(std::make_pair(factory.factoryType, std::reference_wrapper<PeripheralFactoryBase>(factory)));
}

void createPeripheral(const String& peripheralConfig) {
Log.info("Creating peripheral with config: %s",
peripheralConfig.c_str());
PeripheralDeviceConfiguration deviceConfig;
try {
deviceConfig.loadFromString(peripheralConfig);
} catch (const std::exception& e) {
Log.errorln("Failed to parse peripheral config because %s:\n%s",
e.what(), peripheralConfig.c_str());
return;
}

const String& name = deviceConfig.name.get();
const String& factory = deviceConfig.type.get();
try {
unique_ptr<PeripheralBase> peripheral = createPeripheral(name, factory, deviceConfig.params.get().get());
peripherals.push_back(move(peripheral));
} catch (const std::exception& e) {
Log.errorln("Failed to create peripheral '%s' with factory '%s' because %s",
name.c_str(), factory.c_str(), e.what());
} catch (...) {
Log.errorln("Failed to create peripheral '%s' with factory '%s' because of an unknown exception",
name.c_str(), factory.c_str());
}
}

Expand All @@ -190,20 +201,20 @@ class PeripheralManager
Property<JsonAsString> params { this, "params" };
};

unique_ptr<PeripheralBase> createPeripheral(const String& name, const String& type, const String& configJson) {
Log.traceln("Creating peripheral: %s of type %s",
name.c_str(), type.c_str());
auto it = factories.find(type);
unique_ptr<PeripheralBase> createPeripheral(const String& name, const String& factoryType, const String& configJson) {
Log.traceln("Creating peripheral '%s' with factory '%s'",
name.c_str(), factoryType.c_str());
auto it = factories.find(factoryType);
if (it == factories.end()) {
throw PeripheralCreationException(name, "No factory found for peripheral type '" + type + "'");
throw PeripheralCreationException(name, "Factory not found: '" + factoryType + "'");
}
shared_ptr<MqttDriver::MqttRoot> mqttRoot = mqttDeviceRoot->forSuffix("peripherals/" + type + "/" + name);
const String& peripheralType = it->second.get().peripheralType;
shared_ptr<MqttDriver::MqttRoot> mqttRoot = mqttDeviceRoot->forSuffix("peripherals/" + peripheralType + "/" + name);
PeripheralFactoryBase& factory = it->second.get();
return factory.createPeripheral(name, configJson, mqttRoot);
}

const shared_ptr<MqttDriver::MqttRoot> mqttDeviceRoot;
ArrayProperty<JsonAsString>& peripheralsConfig;

// TODO Use an unordered_map?
std::map<String, std::reference_wrapper<PeripheralFactoryBase>> factories;
Expand Down
19 changes: 18 additions & 1 deletion src/devices/UglyDucklingMk4.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

#include <Arduino.h>

#include <kernel/FileSystem.hpp>
#include <kernel/Kernel.hpp>
#include <kernel/Service.hpp>
Expand Down Expand Up @@ -36,12 +38,27 @@ class UglyDucklingMk4 : public DeviceDefinition<Mk4Config> {
GPIO_NUM_26) {
}

void registerPeripheralFactories(PeripheralManager& peripheralManager) override {
void registerDeviceSpecificPeripheralFactories(PeripheralManager& peripheralManager) override {
peripheralManager.registerFactory(valveFactory);
peripheralManager.registerFactory(flowMeterFactory);
peripheralManager.registerFactory(flowControlFactory);
}

std::list<String> getBuiltInPeripherals() override {
// Device address is 0x44 = 68
return {
R"({
"type": "environment:sht3x",
"name": "environment",
"params": {
"address": "0x44",
"sda": 8,
"scl": 9
}
})"
};
}

Drv8801Driver motorDriver {
pwm,
GPIO_NUM_10, // Enable
Expand Down
2 changes: 1 addition & 1 deletion src/devices/UglyDucklingMk5.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class UglyDucklingMk5 : public BatteryPoweredDeviceDefinition<Mk5Config> {
GPIO_NUM_1, 2.4848) {
}

void registerPeripheralFactories(PeripheralManager& peripheralManager) override {
void registerDeviceSpecificPeripheralFactories(PeripheralManager& peripheralManager) override {
peripheralManager.registerFactory(valveFactory);
peripheralManager.registerFactory(flowMeterFactory);
peripheralManager.registerFactory(flowControlFactory);
Expand Down
2 changes: 1 addition & 1 deletion src/devices/UglyDucklingMk6.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class UglyDucklingMk6 : public BatteryPoweredDeviceDefinition<Mk6Config> {
GPIO_NUM_1, 1.2424) {
}

void registerPeripheralFactories(PeripheralManager& peripheralManager) override {
void registerDeviceSpecificPeripheralFactories(PeripheralManager& peripheralManager) override {
peripheralManager.registerFactory(valveFactory);
peripheralManager.registerFactory(flowMeterFactory);
peripheralManager.registerFactory(flowControlFactory);
Expand Down
25 changes: 21 additions & 4 deletions src/kernel/Configuration.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,20 @@ using std::reference_wrapper;

namespace farmhub::kernel {

class ConfigurationException
: public std::exception {
public:
ConfigurationException(const String& message)
: message("ConfigurationException: " + message) {
}

const char* what() const noexcept override {
return message.c_str();
}

const String message;
};

class JsonAsString {
public:
JsonAsString() {
Expand Down Expand Up @@ -63,8 +77,11 @@ class ConfigurationEntry {
void loadFromString(const String& json) {
DynamicJsonDocument jsonDocument(docSizeFor(json));
DeserializationError error = deserializeJson(jsonDocument, json);
if (error == DeserializationError::EmptyInput) {
return;
}
if (error) {
throw "Cannot parse JSON configuration: " + String(error.c_str());
throw ConfigurationException("Cannot parse JSON configuration: " + String(error.c_str()) + json);
}
load(jsonDocument.as<JsonObject>());
}
Expand Down Expand Up @@ -273,21 +290,21 @@ class ConfigurationFile {
} else {
File file = fs.open(path, FILE_READ);
if (!file) {
throw "Cannot open config file " + path;
throw ConfigurationException("Cannot open config file " + path);
}

DynamicJsonDocument json(docSizeFor(file));
DeserializationError error = deserializeJson(json, file);
file.close();
if (error) {
throw "Cannot open config file " + path + " (" + String(error.c_str()) + ")";
throw ConfigurationException("Cannot open config file " + path + " (" + String(error.c_str()) + ")");
}
update(json.as<JsonObject>());
}
onUpdate([&fs, path](const JsonObject& json) {
File file = fs.open(path, FILE_WRITE);
if (!file) {
throw "Cannot open config file " + path;
throw ConfigurationException("Cannot open config file " + path);
}

serializeJson(json, file);
Expand Down
Loading

0 comments on commit 1dcc73b

Please sign in to comment.