diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..2b05da9 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,56 @@ +# CMakeList.txt : CMake project for influxd-wind-svg, include source and define +# project specific logic here. +# +cmake_minimum_required(VERSION 3.12.0) + +# Enable Hot Reload for MSVC compilers if supported. +if (POLICY CMP0141) + cmake_policy(SET CMP0141 NEW) + set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$,$>,$<$:EditAndContinue>,$<$:ProgramDatabase>>") +endif() + +project ("goveebttemplogger" + VERSION 2.20230918-1 + DESCRIPTION "Govee H5074, H5075, H5100, H5174, H5177, H5179, H5181, H5182, and H5183 Bluetooth Low Energy Temperature and Humidity Logger" + HOMEPAGE_URL https://github.com/wcbonner/GoveeBTTempLogger +) + +configure_file(goveebttemplogger-version.h.in goveebttemplogger-version.h) + +# Add source to this project's executable. +add_executable (goveebttemplogger "goveebttemplogger.cpp" "uuid.c" "uuid.h") + +add_executable(gvh-organizelogs "gvh-organizelogs.cpp") + +if (CMAKE_VERSION VERSION_GREATER 3.12) + set_property(TARGET goveebttemplogger PROPERTY CXX_STANDARD 20) +endif() + +target_include_directories(goveebttemplogger PUBLIC + "${PROJECT_BINARY_DIR}" + ${EXTRA_INCLUDES} + ) + +# TODO: Add tests and install targets if needed. + +install(TARGETS goveebttemplogger + DESTINATION bin + RUNTIME DESTINATION "/usr/local/bin/" + LIBRARY DESTINATION "/usr/local/lib/" +) + +install(FILES "GoveeBTTempLogger/usr/local/lib/systemd/system/goveebttemplogger.service" + DESTINATION "/usr/local/lib/systemd/system" + COMPONENT "goveebttemplogger" +) + +set(CPACK_GENERATOR "DEB") +set(CPACK_PACKAGE_CONTACT "wcbonner@users.noreply.github.com") +include(InstallRequiredSystemLibraries) +set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.txt") +set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) +set(CPACK_DEBIAN_PACKAGE_RELEASE ${CMAKE_PROJECT_VERSION_PATCH}) +set(CPACK_DEBIAN_PACKAGE_DEPENDS curl,influxdb) +set(CPACK_DEBIAN_PACKAGE_SECTION custom) +set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_CURRENT_SOURCE_DIR}/GoveeBTTempLogger/DEBIAN/postinst" "${CMAKE_CURRENT_SOURCE_DIR}/GoveeBTTempLogger/DEBIAN/prerm" "${CMAKE_CURRENT_SOURCE_DIR}/GoveeBTTempLogger/DEBIAN/postrm") +include(CPack) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..f4bc98b --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,101 @@ +{ + "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" + } + }, + { + "name": "linux-debug", + "displayName": "Linux Debug", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "installDir": "${sourceDir}/out/install/${presetName}", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + }, + "vendor": { + "microsoft.com/VisualStudioRemoteSettings/CMake/1.0": { + "sourceDir": "$env{HOME}/.vs/$ms{projectDirName}" + } + } + }, + { + "name": "macos-debug", + "displayName": "macOS Debug", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "installDir": "${sourceDir}/out/install/${presetName}", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + }, + "vendor": { + "microsoft.com/VisualStudioRemoteSettings/CMake/1.0": { + "sourceDir": "$env{HOME}/.vs/$ms{projectDirName}" + } + } + } + ] +} diff --git a/GoveeBTTempLogger.vcxproj b/GoveeBTTempLogger.vcxproj index f10a5cb..620d73b 100644 --- a/GoveeBTTempLogger.vcxproj +++ b/GoveeBTTempLogger.vcxproj @@ -92,6 +92,8 @@ + + @@ -108,6 +110,7 @@ + @@ -218,4 +221,4 @@ - + \ No newline at end of file diff --git a/GoveeBTTempLogger/DEBIAN/control b/GoveeBTTempLogger/DEBIAN/control index 26008fa..9e506ef 100644 --- a/GoveeBTTempLogger/DEBIAN/control +++ b/GoveeBTTempLogger/DEBIAN/control @@ -1,5 +1,5 @@ Package: GoveeBTTempLogger -Version: 2.20230906-1 +Version: 2.20230918-1 Section: custom Priority: optional Architecture: armhf diff --git a/README.md b/README.md index bd8a91b..6faa39d 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,8 @@ For the longest time I've had fixed values set in my code for Scan Window and Sc The values are in increments of 0.625 msec. The First value was 11.25 msec, and the second value was (5000 msec). Doing a bunch of reading I came across recommendations to use 40 msec and 30 msec, so I've tried bt_ScanInterval(64) and bt_ScanWindow(48). When it's set this way, I seem to get advertisments, but I'm not able to connect and download. +## --only ff:ff:ff:ff:ff:ff Hack +I included a hack some time ago related to the bluetooth filtering to easily filter on devices that have already been logged. If a filter is specified with all the bits set, the program will submit a filter of known addresses to the stack when scanning is started. This disables new device discovery, but may improve performance in some situations. Connections on bluetooth devices are all based on handles and UUIDs. There are some defined UUIDs that every bluetooth device is required to support, and then there are custom UUIDs. This listing came from a GVH5177. ``` diff --git a/goveebttemplogger-version.h.in b/goveebttemplogger-version.h.in new file mode 100644 index 0000000..de5c872 --- /dev/null +++ b/goveebttemplogger-version.h.in @@ -0,0 +1,4 @@ +#pragma once +#define QUOTE(x) #x +#define STR(x) QUOTE(x) +#define GoveeBTTempLogger_VERSION STR(@CMAKE_PROJECT_VERSION@) diff --git a/goveebttemplogger.cpp b/goveebttemplogger.cpp index 820898f..1de5299 100644 --- a/goveebttemplogger.cpp +++ b/goveebttemplogger.cpp @@ -87,7 +87,7 @@ #include "uuid.h" ///////////////////////////////////////////////////////////////////////////// -static const std::string ProgramVersionString("GoveeBTTempLogger Version 2.20230906-1 Built on: " __DATE__ " at " __TIME__); +static const std::string ProgramVersionString("GoveeBTTempLogger Version 2.20230918-1 Built on: " __DATE__ " at " __TIME__); ///////////////////////////////////////////////////////////////////////////// std::string timeToISO8601(const time_t & TheTime, const bool LocalTime = false) { @@ -246,6 +246,7 @@ int hci_le_set_ext_scan_enable(int dd, uint8_t enable, uint8_t filter_dup, int t int ConsoleVerbosity(1); bool UseBluetooth(true); std::filesystem::path LogDirectory; // If this remains empty, log Files are not created. +std::filesystem::path CacheDirectory; // If this remains empty, cache Files are not used. Cache Files should greatly speed up startup of the program if logged data runs multiple years over many devices. std::filesystem::path SVGDirectory; // If this remains empty, SVG Files are not created. If it's specified, _day, _week, _month, and _year.svg files are created for each bluetooth address seen. int SVGBattery(0); // 0x01 = Draw Battery line on daily, 0x02 = Draw Battery line on weekly, 0x04 = Draw Battery line on monthly, 0x08 = Draw Battery line on yearly int SVGMinMax(0); // 0x01 = Draw Temperature and Humiditiy Minimum and Maximum line on daily, 0x02 = on weekly, 0x04 = on monthly, 0x08 = on yearly @@ -280,6 +281,8 @@ class Govee_Temp { public: time_t Time; std::string WriteTXT(const char seperator = '\t') const; + std::string WriteCache(void) const; + bool ReadCache(const std::string& data); bool ReadMSG(const uint8_t * const data); Govee_Temp() : Time(0), Temperature{ 0, 0, 0, 0 }, TemperatureMin{ DBL_MAX, DBL_MAX, DBL_MAX, DBL_MAX }, TemperatureMax{ -DBL_MAX, -DBL_MAX, -DBL_MAX, -DBL_MAX }, Humidity(0), HumidityMin(DBL_MAX), HumidityMax(-DBL_MAX), Battery(INT_MAX), Averages(0), Model(ThermometerType::Unknown) { }; Govee_Temp(const time_t tim, const double tem, const double hum, const int bat) @@ -407,6 +410,15 @@ std::string Govee_Temp::WriteTXT(const char seperator) const } return(ssValue.str()); } +std::string Govee_Temp::WriteCache(void) const +{ + std::ostringstream ssValue; + ssValue << timeToExcelDate(Time); + +} +bool Govee_Temp::ReadCache(const std::string& data) +{ +} ThermometerType Govee_Temp::SetModel(const std::string& Name) { ThermometerType rval = Model; @@ -1003,6 +1015,99 @@ void GetMRTGOutput(const std::string &TextAddress, const int Minutes) ///////////////////////////////////////////////////////////////////////////// std::map> GoveeMRTGLogs; // memory map of BT addresses and vector structure similar to MRTG Log Files std::map GoveeBluetoothTitles; +///////////////////////////////////////////////////////////////////////////// +std::filesystem::path GenerateCacheFileName(const bdaddr_t& a) +{ + std::string btAddress(ba2string(a)); + for (auto pos = btAddress.find(':'); pos != std::string::npos; pos = btAddress.find(':')) + btAddress.erase(pos, 1); + std::ostringstream OutputFilename; + OutputFilename << "gvh-cache-"; + OutputFilename << btAddress; + OutputFilename << ".txt"; + std::filesystem::path CacheFileName(CacheDirectory / OutputFilename.str()); + return(CacheFileName); +} +bool GenerateCacheFile(const bdaddr_t& a, const std::vector& GoveeMRTGLog) +{ + bool rval(false); + if (!GoveeMRTGLog.empty()) + { + bool bCacheOldOrNonexistant(true); + std::filesystem::path MRTGCacheFile(GenerateCacheFileName(a)); + struct stat64 FileStat; + FileStat.st_mtim.tv_sec = 0; + if (0 == stat64(MRTGCacheFile.c_str(), &FileStat)) // returns 0 if the file-status information is obtained + if ((FileStat.st_mtim.tv_sec > GoveeMRTGLog[0].Time - (60 * 60))) // only write the file if data is at least one hour more recent than cached data + bCacheOldOrNonexistant = false; + if (bCacheOldOrNonexistant) + { + std::ofstream CacheFile(MRTGCacheFile, std::ios_base::out | std::ios_base::trunc); + if (CacheFile.is_open()) + { + if (ConsoleVerbosity > 0) + std::cout << "[" << getTimeISO8601(true) << "] Writing: " << MRTGCacheFile.string() << std::endl; + else + std::cerr << "Writing: " << MRTGCacheFile.string() << std::endl; + for (auto i : GoveeMRTGLog) + CacheFile << i.WriteCache() << std::endl; + CacheFile.close(); + struct utimbuf ut; + ut.actime = GoveeMRTGLog[0].Time; + ut.modtime = GoveeMRTGLog[0].Time; + utime(MRTGCacheFile.c_str(), &ut); + rval = true; + } + } + } + return(rval); +} +void ReadCacheDirectory(void) +{ + if (!CacheDirectory.empty()) + { + std::deque files; + for (auto const& dir_entry : std::filesystem::directory_iterator{ CacheDirectory }) + if (dir_entry.is_regular_file()) + if (dir_entry.path().extension() == ".txt") + if (dir_entry.path().stem().string().substr(0, 10) == "gvh-cache-") + files.push_back(dir_entry); + if (!files.empty()) + { + sort(files.begin(), files.end()); + while (!files.empty()) + { + auto ssBTAddress = files.begin()->stem().string().substr(10, 12); // new filename format (2023-04-03) + for (auto index = ssBTAddress.length() - 2; index > 0; index -= 2) + ssBTAddress.insert(index, ":"); + bdaddr_t TheBlueToothAddress; + str2ba(ssBTAddress.c_str(), &TheBlueToothAddress); + std::vector foo; + auto ret = GoveeMRTGLogs.insert(std::pair>(TheBlueToothAddress, foo)); + std::vector& FakeMRTGFile = ret.first->second; + std::ifstream TheFile(*files.begin()); + if (TheFile.is_open()) + { + FakeMRTGFile.clear(); + if (ConsoleVerbosity > 0) + std::cout << "[" << getTimeISO8601(true) << "] Reading: " << files.begin()->string() << std::endl; + else + std::cerr << "Reading: " << files.begin()->string() << std::endl; + std::string TheLine; + while (std::getline(TheFile, TheLine)) + { + Govee_Temp value; + value.ReadCache(TheLine); + FakeMRTGFile.push_back(value); + } + TheFile.close(); + } + files.pop_front(); + } + } + } +} +///////////////////////////////////////////////////////////////////////////// enum class GraphType { daily, weekly, monthly, yearly}; // Returns a curated vector of data points specific to the requested graph type read directly from a real MRTG log file on disk. void ReadMRTGData(const std::filesystem::path& MRTGLogFileName, std::vector& TheValues, const GraphType graph = GraphType::daily) @@ -2046,7 +2151,10 @@ int bt_LEScan(int BlueToothDevice_Handle, const bool enable, const std::set 0) std::cout << "[" << getTimeISO8601() << "] BlueTooth Address Filter:"; else - std::cerr << "BlueTooth Address Filter:"; + if (difftime(TimeNow, LastScanEnableMessage) > (60 * 5)) // Reduce Spamming Syslog + std::cerr << "BlueTooth Address Filter:"; for (auto it = GoveeMRTGLogs.begin(); it != GoveeMRTGLogs.end(); it++) { const bdaddr_t TheAddress = it->first; @@ -2063,12 +2172,14 @@ int bt_LEScan(int BlueToothDevice_Handle, const bool enable, const std::set 0) std::cout << " [" << ba2string(TheAddress) << "]"; else - std::cerr << " [" << ba2string(TheAddress) << "]"; + if (difftime(TimeNow, LastScanEnableMessage) > (60 * 5)) // Reduce Spamming Syslog + std::cerr << " [" << ba2string(TheAddress) << "]"; } if (ConsoleVerbosity > 0) std::cout << std::endl; else - std::cerr << std::endl; + if (difftime(TimeNow, LastScanEnableMessage) > (60 * 5)) // Reduce Spamming Syslog + std::cerr << std::endl; } else { @@ -2117,9 +2228,6 @@ int bt_LEScan(int BlueToothDevice_Handle, const bool enable, const std::set (60 * 5)) // Reduce Spamming Syslog { LastScanEnableMessage = TimeNow; @@ -2738,6 +2846,7 @@ static void usage(int argc, char **argv) std::cout << " -o | --only XX:XX:XX:XX:XX:XX only report this address" << std::endl; std::cout << " -C | --controller XX:XX:XX:XX:XX:XX use the controller with this address" << std::endl; std::cout << " -a | --average minutes [" << MinutesAverage << "]" << std::endl; + std::cout << " -f | --cache name cache file directory [" << CacheDirectory << "]" << std::endl; std::cout << " -s | --svg name SVG output directory [" << SVGDirectory << "]" << std::endl; std::cout << " -i | --index name HTML index file for SVG files" << std::endl; std::cout << " -T | --titlemap name SVG title fully qualified filename [" << SVGTitleMapFilename << "]" << std::endl; @@ -2749,7 +2858,7 @@ static void usage(int argc, char **argv) std::cout << " -n | --no-bluetooth Monitor Logging Directory and process logs without Bluetooth Scanning" << std::endl; std::cout << std::endl; } -static const char short_options[] = "hl:t:v:m:o:C:a:s:i:T:cb:x:dpn"; +static const char short_options[] = "hl:t:v:m:o:C:a:f:s:i:T:cb:x:dpn"; static const struct option long_options[] = { { "help", no_argument, NULL, 'h' }, { "log", required_argument, NULL, 'l' }, @@ -2759,6 +2868,7 @@ static const struct option long_options[] = { { "only", required_argument, NULL, 'o' }, { "controller", required_argument, NULL, 'C' }, { "average",required_argument, NULL, 'a' }, + { "cache", required_argument, NULL, 'f' }, { "svg", required_argument, NULL, 's' }, { "index", required_argument, NULL, 'i' }, { "titlemap",required_argument,NULL, 'T' }, @@ -2827,6 +2937,13 @@ int main(int argc, char **argv) catch (const std::invalid_argument& ia) { std::cerr << "Invalid argument: " << ia.what() << std::endl; exit(EXIT_FAILURE); } catch (const std::out_of_range& oor) { std::cerr << "Out of Range error: " << oor.what() << std::endl; exit(EXIT_FAILURE); } break; + case 'f': + TempPath = std::string(optarg); + while (TempPath.filename().empty() && (TempPath != TempPath.root_directory())) // This gets rid of the "/" on the end of the path + TempPath = TempPath.parent_path(); + if (ValidateDirectory(TempPath)) + CacheDirectory = TempPath; + break; case 'd': DaysBetweenDataDownload = 14; break; @@ -2889,6 +3006,7 @@ int main(int argc, char **argv) if (ConsoleVerbosity > 1) { std::cout << "[ ] log: " << LogDirectory << std::endl; + std::cout << "[ ] cache: " << CacheDirectory << std::endl; std::cout << "[ ] svg: " << SVGDirectory << std::endl; std::cout << "[ ] battery: " << SVGBattery << std::endl; std::cout << "[ ] minmax: " << SVGMinMax << std::endl;