# Albireo farm pi experiment/water_temperature ###### tags: `Albireo` `raspberry pi` `c/c++` ## Wiring ### GPIO - LED - Peltier Element (H bridge circuit) - ### SPI - BME280 (temperature, pressure, humidity) - MCP3002 - channel0 LM35DZ (peltier element temperature) - channel1 LM35DZ (water temperature) ### I2C - TSL2561 (illuminance) ## Build ```shell= cmake -G Ninja -B build -D BUILD_SHARED_LIBS=ON -D CMAKE_BUILD_TYPE=Debug ninja -v -C build sudo ./build/main ``` ## 処理の流れ 塚ちゃんはここを実装してね - データ収集 一定時間ごと(コマンドラインから変更可能にする)に全てのセンサの値を読み取り、サーバへ送信する。 - LED制御 サーバから設定を読み取り、次の点灯/消灯時間までスレッドを止め、点灯/消灯の処理を行う。 一定時間(コマンドラインから変更可能にする)よりも長くスレッドが止まることがないようにすることにする。 - 温度調節 センサから温度を読み取り、サーバから温度の設定を読み取り、比較してペルチェ素子を作動させる。 制御は一定時間ごと(コマンドラインから変更可能にする)に試みる 温度は一時間ごとに最低/最高温度が設定されているので中間の値を利用する。 > 現在時刻 8:30 > 8時の温度設定 最高/最低 10/20 > 9時の温度設定 最高/最低 20/30 > 制御に用いる温度設定 最高/最低 15/25 ### 注意点 ハードウェアドライバはマルチスレッド非対応なので、mutexを利用して同時にドライバにアクセスすることがないようにする。 ハードウェアの設定は設定ファイル(TOMLがいいなぁ。ライブラリ追加しとくから)から変更可能にする。 ペルチェ素子はずっと電流を流していると熱暴走を起こすのでPWMを利用する。 ## 導入済みライブラリ ### Submodule - cpphttplib https://github.com/yhirose/cpp-httplib/ A C++ header-only HTTP/HTTPS server and client library - cxxopts https://github.com/jarro2783/cxxopts/ Lightweight C++ command line option parser - fmtlib https://github.com/fmtlib/fmt/ A modern formatting library - nlohmann json https://github.com/nlohmann/json/ JSON for Modern C++ - paho-mqtt-c https://github.com/eclipse/paho.mqtt.c An Eclipse Paho C client library for MQTT for Windows, Linux and MacOS. - paho-mqtt-cpp https://github.com/eclipse/paho.mqtt.cpp An Eclipse Paho C++ client library for MQTT for Windows, Linux and MacOS. - pigpio https://github.com/joan2937/pigpio/ pigpio is a C library for the Raspberry which allows control of the General Purpose Input Outputs (GPIO). - spdlog https://github.com/gabime/spdlog/ Fast C++ logging library. - toml++ https://github.com/marzer/tomlplusplus/ Header-only TOML config file parser and serializer for C++17 (and later!). > C言語でやるなら > MQTTはpaho-mqtt-c > HTTPはcurl > JSONはjson-c > TOMLは独自実装(パーサーだね!) > コマンドライン引数も独自実装 > ロギングはlog.c > ハードウェアもpigpioで書いてね(俺は書かないぞ) > スレッドはpthread ## サンプルコード集 #### Logger - criticul - error - warning - info - debug - trace ```cpp= #include <memory> #include <string> #include "spdlog/spdlog.h" #include "spdlog/sinks/stdout_color_sinks.h" #include "spdlog/sinks/daily_file_sink.h" int main() { std::string logger_name = "logger"; auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>(); console_sink->set_level(spdlog::level::info); console_sink->set_pattern("[%Y/%m/%d %H:%M:%S.%e] [%^%l%$] %v"); auto file_sink = std::make_shared<spdlog::sinks::daily_file_sink_mt>("output.log", 0, 0); file_sink->set_level(spdlog::level::trace); file_sink->set_pattern("[%Y/%m/%d %H:%M:%S.%e] [%n] [%l] %v"); spdlog::sinks_init_list sinks_list = {console_sink, file_sink}; auto logger = std::make_shared<spdlog::logger>(logger_name, sinks_list); logger->set_level(spdlog::level::trace); logger->info("Hello!"); } ``` #### Command Line Arguments Parser ```cpp= #include <iostream> #include <string> #include <vector> #include "cxxopts.hpp" int main(const int argc, char** argv) { cxxopts::Options options("sample", "This is a cxxopts sample program."); options.add_options() ("h,help", "show help message") ("b,bool", "bool parameter") ("i,int", "integer parameter", cxxopts::value<int>()) ("s,string", "string parameter", cxxopts::value<std::string>()) ("default", "using default value was false", cxxopts::value<bool>()->default_value("false")) ("implicit", "implicit parameter", cxxopts::value<int>()->implicit_value("12")) ("v,vector", "vector parameters", cxxopts::value<std::vector<int>>()); options.add_options("group") ("option", "group option"); auto result = options.parse(argc, argv); if (result.count("help") != 0) { std::cout << options.help() << std::endl; return 0; } if (result.count("int") != 0) { std::cout << result["int"].as<int>() << std::endl; } if (result.count("vector") != 0) { auto values = result["vector"].as<std::vector<int>>(); std::cout << '['; for(auto&& i : values) { std::cout << i << ','; } std::cout << ']' << std::endl; } if (result["option"].count() != 0) { if(result["option"].as<bool>()) { std::cout << "group enabled" << std::endl; } } } ``` #### JSON Perser ```cpp= #include <iostream> #include <string> #include "nlohmann/json.hpp" int main() { std::string str = R"( { "key": "value" } )"; auto result = nlohmann::json::parse(str); std::cout << "key : " << result["key"] << std::endl; } ``` #### TOML Perser ```toml= [table] key = "value" ``` ```cpp= #include <iostream> #include <string> #include "toml++/toml.h" int main() { std::string filepath = "test.toml"; auto result = toml::parse_file(filepath); auto value = result["table"]["key"].value<std::string>().value(); std::cout << value << std::endl; } ``` #### HTTP Request ```cpp= #include <iostream> #include <string> #include "httplib.h" int main() { std::string base_url = "http://localhost:8000"; httplib::Client cli(base_url); auto res = cli.Get("/endpoint"); std::cout << "status : " << res->status << '\n' << "body : " << res->body << std::endl; } ``` #### MQTT Publish ```cpp= #include <iostream> #include <string> #include "mqtt/client.h" int main() { mqtt::client cli("tcp://localhost:1883", ""); try { cli.connect(); auto msg = mqtt::make_message("topic", "payload"); cli.publish(msg); cli.disconnect(); } catch (const mqtt::exception& e) { std::cout << e.what() << std::endl; return 0; } } ``` #### Device Handler ```cpp= #include <iostream> #include "spdlog/spdlog.h" #include "driver.hpp" #include "device/bme280.hpp" #include "device/light.hpp" int main() { auto logger = spdlog::stdout_color_mt("logger"); Driver driver(logger); driver.initialize(); auto gpio = driver.createGpio(4, driver::Gpio::Mode::output); auto light = createLight(logger, gpio); light->initialize(); light->turnOn(); light->turnOff(); light->finalize(); auto pwm_plus = driver.createPwm(5, 255, 800); auto pwm_minus = driver.createPwm(6, 255, 800); auto peltier = createPeltier( logger, pwm_plus, pwm_minus, 30, 40); peltier->initialize(); auto spi = driver.createSpi(0, 100000, 0, 0, false); auto bme280 = createBme280(logger, spi, Bme280::Sampling::x16, Bme280::Sampling::x16, Bme280::Sampling::x16, Bme280::Mode::forced, Bme280::StandbyDuration::ms1000, Bme280::Filter::x16); bme280->initialize(); bme280->measure(); std::cout << "temperature : " << bme280->getTemperature() << '\n' << "pressure : " << bme280->getPressure() << '\n' << "humidity : " << bme280->getHumidity() << std::endl; bme280->finalize(); driver.finalize(); } ```