// 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 Lesser 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 Lesser 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
//
// !!!IMPORTANT!!!
//
// This first parameter (the service name) must match tha name configured in the D-Bus permissions. See the Readme.md file
// for more information.
//
if (!ggkStart("gobbledegook", "Gobbledegook", "Gobbledegook", 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;
}