From 40041b429b28de1acdc4919243c641537455be9e Mon Sep 17 00:00:00 2001 From: Bas Schouten Date: Mon, 30 Jan 2023 04:38:57 +0100 Subject: [PATCH] Initial commit. --- .gitignore | 2 + .gitmodules | 3 + CMakeLists.txt | 48 ++++++ CMakePresets.json | 61 +++++++ MQTTThermostat/CMakeLists.txt | 26 +++ MQTTThermostat/HeatingController.h | 25 +++ MQTTThermostat/MQTTThermostat.cpp | 246 +++++++++++++++++++++++++++ MQTTThermostat/MQTTThermostat.h | 76 +++++++++ MQTTThermostat/TemperatureSupplier.h | 27 +++ MQTTThermostat/maindaemon.cpp | 245 ++++++++++++++++++++++++++ MQTTThermostat/maintest.cpp | 23 +++ mqttthermostat.conf | 5 + mqttthermostat.service | 16 ++ paho.mqtt.c | 1 + 14 files changed, 804 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 CMakeLists.txt create mode 100644 CMakePresets.json create mode 100644 MQTTThermostat/CMakeLists.txt create mode 100644 MQTTThermostat/HeatingController.h create mode 100644 MQTTThermostat/MQTTThermostat.cpp create mode 100644 MQTTThermostat/MQTTThermostat.h create mode 100644 MQTTThermostat/TemperatureSupplier.h create mode 100644 MQTTThermostat/maindaemon.cpp create mode 100644 MQTTThermostat/maintest.cpp create mode 100644 mqttthermostat.conf create mode 100644 mqttthermostat.service create mode 160000 paho.mqtt.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dd2f824 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.vs/* +out/* \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e0e40ec --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "paho.mqtt.c"] + path = paho.mqtt.c + url = https://github.com/eclipse/paho.mqtt.c diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..748e67f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,48 @@ +# CMakeList.txt : Top-level CMake project file, do global configuration +# and include sub-projects here. +# +cmake_minimum_required (VERSION 3.8) + +project ("MQTTThermostat") + +# Include sub-projects. +add_subdirectory ("paho.mqtt.c") +add_subdirectory ("MQTTThermostat") + +# Directory with configuration files +set (DAEMON_CONF_DIR "/etc") + +# Directory with systemd unit files +set (SYSTEMD_UNIT_DIR "/usr/lib/systemd/system/") + +# Macro for installing configuration files +function(install_conf src dest) + if(NOT IS_ABSOLUTE "${src}") + set(src "${CMAKE_CURRENT_SOURCE_DIR}/${src}") + endif() + get_filename_component(src_name "${src}" NAME) + if (NOT IS_ABSOLUTE "${dest}") + set(dest "${CMAKE_INSTALL_PREFIX}/${dest}") + endif() + install(CODE " + if(NOT EXISTS \"\$ENV{DESTDIR}${dest}/${src_name}\") + #file(INSTALL \"${src}\" DESTINATION \"${dest}\") + message(STATUS \"Installing: \$ENV{DESTDIR}${dest}/${src_name}\") + execute_process(COMMAND \${CMAKE_COMMAND} -E copy \"${src}\" + \"\$ENV{DESTDIR}${dest}/${src_name}\" + RESULT_VARIABLE copy_result + ERROR_VARIABLE error_output) + if(copy_result) + message(FATAL_ERROR \${error_output}) + endif() + else() + message(STATUS \"Skipping : \$ENV{DESTDIR}${dest}/${src_name}\") + endif() + ") +endfunction(install_conf) + +# Install configuration file +install_conf (./mqttthermostat.conf ${DAEMON_CONF_DIR}) + +# Install systemd unit files +install_conf (./mqttthermostat.service ${SYSTEMD_UNIT_DIR}) \ No newline at end of file diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..d3c8220 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,61 @@ +{ + "version": 3, + "configurePresets": [ + { + "name": "windows-base", + "hidden": true, + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "installDir": "${sourceDir}/out/install/${presetName}", + "cacheVariables": { + "CMAKE_C_COMPILER": "cl.exe", + "CMAKE_CXX_COMPILER": "cl.exe" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + }, + { + "name": "x64-debug", + "displayName": "x64 Debug", + "inherits": "windows-base", + "architecture": { + "value": "x64", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "x64-release", + "displayName": "x64 Release", + "inherits": "x64-debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "x86-debug", + "displayName": "x86 Debug", + "inherits": "windows-base", + "architecture": { + "value": "x86", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "x86-release", + "displayName": "x86 Release", + "inherits": "x86-debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + } + ] +} diff --git a/MQTTThermostat/CMakeLists.txt b/MQTTThermostat/CMakeLists.txt new file mode 100644 index 0000000..f62271c --- /dev/null +++ b/MQTTThermostat/CMakeLists.txt @@ -0,0 +1,26 @@ +# CMakeList.txt : CMake project for MQTTThermostat, include source and define +# project specific logic here. +# + + +# Add source to this project's executable. +if (WIN32) +add_executable (mqttthermostat "MQTTThermostat.cpp" "MQTTThermostat.h" "TemperatureSupplier.h" "HeatingController.h" "maintest.cpp") +target_link_libraries (mqttthermostat PRIVATE paho-mqtt3a-static paho-mqtt3c-static) +endif (WIN32) +if (UNIX) +add_executable (mqttthermostat "MQTTThermostat.cpp" "MQTTThermostat.h" "TemperatureSupplier.h" "HeatingController.h" "maindaemon.cpp") +target_link_libraries (mqttthermostat PRIVATE paho-mqtt3a paho-mqtt3c) +endif (UNIX) + +target_include_directories (mqttthermostat PRIVATE ../paho.mqtt.c/src) + +install(TARGETS mqttthermostat + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib/myproject) + +if (CMAKE_VERSION VERSION_GREATER 3.12) + set_property(TARGET mqttthermostat PROPERTY CXX_STANDARD 20) +endif() + diff --git a/MQTTThermostat/HeatingController.h b/MQTTThermostat/HeatingController.h new file mode 100644 index 0000000..bb48085 --- /dev/null +++ b/MQTTThermostat/HeatingController.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +class HeatingController +{ +public: + virtual ~HeatingController() {} + + virtual void setHeatingActive(bool aActive) = 0; +}; + +class DummyHeatingController : public HeatingController +{ +public: + virtual void setHeatingActive(bool aActive) { + if (aActive) { + printf("Heating activated!\n"); + } + else { + printf("Heating deactivated!\n"); + } + } +}; \ No newline at end of file diff --git a/MQTTThermostat/MQTTThermostat.cpp b/MQTTThermostat/MQTTThermostat.cpp new file mode 100644 index 0000000..02f551e --- /dev/null +++ b/MQTTThermostat/MQTTThermostat.cpp @@ -0,0 +1,246 @@ +// MQTTThermostat.cpp : Defines the entry point for the application. +// + +#include +#include +#include +#include + +#include "MQTTThermostat.h" +#include "MQTTClient.h" + +using namespace std; + +#define ADDRESS "tcp://10.0.1.213:1883" + +#define QOS 1 +#define TIMEOUT 10000L + +void delivered(void* context, MQTTClient_deliveryToken dt) +{ + MQTTThermostat* thermostat = static_cast(context); + thermostat->DeliverToken(dt); +} + +int msgarrvd(void* context, char* topicName, int topicLen, MQTTClient_message* message) +{ + MQTTThermostat* thermostat = static_cast(context); + printf("Message arrived\n"); + printf(" topic: %s\n", topicName); + printf(" message: %.*s\n", message->payloadlen, (char*)message->payload); + + float temperatureSet = stof(string((char*)message->payload)); + MQTTClient_freeMessage(&message); + MQTTClient_free(topicName); + + thermostat->SetSetpoint(temperatureSet); + + return 1; +} + +void connlost(void* context, char* cause) +{ + MQTTThermostat* thermostat = static_cast(context); + + printf("\nConnection lost\n"); + printf(" cause: %s\n", cause); + + thermostat->ShouldReconnect(); +} + +void MQTTThermostat::Start() +{ + mShuttingDown = false; + + MQTTClient_message pubmsg = MQTTClient_message_initializer; + MQTTClient_deliveryToken token; + int rc; + MQTTClient_create(&mClient, ADDRESS, mClientID.c_str(), + MQTTCLIENT_PERSISTENCE_NONE, NULL); + + if ((rc = MQTTClient_setCallbacks(mClient, this, connlost, msgarrvd, delivered)) != MQTTCLIENT_SUCCESS) + { + printf("Failed to set callbacks, return code %d\n", rc); + return; + } + + while (true) { + { + unique_lock lock(mSetpointMutex); + if (mSetpointUpdatePending) { + processSetpointUpdate(); + } + if (mShuttingDown) { + break; + } + + mSetpointUpdateCV.wait_for(lock, mMeasurementInterval); + + if (mShuttingDown) { + break; + } + + if (!mTemperatureSupplier) { + continue; + } + + if (mShouldReconnect) { + if (!connectToServer()) { + continue; + } + + mShouldReconnect = false; + } + + if (mSetpointUpdatePending) { + processSetpointUpdate(); + } + } + + float newTemp = mTemperatureSupplier->getTemperature(); + uint32_t newHumidity = mTemperatureSupplier->getHumidity(); + + if (newHumidity != mLastHumidity) { + mLastHumidity = newHumidity; + char cbuf[256]; + sprintf(cbuf, "%d", mLastHumidity); + pubmsg.payload = (void*)cbuf; + pubmsg.payloadlen = strlen(cbuf); + pubmsg.qos = QOS; + pubmsg.retained = 0; + string topic = mTopic; + topic.append("humidity/raw"); + MQTTClient_publishMessage(mClient, topic.c_str(), &pubmsg, &token); + WaitForDelivery(token); + } + + if (newTemp < mLastTemperature + 0.1 && newTemp > mLastTemperature - 0.1) { + continue; + } + + if (mLastTemperature > mCurrentSetpoint && newTemp < mCurrentSetpoint) { + mHeatingController->setHeatingActive(true); + } + else if (mLastTemperature < mCurrentSetpoint && newTemp > mCurrentSetpoint) { + mHeatingController->setHeatingActive(false); + } + + mLastTemperature = newTemp; + char cbuf[256]; + sprintf(cbuf, "%.1f", mLastTemperature); + pubmsg.payload = (void*)cbuf; + pubmsg.payloadlen = strlen(cbuf); + pubmsg.qos = QOS; + pubmsg.retained = 0; + string topic = mTopic; + topic.append("temperature/raw"); + MQTTClient_publishMessage(mClient, topic.c_str(), &pubmsg, &token); + WaitForDelivery(token); + } + + MQTTClient_disconnect(mClient, 10000); + MQTTClient_destroy(&mClient); +} + +void MQTTThermostat::DeliverToken(MQTTClient_deliveryToken& aToken) +{ + unique_lock lock(mDeliveryMutex); + mDeliveredTokens.push(aToken); + mDeliveryCV.notify_one(); +} + +void MQTTThermostat::WaitForDelivery(MQTTClient_deliveryToken& aToken) +{ + while (true) { + unique_lock lock(mDeliveryMutex); + while (mDeliveredTokens.size()) { + if (mDeliveredTokens.top() == aToken) { + mDeliveredTokens = stack(); + return; + } + mDeliveredTokens.pop(); + } + if (mDeliveryCV.wait_for(lock, 1s) == cv_status::timeout) { + printf("Wait for delivery timed out.\n"); + mShouldReconnect = true; + return; + } + } +} + +void MQTTThermostat::SetSetpoint(float aTemperature) +{ + unique_lock lock(mSetpointMutex); + mSetpointUpdatePending = true; + mCurrentSetpoint = aTemperature; + mSetpointUpdateCV.notify_one(); +} + +void MQTTThermostat::Shutdown() +{ + unique_lock lock(mSetpointMutex); + mShuttingDown = true; + mSetpointUpdateCV.notify_one(); +} + +bool MQTTThermostat::connectToServer() +{ + int rc = 0; + MQTTClient_disconnect(mClient, 10000); + + MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer; + conn_opts.keepAliveInterval = 20; + conn_opts.cleansession = 1; + + if ((rc = MQTTClient_connect(mClient, &conn_opts)) != MQTTCLIENT_SUCCESS) + { + printf("Failed to connect, return code %d\n", rc); + return false; + } + + string topic = mTopic; + topic.append("setpoint/set"); + if ((rc = MQTTClient_subscribe(mClient, topic.c_str(), QOS)) != MQTTCLIENT_SUCCESS) + { + printf("Failed to subscribe, return code %d\n", rc); + rc = EXIT_FAILURE; + } + + return true; +} + +void MQTTThermostat::processSetpointUpdate() +{ + if (mCurrentSetpoint > mLastTemperature) { + mHeatingController->setHeatingActive(true); + } + else { + mHeatingController->setHeatingActive(false); + } + if (announceSetpoint()) { + mSetpointUpdatePending = false; + } +} + +bool MQTTThermostat::announceSetpoint() +{ + MQTTClient_message pubmsg = MQTTClient_message_initializer; + MQTTClient_deliveryToken token; + + int rc; + + char cbuf[256]; + sprintf(cbuf, "%.1f", mCurrentSetpoint); + pubmsg.payload = (void*)cbuf; + pubmsg.payloadlen = strlen(cbuf); + pubmsg.qos = QOS; + pubmsg.retained = 0; + string topic = mTopic; + topic.append("setpoint/raw"); + if (MQTTClient_publishMessage(mClient, topic.c_str(), &pubmsg, &token) != MQTTCLIENT_SUCCESS) { + return false; + } + + WaitForDelivery(token); + return true; +} diff --git a/MQTTThermostat/MQTTThermostat.h b/MQTTThermostat/MQTTThermostat.h new file mode 100644 index 0000000..7d0d791 --- /dev/null +++ b/MQTTThermostat/MQTTThermostat.h @@ -0,0 +1,76 @@ +// MQTTThermostat.h : Include file for standard system include files, +// or project specific include files. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "MQTTClient.h" +#include "TemperatureSupplier.h" +#include "HeatingController.h" + +class MQTTThermostat +{ +public: + MQTTThermostat(const MQTTThermostat&) = delete; + MQTTThermostat& operator=(const MQTTThermostat&) = delete; + MQTTThermostat(MQTTThermostat&&) = delete; + MQTTThermostat& operator=(MQTTThermostat&) = delete; + + static auto& instance() + { + static MQTTThermostat instance; + return instance; + } + + void Start(); + + void DeliverToken(MQTTClient_deliveryToken& aToken); + + void WaitForDelivery(MQTTClient_deliveryToken& aToken); + + void SetClientID(const std::string& aID) { mClientID = aID; } + void SetTemperatureSupplier(std::unique_ptr&& aSupplier) { mTemperatureSupplier = std::move(aSupplier); } + void SetHeatingController(std::unique_ptr&& aController) { mHeatingController = std::move(aController); } + void SetMeasurementInterval(std::chrono::milliseconds aMS) { mMeasurementInterval = aMS; } + void SetTopic(const std::string& aTopic) { mTopic = aTopic; } + + void SetSetpoint(float aTemperature); + + void Shutdown(); + + void ShouldReconnect() { mShouldReconnect = true; } + +private: + MQTTThermostat() = default; + + bool connectToServer(); + + void processSetpointUpdate(); + + bool announceSetpoint(); + + std::unique_ptr mTemperatureSupplier; + std::unique_ptr mHeatingController; + float mLastTemperature = 0.0; + uint32_t mLastHumidity = 0; + float mCurrentSetpoint = 19.0; + bool mShouldReconnect = true; + bool mSetpointUpdatePending = true; + bool mShuttingDown = false; + MQTTClient mClient; + std::string mClientID; + std::stack mDeliveredTokens; + std::mutex mDeliveryMutex; + std::mutex mSetpointMutex; + std::condition_variable mDeliveryCV; + std::condition_variable mSetpointUpdateCV; + std::chrono::milliseconds mMeasurementInterval; + std::string mTopic; +}; \ No newline at end of file diff --git a/MQTTThermostat/TemperatureSupplier.h b/MQTTThermostat/TemperatureSupplier.h new file mode 100644 index 0000000..5a563ed --- /dev/null +++ b/MQTTThermostat/TemperatureSupplier.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +class TemperatureSupplier +{ +public: + virtual ~TemperatureSupplier() {} + + virtual float getTemperature() = 0; + virtual uint32_t getHumidity() = 0; +}; + +class DummyTemperatureSupplier : public TemperatureSupplier +{ +public: + virtual float getTemperature() { + mCurrentTemperature += 0.01; + return mCurrentTemperature; + } + virtual uint32_t getHumidity() { + return mCurrentHumidity++; + } +private: + float mCurrentTemperature = 0; + uint32_t mCurrentHumidity = 0; +}; \ No newline at end of file diff --git a/MQTTThermostat/maindaemon.cpp b/MQTTThermostat/maindaemon.cpp new file mode 100644 index 0000000..25706c7 --- /dev/null +++ b/MQTTThermostat/maindaemon.cpp @@ -0,0 +1,245 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "MQTTThermostat.h" + +using namespace std; + +// Linux only Daemon code. + +#define LOG(msg) logStream << msg << "\n"; logStream.flush(); + +static int pid_fd; +static ofstream logStream; +static string pidFile = "~/mqttthermostat.lock"; +static string configFile = "~/mqttthermostat.conf"; + +static bool shuttingDown = false; + +void handle_signal(int sig) +{ + if (sig == SIGINT) { + LOG("Stopping daemon ..."); + shuttingDown = true; + MQTTThermostat::instance().Shutdown(); + + /* Unlock and close lockfile */ + if (pid_fd != -1) { + lockf(pid_fd, F_ULOCK, 0); + close(pid_fd); + } + /* Try to delete lockfile */ + unlink(pidFile.c_str()); + + /* Reset signal handling to default behavior */ + signal(SIGINT, SIG_DFL); + } + else if (sig == SIGHUP) { + LOG("Reloading daemon config file ..."); + MQTTThermostat::instance().Shutdown(); + } + else if (sig == SIGCHLD) { + LOG("Received SIGCHLD signal"); + } +} + +void read_config_file() +{ + ifstream config(configFile, ios::in); + if (!config) { + std::cerr << "Failure to open config file!\n"; + exit(EXIT_FAILURE); + } + + string line, topic, clientid; + unique_ptr tempSupplier; + unique_ptr heatingController; + uint32_t measurementInterval; + + while (getline(config, line)) { + istringstream config_line(line); + string key; + if (std::getline(config_line, key, '=')) + { + std::string value; + if (getline(config_line, value)) { + if (key == "temperature supplier") { + if (value.rfind("dummy", 0) == 0) { + tempSupplier = make_unique(); + } + else { + LOG("Unknown temperature supplier specified."); + } + } + else if (key == "heating controller") { + if (value.rfind("dummy", 0) == 0) { + heatingController = make_unique(); + } + else { + LOG("Unknown heating controller specified."); + } + } + else if (key == "measurement interval") { + measurementInterval = stoi(value); + } + else if (key == "topic") { + topic = value; + } + else if (key == "clientid") { + clientid = value; + } + } + } + } + + if (!tempSupplier || !heatingController) { + LOG("No valid temperature supplier or heating controller found. Exiting."); + exit(EXIT_FAILURE); + } + + logStream << "CONFIG: topic=" << topic << ", client ID=" << clientid << ", measurement interval=" << measurementInterval << "ms.\n"; + logStream.flush(); + + + MQTTThermostat::instance().SetTemperatureSupplier(std::move(tempSupplier)); + MQTTThermostat::instance().SetHeatingController(std::move(heatingController)); + MQTTThermostat::instance().SetMeasurementInterval(chrono::milliseconds{ measurementInterval }); + MQTTThermostat::instance().SetTopic(topic); + MQTTThermostat::instance().SetClientID(clientid); +} + +void daemonize() +{ + pid_t pid, sid; + + pid = fork(); + if (pid < 0) { + exit(EXIT_FAILURE); + } + + if (pid > 0) { + exit(EXIT_SUCCESS); + } + + sid = setsid(); + if (sid < 0) { + LOG("Failed to get unique SID."); + exit(EXIT_FAILURE); + } + + /* Ignore signal sent from child to parent process */ + signal(SIGCHLD, SIG_IGN); + + /* Fork off for the second time*/ + pid = fork(); + + /* An error occurred */ + if (pid < 0) { + exit(EXIT_FAILURE); + } + + /* Success: Let the parent terminate */ + if (pid > 0) { + exit(EXIT_SUCCESS); + } + + umask(0); + + if ((chdir("/")) < 0) { + LOG("Failed to changedir to root."); + exit(EXIT_FAILURE); + } + + long fd; + /* Close all open file descriptors */ + for (fd = sysconf(_SC_OPEN_MAX); fd > 0; fd--) { + close(fd); + } + + /* Try to write PID of daemon to lockfile */ + char str[256]; + uint pid_fd = open(pidFile.c_str(), O_RDWR | O_CREAT, 0640); + if (pid_fd < 0) { + /* Can't open lockfile */ + exit(EXIT_FAILURE); + } + if (lockf(pid_fd, F_TLOCK, 0) < 0) { + /* Can't lock file */ + exit(EXIT_FAILURE); + } + /* Get current PID */ + sprintf(str, "%d\n", getpid()); + /* Write PID to lockfile */ + write(pid_fd, str, strlen(str)); +} + +int main(int argc, char* argv[]) +{ + static struct option long_options[] = { + {"conf_file", required_argument, 0, 'c'}, + {"test_conf", required_argument, 0, 't'}, + {"log_file", required_argument, 0, 'l'}, + {"daemon", no_argument, 0, 'd'}, + {"pid_file", required_argument, 0, 'p'}, + {NULL, 0, 0, 0} + }; + int value, option_index = 0, ret; + string logFile; + int start_daemonized = 0; + + /* Try to process all command line arguments */ + while ((value = getopt_long(argc, argv, "c:l:p:dh", long_options, &option_index)) != -1) { + switch (value) { + case 'c': + configFile = string(optarg); + break; + case 'l': + logFile = string(optarg); + break; + case 'p': + pidFile = string(optarg); + break; + case 'd': + start_daemonized = 1; + break; + default: + break; + } + } + + if (start_daemonized) { + daemonize(); + } + + signal(SIGINT, handle_signal); + signal(SIGHUP, handle_signal); + + if (!logFile.empty()) { + logStream.open(logFile, ios::out | ios::app | ios::ate); + } + + if (!logStream) { + std::cerr << "Failure to open log file!\n"; + exit(EXIT_FAILURE); + } + + LOG("Starting MQTTThermostat daemon..."); + + while (!shuttingDown) { + read_config_file(); + MQTTThermostat::instance().Start(); + } + + return 0; +} diff --git a/MQTTThermostat/maintest.cpp b/MQTTThermostat/maintest.cpp new file mode 100644 index 0000000..af528bc --- /dev/null +++ b/MQTTThermostat/maintest.cpp @@ -0,0 +1,23 @@ +#include +#include +#include +#include +#include + +#include "MQTTThermostat.h" + +using namespace std; + +// Linux only Daemon code. + +int main() +{ + MQTTThermostat::instance().SetTemperatureSupplier(make_unique()); + MQTTThermostat::instance().SetHeatingController(make_unique()); + MQTTThermostat::instance().SetMeasurementInterval(2000ms); + MQTTThermostat::instance().SetTopic("bedroom-staging/thermostat/"); + MQTTThermostat::instance().SetClientID("bedroom-staging"); + MQTTThermostat::instance().Start(); + + return 0; +} diff --git a/mqttthermostat.conf b/mqttthermostat.conf new file mode 100644 index 0000000..1a4e000 --- /dev/null +++ b/mqttthermostat.conf @@ -0,0 +1,5 @@ +temperature supplier=dummy +heating controller=dummy +measurement interval=2000 +topic=staging/thermostat/ +clientid=staging \ No newline at end of file diff --git a/mqttthermostat.service b/mqttthermostat.service new file mode 100644 index 0000000..2d74c83 --- /dev/null +++ b/mqttthermostat.service @@ -0,0 +1,16 @@ +[Unit] +Description=MQTT Thermostat Service + +[Service] +Type=forking +PIDFile=/run/mqttthermostat.pid +ExecStart=/usr/bin/mqttthermostat \ + --conf_file /etc/mqttthermostat.conf \ + --log_file /var/log/mqttthermostat.log \ + --pid_file /run/mqttthermostat.pid \ + --daemon +User=daemoner +ExecReload=/bin/kill -HUP $MAINPID + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/paho.mqtt.c b/paho.mqtt.c new file mode 160000 index 0000000..f7799da --- /dev/null +++ b/paho.mqtt.c @@ -0,0 +1 @@ +Subproject commit f7799da95e347bbc930b201b52a1173ebbad45a7 -- 2.47.3