From 06f646aec4dbce64d28bae1be6111bd833f8e79e Mon Sep 17 00:00:00 2001 From: Paul Nettle Date: Fri, 25 Aug 2017 09:30:39 -0500 Subject: Initial version 1.0 --- src/standalone.cpp | 320 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 320 insertions(+) create mode 100644 src/standalone.cpp (limited to 'src/standalone.cpp') diff --git a/src/standalone.cpp b/src/standalone.cpp new file mode 100644 index 0000000..c8af5d6 --- /dev/null +++ b/src/standalone.cpp @@ -0,0 +1,320 @@ +// Copyright 2017 Paul Nettle. +// +// This file is part of Gobbledegook. +// +// Gobbledegook is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Gobbledegook is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Gobbledegook. If not, see . + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// >> +// >>> INSIDE THIS FILE +// >> +// +// This is an example single-file stand-alone application that runs a Gobbledegook server. +// +// >> +// >>> DISCUSSION +// >> +// +// Very little is required ("MUST") by a stand-alone application to instantiate a valid Gobbledegook server. There are also some +// things that are reocommended ("SHOULD"). +// +// * A stand-alone application MUST: +// +// * Start the server via a call to `ggkStart()`. +// +// Once started the server will run on its own thread. +// +// Two of the parameters to `ggkStart()` are delegates responsible for providing data accessors for the server, a +// `GGKServerDataGetter` delegate and a 'GGKServerDataSetter' delegate. The getter method simply receives a string name (for +// example, "battery/level") and returns a void pointer to that data (for example: `(void *)&batteryLevel`). The setter does +// the same only in reverse. +// +// While the server is running, you will likely need to update the data being served. This is done by calling +// `ggkNofifyUpdatedCharacteristic()` or `ggkNofifyUpdatedDescriptor()` with the full path to the characteristic or delegate +// whose data has been updated. This will trigger your server's `onUpdatedValue()` method, which can perform whatever +// actions are needed such as sending out a change notification (or in BlueZ parlance, a "PropertiesChanged" signal.) +// +// * A stand-alone application SHOULD: +// +// * Shutdown the server before termination +// +// Triggering the server to begin shutting down is done via a call to `ggkTriggerShutdown()`. This is a non-blocking method +// that begins the asynchronous shutdown process. +// +// Before your application terminates, it should wait for the server to be completely stopped. This is done via a call to +// `ggkWait()`. If the server has not yet reached the `EStopped` state when `ggkWait()` is called, it will block until the +// server has done so. +// +// To avoid the blocking behavior of `ggkWait()`, ensure that the server has stopped before calling it. This can be done +// by ensuring `ggkGetServerRunState() == EStopped`. Even if the server has stopped, it is recommended to call `ggkWait()` +// to ensure the server has cleaned up all threads and other internals. +// +// If you want to keep things simple, there is a method `ggkShutdownAndWait()` which will trigger the shutdown and then +// block until the server has stopped. +// +// * Implement signal handling to provide a clean shut-down +// +// This is done by calling `ggkTriggerShutdown()` from any signal received that can terminate your application. For an +// example of this, search for all occurrences of the string "signalHandler" in the code below. +// +// * Register a custom logging mechanism with the server +// +// This is done by calling each of the log registeration methods: +// +// `ggkLogRegisterDebug()` +// `ggkLogRegisterInfo()` +// `ggkLogRegisterStatus()` +// `ggkLogRegisterWarn()` +// `ggkLogRegisterError()` +// `ggkLogRegisterFatal()` +// `ggkLogRegisterAlways()` +// `ggkLogRegisterTrace()` +// +// Each registration method manages a different log level. For a full description of these levels, see the header comment +// in Logger.cpp. +// +// The code below includes a simple logging mechanism that logs to stdout and filters logs based on a few command-line +// options to specify the level of verbosity. +// +// >> +// >>> Building with GOBBLEDEGOOK +// >> +// +// The Gobbledegook distribution includes this file as part of the Gobbledegook files with everything compiling to a single, stand- +// alone binary. It is built this way because Gobbledegook is not intended to be a generic library. You will need to make your +// custom modifications to it. Don't worry, a lot of work went into Gobbledegook to make it almost trivial to customize +// (see Server.cpp). +// +// If it is important to you or your build process that Gobbledegook exist as a library, you are welcome to do so. Just configure +// your build process to build the Gobbledegook files (minus this file) as a library and link against that instead. All that is +// required by applications linking to a Gobbledegook library is to include `include/Gobbledegook.h`. +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +#include +#include +#include +#include + +#include "../include/Gobbledegook.h" + +// +// Constants +// + +// Maximum time to wait for any single async process to timeout during initialization +static const int kMaxAsyncInitTimeoutMS = 30 * 1000; + +// +// Server data values +// + +// The battery level ("battery/level") reported by the server (see Server.cpp) +static uint8_t serverDataBatteryLevel = 78; + +// The text string ("text/string") used by our custom text string service (see Server.cpp) +static std::string serverDataTextString = "Hello, world!"; + +// +// Logging +// + +enum LogLevel +{ + Debug, + Verbose, + Normal, + ErrorsOnly +}; + +// Our log level - defaulted to 'Normal' but can be modified via command-line options +LogLevel logLevel = Normal; + +// Our full set of logging methods (we just log to stdout) +// +// NOTE: Some methods will only log if the appropriate `logLevel` is set +void LogDebug(const char *pText) { if (logLevel <= Debug) { std::cout << " DEBUG: " << pText << std::endl; } } +void LogInfo(const char *pText) { if (logLevel <= Verbose) { std::cout << " INFO: " << pText << std::endl; } } +void LogStatus(const char *pText) { if (logLevel <= Normal) { std::cout << " STATUS: " << pText << std::endl; } } +void LogWarn(const char *pText) { std::cout << "WARNING: " << pText << std::endl; } +void LogError(const char *pText) { std::cout << "!!ERROR: " << pText << std::endl; } +void LogFatal(const char *pText) { std::cout << "**FATAL: " << pText << std::endl; } +void LogAlways(const char *pText) { std::cout << "..Log..: " << pText << std::endl; } +void LogTrace(const char *pText) { std::cout << "-Trace-: " << pText << std::endl; } + +// +// Signal handling +// + +// We setup a couple Unix signals to perform graceful shutdown in the case of SIGTERM or get an SIGING (CTRL-C) +void signalHandler(int signum) +{ + switch (signum) + { + case SIGINT: + LogStatus("SIGINT recieved, shutting down"); + ggkTriggerShutdown(); + break; + case SIGTERM: + LogStatus("SIGTERM recieved, shutting down"); + ggkTriggerShutdown(); + break; + } +} + +// +// Server data management +// + +// Called by the server when it wants to retrieve a named value +// +// This method conforms to `GGKServerDataGetter` and is passed to the server via our call to `ggkStart()`. +// +// The server calls this method from its own thread, so we must ensure our implementation is thread-safe. In our case, we're simply +// sending over stored values, so we don't need to take any additional steps to ensure thread-safety. +const void *dataGetter(const char *pName) +{ + if (nullptr == pName) + { + LogError("NULL name sent to server data getter"); + return nullptr; + } + + std::string strName = pName; + + if (strName == "battery/level") + { + return &serverDataBatteryLevel; + } + else if (strName == "text/string") + { + return serverDataTextString.c_str(); + } + + LogWarn((std::string("Unknown name for server data getter request: '") + pName + "'").c_str()); + return nullptr; +} + +// Called by the server when it wants to update a named value +// +// This method conforms to `GGKServerDataSetter` and is passed to the server via our call to `ggkStart()`. +// +// The server calls this method from its own thread, so we must ensure our implementation is thread-safe. In our case, we're simply +// sending over stored values, so we don't need to take any additional steps to ensure thread-safety. +int dataSetter(const char *pName, const void *pData) +{ + if (nullptr == pName) + { + LogError("NULL name sent to server data setter"); + return 0; + } + if (nullptr == pData) + { + LogError("NULL pData sent to server data setter"); + return 0; + } + + std::string strName = pName; + + if (strName == "battery/level") + { + serverDataBatteryLevel = *static_cast(pData); + LogDebug((std::string("Server data: battery level set to ") + std::to_string(serverDataBatteryLevel)).c_str()); + return 1; + } + else if (strName == "text/string") + { + serverDataTextString = static_cast(pData); + LogDebug((std::string("Server data: text string set to '") + serverDataTextString + "'").c_str()); + return 1; + } + + LogWarn((std::string("Unknown name for server data setter request: '") + pName + "'").c_str()); + + return 0; +} + +// +// Entry point +// + +int main(int argc, char **ppArgv) +{ + // A basic command-line parser + for (int i = 1; i < argc; ++i) + { + std::string arg = ppArgv[i]; + if (arg == "-q") + { + logLevel = ErrorsOnly; + } + else if (arg == "-v") + { + logLevel = Verbose; + } + else if (arg == "-d") + { + logLevel = Debug; + } + else + { + LogFatal((std::string("Unknown parameter: '") + arg + "'").c_str()); + LogFatal(""); + LogFatal("Usage: standalone [-q | -v | -d]"); + return -1; + } + } + + // Setup our signal handlers + signal(SIGINT, signalHandler); + signal(SIGTERM, signalHandler); + + // Register our loggers + ggkLogRegisterDebug(LogDebug); + ggkLogRegisterInfo(LogInfo); + ggkLogRegisterStatus(LogStatus); + ggkLogRegisterWarn(LogWarn); + ggkLogRegisterError(LogError); + ggkLogRegisterFatal(LogFatal); + ggkLogRegisterAlways(LogAlways); + ggkLogRegisterTrace(LogTrace); + + // Start the server's ascync processing + // + // This starts the server on a thread and begins the initialization process + if (!ggkStart(dataGetter, dataSetter, kMaxAsyncInitTimeoutMS)) + { + return -1; + } + + // Wait for the server to start the shutdown process + // + // While we wait, every 15 ticks, drop the battery level by one percent until we reach 0 + while (ggkGetServerRunState() < EStopping) + { + std::this_thread::sleep_for(std::chrono::seconds(15)); + + serverDataBatteryLevel = std::max(serverDataBatteryLevel - 1, 0); + ggkNofifyUpdatedCharacteristic("/com/gobbledegook/battery/level"); + } + + // Wait for the server to come to a complete stop (CTRL-C from the command line) + if (!ggkWait()) + { + return -1; + } + + // Return the final server health status as a success (0) or error (-1) + return ggkGetServerHealth() == EOk ? 0 : 1; +} -- cgit v1.2.3