initial commit

This commit is contained in:
proddy
2020-07-05 18:29:08 +02:00
parent 26b201ea2f
commit c5933e8c14
739 changed files with 86566 additions and 20952 deletions

View File

@@ -0,0 +1,86 @@
#include <APSettingsService.h>
APSettingsService::APSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) :
_httpEndpoint(APSettings::read, APSettings::update, this, server, AP_SETTINGS_SERVICE_PATH, securityManager),
_fsPersistence(APSettings::read, APSettings::update, this, fs, AP_SETTINGS_FILE),
_dnsServer(nullptr),
_lastManaged(0),
_reconfigureAp(false) {
addUpdateHandler([&](const String& originId) { reconfigureAP(); }, false);
}
void APSettingsService::begin() {
_fsPersistence.readFromFS();
reconfigureAP();
}
void APSettingsService::reconfigureAP() {
_lastManaged = millis() - MANAGE_NETWORK_DELAY;
_reconfigureAp = true;
}
void APSettingsService::loop() {
unsigned long currentMillis = millis();
unsigned long manageElapsed = (unsigned long)(currentMillis - _lastManaged);
if (manageElapsed >= MANAGE_NETWORK_DELAY) {
_lastManaged = currentMillis;
manageAP();
}
handleDNS();
}
void APSettingsService::manageAP() {
WiFiMode_t currentWiFiMode = WiFi.getMode();
if (_state.provisionMode == AP_MODE_ALWAYS ||
(_state.provisionMode == AP_MODE_DISCONNECTED && WiFi.status() != WL_CONNECTED)) {
if (_reconfigureAp || currentWiFiMode == WIFI_OFF || currentWiFiMode == WIFI_STA) {
startAP();
}
} else if ((currentWiFiMode == WIFI_AP || currentWiFiMode == WIFI_AP_STA) &&
(_reconfigureAp || !WiFi.softAPgetStationNum())) {
stopAP();
}
_reconfigureAp = false;
}
void APSettingsService::startAP() {
Serial.println(F("Starting software access point"));
IPAddress localIP(192, 168, 4, 1);
IPAddress gateway(192, 168, 4, 1);
IPAddress subnet(255, 255, 255, 0);
WiFi.softAPConfig(localIP, gateway, subnet);
WiFi.softAP(_state.ssid.c_str(), _state.password.c_str());
if (!_dnsServer) {
IPAddress apIp = WiFi.softAPIP();
Serial.print(F("Starting captive portal on "));
Serial.println(apIp);
_dnsServer = new DNSServer;
_dnsServer->start(DNS_PORT, "*", apIp);
}
}
void APSettingsService::stopAP() {
if (_dnsServer) {
Serial.println(F("Stopping captive portal"));
_dnsServer->stop();
delete _dnsServer;
_dnsServer = nullptr;
}
Serial.println(F("Stopping software access point"));
WiFi.softAPdisconnect(true);
}
void APSettingsService::handleDNS() {
if (_dnsServer) {
_dnsServer->processNextRequest();
}
}
APNetworkStatus APSettingsService::getAPNetworkStatus() {
WiFiMode_t currentWiFiMode = WiFi.getMode();
bool apActive = currentWiFiMode == WIFI_AP || currentWiFiMode == WIFI_AP_STA;
if (apActive && _state.provisionMode != AP_MODE_ALWAYS && WiFi.status() == WL_CONNECTED) {
return APNetworkStatus::LINGERING;
}
return apActive ? APNetworkStatus::ACTIVE : APNetworkStatus::INACTIVE;
}

View File

@@ -0,0 +1,99 @@
#ifndef APSettingsConfig_h
#define APSettingsConfig_h
#include <HttpEndpoint.h>
#include <FSPersistence.h>
#include <DNSServer.h>
#include <IPAddress.h>
#define MANAGE_NETWORK_DELAY 10000
#define AP_MODE_ALWAYS 0
#define AP_MODE_DISCONNECTED 1
#define AP_MODE_NEVER 2
#define DNS_PORT 53
#ifndef FACTORY_AP_SSID
#define FACTORY_AP_SSID "ESP8266-React"
#endif
#ifndef FACTORY_AP_PASSWORD
#define FACTORY_AP_PASSWORD "esp-react"
#endif
#ifndef FACTORY_AP_PROVISION_MODE
#define FACTORY_AP_PROVISION_MODE AP_MODE_DISCONNECTED
#endif
#define AP_SETTINGS_FILE "/config/apSettings.json"
#define AP_SETTINGS_SERVICE_PATH "/rest/apSettings"
enum APNetworkStatus {
ACTIVE = 0,
INACTIVE,
LINGERING
};
class APSettings {
public:
uint8_t provisionMode;
String ssid;
String password;
static void read(APSettings& settings, JsonObject& root) {
root["provision_mode"] = settings.provisionMode;
root["ssid"] = settings.ssid;
root["password"] = settings.password;
}
static StateUpdateResult update(JsonObject& root, APSettings& settings) {
APSettings newSettings = {};
newSettings.provisionMode = root["provision_mode"] | FACTORY_AP_PROVISION_MODE;
switch (settings.provisionMode) {
case AP_MODE_ALWAYS:
case AP_MODE_DISCONNECTED:
case AP_MODE_NEVER:
break;
default:
newSettings.provisionMode = AP_MODE_ALWAYS;
}
newSettings.ssid = root["ssid"] | FACTORY_AP_SSID;
newSettings.password = root["password"] | FACTORY_AP_PASSWORD;
if (newSettings.provisionMode == settings.provisionMode && newSettings.ssid.equals(settings.ssid) &&
newSettings.password.equals(settings.password)) {
return StateUpdateResult::UNCHANGED;
}
settings = newSettings;
return StateUpdateResult::CHANGED;
}
};
class APSettingsService : public StatefulService<APSettings> {
public:
APSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
void begin();
void loop();
APNetworkStatus getAPNetworkStatus();
private:
HttpEndpoint<APSettings> _httpEndpoint;
FSPersistence<APSettings> _fsPersistence;
// for the captive portal
DNSServer* _dnsServer;
// for the mangement delay loop
volatile unsigned long _lastManaged;
volatile boolean _reconfigureAp;
void reconfigureAP();
void manageAP();
void startAP();
void stopAP();
void handleDNS();
};
#endif // end APSettingsConfig_h

View File

@@ -0,0 +1,22 @@
#include <APStatus.h>
APStatus::APStatus(AsyncWebServer* server, SecurityManager* securityManager, APSettingsService* apSettingsService) :
_apSettingsService(apSettingsService) {
server->on(AP_STATUS_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&APStatus::apStatus, this, std::placeholders::_1),
AuthenticationPredicates::IS_AUTHENTICATED));
}
void APStatus::apStatus(AsyncWebServerRequest* request) {
AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_AP_STATUS_SIZE);
JsonObject root = response->getRoot();
root["status"] = _apSettingsService->getAPNetworkStatus();
root["ip_address"] = WiFi.softAPIP().toString();
root["mac_address"] = WiFi.softAPmacAddress();
root["station_num"] = WiFi.softAPgetStationNum();
response->setLength();
request->send(response);
}

31
lib/framework/APStatus.h Normal file
View File

@@ -0,0 +1,31 @@
#ifndef APStatus_h
#define APStatus_h
#ifdef ESP32
#include <WiFi.h>
#include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <ESPAsyncWebServer.h>
#include <IPAddress.h>
#include <SecurityManager.h>
#include <APSettingsService.h>
#define MAX_AP_STATUS_SIZE 1024
#define AP_STATUS_SERVICE_PATH "/rest/apStatus"
class APStatus {
public:
APStatus(AsyncWebServer* server, SecurityManager* securityManager, APSettingsService* apSettingsService);
private:
APSettingsService* _apSettingsService;
void apStatus(AsyncWebServerRequest* request);
};
#endif // end APStatus_h

View File

@@ -0,0 +1,144 @@
#include "ArduinoJsonJWT.h"
ArduinoJsonJWT::ArduinoJsonJWT(String secret) : _secret(secret) {
}
void ArduinoJsonJWT::setSecret(String secret) {
_secret = secret;
}
String ArduinoJsonJWT::getSecret() {
return _secret;
}
/*
* ESP32 uses mbedtls, ESP2866 uses bearssl.
*
* Both come with decent HMAC implmentations supporting sha256, as well as others.
*
* No need to pull in additional crypto libraries - lets use what we already have.
*/
String ArduinoJsonJWT::sign(String& payload) {
unsigned char hmacResult[32];
{
#ifdef ESP32
mbedtls_md_context_t ctx;
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
mbedtls_md_init(&ctx);
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 1);
mbedtls_md_hmac_starts(&ctx, (unsigned char*)_secret.c_str(), _secret.length());
mbedtls_md_hmac_update(&ctx, (unsigned char*)payload.c_str(), payload.length());
mbedtls_md_hmac_finish(&ctx, hmacResult);
mbedtls_md_free(&ctx);
#elif defined(ESP8266)
br_hmac_key_context keyCtx;
br_hmac_key_init(&keyCtx, &br_sha256_vtable, _secret.c_str(), _secret.length());
br_hmac_context hmacCtx;
br_hmac_init(&hmacCtx, &keyCtx, 0);
br_hmac_update(&hmacCtx, payload.c_str(), payload.length());
br_hmac_out(&hmacCtx, hmacResult);
#endif
}
return encode((char*)hmacResult, 32);
}
String ArduinoJsonJWT::buildJWT(JsonObject& payload) {
// serialize, then encode payload
String jwt;
serializeJson(payload, jwt);
jwt = encode(jwt.c_str(), jwt.length());
// add the header to payload
jwt = JWT_HEADER + '.' + jwt;
// add signature
jwt += '.' + sign(jwt);
return jwt;
}
void ArduinoJsonJWT::parseJWT(String jwt, JsonDocument& jsonDocument) {
// clear json document before we begin, jsonDocument wil be null on failure
jsonDocument.clear();
// must have the correct header and delimiter
if (!jwt.startsWith(JWT_HEADER) || jwt.indexOf('.') != JWT_HEADER_SIZE) {
return;
}
// check there is a signature delimieter
int signatureDelimiterIndex = jwt.lastIndexOf('.');
if (signatureDelimiterIndex == JWT_HEADER_SIZE) {
return;
}
// check the signature is valid
String signature = jwt.substring(signatureDelimiterIndex + 1);
jwt = jwt.substring(0, signatureDelimiterIndex);
if (sign(jwt) != signature) {
return;
}
// decode payload
jwt = jwt.substring(JWT_HEADER_SIZE + 1);
jwt = decode(jwt);
// parse payload, clearing json document after failure
DeserializationError error = deserializeJson(jsonDocument, jwt);
if (error != DeserializationError::Ok || !jsonDocument.is<JsonObject>()) {
jsonDocument.clear();
}
}
String ArduinoJsonJWT::encode(const char* cstr, int inputLen) {
// prepare encoder
base64_encodestate _state;
#ifdef ESP32
base64_init_encodestate(&_state);
size_t encodedLength = base64_encode_expected_len(inputLen) + 1;
#elif defined(ESP8266)
base64_init_encodestate_nonewlines(&_state);
size_t encodedLength = base64_encode_expected_len_nonewlines(inputLen) + 1;
#endif
// prepare buffer of correct length, returning an empty string on failure
char* buffer = (char*)malloc(encodedLength * sizeof(char));
if (buffer == nullptr) {
return "";
}
// encode to buffer
int len = base64_encode_block(cstr, inputLen, &buffer[0], &_state);
len += base64_encode_blockend(&buffer[len], &_state);
buffer[len] = 0;
// convert to arduino string, freeing buffer
String value = String(buffer);
free(buffer);
buffer = nullptr;
// remove padding and convert to URL safe form
while (value.length() > 0 && value.charAt(value.length() - 1) == '=') {
value.remove(value.length() - 1);
}
value.replace('+', '-');
value.replace('/', '_');
// return as string
return value;
}
String ArduinoJsonJWT::decode(String value) {
// convert to standard base64
value.replace('-', '+');
value.replace('_', '/');
// prepare buffer of correct length
char buffer[base64_decode_expected_len(value.length()) + 1];
// decode
int len = base64_decode_chars(value.c_str(), value.length(), &buffer[0]);
buffer[len] = 0;
// return as string
return String(buffer);
}

View File

@@ -0,0 +1,37 @@
#ifndef ArduinoJsonJWT_H
#define ArduinoJsonJWT_H
#include <Arduino.h>
#include <ArduinoJson.h>
#include <libb64/cdecode.h>
#include <libb64/cencode.h>
#ifdef ESP32
#include <mbedtls/md.h>
#elif defined(ESP8266)
#include <bearssl/bearssl_hmac.h>
#endif
class ArduinoJsonJWT {
private:
String _secret;
const String JWT_HEADER = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
const int JWT_HEADER_SIZE = JWT_HEADER.length();
String sign(String& value);
static String encode(const char* cstr, int len);
static String decode(String value);
public:
ArduinoJsonJWT(String secret);
void setSecret(String secret);
String getSecret();
String buildJWT(JsonObject& payload);
void parseJWT(String jwt, JsonDocument& jsonDocument);
};
#endif

View File

@@ -0,0 +1,48 @@
#include <AuthenticationService.h>
#if FT_ENABLED(FT_SECURITY)
AuthenticationService::AuthenticationService(AsyncWebServer* server, SecurityManager* securityManager) :
_securityManager(securityManager),
_signInHandler(SIGN_IN_PATH,
std::bind(&AuthenticationService::signIn, this, std::placeholders::_1, std::placeholders::_2)) {
server->on(VERIFY_AUTHORIZATION_PATH,
HTTP_GET,
std::bind(&AuthenticationService::verifyAuthorization, this, std::placeholders::_1));
_signInHandler.setMethod(HTTP_POST);
_signInHandler.setMaxContentLength(MAX_AUTHENTICATION_SIZE);
server->addHandler(&_signInHandler);
}
/**
* Verifys that the request supplied a valid JWT.
*/
void AuthenticationService::verifyAuthorization(AsyncWebServerRequest* request) {
Authentication authentication = _securityManager->authenticateRequest(request);
request->send(authentication.authenticated ? 200 : 401);
}
/**
* Signs in a user if the username and password match. Provides a JWT to be used in the Authorization header in
* subsequent requests.
*/
void AuthenticationService::signIn(AsyncWebServerRequest* request, JsonVariant& json) {
if (json.is<JsonObject>()) {
String username = json["username"];
String password = json["password"];
Authentication authentication = _securityManager->authenticate(username, password);
if (authentication.authenticated) {
User* user = authentication.user;
AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_AUTHENTICATION_SIZE);
JsonObject jsonObject = response->getRoot();
jsonObject["access_token"] = _securityManager->generateJWT(user);
response->setLength();
request->send(response);
return;
}
}
AsyncWebServerResponse* response = request->beginResponse(401);
request->send(response);
}
#endif // end FT_ENABLED(FT_SECURITY)

View File

@@ -0,0 +1,30 @@
#ifndef AuthenticationService_H_
#define AuthenticationService_H_
#include <Features.h>
#include <AsyncJson.h>
#include <ESPAsyncWebServer.h>
#include <SecurityManager.h>
#define VERIFY_AUTHORIZATION_PATH "/rest/verifyAuthorization"
#define SIGN_IN_PATH "/rest/signIn"
#define MAX_AUTHENTICATION_SIZE 256
#if FT_ENABLED(FT_SECURITY)
class AuthenticationService {
public:
AuthenticationService(AsyncWebServer* server, SecurityManager* securityManager);
private:
SecurityManager* _securityManager;
AsyncCallbackJsonWebHandler _signInHandler;
// endpoint functions
void signIn(AsyncWebServerRequest* request, JsonVariant& json);
void verifyAuthorization(AsyncWebServerRequest* request);
};
#endif // end FT_ENABLED(FT_SECURITY)
#endif // end SecurityManager_h

View File

@@ -0,0 +1,119 @@
#include <ESP8266React.h>
ESP8266React::ESP8266React(AsyncWebServer * server, FS * fs)
: _featureService(server)
, _securitySettingsService(server, fs)
, _wifiSettingsService(server, fs, &_securitySettingsService)
, _wifiScanner(server, &_securitySettingsService)
, _wifiStatus(server, &_securitySettingsService)
, _apSettingsService(server, fs, &_securitySettingsService)
, _apStatus(server, &_securitySettingsService, &_apSettingsService)
,
#if FT_ENABLED(FT_NTP)
_ntpSettingsService(server, fs, &_securitySettingsService)
, _ntpStatus(server, &_securitySettingsService)
,
#endif
#if FT_ENABLED(FT_OTA)
_otaSettingsService(server, fs, &_securitySettingsService)
,
#endif
#if FT_ENABLED(FT_UPLOAD_FIRMWARE)
_uploadFirmwareService(server, &_securitySettingsService)
,
#endif
#if FT_ENABLED(FT_MQTT)
_mqttSettingsService(server, fs, &_securitySettingsService)
, _mqttStatus(server, &_mqttSettingsService, &_securitySettingsService)
,
#endif
#if FT_ENABLED(FT_SECURITY)
_authenticationService(server, &_securitySettingsService)
,
#endif
_restartService(server, &_securitySettingsService)
, _factoryResetService(server, fs, &_securitySettingsService)
, _systemStatus(server, &_securitySettingsService) {
#ifdef PROGMEM_WWW
// Serve static resources from PROGMEM
WWWData::registerRoutes([server, this](const String & uri, const String & contentType, const uint8_t * content, size_t len) {
ArRequestHandlerFunction requestHandler = [contentType, content, len](AsyncWebServerRequest * request) {
AsyncWebServerResponse * response = request->beginResponse_P(200, contentType, content, len);
response->addHeader("Content-Encoding", "gzip");
request->send(response);
};
server->on(uri.c_str(), HTTP_GET, requestHandler);
// Serving non matching get requests with "/index.html"
// OPTIONS get a straight up 200 response
if (uri.equals("/index.html")) {
server->onNotFound([requestHandler](AsyncWebServerRequest * request) {
if (request->method() == HTTP_GET) {
requestHandler(request);
} else if (request->method() == HTTP_OPTIONS) {
request->send(200);
} else {
request->send(404);
}
});
}
});
#else
// Serve static resources from /www/
server->serveStatic("/js/", *fs, "/www/js/");
server->serveStatic("/css/", *fs, "/www/css/");
server->serveStatic("/fonts/", *fs, "/www/fonts/");
server->serveStatic("/app/", *fs, "/www/app/");
server->serveStatic("/favicon.ico", *fs, "/www/favicon.ico");
// Serving all other get requests with "/www/index.htm"
// OPTIONS get a straight up 200 response
server->onNotFound([](AsyncWebServerRequest * request) {
if (request->method() == HTTP_GET) {
#ifdef ESP32
request->send(SPIFFS, "/www/index.html");
#else
request->send(LittleFS, "/www/index.html"); // added
#endif
} else if (request->method() == HTTP_OPTIONS) {
request->send(200);
} else {
request->send(404);
}
});
#endif
// Disable CORS if required
#if defined(ENABLE_CORS)
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", CORS_ORIGIN);
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "Accept, Content-Type, Authorization");
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Credentials", "true");
#endif
}
void ESP8266React::begin() {
_wifiSettingsService.begin();
_apSettingsService.begin();
#if FT_ENABLED(FT_NTP)
_ntpSettingsService.begin();
#endif
#if FT_ENABLED(FT_OTA)
_otaSettingsService.begin();
#endif
#if FT_ENABLED(FT_MQTT)
_mqttSettingsService.begin();
#endif
#if FT_ENABLED(FT_SECURITY)
_securitySettingsService.begin();
#endif
}
void ESP8266React::loop() {
_wifiSettingsService.loop();
_apSettingsService.loop();
#if FT_ENABLED(FT_OTA)
_otaSettingsService.loop();
#endif
#if FT_ENABLED(FT_MQTT)
_mqttSettingsService.loop();
#endif
}

View File

@@ -0,0 +1,117 @@
#ifndef ESP8266React_h
#define ESP8266React_h
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include <FeaturesService.h>
#include <APSettingsService.h>
#include <APStatus.h>
#include <AuthenticationService.h>
#include <FactoryResetService.h>
#include <MqttSettingsService.h>
#include <MqttStatus.h>
#include <NTPSettingsService.h>
#include <NTPStatus.h>
#include <OTASettingsService.h>
#include <UploadFirmwareService.h>
#include <RestartService.h>
#include <SecuritySettingsService.h>
#include <SystemStatus.h>
#include <WiFiScanner.h>
#include <WiFiSettingsService.h>
#include <WiFiStatus.h>
#ifdef PROGMEM_WWW
#include <WWWData.h>
#endif
class ESP8266React {
public:
ESP8266React(AsyncWebServer* server, FS* fs);
void begin();
void loop();
SecurityManager* getSecurityManager() {
return &_securitySettingsService;
}
#if FT_ENABLED(FT_SECURITY)
StatefulService<SecuritySettings>* getSecuritySettingsService() {
return &_securitySettingsService;
}
#endif
StatefulService<WiFiSettings>* getWiFiSettingsService() {
return &_wifiSettingsService;
}
StatefulService<APSettings>* getAPSettingsService() {
return &_apSettingsService;
}
#if FT_ENABLED(FT_NTP)
StatefulService<NTPSettings>* getNTPSettingsService() {
return &_ntpSettingsService;
}
#endif
#if FT_ENABLED(FT_OTA)
StatefulService<OTASettings>* getOTASettingsService() {
return &_otaSettingsService;
}
#endif
#if FT_ENABLED(FT_MQTT)
StatefulService<MqttSettings>* getMqttSettingsService() {
return &_mqttSettingsService;
}
AsyncMqttClient* getMqttClient() {
return _mqttSettingsService.getMqttClient();
}
#endif
void factoryReset() {
_factoryResetService.factoryReset();
}
private:
FeaturesService _featureService;
SecuritySettingsService _securitySettingsService;
WiFiSettingsService _wifiSettingsService;
WiFiScanner _wifiScanner;
WiFiStatus _wifiStatus;
APSettingsService _apSettingsService;
APStatus _apStatus;
#if FT_ENABLED(FT_NTP)
NTPSettingsService _ntpSettingsService;
NTPStatus _ntpStatus;
#endif
#if FT_ENABLED(FT_OTA)
OTASettingsService _otaSettingsService;
#endif
#if FT_ENABLED(FT_UPLOAD_FIRMWARE)
UploadFirmwareService _uploadFirmwareService;
#endif
#if FT_ENABLED(FT_MQTT)
MqttSettingsService _mqttSettingsService;
MqttStatus _mqttStatus;
#endif
#if FT_ENABLED(FT_SECURITY)
AuthenticationService _authenticationService;
#endif
RestartService _restartService;
FactoryResetService _factoryResetService;
SystemStatus _systemStatus;
};
#endif

17
lib/framework/ESPUtils.h Normal file
View File

@@ -0,0 +1,17 @@
#ifndef ESPUtils_h
#define ESPUtils_h
#include <Arduino.h>
class ESPUtils {
public:
static String defaultDeviceValue(String prefix = "") {
#ifdef ESP32
return prefix + String((unsigned long)ESP.getEfuseMac(), HEX);
#elif defined(ESP8266)
return prefix + String(ESP.getChipId(), HEX);
#endif
}
};
#endif // end ESPUtils

View File

@@ -0,0 +1,98 @@
#ifndef FSPersistence_h
#define FSPersistence_h
#include <StatefulService.h>
#include <FS.h>
template <class T>
class FSPersistence {
public:
FSPersistence(JsonStateReader<T> stateReader,
JsonStateUpdater<T> stateUpdater,
StatefulService<T> * statefulService,
FS * fs,
char const * filePath,
size_t bufferSize = DEFAULT_BUFFER_SIZE)
: _stateReader(stateReader)
, _stateUpdater(stateUpdater)
, _statefulService(statefulService)
, _fs(fs)
, _filePath(filePath)
, _bufferSize(bufferSize)
, _updateHandlerId(0) {
enableUpdateHandler();
}
void readFromFS() {
File settingsFile = _fs->open(_filePath, "r");
if (settingsFile) {
DynamicJsonDocument jsonDocument = DynamicJsonDocument(_bufferSize);
DeserializationError error = deserializeJson(jsonDocument, settingsFile);
if (error == DeserializationError::Ok && jsonDocument.is<JsonObject>()) {
JsonObject jsonObject = jsonDocument.as<JsonObject>();
_statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
settingsFile.close();
return;
}
settingsFile.close();
}
// If we reach here we have not been successful in loading the config,
// hard-coded emergency defaults are now applied.
applyDefaults();
}
bool writeToFS() {
// create and populate a new json object
DynamicJsonDocument jsonDocument = DynamicJsonDocument(_bufferSize);
JsonObject jsonObject = jsonDocument.to<JsonObject>();
_statefulService->read(jsonObject, _stateReader);
// serialize it to filesystem
File settingsFile = _fs->open(_filePath, "w");
// failed to open file, return false
if (!settingsFile) {
return false;
}
// serialize the data to the file
serializeJson(jsonDocument, settingsFile);
settingsFile.close();
return true;
}
void disableUpdateHandler() {
if (_updateHandlerId) {
_statefulService->removeUpdateHandler(_updateHandlerId);
_updateHandlerId = 0;
}
}
void enableUpdateHandler() {
if (!_updateHandlerId) {
_updateHandlerId = _statefulService->addUpdateHandler([&](const String & originId) { writeToFS(); });
}
}
private:
JsonStateReader<T> _stateReader;
JsonStateUpdater<T> _stateUpdater;
StatefulService<T> * _statefulService;
FS * _fs;
char const * _filePath;
size_t _bufferSize;
update_handler_id_t _updateHandlerId;
protected:
// We assume the updater supplies sensible defaults if an empty object
// is supplied, this virtual function allows that to be changed.
virtual void applyDefaults() {
DynamicJsonDocument jsonDocument = DynamicJsonDocument(_bufferSize);
JsonObject jsonObject = jsonDocument.as<JsonObject>();
_statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
}
};
#endif // end FSPersistence

View File

@@ -0,0 +1,34 @@
#include <FactoryResetService.h>
using namespace std::placeholders;
FactoryResetService::FactoryResetService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) : fs(fs) {
server->on(FACTORY_RESET_SERVICE_PATH,
HTTP_POST,
securityManager->wrapRequest(std::bind(&FactoryResetService::handleRequest, this, _1),
AuthenticationPredicates::IS_ADMIN));
}
void FactoryResetService::handleRequest(AsyncWebServerRequest* request) {
request->onDisconnect(std::bind(&FactoryResetService::factoryReset, this));
request->send(200);
}
/**
* Delete function assumes that all files are stored flat, within the config directory
*/
void FactoryResetService::factoryReset() {
#ifdef ESP32
File root = fs->open(FS_CONFIG_DIRECTORY);
File file;
while (file = root.openNextFile()) {
fs->remove(file.name());
}
#elif defined(ESP8266)
Dir configDirectory = fs->openDir(FS_CONFIG_DIRECTORY);
while (configDirectory.next()) {
fs->remove(configDirectory.fileName());
}
#endif
RestartService::restartNow();
}

View File

@@ -0,0 +1,32 @@
#ifndef FactoryResetService_h
#define FactoryResetService_h
#ifdef ESP32
#include <WiFi.h>
#include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include <ESPAsyncWebServer.h>
#include <SecurityManager.h>
#include <RestartService.h>
#include <FS.h>
#define FS_CONFIG_DIRECTORY "/config"
#define FACTORY_RESET_SERVICE_PATH "/rest/factoryReset"
class FactoryResetService {
FS* fs;
public:
FactoryResetService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
void factoryReset();
private:
void handleRequest(AsyncWebServerRequest* request);
};
#endif // end FactoryResetService_h

37
lib/framework/Features.h Normal file
View File

@@ -0,0 +1,37 @@
#ifndef Features_h
#define Features_h
#define FT_ENABLED(feature) feature
// project feature off by default
#ifndef FT_PROJECT
#define FT_PROJECT 0
#endif
// security feature on by default
#ifndef FT_SECURITY
#define FT_SECURITY 1
#endif
// mqtt feature on by default
#ifndef FT_MQTT
#define FT_MQTT 1
#endif
// ntp feature on by default
#ifndef FT_NTP
#define FT_NTP 1
#endif
// mqtt feature on by default
#ifndef FT_OTA
#define FT_OTA 1
#endif
// upload firmware feature off by default
#ifndef FT_UPLOAD_FIRMWARE
#define FT_UPLOAD_FIRMWARE 0
#endif
#endif

View File

@@ -0,0 +1,42 @@
#include <FeaturesService.h>
FeaturesService::FeaturesService(AsyncWebServer* server) {
server->on(FEATURES_SERVICE_PATH, HTTP_GET, std::bind(&FeaturesService::features, this, std::placeholders::_1));
}
void FeaturesService::features(AsyncWebServerRequest* request) {
AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_FEATURES_SIZE);
JsonObject root = response->getRoot();
#if FT_ENABLED(FT_PROJECT)
root["project"] = true;
#else
root["project"] = false;
#endif
#if FT_ENABLED(FT_SECURITY)
root["security"] = true;
#else
root["security"] = false;
#endif
#if FT_ENABLED(FT_MQTT)
root["mqtt"] = true;
#else
root["mqtt"] = false;
#endif
#if FT_ENABLED(FT_NTP)
root["ntp"] = true;
#else
root["ntp"] = false;
#endif
#if FT_ENABLED(FT_OTA)
root["ota"] = true;
#else
root["ota"] = false;
#endif
#if FT_ENABLED(FT_UPLOAD_FIRMWARE)
root["upload_firmware"] = true;
#else
root["upload_firmware"] = false;
#endif
response->setLength();
request->send(response);
}

View File

@@ -0,0 +1,29 @@
#ifndef FeaturesService_h
#define FeaturesService_h
#include <Features.h>
#ifdef ESP32
#include <WiFi.h>
#include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <ESPAsyncWebServer.h>
#define MAX_FEATURES_SIZE 256
#define FEATURES_SERVICE_PATH "/rest/features"
class FeaturesService {
public:
FeaturesService(AsyncWebServer* server);
private:
void features(AsyncWebServerRequest* request);
};
#endif

View File

@@ -0,0 +1,165 @@
#ifndef HttpEndpoint_h
#define HttpEndpoint_h
#include <functional>
#include <AsyncJson.h>
#include <ESPAsyncWebServer.h>
#include <SecurityManager.h>
#include <StatefulService.h>
#define HTTP_ENDPOINT_ORIGIN_ID "http"
template <class T>
class HttpGetEndpoint {
public:
HttpGetEndpoint(JsonStateReader<T> stateReader,
StatefulService<T>* statefulService,
AsyncWebServer* server,
const String& servicePath,
SecurityManager* securityManager,
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
_stateReader(stateReader), _statefulService(statefulService), _bufferSize(bufferSize) {
server->on(servicePath.c_str(),
HTTP_GET,
securityManager->wrapRequest(std::bind(&HttpGetEndpoint::fetchSettings, this, std::placeholders::_1),
authenticationPredicate));
}
HttpGetEndpoint(JsonStateReader<T> stateReader,
StatefulService<T>* statefulService,
AsyncWebServer* server,
const String& servicePath,
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
_stateReader(stateReader), _statefulService(statefulService), _bufferSize(bufferSize) {
server->on(servicePath.c_str(), HTTP_GET, std::bind(&HttpGetEndpoint::fetchSettings, this, std::placeholders::_1));
}
protected:
JsonStateReader<T> _stateReader;
StatefulService<T>* _statefulService;
size_t _bufferSize;
void fetchSettings(AsyncWebServerRequest* request) {
AsyncJsonResponse* response = new AsyncJsonResponse(false, _bufferSize);
JsonObject jsonObject = response->getRoot().to<JsonObject>();
_statefulService->read(jsonObject, _stateReader);
response->setLength();
request->send(response);
}
};
template <class T>
class HttpPostEndpoint {
public:
HttpPostEndpoint(JsonStateReader<T> stateReader,
JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService,
AsyncWebServer* server,
const String& servicePath,
SecurityManager* securityManager,
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
_stateReader(stateReader),
_stateUpdater(stateUpdater),
_statefulService(statefulService),
_updateHandler(
servicePath,
securityManager->wrapCallback(
std::bind(&HttpPostEndpoint::updateSettings, this, std::placeholders::_1, std::placeholders::_2),
authenticationPredicate),
bufferSize),
_bufferSize(bufferSize) {
_updateHandler.setMethod(HTTP_POST);
server->addHandler(&_updateHandler);
}
HttpPostEndpoint(JsonStateReader<T> stateReader,
JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService,
AsyncWebServer* server,
const String& servicePath,
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
_stateReader(stateReader),
_stateUpdater(stateUpdater),
_statefulService(statefulService),
_updateHandler(servicePath,
std::bind(&HttpPostEndpoint::updateSettings, this, std::placeholders::_1, std::placeholders::_2),
bufferSize),
_bufferSize(bufferSize) {
_updateHandler.setMethod(HTTP_POST);
server->addHandler(&_updateHandler);
}
protected:
JsonStateReader<T> _stateReader;
JsonStateUpdater<T> _stateUpdater;
StatefulService<T>* _statefulService;
AsyncCallbackJsonWebHandler _updateHandler;
size_t _bufferSize;
void updateSettings(AsyncWebServerRequest* request, JsonVariant& json) {
if (!json.is<JsonObject>()) {
request->send(400);
return;
}
JsonObject jsonObject = json.as<JsonObject>();
StateUpdateResult outcome = _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
if (outcome == StateUpdateResult::ERROR) {
request->send(400);
return;
}
if (outcome == StateUpdateResult::CHANGED) {
request->onDisconnect([this]() { _statefulService->callUpdateHandlers(HTTP_ENDPOINT_ORIGIN_ID); });
}
AsyncJsonResponse* response = new AsyncJsonResponse(false, _bufferSize);
jsonObject = response->getRoot().to<JsonObject>();
_statefulService->read(jsonObject, _stateReader);
response->setLength();
request->send(response);
}
};
template <class T>
class HttpEndpoint : public HttpGetEndpoint<T>, public HttpPostEndpoint<T> {
public:
HttpEndpoint(JsonStateReader<T> stateReader,
JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService,
AsyncWebServer* server,
const String& servicePath,
SecurityManager* securityManager,
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
HttpGetEndpoint<T>(stateReader,
statefulService,
server,
servicePath,
securityManager,
authenticationPredicate,
bufferSize),
HttpPostEndpoint<T>(stateReader,
stateUpdater,
statefulService,
server,
servicePath,
securityManager,
authenticationPredicate,
bufferSize) {
}
HttpEndpoint(JsonStateReader<T> stateReader,
JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService,
AsyncWebServer* server,
const String& servicePath,
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
HttpGetEndpoint<T>(stateReader, statefulService, server, servicePath, bufferSize),
HttpPostEndpoint<T>(stateReader, stateUpdater, statefulService, server, servicePath, bufferSize) {
}
};
#endif // end HttpEndpoint

22
lib/framework/JsonUtils.h Normal file
View File

@@ -0,0 +1,22 @@
#ifndef JsonUtils_h
#define JsonUtils_h
#include <Arduino.h>
#include <IPAddress.h>
#include <ArduinoJson.h>
class JsonUtils {
public:
static void readIP(JsonObject& root, const String& key, IPAddress& ip) {
if (!root[key].is<String>() || !ip.fromString(root[key].as<String>())) {
ip = INADDR_NONE;
}
}
static void writeIP(JsonObject& root, const String& key, const IPAddress& ip) {
if (ip != INADDR_NONE) {
root[key] = ip.toString();
}
}
};
#endif // end JsonUtils

167
lib/framework/MqttPubSub.h Normal file
View File

@@ -0,0 +1,167 @@
#ifndef MqttPubSub_h
#define MqttPubSub_h
#include <StatefulService.h>
#include <AsyncMqttClient.h>
#define MQTT_ORIGIN_ID "mqtt"
template <class T>
class MqttConnector {
protected:
StatefulService<T>* _statefulService;
AsyncMqttClient* _mqttClient;
size_t _bufferSize;
MqttConnector(StatefulService<T>* statefulService, AsyncMqttClient* mqttClient, size_t bufferSize) :
_statefulService(statefulService), _mqttClient(mqttClient), _bufferSize(bufferSize) {
_mqttClient->onConnect(std::bind(&MqttConnector::onConnect, this));
}
virtual void onConnect() = 0;
public:
inline AsyncMqttClient* getMqttClient() const {
return _mqttClient;
}
};
template <class T>
class MqttPub : virtual public MqttConnector<T> {
public:
MqttPub(JsonStateReader<T> stateReader,
StatefulService<T>* statefulService,
AsyncMqttClient* mqttClient,
const String& pubTopic = "",
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
MqttConnector<T>(statefulService, mqttClient, bufferSize), _stateReader(stateReader), _pubTopic(pubTopic) {
MqttConnector<T>::_statefulService->addUpdateHandler([&](const String& originId) { publish(); }, false);
}
void setPubTopic(const String& pubTopic) {
_pubTopic = pubTopic;
publish();
}
protected:
virtual void onConnect() {
publish();
}
private:
JsonStateReader<T> _stateReader;
String _pubTopic;
void publish() {
if (_pubTopic.length() > 0 && MqttConnector<T>::_mqttClient->connected()) {
// serialize to json doc
DynamicJsonDocument json(MqttConnector<T>::_bufferSize);
JsonObject jsonObject = json.to<JsonObject>();
MqttConnector<T>::_statefulService->read(jsonObject, _stateReader);
// serialize to string
String payload;
serializeJson(json, payload);
// publish the payload
MqttConnector<T>::_mqttClient->publish(_pubTopic.c_str(), 0, false, payload.c_str());
}
}
};
template <class T>
class MqttSub : virtual public MqttConnector<T> {
public:
MqttSub(JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService,
AsyncMqttClient* mqttClient,
const String& subTopic = "",
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
MqttConnector<T>(statefulService, mqttClient, bufferSize), _stateUpdater(stateUpdater), _subTopic(subTopic) {
MqttConnector<T>::_mqttClient->onMessage(std::bind(&MqttSub::onMqttMessage,
this,
std::placeholders::_1,
std::placeholders::_2,
std::placeholders::_3,
std::placeholders::_4,
std::placeholders::_5,
std::placeholders::_6));
}
void setSubTopic(const String& subTopic) {
if (!_subTopic.equals(subTopic)) {
// unsubscribe from the existing topic if one was set
if (_subTopic.length() > 0) {
MqttConnector<T>::_mqttClient->unsubscribe(_subTopic.c_str());
}
// set the new topic and re-configure the subscription
_subTopic = subTopic;
subscribe();
}
}
protected:
virtual void onConnect() {
subscribe();
}
private:
JsonStateUpdater<T> _stateUpdater;
String _subTopic;
void subscribe() {
if (_subTopic.length() > 0) {
MqttConnector<T>::_mqttClient->subscribe(_subTopic.c_str(), 2);
}
}
void onMqttMessage(char* topic,
char* payload,
AsyncMqttClientMessageProperties properties,
size_t len,
size_t index,
size_t total) {
// we only care about the topic we are watching in this class
if (strcmp(_subTopic.c_str(), topic)) {
return;
}
// deserialize from string
DynamicJsonDocument json(MqttConnector<T>::_bufferSize);
DeserializationError error = deserializeJson(json, payload, len);
if (!error && json.is<JsonObject>()) {
JsonObject jsonObject = json.as<JsonObject>();
MqttConnector<T>::_statefulService->update(jsonObject, _stateUpdater, MQTT_ORIGIN_ID);
}
}
};
template <class T>
class MqttPubSub : public MqttPub<T>, public MqttSub<T> {
public:
MqttPubSub(JsonStateReader<T> stateReader,
JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService,
AsyncMqttClient* mqttClient,
const String& pubTopic = "",
const String& subTopic = "",
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
MqttConnector<T>(statefulService, mqttClient, bufferSize),
MqttPub<T>(stateReader, statefulService, mqttClient, pubTopic, bufferSize),
MqttSub<T>(stateUpdater, statefulService, mqttClient, subTopic, bufferSize) {
}
public:
void configureTopics(const String& pubTopic, const String& subTopic) {
MqttSub<T>::setSubTopic(subTopic);
MqttPub<T>::setPubTopic(pubTopic);
}
protected:
void onConnect() {
MqttSub<T>::onConnect();
MqttPub<T>::onConnect();
}
};
#endif // end MqttPubSub

View File

@@ -0,0 +1,161 @@
#include <MqttSettingsService.h>
/**
* Retains a copy of the cstr provided in the pointer provided using dynamic allocation.
*
* Frees the pointer before allocation and leaves it as nullptr if cstr == nullptr.
*/
static char* retainCstr(const char* cstr, char** ptr) {
// free up previously retained value if exists
free(*ptr);
*ptr = nullptr;
// dynamically allocate and copy cstr (if non null)
if (cstr != nullptr) {
*ptr = (char*)malloc(strlen(cstr) + 1);
strcpy(*ptr, cstr);
}
// return reference to pointer for convenience
return *ptr;
}
MqttSettingsService::MqttSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) :
_httpEndpoint(MqttSettings::read, MqttSettings::update, this, server, MQTT_SETTINGS_SERVICE_PATH, securityManager),
_fsPersistence(MqttSettings::read, MqttSettings::update, this, fs, MQTT_SETTINGS_FILE),
_retainedHost(nullptr),
_retainedClientId(nullptr),
_retainedUsername(nullptr),
_retainedPassword(nullptr),
_reconfigureMqtt(false),
_disconnectedAt(0),
_disconnectReason(AsyncMqttClientDisconnectReason::TCP_DISCONNECTED),
_mqttClient() {
#ifdef ESP32
WiFi.onEvent(
std::bind(&MqttSettingsService::onStationModeDisconnected, this, std::placeholders::_1, std::placeholders::_2),
WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED);
WiFi.onEvent(std::bind(&MqttSettingsService::onStationModeGotIP, this, std::placeholders::_1, std::placeholders::_2),
WiFiEvent_t::SYSTEM_EVENT_STA_GOT_IP);
#elif defined(ESP8266)
_onStationModeDisconnectedHandler = WiFi.onStationModeDisconnected(
std::bind(&MqttSettingsService::onStationModeDisconnected, this, std::placeholders::_1));
_onStationModeGotIPHandler =
WiFi.onStationModeGotIP(std::bind(&MqttSettingsService::onStationModeGotIP, this, std::placeholders::_1));
#endif
_mqttClient.onConnect(std::bind(&MqttSettingsService::onMqttConnect, this, std::placeholders::_1));
_mqttClient.onDisconnect(std::bind(&MqttSettingsService::onMqttDisconnect, this, std::placeholders::_1));
addUpdateHandler([&](const String& originId) { onConfigUpdated(); }, false);
}
MqttSettingsService::~MqttSettingsService() {
}
void MqttSettingsService::begin() {
_fsPersistence.readFromFS();
}
void MqttSettingsService::loop() {
if (_reconfigureMqtt || (_disconnectedAt && (unsigned long)(millis() - _disconnectedAt) >= MQTT_RECONNECTION_DELAY)) {
// reconfigure MQTT client
configureMqtt();
// clear the reconnection flags
_reconfigureMqtt = false;
_disconnectedAt = 0;
}
}
bool MqttSettingsService::isEnabled() {
return _state.enabled;
}
bool MqttSettingsService::isConnected() {
return _mqttClient.connected();
}
const char* MqttSettingsService::getClientId() {
return _mqttClient.getClientId();
}
AsyncMqttClientDisconnectReason MqttSettingsService::getDisconnectReason() {
return _disconnectReason;
}
AsyncMqttClient* MqttSettingsService::getMqttClient() {
return &_mqttClient;
}
void MqttSettingsService::onMqttConnect(bool sessionPresent) {
Serial.print(F("Connected to MQTT, "));
if (sessionPresent) {
Serial.println(F("with persistent session"));
} else {
Serial.println(F("without persistent session"));
}
}
void MqttSettingsService::onMqttDisconnect(AsyncMqttClientDisconnectReason reason) {
Serial.print(F("Disconnected from MQTT reason: "));
Serial.println((uint8_t)reason);
_disconnectReason = reason;
_disconnectedAt = millis();
}
void MqttSettingsService::onConfigUpdated() {
_reconfigureMqtt = true;
_disconnectedAt = 0;
}
#ifdef ESP32
void MqttSettingsService::onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info) {
if (_state.enabled) {
Serial.println(F("WiFi connection dropped, starting MQTT client."));
onConfigUpdated();
}
}
void MqttSettingsService::onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) {
if (_state.enabled) {
Serial.println(F("WiFi connection dropped, stopping MQTT client."));
onConfigUpdated();
}
}
#elif defined(ESP8266)
void MqttSettingsService::onStationModeGotIP(const WiFiEventStationModeGotIP& event) {
if (_state.enabled) {
Serial.println(F("WiFi connection dropped, starting MQTT client."));
onConfigUpdated();
}
}
void MqttSettingsService::onStationModeDisconnected(const WiFiEventStationModeDisconnected& event) {
if (_state.enabled) {
Serial.println(F("WiFi connection dropped, stopping MQTT client."));
onConfigUpdated();
}
}
#endif
void MqttSettingsService::configureMqtt() {
// disconnect if currently connected
_mqttClient.disconnect();
// only connect if WiFi is connected and MQTT is enabled
if (_state.enabled && WiFi.isConnected()) {
Serial.println(F("Connecting to MQTT..."));
_mqttClient.setServer(retainCstr(_state.host.c_str(), &_retainedHost), _state.port);
if (_state.username.length() > 0) {
_mqttClient.setCredentials(
retainCstr(_state.username.c_str(), &_retainedUsername),
retainCstr(_state.password.length() > 0 ? _state.password.c_str() : nullptr, &_retainedPassword));
} else {
_mqttClient.setCredentials(retainCstr(nullptr, &_retainedUsername), retainCstr(nullptr, &_retainedPassword));
}
_mqttClient.setClientId(retainCstr(_state.clientId.c_str(), &_retainedClientId));
_mqttClient.setKeepAlive(_state.keepAlive);
_mqttClient.setCleanSession(_state.cleanSession);
_mqttClient.setMaxTopicLength(_state.maxTopicLength);
_mqttClient.connect();
}
}

View File

@@ -0,0 +1,156 @@
#ifndef MqttSettingsService_h
#define MqttSettingsService_h
#include <StatefulService.h>
#include <HttpEndpoint.h>
#include <FSPersistence.h>
#include <AsyncMqttClient.h>
#include <ESPUtils.h>
#define MQTT_RECONNECTION_DELAY 5000
#define MQTT_SETTINGS_FILE "/config/mqttSettings.json"
#define MQTT_SETTINGS_SERVICE_PATH "/rest/mqttSettings"
#ifndef FACTORY_MQTT_ENABLED
#define FACTORY_MQTT_ENABLED false
#endif
#ifndef FACTORY_MQTT_HOST
#define FACTORY_MQTT_HOST "test.mosquitto.org"
#endif
#ifndef FACTORY_MQTT_PORT
#define FACTORY_MQTT_PORT 1883
#endif
#ifndef FACTORY_MQTT_USERNAME
#define FACTORY_MQTT_USERNAME ""
#endif
#ifndef FACTORY_MQTT_PASSWORD
#define FACTORY_MQTT_PASSWORD ""
#endif
#ifndef FACTORY_MQTT_CLIENT_ID
#define FACTORY_MQTT_CLIENT_ID generateClientId()
#endif
#ifndef FACTORY_MQTT_KEEP_ALIVE
#define FACTORY_MQTT_KEEP_ALIVE 16
#endif
#ifndef FACTORY_MQTT_CLEAN_SESSION
#define FACTORY_MQTT_CLEAN_SESSION true
#endif
#ifndef FACTORY_MQTT_MAX_TOPIC_LENGTH
#define FACTORY_MQTT_MAX_TOPIC_LENGTH 128
#endif
static String generateClientId() {
#ifdef ESP32
return ESPUtils::defaultDeviceValue("esp32-");
#elif defined(ESP8266)
return ESPUtils::defaultDeviceValue("esp8266-");
#endif
}
class MqttSettings {
public:
// host and port - if enabled
bool enabled;
String host;
uint16_t port;
// username and password
String username;
String password;
// client id settings
String clientId;
// connection settings
uint16_t keepAlive;
bool cleanSession;
uint16_t maxTopicLength;
static void read(MqttSettings& settings, JsonObject& root) {
root["enabled"] = settings.enabled;
root["host"] = settings.host;
root["port"] = settings.port;
root["username"] = settings.username;
root["password"] = settings.password;
root["client_id"] = settings.clientId;
root["keep_alive"] = settings.keepAlive;
root["clean_session"] = settings.cleanSession;
root["max_topic_length"] = settings.maxTopicLength;
}
static StateUpdateResult update(JsonObject& root, MqttSettings& settings) {
settings.enabled = root["enabled"] | FACTORY_MQTT_ENABLED;
settings.host = root["host"] | FACTORY_MQTT_HOST;
settings.port = root["port"] | FACTORY_MQTT_PORT;
settings.username = root["username"] | FACTORY_MQTT_USERNAME;
settings.password = root["password"] | FACTORY_MQTT_PASSWORD;
settings.clientId = root["client_id"] | FACTORY_MQTT_CLIENT_ID;
settings.keepAlive = root["keep_alive"] | FACTORY_MQTT_KEEP_ALIVE;
settings.cleanSession = root["clean_session"] | FACTORY_MQTT_CLEAN_SESSION;
settings.maxTopicLength = root["max_topic_length"] | FACTORY_MQTT_MAX_TOPIC_LENGTH;
return StateUpdateResult::CHANGED;
}
};
class MqttSettingsService : public StatefulService<MqttSettings> {
public:
MqttSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
~MqttSettingsService();
void begin();
void loop();
bool isEnabled();
bool isConnected();
const char* getClientId();
AsyncMqttClientDisconnectReason getDisconnectReason();
AsyncMqttClient* getMqttClient();
protected:
void onConfigUpdated();
private:
HttpEndpoint<MqttSettings> _httpEndpoint;
FSPersistence<MqttSettings> _fsPersistence;
// Pointers to hold retained copies of the mqtt client connection strings.
// This is required as AsyncMqttClient holds refrences to the supplied connection strings.
char* _retainedHost;
char* _retainedClientId;
char* _retainedUsername;
char* _retainedPassword;
// variable to help manage connection
bool _reconfigureMqtt;
unsigned long _disconnectedAt;
// connection status
AsyncMqttClientDisconnectReason _disconnectReason;
// the MQTT client instance
AsyncMqttClient _mqttClient;
#ifdef ESP32
void onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info);
void onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info);
#elif defined(ESP8266)
WiFiEventHandler _onStationModeDisconnectedHandler;
WiFiEventHandler _onStationModeGotIPHandler;
void onStationModeGotIP(const WiFiEventStationModeGotIP& event);
void onStationModeDisconnected(const WiFiEventStationModeDisconnected& event);
#endif
void onMqttConnect(bool sessionPresent);
void onMqttDisconnect(AsyncMqttClientDisconnectReason reason);
void configureMqtt();
};
#endif // end MqttSettingsService_h

View File

@@ -0,0 +1,24 @@
#include <MqttStatus.h>
MqttStatus::MqttStatus(AsyncWebServer* server,
MqttSettingsService* mqttSettingsService,
SecurityManager* securityManager) :
_mqttSettingsService(mqttSettingsService) {
server->on(MQTT_STATUS_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&MqttStatus::mqttStatus, this, std::placeholders::_1),
AuthenticationPredicates::IS_AUTHENTICATED));
}
void MqttStatus::mqttStatus(AsyncWebServerRequest* request) {
AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_MQTT_STATUS_SIZE);
JsonObject root = response->getRoot();
root["enabled"] = _mqttSettingsService->isEnabled();
root["connected"] = _mqttSettingsService->isConnected();
root["client_id"] = _mqttSettingsService->getClientId();
root["disconnect_reason"] = (uint8_t)_mqttSettingsService->getDisconnectReason();
response->setLength();
request->send(response);
}

View File

@@ -0,0 +1,31 @@
#ifndef MqttStatus_h
#define MqttStatus_h
#ifdef ESP32
#include <WiFi.h>
#include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include <MqttSettingsService.h>
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <ESPAsyncWebServer.h>
#include <SecurityManager.h>
#define MAX_MQTT_STATUS_SIZE 1024
#define MQTT_STATUS_SERVICE_PATH "/rest/mqttStatus"
class MqttStatus {
public:
MqttStatus(AsyncWebServer* server, MqttSettingsService* mqttSettingsService, SecurityManager* securityManager);
private:
MqttSettingsService* _mqttSettingsService;
void mqttStatus(AsyncWebServerRequest* request);
};
#endif // end MqttStatus_h

View File

@@ -0,0 +1,90 @@
#include <NTPSettingsService.h>
NTPSettingsService::NTPSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) :
_httpEndpoint(NTPSettings::read, NTPSettings::update, this, server, NTP_SETTINGS_SERVICE_PATH, securityManager),
_fsPersistence(NTPSettings::read, NTPSettings::update, this, fs, NTP_SETTINGS_FILE),
_timeHandler(TIME_PATH,
securityManager->wrapCallback(
std::bind(&NTPSettingsService::configureTime, this, std::placeholders::_1, std::placeholders::_2),
AuthenticationPredicates::IS_ADMIN)) {
_timeHandler.setMethod(HTTP_POST);
_timeHandler.setMaxContentLength(MAX_TIME_SIZE);
server->addHandler(&_timeHandler);
#ifdef ESP32
WiFi.onEvent(
std::bind(&NTPSettingsService::onStationModeDisconnected, this, std::placeholders::_1, std::placeholders::_2),
WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED);
WiFi.onEvent(std::bind(&NTPSettingsService::onStationModeGotIP, this, std::placeholders::_1, std::placeholders::_2),
WiFiEvent_t::SYSTEM_EVENT_STA_GOT_IP);
#elif defined(ESP8266)
_onStationModeDisconnectedHandler = WiFi.onStationModeDisconnected(
std::bind(&NTPSettingsService::onStationModeDisconnected, this, std::placeholders::_1));
_onStationModeGotIPHandler =
WiFi.onStationModeGotIP(std::bind(&NTPSettingsService::onStationModeGotIP, this, std::placeholders::_1));
#endif
addUpdateHandler([&](const String& originId) { configureNTP(); }, false);
}
void NTPSettingsService::begin() {
_fsPersistence.readFromFS();
configureNTP();
}
#ifdef ESP32
void NTPSettingsService::onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info) {
Serial.println(F("Got IP address, starting NTP Synchronization"));
configureNTP();
}
void NTPSettingsService::onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) {
Serial.println(F("WiFi connection dropped, stopping NTP."));
configureNTP();
}
#elif defined(ESP8266)
void NTPSettingsService::onStationModeGotIP(const WiFiEventStationModeGotIP& event) {
Serial.println(F("Got IP address, starting NTP Synchronization"));
configureNTP();
}
void NTPSettingsService::onStationModeDisconnected(const WiFiEventStationModeDisconnected& event) {
Serial.println(F("WiFi connection dropped, stopping NTP."));
configureNTP();
}
#endif
void NTPSettingsService::configureNTP() {
if (WiFi.isConnected() && _state.enabled) {
Serial.println(F("Starting NTP..."));
#ifdef ESP32
configTzTime(_state.tzFormat.c_str(), _state.server.c_str());
#elif defined(ESP8266)
configTime(_state.tzFormat.c_str(), _state.server.c_str());
#endif
} else {
#ifdef ESP32
setenv("TZ", _state.tzFormat.c_str(), 1);
tzset();
#elif defined(ESP8266)
setTZ(_state.tzFormat.c_str());
#endif
sntp_stop();
}
}
void NTPSettingsService::configureTime(AsyncWebServerRequest* request, JsonVariant& json) {
if (!sntp_enabled() && json.is<JsonObject>()) {
String timeUtc = json["time_utc"];
struct tm tm = {0};
char* s = strptime(timeUtc.c_str(), "%Y-%m-%dT%H:%M:%SZ", &tm);
if (s != nullptr) {
time_t time = mktime(&tm);
struct timeval now = {.tv_sec = time};
settimeofday(&now, nullptr);
AsyncWebServerResponse* response = request->beginResponse(200);
request->send(response);
return;
}
}
AsyncWebServerResponse* response = request->beginResponse(400);
request->send(response);
}

View File

@@ -0,0 +1,84 @@
#ifndef NTPSettingsService_h
#define NTPSettingsService_h
#include <HttpEndpoint.h>
#include <FSPersistence.h>
#include <time.h>
#ifdef ESP32
#include <lwip/apps/sntp.h>
#elif defined(ESP8266)
#include <sntp.h>
#endif
#ifndef FACTORY_NTP_ENABLED
#define FACTORY_NTP_ENABLED true
#endif
#ifndef FACTORY_NTP_TIME_ZONE_LABEL
#define FACTORY_NTP_TIME_ZONE_LABEL "Europe/London"
#endif
#ifndef FACTORY_NTP_TIME_ZONE_FORMAT
#define FACTORY_NTP_TIME_ZONE_FORMAT "GMT0BST,M3.5.0/1,M10.5.0"
#endif
#ifndef FACTORY_NTP_SERVER
#define FACTORY_NTP_SERVER "time.google.com"
#endif
#define NTP_SETTINGS_FILE "/config/ntpSettings.json"
#define NTP_SETTINGS_SERVICE_PATH "/rest/ntpSettings"
#define MAX_TIME_SIZE 256
#define TIME_PATH "/rest/time"
class NTPSettings {
public:
bool enabled;
String tzLabel;
String tzFormat;
String server;
static void read(NTPSettings& settings, JsonObject& root) {
root["enabled"] = settings.enabled;
root["server"] = settings.server;
root["tz_label"] = settings.tzLabel;
root["tz_format"] = settings.tzFormat;
}
static StateUpdateResult update(JsonObject& root, NTPSettings& settings) {
settings.enabled = root["enabled"] | FACTORY_NTP_ENABLED;
settings.server = root["server"] | FACTORY_NTP_SERVER;
settings.tzLabel = root["tz_label"] | FACTORY_NTP_TIME_ZONE_LABEL;
settings.tzFormat = root["tz_format"] | FACTORY_NTP_TIME_ZONE_FORMAT;
return StateUpdateResult::CHANGED;
}
};
class NTPSettingsService : public StatefulService<NTPSettings> {
public:
NTPSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
void begin();
private:
HttpEndpoint<NTPSettings> _httpEndpoint;
FSPersistence<NTPSettings> _fsPersistence;
AsyncCallbackJsonWebHandler _timeHandler;
#ifdef ESP32
void onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info);
void onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info);
#elif defined(ESP8266)
WiFiEventHandler _onStationModeDisconnectedHandler;
WiFiEventHandler _onStationModeGotIPHandler;
void onStationModeGotIP(const WiFiEventStationModeGotIP& event);
void onStationModeDisconnected(const WiFiEventStationModeDisconnected& event);
#endif
void configureNTP();
void configureTime(AsyncWebServerRequest* request, JsonVariant& json);
};
#endif // end NTPSettingsService_h

View File

@@ -0,0 +1,40 @@
#include <NTPStatus.h>
NTPStatus::NTPStatus(AsyncWebServer* server, SecurityManager* securityManager) {
server->on(NTP_STATUS_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&NTPStatus::ntpStatus, this, std::placeholders::_1),
AuthenticationPredicates::IS_AUTHENTICATED));
}
String toISOString(tm* time, bool incOffset) {
char time_string[25];
strftime(time_string, 25, incOffset ? "%FT%T%z" : "%FT%TZ", time);
return String(time_string);
}
void NTPStatus::ntpStatus(AsyncWebServerRequest* request) {
AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_NTP_STATUS_SIZE);
JsonObject root = response->getRoot();
// grab the current instant in unix seconds
time_t now = time(nullptr);
// only provide enabled/disabled status for now
root["status"] = sntp_enabled() ? 1 : 0;
// the current time in UTC
root["time_utc"] = toISOString(gmtime(&now), false);
// local time as ISO String with TZ
root["time_local"] = toISOString(localtime(&now), true);
// the sntp server name
root["server"] = sntp_getservername(0);
// device uptime in seconds
root["uptime"] = millis() / 1000;
response->setLength();
request->send(response);
}

31
lib/framework/NTPStatus.h Normal file
View File

@@ -0,0 +1,31 @@
#ifndef NTPStatus_h
#define NTPStatus_h
#include <time.h>
#ifdef ESP32
#include <WiFi.h>
#include <AsyncTCP.h>
#include <lwip/apps/sntp.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <sntp.h>
#endif
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <ESPAsyncWebServer.h>
#include <SecurityManager.h>
#define MAX_NTP_STATUS_SIZE 1024
#define NTP_STATUS_SERVICE_PATH "/rest/ntpStatus"
class NTPStatus {
public:
NTPStatus(AsyncWebServer* server, SecurityManager* securityManager);
private:
void ntpStatus(AsyncWebServerRequest* request);
};
#endif // end NTPStatus_h

View File

@@ -0,0 +1,71 @@
#include <OTASettingsService.h>
OTASettingsService::OTASettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) :
_httpEndpoint(OTASettings::read, OTASettings::update, this, server, OTA_SETTINGS_SERVICE_PATH, securityManager),
_fsPersistence(OTASettings::read, OTASettings::update, this, fs, OTA_SETTINGS_FILE),
_arduinoOTA(nullptr) {
#ifdef ESP32
WiFi.onEvent(std::bind(&OTASettingsService::onStationModeGotIP, this, std::placeholders::_1, std::placeholders::_2),
WiFiEvent_t::SYSTEM_EVENT_STA_GOT_IP);
#elif defined(ESP8266)
_onStationModeGotIPHandler =
WiFi.onStationModeGotIP(std::bind(&OTASettingsService::onStationModeGotIP, this, std::placeholders::_1));
#endif
addUpdateHandler([&](const String& originId) { configureArduinoOTA(); }, false);
}
void OTASettingsService::begin() {
_fsPersistence.readFromFS();
configureArduinoOTA();
}
void OTASettingsService::loop() {
if (_state.enabled && _arduinoOTA) {
_arduinoOTA->handle();
}
}
void OTASettingsService::configureArduinoOTA() {
if (_arduinoOTA) {
#ifdef ESP32
_arduinoOTA->end();
#endif
delete _arduinoOTA;
_arduinoOTA = nullptr;
}
if (_state.enabled) {
Serial.println(F("Starting OTA Update Service..."));
_arduinoOTA = new ArduinoOTAClass;
_arduinoOTA->setPort(_state.port);
_arduinoOTA->setPassword(_state.password.c_str());
_arduinoOTA->onStart([]() { Serial.println(F("Starting")); });
_arduinoOTA->onEnd([]() { Serial.println(F("\r\nEnd")); });
_arduinoOTA->onProgress([](unsigned int progress, unsigned int total) {
Serial.printf_P(PSTR("Progress: %u%%\r\n"), (progress / (total / 100)));
});
_arduinoOTA->onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR)
Serial.println(F("Auth Failed"));
else if (error == OTA_BEGIN_ERROR)
Serial.println(F("Begin Failed"));
else if (error == OTA_CONNECT_ERROR)
Serial.println(F("Connect Failed"));
else if (error == OTA_RECEIVE_ERROR)
Serial.println(F("Receive Failed"));
else if (error == OTA_END_ERROR)
Serial.println(F("End Failed"));
});
_arduinoOTA->begin();
}
}
#ifdef ESP32
void OTASettingsService::onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info) {
configureArduinoOTA();
}
#elif defined(ESP8266)
void OTASettingsService::onStationModeGotIP(const WiFiEventStationModeGotIP& event) {
configureArduinoOTA();
}
#endif

View File

@@ -0,0 +1,72 @@
#ifndef OTASettingsService_h
#define OTASettingsService_h
#include <HttpEndpoint.h>
#include <FSPersistence.h>
#ifdef ESP32
#include <ESPmDNS.h>
#elif defined(ESP8266)
#include <ESP8266mDNS.h>
#endif
#include <ArduinoOTA.h>
#include <WiFiUdp.h>
#ifndef FACTORY_OTA_PORT
#define FACTORY_OTA_PORT 8266
#endif
#ifndef FACTORY_OTA_PASSWORD
#define FACTORY_OTA_PASSWORD "esp-react"
#endif
#ifndef FACTORY_OTA_ENABLED
#define FACTORY_OTA_ENABLED true
#endif
#define OTA_SETTINGS_FILE "/config/otaSettings.json"
#define OTA_SETTINGS_SERVICE_PATH "/rest/otaSettings"
class OTASettings {
public:
bool enabled;
int port;
String password;
static void read(OTASettings& settings, JsonObject& root) {
root["enabled"] = settings.enabled;
root["port"] = settings.port;
root["password"] = settings.password;
}
static StateUpdateResult update(JsonObject& root, OTASettings& settings) {
settings.enabled = root["enabled"] | FACTORY_OTA_ENABLED;
settings.port = root["port"] | FACTORY_OTA_PORT;
settings.password = root["password"] | FACTORY_OTA_PASSWORD;
return StateUpdateResult::CHANGED;
}
};
class OTASettingsService : public StatefulService<OTASettings> {
public:
OTASettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
void begin();
void loop();
private:
HttpEndpoint<OTASettings> _httpEndpoint;
FSPersistence<OTASettings> _fsPersistence;
ArduinoOTAClass* _arduinoOTA;
void configureArduinoOTA();
#ifdef ESP32
void onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info);
#elif defined(ESP8266)
WiFiEventHandler _onStationModeGotIPHandler;
void onStationModeGotIP(const WiFiEventStationModeGotIP& event);
#endif
};
#endif // end OTASettingsService_h

View File

@@ -0,0 +1,13 @@
#include <RestartService.h>
RestartService::RestartService(AsyncWebServer* server, SecurityManager* securityManager) {
server->on(RESTART_SERVICE_PATH,
HTTP_POST,
securityManager->wrapRequest(std::bind(&RestartService::restart, this, std::placeholders::_1),
AuthenticationPredicates::IS_ADMIN));
}
void RestartService::restart(AsyncWebServerRequest* request) {
request->onDisconnect([]() { RestartService::restartNow(); });
request->send(200);
}

View File

@@ -0,0 +1,31 @@
#ifndef RestartService_h
#define RestartService_h
#ifdef ESP32
#include <WiFi.h>
#include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include <ESPAsyncWebServer.h>
#include <SecurityManager.h>
#define RESTART_SERVICE_PATH "/rest/restart"
class RestartService {
public:
RestartService(AsyncWebServer* server, SecurityManager* securityManager);
static void restartNow() {
WiFi.disconnect(true);
delay(500);
ESP.restart();
}
private:
void restart(AsyncWebServerRequest* request);
};
#endif // end RestartService_h

View File

@@ -0,0 +1,102 @@
#ifndef SecurityManager_h
#define SecurityManager_h
#include <Features.h>
#include <ArduinoJsonJWT.h>
#include <ESPAsyncWebServer.h>
#include <ESPUtils.h>
#include <AsyncJson.h>
#include <list>
#ifndef FACTORY_JWT_SECRET
#define FACTORY_JWT_SECRET ESPUtils::defaultDeviceValue()
#endif
#define ACCESS_TOKEN_PARAMATER "access_token"
#define AUTHORIZATION_HEADER "Authorization"
#define AUTHORIZATION_HEADER_PREFIX "Bearer "
#define AUTHORIZATION_HEADER_PREFIX_LEN 7
#define MAX_JWT_SIZE 128
class User {
public:
String username;
String password;
bool admin;
public:
User(String username, String password, bool admin) : username(username), password(password), admin(admin) {
}
};
class Authentication {
public:
User* user;
boolean authenticated;
public:
Authentication(User& user) : user(new User(user)), authenticated(true) {
}
Authentication() : user(nullptr), authenticated(false) {
}
~Authentication() {
delete (user);
}
};
typedef std::function<boolean(Authentication& authentication)> AuthenticationPredicate;
class AuthenticationPredicates {
public:
static bool NONE_REQUIRED(Authentication& authentication) {
return true;
};
static bool IS_AUTHENTICATED(Authentication& authentication) {
return authentication.authenticated;
};
static bool IS_ADMIN(Authentication& authentication) {
return authentication.authenticated && authentication.user->admin;
};
};
class SecurityManager {
public:
#if FT_ENABLED(FT_SECURITY)
/*
* Authenticate, returning the user if found
*/
virtual Authentication authenticate(const String& username, const String& password) = 0;
/*
* Generate a JWT for the user provided
*/
virtual String generateJWT(User* user) = 0;
#endif
/*
* Check the request header for the Authorization token
*/
virtual Authentication authenticateRequest(AsyncWebServerRequest* request) = 0;
/**
* Filter a request with the provided predicate, only returning true if the predicate matches.
*/
virtual ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate) = 0;
/**
* Wrap the provided request to provide validation against an AuthenticationPredicate.
*/
virtual ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest,
AuthenticationPredicate predicate) = 0;
/**
* Wrap the provided json request callback to provide validation against an AuthenticationPredicate.
*/
virtual ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction onRequest,
AuthenticationPredicate predicate) = 0;
};
#endif // end SecurityManager_h

View File

@@ -0,0 +1,140 @@
#include <SecuritySettingsService.h>
#if FT_ENABLED(FT_SECURITY)
SecuritySettingsService::SecuritySettingsService(AsyncWebServer* server, FS* fs) :
_httpEndpoint(SecuritySettings::read, SecuritySettings::update, this, server, SECURITY_SETTINGS_PATH, this),
_fsPersistence(SecuritySettings::read, SecuritySettings::update, this, fs, SECURITY_SETTINGS_FILE),
_jwtHandler(FACTORY_JWT_SECRET) {
addUpdateHandler([&](const String& originId) { configureJWTHandler(); }, false);
}
void SecuritySettingsService::begin() {
_fsPersistence.readFromFS();
configureJWTHandler();
}
Authentication SecuritySettingsService::authenticateRequest(AsyncWebServerRequest* request) {
AsyncWebHeader* authorizationHeader = request->getHeader(AUTHORIZATION_HEADER);
if (authorizationHeader) {
String value = authorizationHeader->value();
if (value.startsWith(AUTHORIZATION_HEADER_PREFIX)) {
value = value.substring(AUTHORIZATION_HEADER_PREFIX_LEN);
return authenticateJWT(value);
}
} else if (request->hasParam(ACCESS_TOKEN_PARAMATER)) {
AsyncWebParameter* tokenParamater = request->getParam(ACCESS_TOKEN_PARAMATER);
String value = tokenParamater->value();
return authenticateJWT(value);
}
return Authentication();
}
void SecuritySettingsService::configureJWTHandler() {
_jwtHandler.setSecret(_state.jwtSecret);
}
Authentication SecuritySettingsService::authenticateJWT(String& jwt) {
DynamicJsonDocument payloadDocument(MAX_JWT_SIZE);
_jwtHandler.parseJWT(jwt, payloadDocument);
if (payloadDocument.is<JsonObject>()) {
JsonObject parsedPayload = payloadDocument.as<JsonObject>();
String username = parsedPayload["username"];
for (User _user : _state.users) {
if (_user.username == username && validatePayload(parsedPayload, &_user)) {
return Authentication(_user);
}
}
}
return Authentication();
}
Authentication SecuritySettingsService::authenticate(const String& username, const String& password) {
for (User _user : _state.users) {
if (_user.username == username && _user.password == password) {
return Authentication(_user);
}
}
return Authentication();
}
inline void populateJWTPayload(JsonObject& payload, User* user) {
payload["username"] = user->username;
payload["admin"] = user->admin;
}
boolean SecuritySettingsService::validatePayload(JsonObject& parsedPayload, User* user) {
DynamicJsonDocument jsonDocument(MAX_JWT_SIZE);
JsonObject payload = jsonDocument.to<JsonObject>();
populateJWTPayload(payload, user);
return payload == parsedPayload;
}
String SecuritySettingsService::generateJWT(User* user) {
DynamicJsonDocument jsonDocument(MAX_JWT_SIZE);
JsonObject payload = jsonDocument.to<JsonObject>();
populateJWTPayload(payload, user);
return _jwtHandler.buildJWT(payload);
}
ArRequestFilterFunction SecuritySettingsService::filterRequest(AuthenticationPredicate predicate) {
return [this, predicate](AsyncWebServerRequest* request) {
Authentication authentication = authenticateRequest(request);
return predicate(authentication);
};
}
ArRequestHandlerFunction SecuritySettingsService::wrapRequest(ArRequestHandlerFunction onRequest,
AuthenticationPredicate predicate) {
return [this, onRequest, predicate](AsyncWebServerRequest* request) {
Authentication authentication = authenticateRequest(request);
if (!predicate(authentication)) {
request->send(401);
return;
}
onRequest(request);
};
}
ArJsonRequestHandlerFunction SecuritySettingsService::wrapCallback(ArJsonRequestHandlerFunction onRequest,
AuthenticationPredicate predicate) {
return [this, onRequest, predicate](AsyncWebServerRequest* request, JsonVariant& json) {
Authentication authentication = authenticateRequest(request);
if (!predicate(authentication)) {
request->send(401);
return;
}
onRequest(request, json);
};
}
#else
User ADMIN_USER = User(FACTORY_ADMIN_USERNAME, FACTORY_ADMIN_PASSWORD, true);
SecuritySettingsService::SecuritySettingsService(AsyncWebServer* server, FS* fs) : SecurityManager() {
}
SecuritySettingsService::~SecuritySettingsService() {
}
ArRequestFilterFunction SecuritySettingsService::filterRequest(AuthenticationPredicate predicate) {
return [this, predicate](AsyncWebServerRequest* request) { return true; };
}
// Return the admin user on all request - disabling security features
Authentication SecuritySettingsService::authenticateRequest(AsyncWebServerRequest* request) {
return Authentication(ADMIN_USER);
}
// Return the function unwrapped
ArRequestHandlerFunction SecuritySettingsService::wrapRequest(ArRequestHandlerFunction onRequest,
AuthenticationPredicate predicate) {
return onRequest;
}
ArJsonRequestHandlerFunction SecuritySettingsService::wrapCallback(ArJsonRequestHandlerFunction onRequest,
AuthenticationPredicate predicate) {
return onRequest;
}
#endif

View File

@@ -0,0 +1,114 @@
#ifndef SecuritySettingsService_h
#define SecuritySettingsService_h
#include <Features.h>
#include <SecurityManager.h>
#include <HttpEndpoint.h>
#include <FSPersistence.h>
#ifndef FACTORY_ADMIN_USERNAME
#define FACTORY_ADMIN_USERNAME "admin"
#endif
#ifndef FACTORY_ADMIN_PASSWORD
#define FACTORY_ADMIN_PASSWORD "admin"
#endif
#ifndef FACTORY_GUEST_USERNAME
#define FACTORY_GUEST_USERNAME "guest"
#endif
#ifndef FACTORY_GUEST_PASSWORD
#define FACTORY_GUEST_PASSWORD "guest"
#endif
#define SECURITY_SETTINGS_FILE "/config/securitySettings.json"
#define SECURITY_SETTINGS_PATH "/rest/securitySettings"
#if FT_ENABLED(FT_SECURITY)
class SecuritySettings {
public:
String jwtSecret;
std::list<User> users;
static void read(SecuritySettings& settings, JsonObject& root) {
// secret
root["jwt_secret"] = settings.jwtSecret;
// users
JsonArray users = root.createNestedArray("users");
for (User user : settings.users) {
JsonObject userRoot = users.createNestedObject();
userRoot["username"] = user.username;
userRoot["password"] = user.password;
userRoot["admin"] = user.admin;
}
}
static StateUpdateResult update(JsonObject& root, SecuritySettings& settings) {
// secret
settings.jwtSecret = root["jwt_secret"] | FACTORY_JWT_SECRET;
// users
settings.users.clear();
if (root["users"].is<JsonArray>()) {
for (JsonVariant user : root["users"].as<JsonArray>()) {
settings.users.push_back(User(user["username"], user["password"], user["admin"]));
}
} else {
settings.users.push_back(User(FACTORY_ADMIN_USERNAME, FACTORY_ADMIN_PASSWORD, true));
settings.users.push_back(User(FACTORY_GUEST_USERNAME, FACTORY_GUEST_PASSWORD, false));
}
return StateUpdateResult::CHANGED;
}
};
class SecuritySettingsService : public StatefulService<SecuritySettings>, public SecurityManager {
public:
SecuritySettingsService(AsyncWebServer* server, FS* fs);
void begin();
// Functions to implement SecurityManager
Authentication authenticate(const String& username, const String& password);
Authentication authenticateRequest(AsyncWebServerRequest* request);
String generateJWT(User* user);
ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate);
ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate);
ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction callback, AuthenticationPredicate predicate);
private:
HttpEndpoint<SecuritySettings> _httpEndpoint;
FSPersistence<SecuritySettings> _fsPersistence;
ArduinoJsonJWT _jwtHandler;
void configureJWTHandler();
/*
* Lookup the user by JWT
*/
Authentication authenticateJWT(String& jwt);
/*
* Verify the payload is correct
*/
boolean validatePayload(JsonObject& parsedPayload, User* user);
};
#else
class SecuritySettingsService : public SecurityManager {
public:
SecuritySettingsService(AsyncWebServer* server, FS* fs);
~SecuritySettingsService();
// minimal set of functions to support framework with security settings disabled
Authentication authenticateRequest(AsyncWebServerRequest* request);
ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate);
ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate);
ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate);
};
#endif // end FT_ENABLED(FT_SECURITY)
#endif // end SecuritySettingsService_h

View File

@@ -0,0 +1,3 @@
#include <StatefulService.h>
update_handler_id_t StateUpdateHandlerInfo::currentUpdatedHandlerId = 0;

View File

@@ -0,0 +1,148 @@
#ifndef StatefulService_h
#define StatefulService_h
#include <Arduino.h>
#include <ArduinoJson.h>
#include <list>
#include <functional>
#ifdef ESP32
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#endif
#ifndef DEFAULT_BUFFER_SIZE
#define DEFAULT_BUFFER_SIZE 1024
#endif
enum class StateUpdateResult {
CHANGED = 0, // The update changed the state and propagation should take place if required
UNCHANGED, // The state was unchanged, propagation should not take place
ERROR // There was a problem updating the state, propagation should not take place
};
template <typename T>
using JsonStateUpdater = std::function<StateUpdateResult(JsonObject& root, T& settings)>;
template <typename T>
using JsonStateReader = std::function<void(T& settings, JsonObject& root)>;
typedef size_t update_handler_id_t;
typedef std::function<void(const String& originId)> StateUpdateCallback;
typedef struct StateUpdateHandlerInfo {
static update_handler_id_t currentUpdatedHandlerId;
update_handler_id_t _id;
StateUpdateCallback _cb;
bool _allowRemove;
StateUpdateHandlerInfo(StateUpdateCallback cb, bool allowRemove) :
_id(++currentUpdatedHandlerId), _cb(cb), _allowRemove(allowRemove){};
} StateUpdateHandlerInfo_t;
template <class T>
class StatefulService {
public:
template <typename... Args>
#ifdef ESP32
StatefulService(Args&&... args) :
_state(std::forward<Args>(args)...), _accessMutex(xSemaphoreCreateRecursiveMutex()) {
}
#else
StatefulService(Args&&... args) : _state(std::forward<Args>(args)...) {
}
#endif
update_handler_id_t addUpdateHandler(StateUpdateCallback cb, bool allowRemove = true) {
if (!cb) {
return 0;
}
StateUpdateHandlerInfo_t updateHandler(cb, allowRemove);
_updateHandlers.push_back(updateHandler);
return updateHandler._id;
}
void removeUpdateHandler(update_handler_id_t id) {
for (auto i = _updateHandlers.begin(); i != _updateHandlers.end();) {
if ((*i)._allowRemove && (*i)._id == id) {
i = _updateHandlers.erase(i);
} else {
++i;
}
}
}
StateUpdateResult update(std::function<StateUpdateResult(T&)> stateUpdater, const String& originId) {
beginTransaction();
StateUpdateResult result = stateUpdater(_state);
endTransaction();
if (result == StateUpdateResult::CHANGED) {
callUpdateHandlers(originId);
}
return result;
}
StateUpdateResult updateWithoutPropagation(std::function<StateUpdateResult(T&)> stateUpdater) {
beginTransaction();
StateUpdateResult result = stateUpdater(_state);
endTransaction();
return result;
}
StateUpdateResult update(JsonObject& jsonObject, JsonStateUpdater<T> stateUpdater, const String& originId) {
beginTransaction();
StateUpdateResult result = stateUpdater(jsonObject, _state);
endTransaction();
if (result == StateUpdateResult::CHANGED) {
callUpdateHandlers(originId);
}
return result;
}
StateUpdateResult updateWithoutPropagation(JsonObject& jsonObject, JsonStateUpdater<T> stateUpdater) {
beginTransaction();
StateUpdateResult result = stateUpdater(jsonObject, _state);
endTransaction();
return result;
}
void read(std::function<void(T&)> stateReader) {
beginTransaction();
stateReader(_state);
endTransaction();
}
void read(JsonObject& jsonObject, JsonStateReader<T> stateReader) {
beginTransaction();
stateReader(_state, jsonObject);
endTransaction();
}
void callUpdateHandlers(const String& originId) {
for (const StateUpdateHandlerInfo_t& updateHandler : _updateHandlers) {
updateHandler._cb(originId);
}
}
protected:
T _state;
inline void beginTransaction() {
#ifdef ESP32
xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY);
#endif
}
inline void endTransaction() {
#ifdef ESP32
xSemaphoreGiveRecursive(_accessMutex);
#endif
}
private:
#ifdef ESP32
SemaphoreHandle_t _accessMutex;
#endif
std::list<StateUpdateHandlerInfo_t> _updateHandlers;
};
#endif // end StatefulService_h

View File

@@ -0,0 +1,45 @@
#include <SystemStatus.h>
SystemStatus::SystemStatus(AsyncWebServer* server, SecurityManager* securityManager) {
server->on(SYSTEM_STATUS_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&SystemStatus::systemStatus, this, std::placeholders::_1),
AuthenticationPredicates::IS_AUTHENTICATED));
}
void SystemStatus::systemStatus(AsyncWebServerRequest* request) {
AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_ESP_STATUS_SIZE);
JsonObject root = response->getRoot();
#ifdef ESP32
root["esp_platform"] = "esp32";
root["max_alloc_heap"] = ESP.getMaxAllocHeap();
root["psram_size"] = ESP.getPsramSize();
root["free_psram"] = ESP.getFreePsram();
#elif defined(ESP8266)
root["esp_platform"] = "esp8266";
root["max_alloc_heap"] = ESP.getMaxFreeBlockSize();
root["heap_fragmentation"] = ESP.getHeapFragmentation();
#endif
root["cpu_freq_mhz"] = ESP.getCpuFreqMHz();
root["free_heap"] = ESP.getFreeHeap();
root["sketch_size"] = ESP.getSketchSize();
root["free_sketch_space"] = ESP.getFreeSketchSpace();
root["sdk_version"] = ESP.getSdkVersion();
root["flash_chip_size"] = ESP.getFlashChipSize();
root["flash_chip_speed"] = ESP.getFlashChipSpeed();
// TODO - Ideally this class will take an *FS and extract the file system information from there.
// ESP8266 and ESP32 do not have feature parity in FS.h which currently makes that difficult.
#ifdef ESP32
root["fs_total"] = SPIFFS.totalBytes();
root["fs_used"] = SPIFFS.usedBytes();
#elif defined(ESP8266)
FSInfo fs_info;
LittleFS.info(fs_info); // TODO added littlefs
root["fs_total"] = fs_info.totalBytes;
root["fs_used"] = fs_info.usedBytes;
#endif
response->setLength();
request->send(response);
}

View File

@@ -0,0 +1,31 @@
#ifndef SystemStatus_h
#define SystemStatus_h
#ifdef ESP32
#include <WiFi.h>
#include <AsyncTCP.h>
#include <SPIFFS.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <FS.h>
#include <LittleFS.h> // TODO added littlefs
#endif
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <ESPAsyncWebServer.h>
#include <SecurityManager.h>
#define MAX_ESP_STATUS_SIZE 1024
#define SYSTEM_STATUS_SERVICE_PATH "/rest/systemStatus"
class SystemStatus {
public:
SystemStatus(AsyncWebServer* server, SecurityManager* securityManager);
private:
void systemStatus(AsyncWebServerRequest* request);
};
#endif // end SystemStatus_h

View File

@@ -0,0 +1,85 @@
#include <UploadFirmwareService.h>
UploadFirmwareService::UploadFirmwareService(AsyncWebServer* server, SecurityManager* securityManager) :
_securityManager(securityManager) {
server->on(UPLOAD_FIRMWARE_PATH,
HTTP_POST,
std::bind(&UploadFirmwareService::uploadComplete, this, std::placeholders::_1),
std::bind(&UploadFirmwareService::handleUpload,
this,
std::placeholders::_1,
std::placeholders::_2,
std::placeholders::_3,
std::placeholders::_4,
std::placeholders::_5,
std::placeholders::_6));
#ifdef ESP8266
Update.runAsync(true);
#endif
}
void UploadFirmwareService::handleUpload(AsyncWebServerRequest* request,
const String& filename,
size_t index,
uint8_t* data,
size_t len,
bool final) {
if (!index) {
Authentication authentication = _securityManager->authenticateRequest(request);
if (AuthenticationPredicates::IS_ADMIN(authentication)) {
if (Update.begin(request->contentLength())) {
// success, let's make sure we end the update if the client hangs up
request->onDisconnect(UploadFirmwareService::handleEarlyDisconnect);
} else {
// failed to begin, send an error response
Update.printError(Serial);
handleError(request, 500);
}
} else {
// send the forbidden response
handleError(request, 403);
}
}
// if we haven't delt with an error, continue with the update
if (!request->_tempObject) {
if (Update.write(data, len) != len) {
Update.printError(Serial);
handleError(request, 500);
}
if (final) {
if (!Update.end(true)) {
Update.printError(Serial);
handleError(request, 500);
}
}
}
}
void UploadFirmwareService::uploadComplete(AsyncWebServerRequest* request) {
// if no error, send the success response
if (!request->_tempObject) {
request->onDisconnect(RestartService::restartNow);
AsyncWebServerResponse* response = request->beginResponse(200);
request->send(response);
}
}
void UploadFirmwareService::handleError(AsyncWebServerRequest* request, int code) {
// if we have had an error already, do nothing
if (request->_tempObject) {
return;
}
// send the error code to the client and record the error code in the temp object
request->_tempObject = new int(code);
AsyncWebServerResponse* response = request->beginResponse(code);
request->send(response);
}
void UploadFirmwareService::handleEarlyDisconnect() {
#ifdef ESP32
Update.abort();
#elif defined(ESP8266)
Update.end();
#endif
}

View File

@@ -0,0 +1,38 @@
#ifndef UploadFirmwareService_h
#define UploadFirmwareService_h
#include <Arduino.h>
#ifdef ESP32
#include <Update.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include <ESPAsyncWebServer.h>
#include <SecurityManager.h>
#include <RestartService.h>
#define UPLOAD_FIRMWARE_PATH "/rest/uploadFirmware"
class UploadFirmwareService {
public:
UploadFirmwareService(AsyncWebServer* server, SecurityManager* securityManager);
private:
SecurityManager* _securityManager;
void handleUpload(AsyncWebServerRequest* request,
const String& filename,
size_t index,
uint8_t* data,
size_t len,
bool final);
void uploadComplete(AsyncWebServerRequest* request);
void handleError(AsyncWebServerRequest* request, int code);
static void handleEarlyDisconnect();
};
#endif // end UploadFirmwareService_h

View File

@@ -0,0 +1,272 @@
#ifndef WebSocketTxRx_h
#define WebSocketTxRx_h
#include <StatefulService.h>
#include <ESPAsyncWebServer.h>
#define WEB_SOCKET_CLIENT_ID_MSG_SIZE 128
#define WEB_SOCKET_ORIGIN "websocket"
#define WEB_SOCKET_ORIGIN_CLIENT_ID_PREFIX "websocket:"
template <class T>
class WebSocketConnector {
protected:
StatefulService<T>* _statefulService;
AsyncWebServer* _server;
AsyncWebSocket _webSocket;
size_t _bufferSize;
WebSocketConnector(StatefulService<T>* statefulService,
AsyncWebServer* server,
char const* webSocketPath,
SecurityManager* securityManager,
AuthenticationPredicate authenticationPredicate,
size_t bufferSize) :
_statefulService(statefulService), _server(server), _webSocket(webSocketPath), _bufferSize(bufferSize) {
_webSocket.setFilter(securityManager->filterRequest(authenticationPredicate));
_webSocket.onEvent(std::bind(&WebSocketConnector::onWSEvent,
this,
std::placeholders::_1,
std::placeholders::_2,
std::placeholders::_3,
std::placeholders::_4,
std::placeholders::_5,
std::placeholders::_6));
_server->addHandler(&_webSocket);
_server->on(webSocketPath, HTTP_GET, std::bind(&WebSocketConnector::forbidden, this, std::placeholders::_1));
}
WebSocketConnector(StatefulService<T>* statefulService,
AsyncWebServer* server,
char const* webSocketPath,
size_t bufferSize) :
_statefulService(statefulService), _server(server), _webSocket(webSocketPath), _bufferSize(bufferSize) {
_webSocket.onEvent(std::bind(&WebSocketConnector::onWSEvent,
this,
std::placeholders::_1,
std::placeholders::_2,
std::placeholders::_3,
std::placeholders::_4,
std::placeholders::_5,
std::placeholders::_6));
_server->addHandler(&_webSocket);
}
virtual void onWSEvent(AsyncWebSocket* server,
AsyncWebSocketClient* client,
AwsEventType type,
void* arg,
uint8_t* data,
size_t len) = 0;
String clientId(AsyncWebSocketClient* client) {
return WEB_SOCKET_ORIGIN_CLIENT_ID_PREFIX + String(client->id());
}
private:
void forbidden(AsyncWebServerRequest* request) {
request->send(403);
}
};
template <class T>
class WebSocketTx : virtual public WebSocketConnector<T> {
public:
WebSocketTx(JsonStateReader<T> stateReader,
StatefulService<T>* statefulService,
AsyncWebServer* server,
char const* webSocketPath,
SecurityManager* securityManager,
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
WebSocketConnector<T>(statefulService,
server,
webSocketPath,
securityManager,
authenticationPredicate,
bufferSize),
_stateReader(stateReader) {
WebSocketConnector<T>::_statefulService->addUpdateHandler(
[&](const String& originId) { transmitData(nullptr, originId); }, false);
}
WebSocketTx(JsonStateReader<T> stateReader,
StatefulService<T>* statefulService,
AsyncWebServer* server,
char const* webSocketPath,
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
WebSocketConnector<T>(statefulService, server, webSocketPath, bufferSize), _stateReader(stateReader) {
WebSocketConnector<T>::_statefulService->addUpdateHandler(
[&](const String& originId) { transmitData(nullptr, originId); }, false);
}
protected:
virtual void onWSEvent(AsyncWebSocket* server,
AsyncWebSocketClient* client,
AwsEventType type,
void* arg,
uint8_t* data,
size_t len) {
if (type == WS_EVT_CONNECT) {
// when a client connects, we transmit it's id and the current payload
transmitId(client);
transmitData(client, WEB_SOCKET_ORIGIN);
}
}
private:
JsonStateReader<T> _stateReader;
void transmitId(AsyncWebSocketClient* client) {
DynamicJsonDocument jsonDocument = DynamicJsonDocument(WEB_SOCKET_CLIENT_ID_MSG_SIZE);
JsonObject root = jsonDocument.to<JsonObject>();
root["type"] = "id";
root["id"] = WebSocketConnector<T>::clientId(client);
size_t len = measureJson(jsonDocument);
AsyncWebSocketMessageBuffer* buffer = WebSocketConnector<T>::_webSocket.makeBuffer(len);
if (buffer) {
serializeJson(jsonDocument, (char*)buffer->get(), len + 1);
client->text(buffer);
}
}
/**
* Broadcasts the payload to the destination, if provided. Otherwise broadcasts to all clients except the origin, if
* specified.
*
* Original implementation sent clients their own IDs so they could ignore updates they initiated. This approach
* simplifies the client and the server implementation but may not be sufficent for all use-cases.
*/
void transmitData(AsyncWebSocketClient* client, const String& originId) {
DynamicJsonDocument jsonDocument = DynamicJsonDocument(WebSocketConnector<T>::_bufferSize);
JsonObject root = jsonDocument.to<JsonObject>();
root["type"] = "payload";
root["origin_id"] = originId;
JsonObject payload = root.createNestedObject("payload");
WebSocketConnector<T>::_statefulService->read(payload, _stateReader);
size_t len = measureJson(jsonDocument);
AsyncWebSocketMessageBuffer* buffer = WebSocketConnector<T>::_webSocket.makeBuffer(len);
if (buffer) {
serializeJson(jsonDocument, (char*)buffer->get(), len + 1);
if (client) {
client->text(buffer);
} else {
WebSocketConnector<T>::_webSocket.textAll(buffer);
}
}
}
};
template <class T>
class WebSocketRx : virtual public WebSocketConnector<T> {
public:
WebSocketRx(JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService,
AsyncWebServer* server,
char const* webSocketPath,
SecurityManager* securityManager,
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
WebSocketConnector<T>(statefulService,
server,
webSocketPath,
securityManager,
authenticationPredicate,
bufferSize),
_stateUpdater(stateUpdater) {
}
WebSocketRx(JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService,
AsyncWebServer* server,
char const* webSocketPath,
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
WebSocketConnector<T>(statefulService, server, webSocketPath, bufferSize), _stateUpdater(stateUpdater) {
}
protected:
virtual void onWSEvent(AsyncWebSocket* server,
AsyncWebSocketClient* client,
AwsEventType type,
void* arg,
uint8_t* data,
size_t len) {
if (type == WS_EVT_DATA) {
AwsFrameInfo* info = (AwsFrameInfo*)arg;
if (info->final && info->index == 0 && info->len == len) {
if (info->opcode == WS_TEXT) {
DynamicJsonDocument jsonDocument = DynamicJsonDocument(WebSocketConnector<T>::_bufferSize);
DeserializationError error = deserializeJson(jsonDocument, (char*)data);
if (!error && jsonDocument.is<JsonObject>()) {
JsonObject jsonObject = jsonDocument.as<JsonObject>();
WebSocketConnector<T>::_statefulService->update(
jsonObject, _stateUpdater, WebSocketConnector<T>::clientId(client));
}
}
}
}
}
private:
JsonStateUpdater<T> _stateUpdater;
};
template <class T>
class WebSocketTxRx : public WebSocketTx<T>, public WebSocketRx<T> {
public:
WebSocketTxRx(JsonStateReader<T> stateReader,
JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService,
AsyncWebServer* server,
char const* webSocketPath,
SecurityManager* securityManager,
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
WebSocketConnector<T>(statefulService,
server,
webSocketPath,
securityManager,
authenticationPredicate,
bufferSize),
WebSocketTx<T>(stateReader,
statefulService,
server,
webSocketPath,
securityManager,
authenticationPredicate,
bufferSize),
WebSocketRx<T>(stateUpdater,
statefulService,
server,
webSocketPath,
securityManager,
authenticationPredicate,
bufferSize) {
}
WebSocketTxRx(JsonStateReader<T> stateReader,
JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService,
AsyncWebServer* server,
char const* webSocketPath,
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
WebSocketConnector<T>(statefulService, server, webSocketPath, bufferSize),
WebSocketTx<T>(stateReader, statefulService, server, webSocketPath, bufferSize),
WebSocketRx<T>(stateUpdater, statefulService, server, webSocketPath, bufferSize) {
}
protected:
void onWSEvent(AsyncWebSocket* server,
AsyncWebSocketClient* client,
AwsEventType type,
void* arg,
uint8_t* data,
size_t len) {
WebSocketRx<T>::onWSEvent(server, client, type, arg, data, len);
WebSocketTx<T>::onWSEvent(server, client, type, arg, data, len);
}
};
#endif

View File

@@ -0,0 +1,70 @@
#include <WiFiScanner.h>
WiFiScanner::WiFiScanner(AsyncWebServer* server, SecurityManager* securityManager) {
server->on(SCAN_NETWORKS_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&WiFiScanner::scanNetworks, this, std::placeholders::_1),
AuthenticationPredicates::IS_ADMIN));
server->on(LIST_NETWORKS_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&WiFiScanner::listNetworks, this, std::placeholders::_1),
AuthenticationPredicates::IS_ADMIN));
};
void WiFiScanner::scanNetworks(AsyncWebServerRequest* request) {
if (WiFi.scanComplete() != -1) {
WiFi.scanDelete();
WiFi.scanNetworks(true);
}
request->send(202);
}
void WiFiScanner::listNetworks(AsyncWebServerRequest* request) {
int numNetworks = WiFi.scanComplete();
if (numNetworks > -1) {
AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_WIFI_SCANNER_SIZE);
JsonObject root = response->getRoot();
JsonArray networks = root.createNestedArray("networks");
for (int i = 0; i < numNetworks; i++) {
JsonObject network = networks.createNestedObject();
network["rssi"] = WiFi.RSSI(i);
network["ssid"] = WiFi.SSID(i);
network["bssid"] = WiFi.BSSIDstr(i);
network["channel"] = WiFi.channel(i);
#ifdef ESP32
network["encryption_type"] = (uint8_t)WiFi.encryptionType(i);
#elif defined(ESP8266)
network["encryption_type"] = convertEncryptionType(WiFi.encryptionType(i));
#endif
}
response->setLength();
request->send(response);
} else if (numNetworks == -1) {
request->send(202);
} else {
scanNetworks(request);
}
}
#ifdef ESP8266
/*
* Convert encryption type to standard used by ESP32 rather than the translated form which the esp8266 libaries expose.
*
* This allows us to use a single set of mappings in the UI.
*/
uint8_t WiFiScanner::convertEncryptionType(uint8_t encryptionType) {
switch (encryptionType) {
case ENC_TYPE_NONE:
return AUTH_OPEN;
case ENC_TYPE_WEP:
return AUTH_WEP;
case ENC_TYPE_TKIP:
return AUTH_WPA_PSK;
case ENC_TYPE_CCMP:
return AUTH_WPA2_PSK;
case ENC_TYPE_AUTO:
return AUTH_WPA_WPA2_PSK;
}
return -1;
}
#endif

View File

@@ -0,0 +1,35 @@
#ifndef WiFiScanner_h
#define WiFiScanner_h
#ifdef ESP32
#include <WiFi.h>
#include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <ESPAsyncWebServer.h>
#include <SecurityManager.h>
#define SCAN_NETWORKS_SERVICE_PATH "/rest/scanNetworks"
#define LIST_NETWORKS_SERVICE_PATH "/rest/listNetworks"
#define MAX_WIFI_SCANNER_SIZE 1024
class WiFiScanner {
public:
WiFiScanner(AsyncWebServer* server, SecurityManager* securityManager);
private:
void scanNetworks(AsyncWebServerRequest* request);
void listNetworks(AsyncWebServerRequest* request);
#ifdef ESP8266
uint8_t convertEncryptionType(uint8_t encryptionType);
#endif
};
#endif // end WiFiScanner_h

View File

@@ -0,0 +1,100 @@
#include <WiFiSettingsService.h>
WiFiSettingsService::WiFiSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) :
_httpEndpoint(WiFiSettings::read, WiFiSettings::update, this, server, WIFI_SETTINGS_SERVICE_PATH, securityManager),
_fsPersistence(WiFiSettings::read, WiFiSettings::update, this, fs, WIFI_SETTINGS_FILE),
_lastConnectionAttempt(0) {
// We want the device to come up in opmode=0 (WIFI_OFF), when erasing the flash this is not the default.
// If needed, we save opmode=0 before disabling persistence so the device boots with WiFi disabled in the future.
if (WiFi.getMode() != WIFI_OFF) {
WiFi.mode(WIFI_OFF);
}
// Disable WiFi config persistance and auto reconnect
WiFi.persistent(false);
WiFi.setAutoReconnect(false);
#ifdef ESP32
// Init the wifi driver on ESP32
WiFi.mode(WIFI_MODE_MAX);
WiFi.mode(WIFI_MODE_NULL);
WiFi.onEvent(
std::bind(&WiFiSettingsService::onStationModeDisconnected, this, std::placeholders::_1, std::placeholders::_2),
WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED);
WiFi.onEvent(std::bind(&WiFiSettingsService::onStationModeStop, this, std::placeholders::_1, std::placeholders::_2),
WiFiEvent_t::SYSTEM_EVENT_STA_STOP);
#elif defined(ESP8266)
_onStationModeDisconnectedHandler = WiFi.onStationModeDisconnected(
std::bind(&WiFiSettingsService::onStationModeDisconnected, this, std::placeholders::_1));
#endif
addUpdateHandler([&](const String& originId) { reconfigureWiFiConnection(); }, false);
}
void WiFiSettingsService::begin() {
_fsPersistence.readFromFS();
reconfigureWiFiConnection();
}
void WiFiSettingsService::reconfigureWiFiConnection() {
// reset last connection attempt to force loop to reconnect immediately
_lastConnectionAttempt = 0;
// disconnect and de-configure wifi
#ifdef ESP32
if (WiFi.disconnect(true)) {
_stopping = true;
}
#elif defined(ESP8266)
WiFi.disconnect(true);
#endif
}
void WiFiSettingsService::loop() {
unsigned long currentMillis = millis();
if (!_lastConnectionAttempt || (unsigned long)(currentMillis - _lastConnectionAttempt) >= WIFI_RECONNECTION_DELAY) {
_lastConnectionAttempt = currentMillis;
manageSTA();
}
}
void WiFiSettingsService::manageSTA() {
// Abort if already connected, or if we have no SSID
if (WiFi.isConnected() || _state.ssid.length() == 0) {
return;
}
// Connect or reconnect as required
if ((WiFi.getMode() & WIFI_STA) == 0) {
Serial.println(F("Connecting to WiFi."));
if (_state.staticIPConfig) {
// configure for static IP
WiFi.config(_state.localIP, _state.gatewayIP, _state.subnetMask, _state.dnsIP1, _state.dnsIP2);
} else {
// configure for DHCP
#ifdef ESP32
WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
WiFi.setHostname(_state.hostname.c_str());
#elif defined(ESP8266)
WiFi.config(INADDR_ANY, INADDR_ANY, INADDR_ANY);
WiFi.hostname(_state.hostname);
#endif
}
// attempt to connect to the network
WiFi.begin(_state.ssid.c_str(), _state.password.c_str());
}
}
#ifdef ESP32
void WiFiSettingsService::onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) {
WiFi.disconnect(true);
}
void WiFiSettingsService::onStationModeStop(WiFiEvent_t event, WiFiEventInfo_t info) {
if (_stopping) {
_lastConnectionAttempt = 0;
_stopping = false;
}
}
#elif defined(ESP8266)
void WiFiSettingsService::onStationModeDisconnected(const WiFiEventStationModeDisconnected& event) {
WiFi.disconnect(true);
}
#endif

View File

@@ -0,0 +1,111 @@
#ifndef WiFiSettingsService_h
#define WiFiSettingsService_h
#include <StatefulService.h>
#include <FSPersistence.h>
#include <HttpEndpoint.h>
#include <JsonUtils.h>
#define WIFI_SETTINGS_FILE "/config/wifiSettings.json"
#define WIFI_SETTINGS_SERVICE_PATH "/rest/wifiSettings"
#define WIFI_RECONNECTION_DELAY 1000 * 30
#ifndef FACTORY_WIFI_SSID
#define FACTORY_WIFI_SSID ""
#endif
#ifndef FACTORY_WIFI_PASSWORD
#define FACTORY_WIFI_PASSWORD ""
#endif
#ifndef FACTORY_WIFI_HOSTNAME
#define FACTORY_WIFI_HOSTNAME ""
#endif
class WiFiSettings {
public:
// core wifi configuration
String ssid;
String password;
String hostname;
bool staticIPConfig;
// optional configuration for static IP address
IPAddress localIP;
IPAddress gatewayIP;
IPAddress subnetMask;
IPAddress dnsIP1;
IPAddress dnsIP2;
static void read(WiFiSettings& settings, JsonObject& root) {
// connection settings
root["ssid"] = settings.ssid;
root["password"] = settings.password;
root["hostname"] = settings.hostname;
root["static_ip_config"] = settings.staticIPConfig;
// extended settings
JsonUtils::writeIP(root, "local_ip", settings.localIP);
JsonUtils::writeIP(root, "gateway_ip", settings.gatewayIP);
JsonUtils::writeIP(root, "subnet_mask", settings.subnetMask);
JsonUtils::writeIP(root, "dns_ip_1", settings.dnsIP1);
JsonUtils::writeIP(root, "dns_ip_2", settings.dnsIP2);
}
static StateUpdateResult update(JsonObject& root, WiFiSettings& settings) {
settings.ssid = root["ssid"] | FACTORY_WIFI_SSID;
settings.password = root["password"] | FACTORY_WIFI_PASSWORD;
settings.hostname = root["hostname"] | FACTORY_WIFI_HOSTNAME;
settings.staticIPConfig = root["static_ip_config"] | false;
// extended settings
JsonUtils::readIP(root, "local_ip", settings.localIP);
JsonUtils::readIP(root, "gateway_ip", settings.gatewayIP);
JsonUtils::readIP(root, "subnet_mask", settings.subnetMask);
JsonUtils::readIP(root, "dns_ip_1", settings.dnsIP1);
JsonUtils::readIP(root, "dns_ip_2", settings.dnsIP2);
// Swap around the dns servers if 2 is populated but 1 is not
if (settings.dnsIP1 == INADDR_NONE && settings.dnsIP2 != INADDR_NONE) {
settings.dnsIP1 = settings.dnsIP2;
settings.dnsIP2 = INADDR_NONE;
}
// Turning off static ip config if we don't meet the minimum requirements
// of ipAddress, gateway and subnet. This may change to static ip only
// as sensible defaults can be assumed for gateway and subnet
if (settings.staticIPConfig &&
(settings.localIP == INADDR_NONE || settings.gatewayIP == INADDR_NONE || settings.subnetMask == INADDR_NONE)) {
settings.staticIPConfig = false;
}
return StateUpdateResult::CHANGED;
}
};
class WiFiSettingsService : public StatefulService<WiFiSettings> {
public:
WiFiSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
void begin();
void loop();
private:
HttpEndpoint<WiFiSettings> _httpEndpoint;
FSPersistence<WiFiSettings> _fsPersistence;
unsigned long _lastConnectionAttempt;
#ifdef ESP32
bool _stopping;
void onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info);
void onStationModeStop(WiFiEvent_t event, WiFiEventInfo_t info);
#elif defined(ESP8266)
WiFiEventHandler _onStationModeDisconnectedHandler;
void onStationModeDisconnected(const WiFiEventStationModeDisconnected& event);
#endif
void reconfigureWiFiConnection();
void manageSTA();
};
#endif // end WiFiSettingsService_h

View File

@@ -0,0 +1,75 @@
#include <WiFiStatus.h>
WiFiStatus::WiFiStatus(AsyncWebServer* server, SecurityManager* securityManager) {
server->on(WIFI_STATUS_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&WiFiStatus::wifiStatus, this, std::placeholders::_1),
AuthenticationPredicates::IS_AUTHENTICATED));
#ifdef ESP32
WiFi.onEvent(onStationModeConnected, WiFiEvent_t::SYSTEM_EVENT_STA_CONNECTED);
WiFi.onEvent(onStationModeDisconnected, WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED);
WiFi.onEvent(onStationModeGotIP, WiFiEvent_t::SYSTEM_EVENT_STA_GOT_IP);
#elif defined(ESP8266)
_onStationModeConnectedHandler = WiFi.onStationModeConnected(onStationModeConnected);
_onStationModeDisconnectedHandler = WiFi.onStationModeDisconnected(onStationModeDisconnected);
_onStationModeGotIPHandler = WiFi.onStationModeGotIP(onStationModeGotIP);
#endif
}
#ifdef ESP32
void WiFiStatus::onStationModeConnected(WiFiEvent_t event, WiFiEventInfo_t info) {
Serial.println(F("WiFi Connected."));
}
void WiFiStatus::onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) {
Serial.print(F("WiFi Disconnected. Reason code="));
Serial.println(info.disconnected.reason);
}
void WiFiStatus::onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info) {
Serial.printf_P(
PSTR("WiFi Got IP. localIP=%s, hostName=%s\r\n"), WiFi.localIP().toString().c_str(), WiFi.getHostname());
}
#elif defined(ESP8266)
void WiFiStatus::onStationModeConnected(const WiFiEventStationModeConnected& event) {
Serial.print(F("WiFi Connected. SSID="));
Serial.println(event.ssid);
}
void WiFiStatus::onStationModeDisconnected(const WiFiEventStationModeDisconnected& event) {
Serial.print(F("WiFi Disconnected. Reason code="));
Serial.println(event.reason);
}
void WiFiStatus::onStationModeGotIP(const WiFiEventStationModeGotIP& event) {
Serial.printf_P(
PSTR("WiFi Got IP. localIP=%s, hostName=%s\r\n"), event.ip.toString().c_str(), WiFi.hostname().c_str());
}
#endif
void WiFiStatus::wifiStatus(AsyncWebServerRequest* request) {
AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_WIFI_STATUS_SIZE);
JsonObject root = response->getRoot();
wl_status_t status = WiFi.status();
root["status"] = (uint8_t)status;
if (status == WL_CONNECTED) {
root["local_ip"] = WiFi.localIP().toString();
root["mac_address"] = WiFi.macAddress();
root["rssi"] = WiFi.RSSI();
root["ssid"] = WiFi.SSID();
root["bssid"] = WiFi.BSSIDstr();
root["channel"] = WiFi.channel();
root["subnet_mask"] = WiFi.subnetMask().toString();
root["gateway_ip"] = WiFi.gatewayIP().toString();
IPAddress dnsIP1 = WiFi.dnsIP(0);
IPAddress dnsIP2 = WiFi.dnsIP(1);
if (dnsIP1 != INADDR_NONE) {
root["dns_ip_1"] = dnsIP1.toString();
}
if (dnsIP2 != INADDR_NONE) {
root["dns_ip_2"] = dnsIP2.toString();
}
}
response->setLength();
request->send(response);
}

View File

@@ -0,0 +1,45 @@
#ifndef WiFiStatus_h
#define WiFiStatus_h
#ifdef ESP32
#include <WiFi.h>
#include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <ESPAsyncWebServer.h>
#include <IPAddress.h>
#include <SecurityManager.h>
#define MAX_WIFI_STATUS_SIZE 1024
#define WIFI_STATUS_SERVICE_PATH "/rest/wifiStatus"
class WiFiStatus {
public:
WiFiStatus(AsyncWebServer* server, SecurityManager* securityManager);
private:
#ifdef ESP32
// static functions for logging WiFi events to the UART
static void onStationModeConnected(WiFiEvent_t event, WiFiEventInfo_t info);
static void onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info);
static void onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info);
#elif defined(ESP8266)
// handler refrences for logging important WiFi events over serial
WiFiEventHandler _onStationModeConnectedHandler;
WiFiEventHandler _onStationModeDisconnectedHandler;
WiFiEventHandler _onStationModeGotIPHandler;
// static functions for logging WiFi events to the UART
static void onStationModeConnected(const WiFiEventStationModeConnected& event);
static void onStationModeDisconnected(const WiFiEventStationModeDisconnected& event);
static void onStationModeGotIP(const WiFiEventStationModeGotIP& event);
#endif
void wifiStatus(AsyncWebServerRequest* request);
};
#endif // end WiFiStatus_h

674
lib/uuid-common/COPYING Normal file
View File

@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program 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.
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

View File

@@ -0,0 +1,25 @@
mcu-uuid-common |Build Status|
==============================
Description
-----------
Microcontroller common utilities library
Purpose
-------
The primary purpose of this library is to maintain a common 64-bit uptime in
milliseconds with overflow handling, as long as the loop function is called
regularly.
Documentation
-------------
`Read the documentation <https://mcu-uuid-common.readthedocs.io/>`_ generated
from the docs_ directory.
.. _docs: docs/
.. |Build Status| image:: https://travis-ci.org/nomis/mcu-uuid-common.svg?branch=master
:target: https://travis-ci.org/nomis/mcu-uuid-common

View File

@@ -0,0 +1,31 @@
{
"name": "uuid-common",
"description": "Common utilities library",
"keywords": "utility, uptime",
"authors": [
{
"name": "Simon Arlott",
"maintainer": true
}
],
"repository": {
"type": "git",
"url": "https://github.com/nomis/mcu-uuid-common.git"
},
"version": "1.1.0",
"license": "GPL-3.0-or-later",
"homepage": "https://mcu-uuid-common.readthedocs.io/",
"export": {
"exclude": [
".travis.yml",
"test/*"
]
},
"frameworks": [
"arduino"
],
"build": {
"flags": "-Wall -Wextra",
"libLDFMode": "off"
}
}

View File

@@ -0,0 +1,19 @@
/*
* uuid-common - Microcontroller common utilities
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <uuid/common.h>

View File

@@ -0,0 +1,65 @@
/*
* uuid-common - Microcontroller common utilities
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <uuid/common.h>
#include <Arduino.h>
namespace uuid {
#define UPTIME_OVERFLOW 4294967295 // Uptime overflow value
// returns system uptime in seconds
uint32_t get_uptime_sec() {
static uint32_t last_uptime = 0;
static uint8_t uptime_overflows = 0;
if (millis() < last_uptime) {
++uptime_overflows;
}
last_uptime = millis();
uint32_t uptime_seconds = uptime_overflows * (UPTIME_OVERFLOW / 1000) + (last_uptime / 1000);
return uptime_seconds;
}
uint64_t get_uptime_ms() {
static uint32_t high_millis = 0;
static uint32_t low_millis = 0;
if (get_uptime() < low_millis) {
high_millis++;
}
low_millis = get_uptime();
return ((uint64_t)high_millis << 32) | low_millis;
}
// added by proddy
static uint32_t now_millis;
void set_uptime() {
now_millis = ::millis();
}
uint32_t get_uptime() {
return now_millis;
}
} // namespace uuid

View File

@@ -0,0 +1,28 @@
/*
* uuid-common - Microcontroller common utilities
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <uuid/common.h>
namespace uuid {
void loop() {
set_uptime(); // added by proddy
get_uptime_ms();
}
} // namespace uuid

View File

@@ -0,0 +1,62 @@
/*
* uuid-common - Microcontroller common utilities
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <uuid/common.h>
#include <Arduino.h>
#include <string>
namespace uuid {
class PrintableString : public ::Print {
public:
explicit PrintableString(std::string & output)
: output_(output) {
}
~PrintableString() = default;
size_t write(uint8_t data) final override {
output_.append(1, reinterpret_cast<unsigned char>(data));
return 1;
}
size_t write(const uint8_t * buffer, size_t size) final override {
output_.append(reinterpret_cast<const char *>(buffer), size);
return size;
}
private:
std::string & output_;
};
size_t print_to_string(const Printable & printable, std::string & output) {
PrintableString pstr{output};
return printable.printTo(pstr);
}
std::string printable_to_string(const Printable & printable) {
std::string str;
print_to_string(printable, str);
return str;
}
} // namespace uuid

View File

@@ -0,0 +1,35 @@
/*
* uuid-common - Microcontroller common utilities
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <uuid/common.h>
#include <Arduino.h>
#include <string>
namespace uuid {
std::string read_flash_string(const __FlashStringHelper * flash_str) {
std::string str(::strlen_P(reinterpret_cast<PGM_P>(flash_str)), '\0');
::strncpy_P(&str[0], reinterpret_cast<PGM_P>(flash_str), str.capacity() + 1);
return str;
}
} // namespace uuid

View File

@@ -0,0 +1,96 @@
/*
* uuid-common - Microcontroller common utilities
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef UUID_COMMON_H_
#define UUID_COMMON_H_
#include <Arduino.h>
#include <string>
#include <vector>
/**
* Common utilities.
*
* - <a href="https://github.com/nomis/mcu-uuid-common/">Git Repository</a>
* - <a href="https://mcu-uuid-common.readthedocs.io/">Documentation</a>
*/
namespace uuid {
/**
* Read a string from flash and convert it to a std::string.
*
* The flash string must be stored with appropriate alignment for
* reading it on the platform.
*
* @param[in] flash_str Pointer to string stored in flash.
* @return A string copy of the flash string.
* @since 1.0.0
*/
std::string read_flash_string(const __FlashStringHelper * flash_str);
/**
* Append to a std::string by printing a Printable object.
*
* @param[in] printable Printable object.
* @param[in,out] output String to append to.
* @return The number of bytes that were written.
* @since 1.1.0
*/
size_t print_to_string(const Printable & printable, std::string & output);
/**
* Create a std::string from a Printable object.
*
* @param[in] printable Printable object.
* @return A string containing the printed output.
* @since 1.1.0
*/
std::string printable_to_string(const Printable & printable);
/**
* Type definition for a std::vector of flash strings.
*
* @since 1.0.0
*/
using flash_string_vector = std::vector<const __FlashStringHelper *>;
/**
* Loop function that must be called regularly to detect a 32-bit
* uptime overflow.
*
* @since 1.0.0
*/
void loop();
/**
* Get the current uptime as a 64-bit milliseconds value.
*
* @return The current uptime in milliseconds.
* @since 1.0.0
*/
uint64_t get_uptime_ms();
uint32_t get_uptime(); // added by proddy
uint32_t get_uptime_sec(); // added by proddy
void set_uptime();
} // namespace uuid
#endif

674
lib/uuid-console/COPYING Normal file
View File

@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program 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.
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

View File

@@ -0,0 +1,25 @@
mcu-uuid-console |Build Status|
===============================
Description
-----------
Microcontroller console shell
Purpose
-------
Provides a framework for creating a console shell with commands. The
container of commands (``uuid::console::Commands``) can be shared
across multiple shell instances.
Documentation
-------------
`Read the documentation <https://mcu-uuid-console.readthedocs.io/>`_ generated
from the docs_ directory.
.. _docs: docs/
.. |Build Status| image:: https://travis-ci.org/nomis/mcu-uuid-console.svg?branch=master
:target: https://travis-ci.org/nomis/mcu-uuid-console

View File

@@ -0,0 +1,35 @@
{
"name": "uuid-console",
"description": "Console shell",
"keywords": "console, shell, command line",
"authors": [
{
"name": "Simon Arlott",
"maintainer": true
}
],
"repository": {
"type": "git",
"url": "https://github.com/nomis/mcu-uuid-console.git"
},
"version": "0.7.3",
"license": "GPL-3.0-or-later",
"homepage": "https://mcu-uuid-console.readthedocs.io/",
"export": {
"exclude": [
".travis.yml",
"test/*"
]
},
"frameworks": [
"arduino"
],
"dependencies": {
"uuid-common": "^1.0.0",
"uuid-log": "^2.0.3"
},
"build": {
"flags": "-Wall -Wextra",
"libLDFMode": "off"
}
}

View File

@@ -0,0 +1,166 @@
/*
* uuid-console - Microcontroller console shell
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <uuid/console.h>
#include <string>
#include <vector>
namespace uuid {
namespace console {
CommandLine::CommandLine(const std::string & line) {
bool string_escape_double = false;
bool string_escape_single = false;
bool char_escape = false;
bool quoted_argument = false;
if (!line.empty()) {
parameters_.emplace_back(std::string{});
}
for (char c : line) {
switch (c) {
case ' ':
if (string_escape_double || string_escape_single) {
if (char_escape) {
parameters_.back().push_back('\\');
char_escape = false;
}
parameters_.back().push_back(' ');
} else if (char_escape) {
parameters_.back().push_back(' ');
char_escape = false;
} else {
// Begin a new argument if the previous
// one is not empty or it was quoted
if (quoted_argument || !parameters_.back().empty()) {
parameters_.emplace_back(std::string{});
}
quoted_argument = false;
}
break;
case '"':
if (char_escape || string_escape_single) {
parameters_.back().push_back('"');
char_escape = false;
} else {
string_escape_double = !string_escape_double;
quoted_argument = true;
}
break;
case '\'':
if (char_escape || string_escape_double) {
parameters_.back().push_back('\'');
char_escape = false;
} else {
string_escape_single = !string_escape_single;
quoted_argument = true;
}
break;
case '\\':
if (char_escape) {
parameters_.back().push_back('\\');
char_escape = false;
} else {
char_escape = true;
}
break;
default:
if (char_escape) {
parameters_.back().push_back('\\');
char_escape = false;
}
parameters_.back().push_back(c);
break;
}
}
if (!parameters_.empty() && parameters_.back().empty() && !quoted_argument) {
parameters_.pop_back();
if (!parameters_.empty()) {
trailing_space = true;
}
}
}
CommandLine::CommandLine(std::initializer_list<const std::vector<std::string>> arguments) {
for (auto & argument : arguments) {
parameters_.insert(parameters_.end(), argument.begin(), argument.end());
}
}
std::string CommandLine::to_string(size_t reserve) const {
std::string line;
size_t escape = escape_parameters_;
line.reserve(reserve);
for (auto & item : parameters_) {
if (!line.empty()) {
line += ' ';
}
if (item.empty()) {
line += '\"';
line += '\"';
goto next;
}
for (char c : item) {
switch (c) {
case ' ':
case '\"':
case '\'':
case '\\':
if (escape > 0) {
line += '\\';
}
break;
}
line += c;
}
next:
if (escape > 0) {
escape--;
}
}
if (trailing_space && !line.empty()) {
line += ' ';
}
return line;
}
void CommandLine::reset() {
parameters_.clear();
escape_all_parameters();
trailing_space = false;
}
} // namespace console
} // namespace uuid

View File

@@ -0,0 +1,550 @@
/*
* uuid-console - Microcontroller console shell
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 std::copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <uuid/console.h>
#include <Arduino.h>
#include <algorithm>
#include <memory>
#include <map>
#include <set>
#include <string>
#include <vector>
#ifndef __cpp_lib_make_unique
namespace std {
template <typename _Tp, typename... _Args>
inline unique_ptr<_Tp> make_unique(_Args &&... __args) {
return unique_ptr<_Tp>(new _Tp(std::forward<_Args>(__args)...));
}
} // namespace std
#endif
namespace uuid {
namespace console {
void Commands::add_command(const flash_string_vector & name, command_function function) {
add_command(0, 0, name, flash_string_vector{}, function, nullptr);
}
void Commands::add_command(const flash_string_vector & name, const flash_string_vector & arguments, command_function function) {
add_command(0, 0, name, arguments, function, nullptr);
}
void Commands::add_command(const flash_string_vector & name,
const flash_string_vector & arguments,
command_function function,
argument_completion_function arg_function) {
add_command(0, 0, name, arguments, function, arg_function);
}
void Commands::add_command(unsigned int context, unsigned int flags, const flash_string_vector & name, command_function function) {
add_command(context, flags, name, flash_string_vector{}, function, nullptr);
}
void Commands::add_command(unsigned int context,
unsigned int flags,
const flash_string_vector & name,
const flash_string_vector & arguments,
command_function function) {
add_command(context, flags, name, arguments, function, nullptr);
}
void Commands::add_command(unsigned int context,
unsigned int flags,
const flash_string_vector & name,
const flash_string_vector & arguments,
command_function function,
argument_completion_function arg_function) {
commands_.emplace(std::piecewise_construct, std::forward_as_tuple(context), std::forward_as_tuple(flags, name, arguments, function, arg_function));
}
// added by proddy
// note we should really iterate and free up the lambda code and any flashstrings
void Commands::remove_all_commands() {
commands_.clear();
}
// added by proddy
// note we should really iterate and free up the lambda code and any flashstrings
void Commands::remove_context_commands(unsigned int context) {
commands_.erase(context);
/*
auto commands = commands_.equal_range(context);
for (auto command_it = commands.first; command_it != commands.second; command_it++) {
shell.printf("Got: ");
for (auto flash_name : command_it->second.name_) {
shell.printf("%s ", read_flash_string(flash_name).c_str());
}
shell.println();
}
size_t nun = commands_.erase(context);
shell.printfln("Erased %d commands", nun);
*/
}
Commands::Execution Commands::execute_command(Shell & shell, CommandLine && command_line) {
auto commands = find_command(shell, command_line);
auto longest = commands.exact.crbegin();
Execution result;
result.error = nullptr;
if (commands.exact.empty()) {
result.error = F("Command not found");
} else if (commands.exact.count(longest->first) == 1) {
auto & command = longest->second;
std::vector<std::string> arguments;
for (auto it = std::next(command_line->cbegin(), command->name_.size()); it != command_line->cend(); it++) {
arguments.push_back(std::move(*it));
}
command_line.reset();
if (commands.partial.upper_bound(longest->first) != commands.partial.end() && !arguments.empty()) {
result.error = F("Command not found");
} else if (arguments.size() < command->minimum_arguments()) {
result.error = F("Not enough arguments for command");
} else if (arguments.size() > command->maximum_arguments()) {
result.error = F("Too many arguments for command");
} else {
command->function_(shell, arguments);
}
} else {
result.error = F("Fatal error (multiple commands found)");
}
return result;
}
bool Commands::find_longest_common_prefix(const std::multimap<size_t, const Command *> & commands, std::vector<std::string> & longest_name) {
size_t component_prefix = 0;
size_t shortest_match = commands.begin()->first;
longest_name.reserve(shortest_match);
{
// Check if any of the commands have a common prefix of components
auto & first = commands.begin()->second->name_;
bool all_match = true;
for (size_t length = 0; all_match && length < shortest_match; length++) {
for (auto command_it = std::next(commands.begin()); command_it != commands.end(); command_it++) {
if (read_flash_string(*std::next(first.begin(), length)) != read_flash_string(*std::next(command_it->second->name_.begin(), length))) {
all_match = false;
break;
}
}
if (all_match) {
component_prefix = length + 1;
}
}
auto name_it = first.begin();
for (size_t i = 0; i < component_prefix; i++) {
longest_name.push_back(std::move(read_flash_string(*name_it)));
name_it++;
}
}
if (component_prefix < shortest_match) {
// Check if the next component has a common substring
auto first = *std::next(commands.begin()->second->name_.begin(), component_prefix);
bool all_match = true;
size_t chars_prefix = 0;
for (size_t length = 0; all_match; length++) {
for (auto command_it = std::next(commands.begin()); command_it != commands.end(); command_it++) {
// This relies on the null terminator character limiting the
// length before it becomes longer than any of the strings
if (pgm_read_byte(reinterpret_cast<PGM_P>(first) + length)
!= pgm_read_byte(reinterpret_cast<PGM_P>(*std::next(command_it->second->name_.begin(), component_prefix)) + length)) {
all_match = false;
break;
}
}
if (all_match) {
chars_prefix = length + 1;
}
}
if (chars_prefix > 0) {
longest_name.push_back(std::move(read_flash_string(first).substr(0, chars_prefix)));
return false;
}
}
return true;
}
std::string Commands::find_longest_common_prefix(const std::vector<std::string> & arguments) {
auto & first = *arguments.begin();
bool all_match = true;
size_t chars_prefix = 0;
for (size_t length = 0; all_match; length++) {
for (auto argument_it = std::next(arguments.begin()); argument_it != arguments.end(); argument_it++) {
// This relies on the null terminator character limiting the
// length before it becomes longer than any of the strings
if (first[length] != (*argument_it)[length]) {
all_match = false;
break;
}
}
if (all_match) {
chars_prefix = length + 1;
}
}
return arguments.begin()->substr(0, chars_prefix);
}
Commands::Completion Commands::complete_command(Shell & shell, const CommandLine & command_line) {
auto commands = find_command(shell, command_line);
Completion result;
auto match = commands.partial.begin();
size_t count;
if (match != commands.partial.end()) {
count = commands.partial.count(match->first);
} else if (!commands.exact.empty()) {
// Use prev() because both iterators must be forwards
match = std::prev(commands.exact.end());
count = commands.exact.count(match->first);
} else {
return result;
}
std::unique_ptr<Command> temp_command;
std::vector<std::string> temp_command_name;
std::multimap<size_t, const Command *>::iterator temp_command_it;
if (commands.partial.size() > 1 && (commands.exact.empty() || command_line.total_size() > commands.exact.begin()->second->name_.size())) {
// There are multiple partial matching commands, find the longest common prefix
bool whole_components = find_longest_common_prefix(commands.partial, temp_command_name);
if (count == 1 && whole_components && temp_command_name.size() == match->first) {
// If the longest common prefix is the same as the single shortest matching command
// then there's no need for a temporary command, but add a trailing space because
// there are longer commands that matched.
temp_command_name.clear();
result.replacement.trailing_space = true;
}
if (!temp_command_name.empty() && command_line.total_size() <= temp_command_name.size()) {
temp_command = std::make_unique<Command>(0, flash_string_vector{}, flash_string_vector{}, nullptr, nullptr);
count = 1;
match = commands.partial.end();
result.replacement.trailing_space = whole_components;
for (auto & name : temp_command_name) {
result.replacement->emplace_back(name);
}
}
}
if (count == 1 && !temp_command) {
// Construct a replacement string for a single matching command
auto & matching_command = match->second;
for (auto & name : matching_command->name_) {
result.replacement->push_back(std::move(read_flash_string(name)));
}
if (command_line.total_size() > result.replacement->size()
&& command_line.total_size() <= matching_command->name_.size() + matching_command->maximum_arguments()) {
// Try to auto-complete arguments
std::vector<std::string> arguments{std::next(command_line->cbegin(), result.replacement->size()), command_line->cend()};
result.replacement->insert(result.replacement->end(), arguments.cbegin(), arguments.cend());
result.replacement.trailing_space = command_line.trailing_space;
auto current_args_count = arguments.size();
std::string last_argument;
if (!command_line.trailing_space) {
// Remove the last argument so that it can be auto-completed
last_argument = std::move(result.replacement->back());
result.replacement->pop_back();
if (!arguments.empty()) {
arguments.pop_back();
current_args_count--;
}
}
auto potential_arguments = matching_command->arg_function_ ? matching_command->arg_function_(shell, arguments) : std::vector<std::string>{};
// Remove arguments that can't match
if (!command_line.trailing_space) {
for (auto it = potential_arguments.begin(); it != potential_arguments.end();) {
if (it->rfind(last_argument, 0) == std::string::npos) {
it = potential_arguments.erase(it);
} else {
it++;
}
}
}
// Auto-complete if there's something present in the last argument
// or the only potential argument is an empty string.
if (!command_line.trailing_space) {
if (potential_arguments.size() == 1) {
if (last_argument == *potential_arguments.begin()) {
if (result.replacement->size() + 1 < matching_command->name_.size() + matching_command->maximum_arguments()) {
// Add a space because this argument is complete and there are more arguments for this command
result.replacement.trailing_space = true;
}
}
last_argument = *potential_arguments.begin();
potential_arguments.clear();
// Remaining help should skip the replaced argument
current_args_count++;
} else if (potential_arguments.size() > 1) {
last_argument = find_longest_common_prefix(potential_arguments);
}
}
// Put the last argument back
if (!command_line.trailing_space) {
result.replacement->push_back(std::move(last_argument));
}
CommandLine remaining_help;
if (!potential_arguments.empty()) {
// Remaining help should skip the suggested argument
current_args_count++;
}
if (current_args_count < matching_command->maximum_arguments()) {
remaining_help.escape_initial_parameters();
for (auto it = std::next(matching_command->arguments_.cbegin(), current_args_count); it != matching_command->arguments_.cend(); it++) {
remaining_help->push_back(std::move(read_flash_string(*it)));
}
}
if (potential_arguments.empty()) {
if (!remaining_help->empty()) {
result.help.push_back(std::move(remaining_help));
}
} else {
for (auto potential_argument : potential_arguments) {
CommandLine help;
help->emplace_back(potential_argument);
if (!remaining_help->empty()) {
help.escape_initial_parameters();
help->insert(help->end(), remaining_help->begin(), remaining_help->end());
}
result.help.push_back(std::move(help));
}
}
} else if (result.replacement->size() < matching_command->name_.size() + matching_command->maximum_arguments()) {
// Add a space because there are more arguments for this command
result.replacement.trailing_space = true;
}
} else if (count > 1 || temp_command) {
// Provide help for all of the potential commands
for (auto command_it = commands.partial.begin(); command_it != commands.partial.end(); command_it++) {
CommandLine help;
auto line_it = command_line->cbegin();
auto flash_name_it = command_it->second->name_.cbegin();
if (temp_command) {
// Skip parts of the command name/line when the longest common prefix was used
size_t skip = temp_command_name.size();
if (!result.replacement.trailing_space) {
skip--;
}
flash_name_it += skip;
while (line_it != command_line->cend()) {
line_it++;
}
}
for (; flash_name_it != command_it->second->name_.cend(); flash_name_it++) {
std::string name = read_flash_string(*flash_name_it);
// Skip parts of the command name that match the command line
if (line_it != command_line->cend()) {
if (name == *line_it++) {
continue;
} else {
line_it = command_line->cend();
}
}
help->emplace_back(name);
}
help.escape_initial_parameters();
for (auto argument : command_it->second->arguments_) {
// Skip parts of the command arguments that exist in the command line
if (line_it != command_line->cend()) {
line_it++;
continue;
}
help->push_back(std::move(read_flash_string(argument)));
}
result.help.push_back(std::move(help));
}
}
if (count > 1 && !commands.exact.empty()) {
// Try to add a space to exact matches
auto longest = commands.exact.crbegin();
if (commands.exact.count(longest->first) == 1) {
for (auto & name : longest->second->name_) {
result.replacement->push_back(std::move(read_flash_string(name)));
}
// Add a space because there are sub-commands for a command that has matched exactly
result.replacement.trailing_space = true;
}
}
// Don't try to shorten the command line or offer an identical replacement
if (command_line.total_size() > result.replacement.total_size() || result.replacement == command_line) {
result.replacement.reset();
}
return result;
}
Commands::Match Commands::find_command(Shell & shell, const CommandLine & command_line) {
Match commands;
auto context_commands = commands_.equal_range(shell.context());
for (auto it = context_commands.first; it != context_commands.second; it++) {
auto & command = it->second;
bool match = true;
bool exact = true;
if (!shell.has_flags(command.flags_)) {
continue;
}
auto name_it = command.name_.cbegin();
auto line_it = command_line->cbegin();
for (; name_it != command.name_.cend() && line_it != command_line->cend(); name_it++, line_it++) {
std::string name = read_flash_string(*name_it);
size_t found = name.rfind(*line_it, 0);
if (found == std::string::npos) {
match = false;
break;
} else if (line_it->length() != name.length()) {
for (auto line_check_it = std::next(line_it); line_check_it != command_line->cend(); line_check_it++) {
if (!line_check_it->empty()) {
// If there's more in the command line then this can't match
match = false;
}
}
if (command_line.trailing_space) {
// If there's a trailing space in the command line then this can't be a partial match
match = false;
}
// Don't check the rest of the command if this is only a partial match
break;
}
}
if (name_it != command.name_.cend()) {
exact = false;
}
if (match) {
if (exact) {
commands.exact.emplace(command.name_.size(), &command);
} else {
commands.partial.emplace(command.name_.size(), &command);
}
}
}
return commands;
}
void Commands::for_each_available_command(Shell & shell, apply_function f) const {
auto commands = commands_.equal_range(shell.context());
for (auto command_it = commands.first; command_it != commands.second; command_it++) {
if (shell.has_flags(command_it->second.flags_)) {
std::vector<std::string> name;
std::vector<std::string> arguments;
name.reserve(command_it->second.name_.size());
for (auto flash_name : command_it->second.name_) {
name.push_back(std::move(read_flash_string(flash_name)));
}
arguments.reserve(command_it->second.arguments_.size());
for (auto flash_argument : command_it->second.arguments_) {
arguments.push_back(std::move(read_flash_string(flash_argument)));
}
f(name, arguments);
}
}
}
Commands::Command::Command(unsigned int flags,
const flash_string_vector name,
const flash_string_vector arguments,
command_function function,
argument_completion_function arg_function)
: flags_(flags)
, name_(name)
, arguments_(arguments)
, function_(function)
, arg_function_(arg_function) {
}
Commands::Command::~Command() {
}
size_t Commands::Command::minimum_arguments() const {
return std::count_if(arguments_.cbegin(), arguments_.cend(), [](const __FlashStringHelper * argument) { return pgm_read_byte(argument) == '<'; });
}
} // namespace console
} // namespace uuid

View File

@@ -0,0 +1,19 @@
/*
* uuid-console - Microcontroller console shell
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <uuid/console.h>

View File

@@ -0,0 +1,519 @@
/*
* uuid-console - Microcontroller console shell
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <uuid/console.h>
#include <Arduino.h>
#include <stdarg.h>
#include <algorithm>
#include <memory>
#include <set>
#include <string>
#include <vector>
#include <uuid/common.h>
#include <uuid/log.h>
#ifndef __cpp_lib_make_unique
namespace std {
template <typename _Tp, typename... _Args>
inline unique_ptr<_Tp> make_unique(_Args &&... __args) {
return unique_ptr<_Tp>(new _Tp(std::forward<_Args>(__args)...));
}
} // namespace std
#endif
namespace uuid {
namespace console {
// cppcheck-suppress passedByValue
Shell::Shell(std::shared_ptr<Commands> commands, unsigned int context, unsigned int flags)
: commands_(std::move(commands))
, flags_(flags) {
enter_context(context);
}
Shell::~Shell() {
uuid::log::Logger::unregister_handler(this);
}
void Shell::start() {
#ifdef EMSESP_DEBUG
uuid::log::Logger::register_handler(this, uuid::log::Level::DEBUG); // added by proddy
//uuid::log::Logger::register_handler(this, uuid::log::Level::INFO); // added by proddy
#else
uuid::log::Logger::register_handler(this, uuid::log::Level::NOTICE);
#endif
line_buffer_.reserve(maximum_command_line_length_);
display_banner();
display_prompt();
shells_.insert(shared_from_this());
idle_time_ = uuid::get_uptime_ms();
started();
};
void Shell::started() {
}
bool Shell::running() const {
return !stopped_;
}
void Shell::stop() {
if (mode_ == Mode::BLOCKING) {
auto * blocking_data = reinterpret_cast<Shell::BlockingData *>(mode_data_.get());
blocking_data->stop_ = true;
} else {
if (running()) {
stopped_ = true;
stopped();
}
}
}
void Shell::stopped() {
}
bool Shell::exit_context() {
if (context_.size() > 1) {
context_.pop_back();
return true;
} else {
return false;
}
}
void Shell::loop_one() {
if (!running()) {
return;
}
switch (mode_) {
case Mode::NORMAL:
output_logs();
loop_normal();
break;
case Mode::PASSWORD:
output_logs();
loop_password();
break;
case Mode::DELAY:
output_logs();
loop_delay();
break;
case Mode::BLOCKING:
loop_blocking();
break;
}
}
void Shell::loop_normal() {
const int input = read_one_char();
if (input < 0) {
check_idle_timeout();
return;
}
const unsigned char c = input;
switch (c) {
case '\x03':
// Interrupt (^C)
line_buffer_.clear();
println();
prompt_displayed_ = false;
display_prompt();
break;
case '\x04':
// End of transmission (^D)
if (line_buffer_.empty()) {
end_of_transmission();
}
break;
case '\x08':
case '\x7F':
// Backspace (^H)
// Delete (^?)
if (!line_buffer_.empty()) {
erase_characters(1);
line_buffer_.pop_back();
}
break;
case '\x09':
// Tab (^I)
process_completion();
break;
case '\x0A':
// Line feed (^J)
if (previous_ != '\x0D') {
process_command();
}
break;
case '\x0C':
// New page (^L)
erase_current_line();
prompt_displayed_ = false;
display_prompt();
break;
case '\x0D':
// Carriage return (^M)
process_command();
break;
case '\x15':
// Delete line (^U)
erase_current_line();
prompt_displayed_ = false;
line_buffer_.clear();
display_prompt();
break;
case '\x17':
// Delete word (^W)
delete_buffer_word(true);
break;
default:
if (c >= '\x20' && c <= '\x7E') {
// ASCII text
if (line_buffer_.length() < maximum_command_line_length_) {
line_buffer_.push_back(c);
write((uint8_t)c);
}
}
break;
}
previous_ = c;
// This is a hack to let TelnetStream know that command
// execution is complete and that output can be flushed.
available_char();
idle_time_ = uuid::get_uptime_ms();
}
Shell::PasswordData::PasswordData(const __FlashStringHelper * password_prompt, password_function && password_function)
: password_prompt_(password_prompt)
, password_function_(std::move(password_function)) {
}
void Shell::loop_password() {
const int input = read_one_char();
if (input < 0) {
check_idle_timeout();
return;
}
const unsigned char c = input;
switch (c) {
case '\x03':
// Interrupt (^C)
process_password(false);
break;
case '\x08':
case '\x7F':
// Backspace (^H)
// Delete (^?)
if (!line_buffer_.empty()) {
line_buffer_.pop_back();
}
break;
case '\x0A':
// Line feed (^J)
if (previous_ != '\x0D') {
process_password(true);
}
break;
case '\x0C':
// New page (^L)
erase_current_line();
prompt_displayed_ = false;
display_prompt();
break;
case '\x0D':
// Carriage return (^M)
process_password(true);
break;
case '\x15':
// Delete line (^U)
line_buffer_.clear();
break;
case '\x17':
// Delete word (^W)
delete_buffer_word(false);
break;
default:
if (c >= '\x20' && c <= '\x7E') {
// ASCII text
if (line_buffer_.length() < maximum_command_line_length_) {
line_buffer_.push_back(c);
}
}
break;
}
previous_ = c;
// This is a hack to let TelnetStream know that command
// execution is complete and that output can be flushed.
available_char();
idle_time_ = uuid::get_uptime_ms();
}
Shell::DelayData::DelayData(uint64_t delay_time, delay_function && delay_function)
: delay_time_(delay_time)
, delay_function_(std::move(delay_function)) {
}
void Shell::loop_delay() {
auto * delay_data = reinterpret_cast<Shell::DelayData *>(mode_data_.get());
if (uuid::get_uptime_ms() >= delay_data->delay_time_) {
auto function_copy = delay_data->delay_function_;
mode_ = Mode::NORMAL;
mode_data_.reset();
function_copy(*this);
if (running()) {
display_prompt();
}
idle_time_ = uuid::get_uptime_ms();
}
}
Shell::BlockingData::BlockingData(blocking_function && blocking_function)
: blocking_function_(std::move(blocking_function)) {
}
void Shell::loop_blocking() {
auto * blocking_data = reinterpret_cast<Shell::BlockingData *>(mode_data_.get());
/* It is not possible to change mode while executing this function,
* because that would require copying either the std::shared_ptr or
* the std::function on every loop execution (to ensure that the
* function captures aren't destroyed while executing).
*/
if (blocking_data->blocking_function_(*this, blocking_data->stop_)) {
bool stop_pending = blocking_data->stop_;
mode_ = Mode::NORMAL;
mode_data_.reset();
if (stop_pending) {
stop();
}
if (running()) {
display_prompt();
}
idle_time_ = uuid::get_uptime_ms();
}
}
void Shell::enter_password(const __FlashStringHelper * prompt, password_function function) {
if (mode_ == Mode::NORMAL) {
mode_ = Mode::PASSWORD;
mode_data_ = std::make_unique<Shell::PasswordData>(prompt, std::move(function));
}
}
void Shell::delay_for(unsigned long ms, delay_function function) {
delay_until(uuid::get_uptime_ms() + ms, std::move(function));
}
void Shell::delay_until(uint64_t ms, delay_function function) {
if (mode_ == Mode::NORMAL) {
mode_ = Mode::DELAY;
mode_data_ = std::make_unique<Shell::DelayData>(ms, std::move(function));
}
}
void Shell::block_with(blocking_function function) {
if (mode_ == Mode::NORMAL) {
mode_ = Mode::BLOCKING;
mode_data_ = std::make_unique<Shell::BlockingData>(std::move(function));
}
}
void Shell::delete_buffer_word(bool display) {
size_t pos = line_buffer_.find_last_of(' ');
if (pos == std::string::npos) {
line_buffer_.clear();
if (display) {
erase_current_line();
prompt_displayed_ = false;
display_prompt();
}
} else {
if (display) {
erase_characters(line_buffer_.length() - pos);
}
line_buffer_.resize(pos);
}
}
size_t Shell::maximum_command_line_length() const {
return maximum_command_line_length_;
}
void Shell::maximum_command_line_length(size_t length) {
maximum_command_line_length_ = std::max((size_t)1, length);
line_buffer_.reserve(maximum_command_line_length_);
}
void Shell::process_command() {
CommandLine command_line{line_buffer_};
line_buffer_.clear();
println();
prompt_displayed_ = false;
if (!command_line->empty()) {
if (commands_) {
auto execution = commands_->execute_command(*this, std::move(command_line));
if (execution.error != nullptr) {
println(execution.error);
}
} else {
println(F("No commands configured"));
}
}
if (running()) {
display_prompt();
}
::yield();
}
void Shell::process_completion() {
CommandLine command_line{line_buffer_};
if (!command_line->empty() && commands_) {
auto completion = commands_->complete_command(*this, command_line);
bool redisplay = false;
if (!completion.help.empty()) {
println();
redisplay = true;
for (auto & help : completion.help) {
std::string help_line = help.to_string(maximum_command_line_length_);
println(help_line);
}
}
if (!completion.replacement->empty()) {
if (!redisplay) {
erase_current_line();
prompt_displayed_ = false;
redisplay = true;
}
line_buffer_ = completion.replacement.to_string(maximum_command_line_length_);
}
if (redisplay) {
display_prompt();
}
}
::yield();
}
void Shell::process_password(bool completed) {
println();
auto * password_data = reinterpret_cast<Shell::PasswordData *>(mode_data_.get());
auto function_copy = password_data->password_function_;
mode_ = Mode::NORMAL;
mode_data_.reset();
function_copy(*this, completed, line_buffer_);
line_buffer_.clear();
if (running()) {
display_prompt();
}
}
void Shell::invoke_command(const std::string & line) {
if (!line_buffer_.empty()) {
println();
prompt_displayed_ = false;
}
if (!prompt_displayed_) {
display_prompt();
}
line_buffer_ = line;
print(line_buffer_);
process_command();
}
unsigned long Shell::idle_timeout() const {
return idle_timeout_ / 1000;
}
void Shell::idle_timeout(unsigned long timeout) {
idle_timeout_ = (uint64_t)timeout * 1000;
}
void Shell::check_idle_timeout() {
if (idle_timeout_ > 0 && uuid::get_uptime_ms() - idle_time_ >= idle_timeout_) {
println();
stop();
}
}
} // namespace console
} // namespace uuid

View File

@@ -0,0 +1,107 @@
/*
* uuid-console - Microcontroller console shell
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <uuid/console.h>
#include <Arduino.h>
#include <algorithm>
#include <memory>
#include <string>
#include <uuid/log.h>
namespace uuid {
namespace console {
static const char __pstr__logger_name[] __attribute__((__aligned__(sizeof(int)))) PROGMEM = "shell";
const uuid::log::Logger Shell::logger_{reinterpret_cast<const __FlashStringHelper *>(__pstr__logger_name), uuid::log::Facility::LPR};
Shell::QueuedLogMessage::QueuedLogMessage(unsigned long id, std::shared_ptr<uuid::log::Message> && content)
: id_(id)
, content_(std::move(content)) {
}
void Shell::operator<<(std::shared_ptr<uuid::log::Message> message) {
if (log_messages_.size() >= maximum_log_messages_) {
log_messages_.pop_front();
}
log_messages_.emplace_back(log_message_id_++, std::move(message));
}
uuid::log::Level Shell::log_level() const {
return uuid::log::Logger::get_log_level(this);
}
void Shell::log_level(uuid::log::Level level) {
uuid::log::Logger::register_handler(this, level);
}
size_t Shell::maximum_log_messages() const {
return maximum_log_messages_;
}
void Shell::maximum_log_messages(size_t count) {
maximum_log_messages_ = std::max((size_t)1, count);
while (log_messages_.size() > maximum_log_messages_) {
log_messages_.pop_front();
}
}
void Shell::output_logs() {
if (!log_messages_.empty()) {
if (mode_ != Mode::DELAY) {
erase_current_line();
prompt_displayed_ = false;
}
while (!log_messages_.empty()) {
auto message = std::move(log_messages_.front());
log_messages_.pop_front();
print(uuid::log::format_timestamp_ms(message.content_->uptime_ms, 3));
printf(F(" %c %lu: [%S] "), uuid::log::format_level_char(message.content_->level), message.id_, message.content_->name);
if ((message.content_->level == uuid::log::Level::ERR) || (message.content_->level == uuid::log::Level::WARNING)) {
print(COLOR_RED);
println(message.content_->text);
print(COLOR_RESET);
} else if (message.content_->level == uuid::log::Level::INFO) {
print(COLOR_YELLOW);
println(message.content_->text);
print(COLOR_RESET);
} else if (message.content_->level == uuid::log::Level::DEBUG) {
print(COLOR_CYAN);
println(message.content_->text);
print(COLOR_RESET);
} else {
println(message.content_->text);
}
::yield();
display_prompt();
}
}
}
} // namespace console
} // namespace uuid

View File

@@ -0,0 +1,45 @@
/*
* uuid-console - Microcontroller console shell
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <uuid/console.h>
#include <memory>
#include <set>
namespace uuid {
namespace console {
std::set<std::shared_ptr<Shell>> Shell::shells_;
void Shell::loop_all() {
for (auto shell = shells_.begin(); shell != shells_.end();) {
shell->get()->loop_one();
// This avoids copying the shared_ptr every time loop_one() is called
if (!shell->get()->running()) {
shell = shells_.erase(shell);
} else {
shell++;
}
}
}
} // namespace console
} // namespace uuid

View File

@@ -0,0 +1,164 @@
/*
* uuid-console - Microcontroller console shell
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <uuid/console.h>
#include <Arduino.h>
#include <stdarg.h>
#include <string>
namespace uuid {
namespace console {
size_t Shell::print(const std::string & data) {
if (data.empty()) {
return 0;
} else {
return write(reinterpret_cast<const uint8_t *>(data.c_str()), data.length());
}
}
size_t Shell::println(const std::string & data) {
size_t len = print(data);
len += println();
return len;
}
size_t Shell::printf(const char * format, ...) {
va_list ap;
va_start(ap, format);
size_t len = vprintf(format, ap);
va_end(ap);
return len;
}
size_t Shell::printf(const __FlashStringHelper * format, ...) {
va_list ap;
va_start(ap, format);
size_t len = vprintf(format, ap);
va_end(ap);
return len;
}
size_t Shell::printfln(const char * format, ...) {
va_list ap;
va_start(ap, format);
size_t len = vprintf(format, ap);
va_end(ap);
len += println();
return len;
}
size_t Shell::printfln(const __FlashStringHelper * format, ...) {
va_list ap;
va_start(ap, format);
size_t len = vprintf(format, ap);
va_end(ap);
len += println();
return len;
}
size_t Shell::vprintf(const char * format, va_list ap) {
size_t print_len = 0;
va_list copy_ap;
va_copy(copy_ap, ap);
int format_len = ::vsnprintf(nullptr, 0, format, ap);
if (format_len > 0) {
std::string text(static_cast<std::string::size_type>(format_len), '\0');
::vsnprintf(&text[0], text.capacity() + 1, format, copy_ap);
print_len = print(text);
}
va_end(copy_ap);
return print_len;
}
size_t Shell::vprintf(const __FlashStringHelper * format, va_list ap) {
size_t print_len = 0;
va_list copy_ap;
va_copy(copy_ap, ap);
int format_len = ::vsnprintf_P(nullptr, 0, reinterpret_cast<PGM_P>(format), ap);
if (format_len > 0) {
std::string text(static_cast<std::string::size_type>(format_len), '\0');
::vsnprintf_P(&text[0], text.capacity() + 1, reinterpret_cast<PGM_P>(format), copy_ap);
print_len = print(text);
}
va_end(copy_ap);
return print_len;
}
// modified by proddy
void Shell::print_all_available_commands() {
/*
commands_->for_each_available_command(*this, [this](std::vector<std::string> & name, std::vector<std::string> & arguments) {
CommandLine command_line{name, arguments};
command_line.escape_initial_parameters(name.size());
name.clear();
arguments.clear();
println(command_line.to_string(maximum_command_line_length()));
});
*/
// changed by proddy - sort the help commands
std::list<std::string> sorted_cmds;
commands_->for_each_available_command(*this, [&](std::vector<std::string> & name, std::vector<std::string> & arguments) {
CommandLine command_line{name, arguments};
command_line.escape_initial_parameters(name.size());
name.clear();
arguments.clear();
sorted_cmds.push_back(command_line.to_string(maximum_command_line_length()));
});
sorted_cmds.sort();
for (auto & cl : sorted_cmds) {
// print(" ");
println(cl);
}
}
void Shell::erase_current_line() {
print(F("\033[0G\033[K"));
}
void Shell::erase_characters(size_t count) {
print(std::string(count, '\x08'));
print(F("\033[K"));
}
} // namespace console
} // namespace uuid

View File

@@ -0,0 +1,93 @@
/*
* uuid-console - Microcontroller console shell
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <uuid/console.h>
#include <string>
namespace uuid {
namespace console {
void Shell::display_banner() {
}
std::string Shell::hostname_text() {
return std::string{};
}
std::string Shell::context_text() {
return std::string{};
}
std::string Shell::prompt_prefix() {
return std::string{};
}
std::string Shell::prompt_suffix() {
return std::string{'$'};
}
void Shell::end_of_transmission() {
if (idle_timeout_ > 0) {
println();
stop();
}
}
void Shell::display_prompt() {
switch (mode_) {
case Mode::DELAY:
case Mode::BLOCKING:
break;
case Mode::PASSWORD:
print(reinterpret_cast<Shell::PasswordData *>(mode_data_.get())->password_prompt_);
break;
case Mode::NORMAL:
std::string hostname = hostname_text();
std::string context = context_text();
print(prompt_prefix());
// colors added by proddy
if (!hostname.empty()) {
print(COLOR_BRIGHT_GREEN);
print(COLOR_BOLD_ON);
print(hostname);
print(COLOR_RESET);
print(':');
}
if (!context.empty()) {
print(COLOR_BRIGHT_BLUE);
print(COLOR_BOLD_ON);
print(context);
print(COLOR_RESET);
// print(' ');
}
print(prompt_suffix());
print(' ');
print(line_buffer_);
prompt_displayed_ = true;
break;
}
}
} // namespace console
} // namespace uuid

View File

@@ -0,0 +1,125 @@
/*
* uuid-console - Microcontroller console shell
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <uuid/console.h>
#include <Arduino.h>
namespace uuid {
namespace console {
int Shell::available() {
if (mode_ == Mode::BLOCKING) {
auto * blocking_data = reinterpret_cast<Shell::BlockingData *>(mode_data_.get());
if (!available_char()) {
return 0;
}
if (blocking_data->consume_line_feed_) {
const int input = peek_one_char();
if (input >= 0) {
const unsigned char c = input;
blocking_data->consume_line_feed_ = false;
if (previous_ == '\x0D' && c == '\x0A') {
// Consume the first LF following a CR
read_one_char();
previous_ = c;
return available();
}
} else {
// The underlying stream has not implemented peek,
// so the next read() could return -1 if LF is
// filtered out.
}
}
return 1;
} else {
return 0;
}
}
int Shell::read() {
if (mode_ == Mode::BLOCKING) {
auto * blocking_data = reinterpret_cast<Shell::BlockingData *>(mode_data_.get());
const int input = read_one_char();
if (input >= 0) {
const unsigned char c = input;
if (blocking_data->consume_line_feed_) {
blocking_data->consume_line_feed_ = false;
if (previous_ == '\x0D' && c == '\x0A') {
// Consume the first LF following a CR
previous_ = c;
return read();
}
}
// Track read characters so that a final CR means we ignore the next LF
previous_ = c;
}
return input;
} else {
return -1;
}
}
int Shell::peek() {
if (mode_ == Mode::BLOCKING) {
auto * blocking_data = reinterpret_cast<Shell::BlockingData *>(mode_data_.get());
const int input = peek_one_char();
if (blocking_data->consume_line_feed_) {
if (input >= 0) {
const unsigned char c = input;
blocking_data->consume_line_feed_ = false;
if (previous_ == '\x0D' && c == '\x0A') {
// Consume the first LF following a CR
read_one_char();
previous_ = c;
return peek();
}
}
}
return input;
} else {
return -1;
}
}
void Shell::flush() {
// This is a pure virtual function in Arduino's Stream class, which
// makes no sense because that class is for input and this is an
// output function. Later versions move it to Print as an empty
// virtual function so this is here for backward compatibility.
}
} // namespace console
} // namespace uuid

View File

@@ -0,0 +1,63 @@
/*
* uuid-console - Microcontroller console shell
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <uuid/console.h>
#include <Arduino.h>
#include <memory>
#include <string>
namespace uuid {
namespace console {
StreamConsole::StreamConsole(Stream & stream)
: Shell()
, stream_(stream) {
}
// cppcheck-suppress passedByValue
StreamConsole::StreamConsole(std::shared_ptr<Commands> commands, Stream & stream, unsigned int context, unsigned int flags)
: Shell(std::move(commands), context, flags)
, stream_(stream) {
}
size_t StreamConsole::write(uint8_t data) {
return stream_.write(data);
}
size_t StreamConsole::write(const uint8_t * buffer, size_t size) {
return stream_.write(buffer, size);
}
bool StreamConsole::available_char() {
return stream_.available() > 0;
}
int StreamConsole::read_one_char() {
return stream_.read();
}
int StreamConsole::peek_one_char() {
return stream_.peek();
}
} // namespace console
} // namespace uuid

File diff suppressed because it is too large Load Diff

674
lib/uuid-log/COPYING Normal file
View File

@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program 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.
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

25
lib/uuid-log/README.rst Normal file
View File

@@ -0,0 +1,25 @@
mcu-uuid-log |Build Status|
===========================
Description
-----------
Microcontroller logging framework
Purpose
-------
Provides a framework for handling log messages. This library is for
single threaded applications and cannot be used from an interrupt
context.
Documentation
-------------
`Read the documentation <https://mcu-uuid-log.readthedocs.io/>`_ generated
from the docs_ directory.
.. _docs: docs/
.. |Build Status| image:: https://travis-ci.org/nomis/mcu-uuid-log.svg?branch=master
:target: https://travis-ci.org/nomis/mcu-uuid-log

34
lib/uuid-log/library.json Normal file
View File

@@ -0,0 +1,34 @@
{
"name": "uuid-log",
"description": "Logging framework",
"keywords": "logging",
"authors": [
{
"name": "Simon Arlott",
"maintainer": true
}
],
"repository": {
"type": "git",
"url": "https://github.com/nomis/mcu-uuid-log.git"
},
"version": "2.1.1",
"license": "GPL-3.0-or-later",
"homepage": "https://mcu-uuid-log.readthedocs.io/",
"export": {
"exclude": [
".travis.yml",
"test/*"
]
},
"frameworks": [
"arduino"
],
"dependencies": {
"uuid-common": "^1.0.0"
},
"build": {
"flags": "-Wall -Wextra",
"libLDFMode": "off"
}
}

View File

@@ -0,0 +1,32 @@
/*
* uuid-log - Microcontroller logging framework
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <uuid/log.h>
namespace uuid {
namespace log {
char format_level_char(Level level) {
constexpr char log_level_chars[(int)Level::ALL - (int)Level::OFF + 1] = {' ', 'P', 'A', 'C', 'E', 'W', 'N', 'I', 'D', 'T', ' '};
return log_level_chars[(int)level + 1];
}
} // namespace log
} // namespace uuid

View File

@@ -0,0 +1,60 @@
/*
* uuid-log - Microcontroller logging framework
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <uuid/log.h>
#include <Arduino.h>
#include <string>
namespace uuid {
namespace log {
static constexpr const char * pstr_level_lowercase_off __attribute__((__aligned__(sizeof(int)))) PROGMEM = "off";
static constexpr const char * pstr_level_lowercase_emerg __attribute__((__aligned__(sizeof(int)))) PROGMEM = "emerg";
static constexpr const char * pstr_level_lowercase_crit __attribute__((__aligned__(sizeof(int)))) PROGMEM = "crit";
static constexpr const char * pstr_level_lowercase_alert __attribute__((__aligned__(sizeof(int)))) PROGMEM = "alert";
static constexpr const char * pstr_level_lowercase_err __attribute__((__aligned__(sizeof(int)))) PROGMEM = "err";
static constexpr const char * pstr_level_lowercase_warning __attribute__((__aligned__(sizeof(int)))) PROGMEM = "warning";
static constexpr const char * pstr_level_lowercase_notice __attribute__((__aligned__(sizeof(int)))) PROGMEM = "notice";
static constexpr const char * pstr_level_lowercase_info __attribute__((__aligned__(sizeof(int)))) PROGMEM = "info";
static constexpr const char * pstr_level_lowercase_debug __attribute__((__aligned__(sizeof(int)))) PROGMEM = "debug";
static constexpr const char * pstr_level_lowercase_trace __attribute__((__aligned__(sizeof(int)))) PROGMEM = "trace";
static constexpr const char * pstr_level_lowercase_all __attribute__((__aligned__(sizeof(int)))) PROGMEM = "all";
static const __FlashStringHelper * log_level_lowercase[(int)Level::ALL - (int)Level::OFF + 1] __attribute__((__aligned__(sizeof(int))))
PROGMEM = {reinterpret_cast<const __FlashStringHelper *>(pstr_level_lowercase_off),
reinterpret_cast<const __FlashStringHelper *>(pstr_level_lowercase_emerg),
reinterpret_cast<const __FlashStringHelper *>(pstr_level_lowercase_crit),
reinterpret_cast<const __FlashStringHelper *>(pstr_level_lowercase_alert),
reinterpret_cast<const __FlashStringHelper *>(pstr_level_lowercase_err),
reinterpret_cast<const __FlashStringHelper *>(pstr_level_lowercase_warning),
reinterpret_cast<const __FlashStringHelper *>(pstr_level_lowercase_notice),
reinterpret_cast<const __FlashStringHelper *>(pstr_level_lowercase_info),
reinterpret_cast<const __FlashStringHelper *>(pstr_level_lowercase_debug),
reinterpret_cast<const __FlashStringHelper *>(pstr_level_lowercase_trace),
reinterpret_cast<const __FlashStringHelper *>(pstr_level_lowercase_all)};
const __FlashStringHelper * format_level_lowercase(Level level) {
return log_level_lowercase[(int)level + 1];
}
} // namespace log
} // namespace uuid

View File

@@ -0,0 +1,60 @@
/*
* uuid-log - Microcontroller logging framework
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <uuid/log.h>
#include <Arduino.h>
#include <string>
namespace uuid {
namespace log {
static constexpr const char * pstr_level_uppercase_off __attribute__((__aligned__(sizeof(int)))) PROGMEM = "OFF";
static constexpr const char * pstr_level_uppercase_emerg __attribute__((__aligned__(sizeof(int)))) PROGMEM = "EMERG";
static constexpr const char * pstr_level_uppercase_crit __attribute__((__aligned__(sizeof(int)))) PROGMEM = "CRIT";
static constexpr const char * pstr_level_uppercase_alert __attribute__((__aligned__(sizeof(int)))) PROGMEM = "ALERT";
static constexpr const char * pstr_level_uppercase_err __attribute__((__aligned__(sizeof(int)))) PROGMEM = "ERR";
static constexpr const char * pstr_level_uppercase_warning __attribute__((__aligned__(sizeof(int)))) PROGMEM = "WARNING";
static constexpr const char * pstr_level_uppercase_notice __attribute__((__aligned__(sizeof(int)))) PROGMEM = "NOTICE";
static constexpr const char * pstr_level_uppercase_info __attribute__((__aligned__(sizeof(int)))) PROGMEM = "INFO";
static constexpr const char * pstr_level_uppercase_debug __attribute__((__aligned__(sizeof(int)))) PROGMEM = "DEBUG";
static constexpr const char * pstr_level_uppercase_trace __attribute__((__aligned__(sizeof(int)))) PROGMEM = "TRACE";
static constexpr const char * pstr_level_uppercase_all __attribute__((__aligned__(sizeof(int)))) PROGMEM = "ALL";
static const __FlashStringHelper * log_level_uppercase[(int)Level::ALL - (int)Level::OFF + 1] __attribute__((__aligned__(sizeof(int))))
PROGMEM = {reinterpret_cast<const __FlashStringHelper *>(pstr_level_uppercase_off),
reinterpret_cast<const __FlashStringHelper *>(pstr_level_uppercase_emerg),
reinterpret_cast<const __FlashStringHelper *>(pstr_level_uppercase_crit),
reinterpret_cast<const __FlashStringHelper *>(pstr_level_uppercase_alert),
reinterpret_cast<const __FlashStringHelper *>(pstr_level_uppercase_err),
reinterpret_cast<const __FlashStringHelper *>(pstr_level_uppercase_warning),
reinterpret_cast<const __FlashStringHelper *>(pstr_level_uppercase_notice),
reinterpret_cast<const __FlashStringHelper *>(pstr_level_uppercase_info),
reinterpret_cast<const __FlashStringHelper *>(pstr_level_uppercase_debug),
reinterpret_cast<const __FlashStringHelper *>(pstr_level_uppercase_trace),
reinterpret_cast<const __FlashStringHelper *>(pstr_level_uppercase_all)};
const __FlashStringHelper * format_level_uppercase(Level level) {
return log_level_uppercase[(int)level + 1];
}
} // namespace log
} // namespace uuid

View File

@@ -0,0 +1,57 @@
/*
* uuid-log - Microcontroller logging framework
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <uuid/log.h>
#include <Arduino.h>
#include <cstdint>
#include <string>
namespace uuid {
namespace log {
std::string format_timestamp_ms(uint64_t timestamp_ms, unsigned int days_width) {
unsigned long days;
unsigned int hours, minutes, seconds, milliseconds;
days = timestamp_ms / 86400000UL;
timestamp_ms %= 86400000UL;
hours = timestamp_ms / 3600000UL;
timestamp_ms %= 3600000UL;
minutes = timestamp_ms / 60000UL;
timestamp_ms %= 60000UL;
seconds = timestamp_ms / 1000UL;
timestamp_ms %= 1000UL;
milliseconds = timestamp_ms;
std::vector<char> text(10 + 1 /* days */ + 2 + 1 /* hours */ + 2 + 1 /* minutes */ + 2 + 1 /* seconds */ + 3 /* milliseconds */ + 1);
snprintf_P(text.data(), text.size(), PSTR("%0*lu+%02u:%02u:%02u.%03u"), std::min(days_width, 10U), days, hours, minutes, seconds, milliseconds);
return text.data();
}
} // namespace log
} // namespace uuid

View File

@@ -0,0 +1,43 @@
/*
* uuid-log - Microcontroller logging framework
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <uuid/log.h>
#include <vector>
namespace uuid {
namespace log {
std::vector<Level> levels() {
return {Level::OFF,
Level::EMERG,
Level::ALERT,
Level::CRIT,
Level::ERR,
Level::WARNING,
Level::NOTICE,
Level::INFO,
Level::DEBUG,
Level::TRACE,
Level::ALL};
}
} // namespace log
} // namespace uuid

View File

@@ -0,0 +1,45 @@
/*
* uuid-log - Microcontroller logging framework
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <uuid/log.h>
#include <vector>
#include <uuid/common.h>
namespace uuid {
namespace log {
std::vector<std::string> levels_lowercase() {
return {uuid::read_flash_string(format_level_lowercase(Level::OFF)),
uuid::read_flash_string(format_level_lowercase(Level::EMERG)),
uuid::read_flash_string(format_level_lowercase(Level::ALERT)),
uuid::read_flash_string(format_level_lowercase(Level::CRIT)),
uuid::read_flash_string(format_level_lowercase(Level::ERR)),
uuid::read_flash_string(format_level_lowercase(Level::WARNING)),
uuid::read_flash_string(format_level_lowercase(Level::NOTICE)),
uuid::read_flash_string(format_level_lowercase(Level::INFO)),
uuid::read_flash_string(format_level_lowercase(Level::DEBUG)),
uuid::read_flash_string(format_level_lowercase(Level::TRACE)),
uuid::read_flash_string(format_level_lowercase(Level::ALL))};
}
} // namespace log
} // namespace uuid

View File

@@ -0,0 +1,45 @@
/*
* uuid-log - Microcontroller logging framework
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <uuid/log.h>
#include <vector>
#include <uuid/common.h>
namespace uuid {
namespace log {
std::vector<std::string> levels_uppercase() {
return {uuid::read_flash_string(format_level_uppercase(Level::OFF)),
uuid::read_flash_string(format_level_uppercase(Level::EMERG)),
uuid::read_flash_string(format_level_uppercase(Level::ALERT)),
uuid::read_flash_string(format_level_uppercase(Level::CRIT)),
uuid::read_flash_string(format_level_uppercase(Level::ERR)),
uuid::read_flash_string(format_level_uppercase(Level::WARNING)),
uuid::read_flash_string(format_level_uppercase(Level::NOTICE)),
uuid::read_flash_string(format_level_uppercase(Level::INFO)),
uuid::read_flash_string(format_level_uppercase(Level::DEBUG)),
uuid::read_flash_string(format_level_uppercase(Level::TRACE)),
uuid::read_flash_string(format_level_uppercase(Level::ALL))};
}
} // namespace log
} // namespace uuid

335
lib/uuid-log/src/log.cpp Normal file
View File

@@ -0,0 +1,335 @@
/*
* uuid-log - Microcontroller logging framework
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <uuid/log.h>
#include <Arduino.h>
#include <cstdarg>
#include <cstdint>
#include <list>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
namespace uuid {
namespace log {
std::map<Handler *, Level> Logger::handlers_;
Level Logger::level_ = Level::OFF;
Message::Message(uint64_t uptime_ms, Level level, Facility facility, const __FlashStringHelper * name, const std::string && text)
: uptime_ms(uptime_ms)
, level(level)
, facility(facility)
, name(name)
, text(std::move(text)) {
}
Logger::Logger(const __FlashStringHelper * name, Facility facility)
: name_(name)
, facility_(facility){
};
void Logger::register_handler(Handler * handler, Level level) {
handlers_[handler] = level;
refresh_log_level();
};
void Logger::unregister_handler(Handler * handler) {
handlers_.erase(handler);
refresh_log_level();
};
Level Logger::get_log_level(const Handler * handler) {
const auto level = handlers_.find(const_cast<Handler *>(handler));
if (level != handlers_.end()) {
return level->second;
}
return Level::OFF;
}
void Logger::emerg(const char * format, ...) const {
if (enabled(Level::EMERG)) {
va_list ap;
va_start(ap, format);
vlog(Level::EMERG, format, ap);
va_end(ap);
}
};
void Logger::emerg(const __FlashStringHelper * format, ...) const {
if (enabled(Level::EMERG)) {
va_list ap;
va_start(ap, format);
vlog(Level::EMERG, format, ap);
va_end(ap);
}
};
void Logger::crit(const char * format, ...) const {
if (enabled(Level::CRIT)) {
va_list ap;
va_start(ap, format);
vlog(Level::CRIT, format, ap);
va_end(ap);
}
};
void Logger::crit(const __FlashStringHelper * format, ...) const {
if (enabled(Level::CRIT)) {
va_list ap;
va_start(ap, format);
vlog(Level::CRIT, format, ap);
va_end(ap);
}
};
void Logger::alert(const char * format, ...) const {
if (enabled(Level::ALERT)) {
va_list ap;
va_start(ap, format);
vlog(Level::ALERT, format, ap);
va_end(ap);
}
};
void Logger::alert(const __FlashStringHelper * format, ...) const {
if (enabled(Level::ALERT)) {
va_list ap;
va_start(ap, format);
vlog(Level::ALERT, format, ap);
va_end(ap);
}
};
void Logger::err(const char * format, ...) const {
if (enabled(Level::ERR)) {
va_list ap;
va_start(ap, format);
vlog(Level::ERR, format, ap);
va_end(ap);
}
};
void Logger::err(const __FlashStringHelper * format, ...) const {
if (enabled(Level::ERR)) {
va_list ap;
va_start(ap, format);
vlog(Level::ERR, format, ap);
va_end(ap);
}
};
void Logger::warning(const char * format, ...) const {
if (enabled(Level::WARNING)) {
va_list ap;
va_start(ap, format);
vlog(Level::WARNING, format, ap);
va_end(ap);
}
};
void Logger::warning(const __FlashStringHelper * format, ...) const {
if (enabled(Level::WARNING)) {
va_list ap;
va_start(ap, format);
vlog(Level::WARNING, format, ap);
va_end(ap);
}
};
void Logger::notice(const char * format, ...) const {
if (enabled(Level::NOTICE)) {
va_list ap;
va_start(ap, format);
vlog(Level::NOTICE, format, ap);
va_end(ap);
}
};
void Logger::notice(const __FlashStringHelper * format, ...) const {
if (enabled(Level::NOTICE)) {
va_list ap;
va_start(ap, format);
vlog(Level::NOTICE, format, ap);
va_end(ap);
}
};
void Logger::info(const char * format, ...) const {
if (enabled(Level::INFO)) {
va_list ap;
va_start(ap, format);
vlog(Level::INFO, format, ap);
va_end(ap);
}
};
void Logger::info(const __FlashStringHelper * format, ...) const {
if (enabled(Level::INFO)) {
va_list ap;
va_start(ap, format);
vlog(Level::INFO, format, ap);
va_end(ap);
}
};
void Logger::debug(const char * format, ...) const {
if (enabled(Level::DEBUG)) {
va_list ap;
va_start(ap, format);
vlog(Level::DEBUG, format, ap);
va_end(ap);
}
};
void Logger::debug(const __FlashStringHelper * format, ...) const {
if (enabled(Level::DEBUG)) {
va_list ap;
va_start(ap, format);
vlog(Level::DEBUG, format, ap);
va_end(ap);
}
};
void Logger::trace(const char * format, ...) const {
if (enabled(Level::TRACE)) {
va_list ap;
va_start(ap, format);
vlog(Level::TRACE, format, ap);
va_end(ap);
}
};
void Logger::trace(const __FlashStringHelper * format, ...) const {
if (enabled(Level::TRACE)) {
va_list ap;
va_start(ap, format);
vlog(Level::TRACE, format, ap);
va_end(ap);
}
};
void Logger::log(Level level, Facility facility, const char * format, ...) const {
if (level < Level::EMERG) {
level = Level::EMERG;
} else if (level > Level::TRACE) {
level = Level::TRACE;
}
if (enabled(level)) {
va_list ap;
va_start(ap, format);
vlog(level, facility, format, ap);
va_end(ap);
}
};
void Logger::log(Level level, Facility facility, const __FlashStringHelper * format, ...) const {
if (level < Level::EMERG) {
level = Level::EMERG;
} else if (level > Level::TRACE) {
level = Level::TRACE;
}
if (enabled(level)) {
va_list ap;
va_start(ap, format);
vlog(level, facility, format, ap);
va_end(ap);
}
};
void Logger::vlog(Level level, const char * format, va_list ap) const {
vlog(level, facility_, format, ap);
}
void Logger::vlog(Level level, Facility facility, const char * format, va_list ap) const {
std::vector<char> text(MAX_LOG_LENGTH + 1);
if (vsnprintf(text.data(), text.size(), format, ap) <= 0) {
return;
}
dispatch(level, facility, text);
}
void Logger::vlog(Level level, const __FlashStringHelper * format, va_list ap) const {
vlog(level, facility_, format, ap);
}
void Logger::vlog(Level level, Facility facility, const __FlashStringHelper * format, va_list ap) const {
std::vector<char> text(MAX_LOG_LENGTH + 1);
if (vsnprintf_P(text.data(), text.size(), reinterpret_cast<PGM_P>(format), ap) <= 0) {
return;
}
dispatch(level, facility, text);
}
void Logger::dispatch(Level level, Facility facility, std::vector<char> & text) const {
std::shared_ptr<Message> message = std::make_shared<Message>(get_uptime_ms(), level, facility, name_, text.data());
text.resize(0);
for (auto & handler : handlers_) {
if (level <= handler.second) {
*handler.first << message;
}
}
}
void Logger::refresh_log_level() {
level_ = Level::OFF;
for (auto & handler : handlers_) {
if (level_ < handler.second) {
level_ = handler.second;
}
}
}
} // namespace log
} // namespace uuid

View File

@@ -0,0 +1,41 @@
/*
* uuid-log - Microcontroller logging framework
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <uuid/log.h>
#include <Arduino.h>
#include <string>
namespace uuid {
namespace log {
bool parse_level_lowercase(const std::string & name, Level & level) {
for (auto value : levels()) {
if (!strcmp_P(name.c_str(), reinterpret_cast<PGM_P>(format_level_lowercase(value)))) {
level = value;
return true;
}
}
return false;
}
} // namespace log
} // namespace uuid

View File

@@ -0,0 +1,41 @@
/*
* uuid-log - Microcontroller logging framework
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <uuid/log.h>
#include <Arduino.h>
#include <string>
namespace uuid {
namespace log {
bool parse_level_uppercase(const std::string & name, Level & level) {
for (auto value : levels()) {
if (!strcmp_P(name.c_str(), reinterpret_cast<PGM_P>(format_level_uppercase(value)))) {
level = value;
return true;
}
}
return false;
}
} // namespace log
} // namespace uuid

633
lib/uuid-log/src/uuid/log.h Normal file
View File

@@ -0,0 +1,633 @@
/*
* uuid-log - Microcontroller logging framework
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef UUID_LOG_H_
#define UUID_LOG_H_
#include <Arduino.h>
#include <cstdarg>
#include <cstdint>
#include <list>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include <uuid/common.h>
namespace uuid {
/**
* Logging framework.
*
* Provides a framework for handling log messages. This library is for
* single threaded applications and cannot be used from an interrupt
* context.
*
* - <a href="https://github.com/nomis/mcu-uuid-log/">Git Repository</a>
* - <a href="https://mcu-uuid-log.readthedocs.io/">Documentation</a>
*/
// ANSI Colors - added by Proddy
// See https://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html
#define COLOR_RESET "\x1B[0m"
#define COLOR_BLACK "\x1B[0;30m"
#define COLOR_RED "\x1B[0;31m"
#define COLOR_GREEN "\x1B[0;32m"
#define COLOR_YELLOW "\x1B[0;33m"
#define COLOR_BLUE "\x1B[0;34m"
#define COLOR_MAGENTA "\x1B[0;35m"
#define COLOR_CYAN "\x1B[0;36m"
#define COLOR_WHITE "\x1B[0;37m"
#define COLOR_BOLD_ON "\x1B[1m"
#define COLOR_BOLD_OFF "\x1B[22m"
#define COLOR_BRIGHT_BLACK "\x1B[0;90m"
#define COLOR_BRIGHT_RED "\x1B[0;91m"
#define COLOR_BRIGHT_GREEN "\x1B[0;92m"
#define COLOR_BRIGHT_YELLOW "\x1B[0;99m"
#define COLOR_BRIGHT_BLUE "\x1B[0;94m"
#define COLOR_BRIGHT_MAGENTA "\x1B[0;95m"
#define COLOR_BRIGHT_CYAN "\x1B[0;96m"
#define COLOR_BRIGHT_WHITE "\x1B[0;97m"
#define COLOR_UNDERLINE "\x1B[4m"
#define COLOR_BRIGHT_RED_BACKGROUND "\x1B[41;1m"
namespace log {
/**
* Severity level of log messages.
*
* @since 1.0.0
*/
enum Level : int8_t {
OFF = -1, /*!< Meta level representing no log messages. @since 1.0.0 */
EMERG = 0, /*!< System is unusable. @since 1.0.0 */
ALERT, /*!< Action must be taken immediately. @since 1.0.0 */
CRIT, /*!< Critical conditions. @since 1.0.0 */
ERR, /*!< Error conditions. @since 1.0.0 */
WARNING, /*!< Warning conditions. @since 1.0.0 */
NOTICE, /*!< Normal but significant conditions. @since 1.0.0 */
INFO, /*!< Informational messages. @since 1.0.0 */
DEBUG, /*!< Debug-level messages. @since 1.0.0 */
TRACE, /*!< Trace messages. @since 1.0.0 */
ALL, /*!< Meta level representing all log messages. @since 1.0.0 */
};
/**
* Facility type of the process logging a message.
*
* @since 1.0.0
*/
enum Facility : uint8_t {
KERN = 0, /*!< Kernel messages. @since 1.0.0 */
USER, /*!< User-level messages. @since 1.0.0 */
MAIL, /*!< Mail system. @since 1.0.0 */
DAEMON, /*!< System daemons. @since 1.0.0 */
AUTH, /*!< Security/authentication messages. @since 1.0.0 */
SYSLOG, /*!< Messages generated internally by logger. @since 1.0.0 */
LPR, /*!< Line printer subsystem. @since 1.0.0 */
NEWS, /*!< Network news subsystem. @since 1.0.0 */
UUCP, /*!< UUCP subsystem. @since 1.0.0 */
CRON, /*!< Clock daemon. @since 1.0.0 */
AUTHPRIV, /*!< Security/authentication messages (private). @since 1.0.0 */
FTP, /*!< FTP daemon. @since 1.0.0 */
NTP, /*!< NTP subsystem. @since 1.0.0 */
SECURITY, /*!< Log audit. @since 1.0.0 */
CONSOLE, /*!< Log alert. @since 1.0.0 */
CRON2, /*!< Scheduling daemon. @since 1.0.0 */
LOCAL0, /*!< Locally used facility 0. @since 1.0.0 */
LOCAL1, /*!< Locally used facility 1. @since 1.0.0 */
LOCAL2, /*!< Locally used facility 2. @since 1.0.0 */
LOCAL3, /*!< Locally used facility 3. @since 1.0.0 */
LOCAL4, /*!< Locally used facility 4. @since 1.0.0 */
LOCAL5, /*!< Locally used facility 5. @since 1.0.0 */
LOCAL6, /*!< Locally used facility 6. @since 1.0.0 */
LOCAL7, /*!< Locally used facility 7. @since 1.0.0 */
};
/**
* Format a system uptime timestamp as a string.
*
* Using the format "d+HH:mm:ss.SSS" with leading zeros for the days.
*
* @param[in] timestamp_ms System uptime in milliseconds, see uuid::get_uptime_ms().
* @param[in] days_width Leading zeros for the days part of the output.
* @return String with the formatted system uptime.
* @since 1.0.0
*/
std::string format_timestamp_ms(uint64_t timestamp_ms, unsigned int days_width = 1);
/**
* Get all log levels.
*
* @return A list of all log levels in priority order from
* uuid::log::Level::OFF to uuid::log::Level::ALL.
* @since 2.1.0
*/
std::vector<Level> levels();
/**
* Format a log level as a single character.
*
* Level::EMERG is represented as 'P' because it conflicts with
* Level::ERR and it used to be the "panic" level.
*
* @param[in] level Log level.
* @return Single character uppercase representation of the log level.
* @since 1.0.0
*/
char format_level_char(Level level);
/**
* Format a log level as an uppercase string.
*
* @param[in] level Log level.
* @return Uppercase name of the log level (flash string).
* @since 1.0.0
*/
const __FlashStringHelper * format_level_uppercase(Level level);
/**
* Get all log levels as uppercase strings.
*
* @return A list of all log levels in priority order from
* uuid::log::Level::OFF to uuid::log::Level::ALL
* as uppercase strings.
* @since 2.1.0
*/
std::vector<std::string> levels_uppercase();
/**
* Parse an uppercase string to a log level.
*
* @param[in] name Uppercase name of the log level.
* @param[out] level Log level.
* @return True if the named level is valid, otherwise false.
* @since 2.1.0
*/
bool parse_level_uppercase(const std::string & name, Level & level);
/**
* Format a log level as a lowercase string.
*
* @param[in] level Log level.
* @return Lowercase name of the log level (flash string).
* @since 1.0.0
*/
const __FlashStringHelper * format_level_lowercase(Level level);
/**
* Get all log levels as lowercase strings.
*
* @return A list of all log levels in priority order from
* uuid::log::Level::OFF to uuid::log::Level::ALL
* as lowercase strings.
* @since 2.1.0
*/
std::vector<std::string> levels_lowercase();
/**
* Parse a lowercase string to a log level.
*
* @param[in] name Lowercase name of the log level.
* @param[out] level Log level.
* @return True if the named level is valid, otherwise false.
* @since 2.1.0
*/
bool parse_level_lowercase(const std::string & name, Level & level);
/**
* Log message text with timestamp and logger attributes.
*
* These will be created when a message is logged and then passed to
* all registered handlers.
*
* @since 1.0.0
*/
struct Message {
/**
* Create a new log message (not directly useful).
*
* @param[in] uptime_ms System uptime, see uuid::get_uptime_ms().
* @param[in] level Severity level of the message.
* @param[in] facility Facility type of the process logging the message.
* @param[in] name Logger name (flash string).
* @param[in] text Log message text.
* @since 1.0.0
*/
Message(uint64_t uptime_ms, Level level, Facility facility, const __FlashStringHelper * name, const std::string && text);
~Message() = default;
/**
* System uptime at the time the message was logged.
*
* @see uuid::get_uptime_ms()
* @since 1.0.0
*/
const uint64_t uptime_ms;
/**
* Severity level of the message.
*
* @since 1.0.0
*/
const Level level;
/**
* Facility type of the process that logged the message.
*
* @since 1.0.0
*/
const Facility facility;
/**
* Name of the logger used (flash string).
*
* @since 1.0.0
*/
const __FlashStringHelper * name;
/**
* Formatted log message text.
*
* Does not include any of the other message attributes, those must
* be added by the handler when outputting messages.
*
* @since 1.0.0
*/
const std::string text;
};
/**
* Logger handler used to process log messages.
*
* @since 1.0.0
*/
class Handler {
public:
virtual ~Handler() = default;
/**
* Add a new log message.
*
* This should normally be put in a queue instead of being
* processed immediately so that log messages have minimal impact
* at the time of use.
*
* Queues should have a maximum size and discard the oldest message
* when full.
*
* @param[in] message New log message, shared by all handlers.
* @since 1.0.0
*/
virtual void operator<<(std::shared_ptr<Message> message) = 0;
protected:
Handler() = default;
};
/**
* Logger instance used to make log messages.
*
* @since 1.0.0
*/
class Logger {
public:
/**
* This is the maximum length of any log message.
*
* Determines the size of the buffer used for format string
* printing.
*
* @since 1.0.0
*/
static constexpr size_t MAX_LOG_LENGTH = 255;
/**
* Create a new logger with the given name and logging facility.
*
* @param[in] name Logger name (flash string).
* @param[in] facility Default logging facility for messages.
*
* @since 1.0.0
*/
Logger(const __FlashStringHelper * name, Facility facility = Facility::LOCAL0);
~Logger() = default;
/**
* Register a log handler.
*
* Call again to change the log level.
*
* Do not call this function from a static initializer.
*
* @param[in] handler Handler object that will handle log
* messages.
* @param[in] level Minimum log level that the handler is
* interested in.
* @since 1.0.0
*/
static void register_handler(Handler * handler, Level level);
/**
* Unregister a log handler.
*
* It is safe to call this with a handler that is not registered.
*
* Do not call this function from a static initializer.
*
* @param[in] handler Handler object that will no longer handle
* log messages.
* @since 1.0.0
*/
static void unregister_handler(Handler * handler);
/**
* Get the current log level of a handler.
*
* It is safe to call this with a handler that is not registered.
*
* Do not call this function from a static initializer.
*
* @param[in] handler Handler object that may handle log
* messages.
* @return The current log level of the specified handler.
* @since 1.0.0
*/
static Level get_log_level(const Handler * handler);
/**
* Determine if the current log level is enabled by any registered
* handlers.
*
* @return The current minimum global log level across all
* handlers.
* @since 1.0.0
*/
static inline bool enabled(Level level) {
return level <= level_;
}
/**
* Log a message at level Level::EMERG.
*
* @param[in] format Format string.
* @param[in] ... Format string arguments.
* @since 1.0.0
*/
void emerg(const char * format, ...) const /* __attribute__((format (printf, 2, 3))) */;
/**
* Log a message at level Level::EMERG.
*
* @param[in] format Format string (flash string).
* @param[in] ... Format string arguments.
* @since 1.0.0
*/
void emerg(const __FlashStringHelper * format, ...) const /* __attribute__((format (printf, 2, 3))) */;
/**
* Log a message at level Level::ALERT.
*
* @param[in] format Format string.
* @param[in] ... Format string arguments.
* @since 1.0.0
*/
void alert(const char * format, ...) const /* __attribute__((format (printf, 2, 3))) */;
/**
* Log a message at level Level::ALERT.
*
* @param[in] format Format string (flash string).
* @param[in] ... Format string arguments.
* @since 1.0.0
*/
void alert(const __FlashStringHelper * format, ...) const /* __attribute__((format (printf, 2, 3))) */;
/**
* Log a message at level Level::CRIT.
*
* @param[in] format Format string.
* @param[in] ... Format string arguments.
* @since 1.0.0
*/
void crit(const char * format, ...) const /* __attribute__((format (printf, 2, 3))) */;
/**
* Log a message at level Level::CRIT.
*
* @param[in] format Format string (flash string).
* @param[in] ... Format string arguments.
* @since 1.0.0
*/
void crit(const __FlashStringHelper * format, ...) const /* __attribute__((format (printf, 2, 3))) */;
/**
* Log a message at level Level::ERR.
*
* @param[in] format Format string.
* @param[in] ... Format string arguments.
* @since 1.0.0
*/
void err(const char * format, ...) const /* __attribute__((format (printf, 2, 3))) */;
/**
* Log a message at level Level::ERR.
*
* @param[in] format Format string (flash string).
* @param[in] ... Format string arguments.
* @since 1.0.0
*/
void err(const __FlashStringHelper * format, ...) const /* __attribute__((format (printf, 2, 3))) */;
/**
* Log a message at level Level::WARNING.
*
* @param[in] format Format string.
* @param[in] ... Format string arguments.
* @since 1.0.0
*/
void warning(const char * format, ...) const /* __attribute__((format (printf, 2, 3))) */;
/**
* Log a message at level Level::WARNING.
*
* @param[in] format Format string (flash string).
* @param[in] ... Format string arguments.
* @since 1.0.0
*/
void warning(const __FlashStringHelper * format, ...) const /* __attribute__((format (printf, 2, 3))) */;
/**
* Log a message at level Level::NOTICE.
*
* @param[in] format Format string.
* @param[in] ... Format string arguments.
* @since 1.0.0
*/
void notice(const char * format, ...) const /* __attribute__((format (printf, 2, 3))) */;
/**
* Log a message at level Level::NOTICE.
*
* @param[in] format Format string (flash string).
* @param[in] ... Format string arguments.
* @since 1.0.0
*/
void notice(const __FlashStringHelper * format, ...) const /* __attribute__((format (printf, 2, 3))) */;
/**
* Log a message at level Level::INFO.
*
* @param[in] format Format string.
* @param[in] ... Format string arguments.
* @since 1.0.0
*/
void info(const char * format, ...) const /* __attribute__((format (printf, 2, 3))) */;
/**
* Log a message at level Level::INFO.
*
* @param[in] format Format string (flash string).
* @param[in] ... Format string arguments.
*/
void info(const __FlashStringHelper * format, ...) const /* __attribute__((format (printf, 2, 3))) */;
/**
* Log a message at level Level::DEBUG.
*
* @param[in] format Format string.
* @param[in] ... Format string arguments.
* @since 1.0.0
*/
void debug(const char * format, ...) const /* __attribute__((format (printf, 2, 3))) */;
/**
* Log a message at level Level::DEBUG.
*
* @param[in] format Format string (flash string).
* @param[in] ... Format string arguments.
* @since 1.0.0
*/
void debug(const __FlashStringHelper * format, ...) const /* __attribute__((format (printf, 2, 3))) */;
/**
* Log a message at level Level::TRACE.
*
* @param[in] format Format string.
* @param[in] ... Format string arguments.
* @since 1.0.0
*/
void trace(const char * format, ...) const /* __attribute__((format (printf, 2, 3))) */;
/**
* Log a message at level Level::TRACE.
*
* @param[in] format Format string (flash string).
* @param[in] ... Format string arguments.
* @since 1.0.0
*/
void trace(const __FlashStringHelper * format, ...) const /* __attribute__((format (printf, 2, 3))) */;
/**
* Log a message with a custom facility.
*
* @param[in] level Severity level of the message.
* @param[in] facility Facility type of the process logging the message.
* @param[in] format Format string.
* @param[in] ... Format string arguments.
* @since 1.0.0
*/
void log(Level level, Facility facility, const char * format, ...) const /* __attribute__((format (printf, 3, 4))) */;
/**
* Log a message with a custom facility.
*
* @param[in] level Severity level of the message.
* @param[in] facility Facility type of the process logging the message.
* @param[in] format Format string (flash string).
* @param[in] ... Format string arguments.
* @since 1.0.0
*/
void log(Level level, Facility facility, const __FlashStringHelper * format, ...) const /* __attribute__((format (printf, 4, 5))) */;
private:
/**
* Refresh the minimum global log level across all handlers.
*
* @since 1.0.0
*/
static void refresh_log_level();
/**
* Log a message at the specified level.
*
* @param[in] level Severity level of the message.
* @param[in] format Format string.
* @param[in] ap Variable arguments pointer for format string.
* @since 1.0.0
*/
void vlog(Level level, const char * format, va_list ap) const;
/**
* Log a message at the specified level.
*
* @param[in] level Severity level of the message.
* @param[in] format Format string (flash string).
* @param[in] ap Variable arguments pointer for format string.
* @since 1.0.0
*/
void vlog(Level level, const __FlashStringHelper * format, va_list ap) const;
/**
* Log a message at the specified level and facility.
*
* @param[in] level Severity level of the message.
* @param[in] facility Facility type of the process logging the message.
* @param[in] format Format string.
* @param[in] ap Variable arguments pointer for format string.
* @since 1.0.0
*/
void vlog(Level level, Facility facility, const char * format, va_list ap) const;
/**
* Log a message at the specified level and facility.
*
* @param[in] level Severity level of the message.
* @param[in] facility Facility type of the process logging the message.
* @param[in] format Format string (flash string).
* @param[in] ap Variable arguments pointer for format string.
* @since 1.0.0
*/
void vlog(Level level, Facility facility, const __FlashStringHelper * format, va_list ap) const;
/**
* Dispatch a log message to all handlers that are registered to
* handle messages of the specified level.
*
* Automatically sets the timestamp of the message to the current
* system uptime.
*
* @param[in] level Severity level of the message.
* @param[in] facility Facility type of the process logging the message.
* @param[in] text Log message text.
* @since 1.0.0
*/
void dispatch(Level level, Facility facility, std::vector<char> & text) const;
static std::map<Handler *, Level> handlers_; /*!< Registered log handlers. @since 1.0.0 */
static Level level_; /*!< Minimum global log level across all handlers. @since 1.0.0 */
const __FlashStringHelper * name_; /*!< Logger name (flash string). @since 1.0.0 */
const Facility facility_; /*!< Default logging facility for messages. @since 1.0.0 */
};
} // namespace log
} // namespace uuid
#endif

674
lib/uuid-syslog/COPYING Normal file
View File

@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program 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.
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

View File

@@ -0,0 +1,24 @@
mcu-uuid-syslog |Build Status|
==============================
Description
-----------
Microcontroller syslog service
Purpose
-------
Provides a log handler that sends messages to a syslog server (using
the `RFC 5424 protocol <https://tools.ietf.org/html/rfc5424>`_).
Documentation
-------------
`Read the documentation <https://mcu-uuid-syslog.readthedocs.io/>`_
generated from the docs_ directory.
.. _docs: docs/
.. |Build Status| image:: https://travis-ci.org/nomis/mcu-uuid-syslog.svg?branch=master
:target: https://travis-ci.org/nomis/mcu-uuid-syslog

View File

@@ -0,0 +1,35 @@
{
"name": "uuid-syslog",
"description": "Syslog service",
"keywords": "logging,syslog",
"authors": [
{
"name": "Simon Arlott",
"maintainer": true
}
],
"repository": {
"type": "git",
"url": "https://github.com/nomis/mcu-uuid-syslog.git"
},
"version": "2.0.4",
"license": "GPL-3.0-or-later",
"homepage": "https://mcu-uuid-syslog.readthedocs.io/",
"export": {
"exclude": [
".travis.yml",
"test/*"
]
},
"frameworks": [
"arduino"
],
"dependencies": {
"uuid-common": "^1.0.2",
"uuid-log": "^2.0.3"
},
"build": {
"flags": "-Wall -Wextra",
"libLDFMode": "chain+"
}
}

View File

@@ -0,0 +1,439 @@
/*
* uuid-syslog - Syslog service
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "uuid/syslog.h"
#include <Arduino.h>
#ifdef ARDUINO_ARCH_ESP8266
#include <ESP8266WiFi.h>
#else
#include <WiFi.h>
#endif
#include <WiFiUdp.h>
#ifndef UUID_SYSLOG_HAVE_GETTIMEOFDAY
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
// time() does not return UTC on the ESP8266: https://github.com/esp8266/Arduino/issues/4637
#define UUID_SYSLOG_HAVE_GETTIMEOFDAY 1
#endif
#endif
#ifndef UUID_SYSLOG_HAVE_GETTIMEOFDAY
#define UUID_SYSLOG_HAVE_GETTIMEOFDAY 0
#endif
#ifndef UUID_SYSLOG_HAVE_IPADDRESS_TYPE
#if defined(ARDUINO_ARCH_ESP8266)
#define UUID_SYSLOG_HAVE_IPADDRESS_TYPE 1
#endif
#endif
#ifndef UUID_SYSLOG_HAVE_IPADDRESS_TYPE
#define UUID_SYSLOG_HAVE_IPADDRESS_TYPE 0
#endif
#ifndef UUID_SYSLOG_ARP_CHECK
#if defined(LWIP_VERSION_MAJOR) && defined(LWIP_IPV4) && LWIP_VERSION_MAJOR >= 2 && LWIP_IPV4
#define UUID_SYSLOG_ARP_CHECK 1
#endif
#endif
#ifndef UUID_SYSLOG_ARP_CHECK
#define UUID_SYSLOG_ARP_CHECK 0
#endif
#ifndef UUID_SYSLOG_NDP_CHECK
#if defined(LWIP_VERSION_MAJOR) && defined(LWIP_IPV6) && LWIP_VERSION_MAJOR >= 2 && LWIP_IPV6
#define UUID_SYSLOG_NDP_CHECK 1
#endif
#endif
#ifndef UUID_SYSLOG_NDP_CHECK
#define UUID_SYSLOG_NDP_CHECK 0
#endif
#if UUID_SYSLOG_ARP_CHECK or UUID_SYSLOG_NDP_CHECK
#include <lwip/netif.h>
#endif
#if UUID_SYSLOG_ARP_CHECK
#include <lwip/ip4_addr.h>
#include <lwip/etharp.h>
#endif
#if UUID_SYSLOG_NDP_CHECK
#include <lwip/ip6_addr.h>
#include <lwip/nd6.h>
#endif
#include <algorithm>
#include <list>
#include <memory>
#include <string>
#include <uuid/common.h>
#include <uuid/log.h>
static const char __pstr__logger_name[] __attribute__((__aligned__(sizeof(int)))) PROGMEM = "syslog";
namespace uuid {
namespace syslog {
uuid::log::Logger SyslogService::logger_{FPSTR(__pstr__logger_name), uuid::log::Facility::SYSLOG};
bool SyslogService::QueuedLogMessage::time_good_ = false;
SyslogService::~SyslogService() {
uuid::log::Logger::unregister_handler(this);
}
void SyslogService::start() {
uuid::log::Logger::register_handler(this, uuid::log::Level::ALL);
}
uuid::log::Level SyslogService::log_level() const {
return uuid::log::Logger::get_log_level(this);
}
void SyslogService::remove_queued_messages(uuid::log::Level level) {
unsigned long offset = 0;
for (auto it = log_messages_.begin(); it != log_messages_.end();) {
if (it->content_->level > level) {
offset++;
it = log_messages_.erase(it);
} else {
it->id_ -= offset;
it++;
}
}
log_message_id_ -= offset;
}
void SyslogService::log_level(uuid::log::Level level) {
if (!started_) {
remove_queued_messages(level);
}
static bool level_set = false;
bool level_changed = !level_set || (level != log_level());
level_set = true;
if (level_changed && level < uuid::log::Level::NOTICE) {
logger_.info(F("Log level set to %S"), uuid::log::format_level_uppercase(level));
}
uuid::log::Logger::register_handler(this, level);
if (level_changed && level >= uuid::log::Level::NOTICE) {
logger_.info(F("Log level set to %S"), uuid::log::format_level_uppercase(level));
}
}
size_t SyslogService::maximum_log_messages() const {
return maximum_log_messages_;
}
void SyslogService::maximum_log_messages(size_t count) {
maximum_log_messages_ = std::max((size_t)1, count);
while (log_messages_.size() > maximum_log_messages_) {
log_messages_.pop_front();
}
}
std::pair<IPAddress, uint16_t> SyslogService::destination() const {
return std::make_pair(host_, port_);
}
void SyslogService::destination(IPAddress host, uint16_t port) {
host_ = host;
port_ = port;
if ((uint32_t)host_ == (uint32_t)0) {
started_ = false;
remove_queued_messages(log_level());
}
}
std::string SyslogService::hostname() const {
return hostname_;
}
void SyslogService::hostname(std::string hostname) {
if (hostname.empty() || hostname.find(' ') != std::string::npos) {
hostname_ = '-';
} else {
hostname_ = std::move(hostname);
}
}
unsigned long SyslogService::mark_interval() const {
return mark_interval_ / 1000;
}
void SyslogService::mark_interval(unsigned long interval) {
mark_interval_ = (uint64_t)interval * 1000;
}
SyslogService::QueuedLogMessage::QueuedLogMessage(unsigned long id, std::shared_ptr<uuid::log::Message> && content)
: id_(id)
, content_(std::move(content)) {
if (time_good_ || WiFi.status() == WL_CONNECTED) {
#if UUID_SYSLOG_HAVE_GETTIMEOFDAY
if (gettimeofday(&time_, nullptr) != 0) {
time_.tv_sec = (time_t)-1;
}
#else
time_.tv_sec = time(nullptr);
time_.tv_usec = 0;
#endif
if (time_.tv_sec >= 0 && time_.tv_sec < 18140 * 86400) {
time_.tv_sec = (time_t)-1;
}
if (time_.tv_sec != (time_t)-1) {
time_good_ = true;
}
} else {
time_.tv_sec = (time_t)-1;
}
}
void SyslogService::operator<<(std::shared_ptr<uuid::log::Message> message) {
if (log_messages_.size() >= maximum_log_messages_) {
log_messages_overflow_ = true;
log_messages_.pop_front();
}
log_messages_.emplace_back(log_message_id_++, std::move(message));
}
void SyslogService::loop() {
while (!log_messages_.empty() && can_transmit()) {
auto message = log_messages_.front();
started_ = true;
log_messages_overflow_ = false;
auto ok = transmit(message);
if (ok) {
// The transmit() may have called yield() allowing
// other messages to have been added to the queue.
if (!log_messages_overflow_) {
log_messages_.pop_front();
}
last_message_ = uuid::get_uptime_ms();
}
::yield();
if (!ok) {
break;
}
}
if (started_ && mark_interval_ != 0 && log_messages_.empty()) {
if (uuid::get_uptime_ms() - last_message_ >= mark_interval_) {
// This is generated manually because the log level may not
// be high enough to receive INFO messages.
operator<<(std::make_shared<uuid::log::Message>(uuid::get_uptime_ms(),
uuid::log::Level::INFO,
uuid::log::Facility::SYSLOG,
reinterpret_cast<const __FlashStringHelper *>(__pstr__logger_name),
uuid::read_flash_string(F("-- MARK --"))));
}
}
}
bool SyslogService::can_transmit() {
#if UUID_SYSLOG_HAVE_IPADDRESS_TYPE
if (host_.isV4() && (uint32_t)host_ == (uint32_t)0) {
return false;
}
#else
if ((uint32_t)host_ == (uint32_t)0) {
return false;
}
#endif
if (WiFi.status() != WL_CONNECTED) {
return false;
}
const uint64_t now = uuid::get_uptime_ms();
uint64_t message_delay = 100;
#if UUID_SYSLOG_ARP_CHECK
#if UUID_SYSLOG_HAVE_IPADDRESS_TYPE
if (host_.isV4())
#endif
{
message_delay = 10;
}
#endif
#if UUID_SYSLOG_NDP_CHECK && UUID_SYSLOG_HAVE_IPADDRESS_TYPE
if (host_.isV6()) {
message_delay = 10;
}
#endif
if (now < last_transmit_ || now - last_transmit_ < message_delay) {
return false;
}
#if UUID_SYSLOG_ARP_CHECK
#if UUID_SYSLOG_HAVE_IPADDRESS_TYPE
if (host_.isV4())
#endif
{
ip4_addr_t ipaddr;
ip4_addr_set_u32(&ipaddr, (uint32_t)host_);
if (!ip4_addr_isloopback(&ipaddr) && !ip4_addr_ismulticast(&ipaddr) && !ip4_addr_isbroadcast(&ipaddr, netif_default)) {
struct eth_addr * eth_ret = nullptr;
const ip4_addr_t * ip_ret = nullptr;
if (!ip4_addr_netcmp(&ipaddr, netif_ip4_addr(netif_default), netif_ip4_netmask(netif_default))) {
// Replace addresses outside the network with the gateway address
const ip4_addr_t * gw_addr = netif_ip4_gw(netif_default);
if (gw_addr != nullptr) {
ipaddr = *gw_addr;
}
}
if (etharp_find_addr(netif_default, &ipaddr, &eth_ret, &ip_ret) == -1) {
etharp_query(netif_default, &ipaddr, NULL);
// Avoid querying lwIP again for 1 second
last_transmit_ = uuid::get_uptime_ms() + (uint64_t)1000 - message_delay;
return false;
}
}
}
#endif
#if UUID_SYSLOG_NDP_CHECK && UUID_SYSLOG_HAVE_IPADDRESS_TYPE
if (host_.isV6()) {
ip6_addr_t ip6addr;
IP6_ADDR(&ip6addr, host_.raw6()[0], host_.raw6()[1], host_.raw6()[2], host_.raw6()[3]);
ip6_addr_assign_zone(&ip6addr, IP6_UNICAST, netif_default);
if (!ip6_addr_isloopback(&ip6addr) && !ip6_addr_ismulticast(&ip6addr)) {
// Don't send to a scoped address until we have a valid address of the same type
bool have_address = false;
const u8_t * hwaddr = nullptr;
for (size_t i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
if (ip6_addr_isvalid(netif_ip6_addr_state(netif_default, i))) {
if (ip6_addr_isglobal(&ip6addr)) {
if (ip6_addr_isglobal(netif_ip6_addr(netif_default, i))) {
have_address = true;
break;
}
} else if (ip6_addr_issitelocal(&ip6addr)) {
if (ip6_addr_issitelocal(netif_ip6_addr(netif_default, i))) {
have_address = true;
break;
}
} else if (ip6_addr_isuniquelocal(&ip6addr)) {
if (ip6_addr_isuniquelocal(netif_ip6_addr(netif_default, i))) {
have_address = true;
break;
}
} else if (ip6_addr_islinklocal(&ip6addr)) {
if (ip6_addr_islinklocal(netif_ip6_addr(netif_default, i))) {
have_address = true;
break;
}
} else {
have_address = true;
break;
}
}
}
if (!have_address) {
// Avoid checking lwIP again for 1 second
last_transmit_ = uuid::get_uptime_ms() + (uint64_t)1000 - message_delay;
return false;
} else if (nd6_get_next_hop_addr_or_queue(netif_default, NULL, &ip6addr, &hwaddr) != ERR_OK) {
// Avoid querying lwIP again for 1 second
last_transmit_ = uuid::get_uptime_ms() + (uint64_t)1000 - message_delay;
return false;
}
}
}
#endif
return true;
}
bool SyslogService::transmit(const QueuedLogMessage & message) {
/*
// modifications by Proddy. From https://github.com/proddy/EMS-ESP/issues/395#issuecomment-640053528
struct tm tm;
tm.tm_year = 0;
if (message.time_.tv_sec != (time_t)-1) {
gmtime_r(&message.time_.tv_sec, &tm);
}
*/
if (udp_.beginPacket(host_, port_) != 1) {
last_transmit_ = uuid::get_uptime_ms();
return false;
}
udp_.printf_P(PSTR("<%u>1 "), ((unsigned int)message.content_->facility * 8) + std::min(7U, (unsigned int)message.content_->level));
/*
if (tm.tm_year != 0) {
udp_.printf_P(PSTR("%04u-%02u-%02uT%02u:%02u:%02u.%06luZ"),
tm.tm_year + 1900,
tm.tm_mon + 1,
tm.tm_mday,
tm.tm_hour,
tm.tm_min,
tm.tm_sec,
(unsigned long)message.time_.tv_usec);
} else {
udp_.print('-');
}
*/
udp_.print('-');
udp_.printf_P(PSTR(" %s - - - - \xEF\xBB\xBF"), hostname_.c_str());
udp_.print(uuid::log::format_timestamp_ms(message.content_->uptime_ms, 3).c_str());
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat"
udp_.printf_P(PSTR(" %c %lu: [%S] "), uuid::log::format_level_char(message.content_->level), message.id_, message.content_->name);
#pragma GCC diagnostic pop
udp_.print(message.content_->text.c_str());
bool ok = (udp_.endPacket() == 1);
last_transmit_ = uuid::get_uptime_ms();
return ok;
}
} // namespace syslog
} // namespace uuid

View File

@@ -0,0 +1,263 @@
/*
* uuid-syslog - Syslog service
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef UUID_SYSLOG_H_
#define UUID_SYSLOG_H_
#include <Arduino.h>
#ifdef ARDUINO_ARCH_ESP8266
#include <ESP8266WiFi.h>
#else
#include <WiFi.h>
#endif
#include <WiFiUdp.h>
#include <time.h>
#include <atomic>
#include <list>
#include <memory>
#include <string>
#include <uuid/log.h>
namespace uuid {
/**
* Syslog service.
*
* - <a href="https://github.com/nomis/mcu-uuid-syslog/">Git Repository</a>
* - <a href="https://mcu-uuid-syslog.readthedocs.io/">Documentation</a>
*/
namespace syslog {
/**
* Log handler for sending messages to a syslog server.
*
* @since 1.0.0
*/
class SyslogService : public uuid::log::Handler {
public:
static constexpr size_t MAX_LOG_MESSAGES = 50; /*!< Maximum number of log messages to buffer before they are output. @since 1.0.0 */
static constexpr uint16_t DEFAULT_PORT = 514; /*!< Default UDP port to send messages to. @since 1.0.0 */
/**
* Create a new syslog service log handler.
*
* @since 1.0.0
*/
SyslogService() = default;
~SyslogService();
/**
* Register the log handler with the logging framework.
*
* @since 1.0.0
*/
void start();
/**
* Get the current log level.
*
* This only affects newly received log messages, not messages that
* have already been queued.
*
* @return The current log level.
* @since 2.0.0
*/
uuid::log::Level log_level() const;
/**
* Set the current log level.
*
* Unless this is the first time the log level is being set, this
* only affects newly received log messages, not messages that have
* already been queued.
*
* @param[in] level Minimum log level that will be sent to the
* syslog server.
* @since 2.0.0
*/
void log_level(uuid::log::Level level);
/**
* Get the maximum number of queued log messages.
*
* @return The maximum number of queued log messages.
* @since 2.0.0
*/
size_t maximum_log_messages() const;
/**
* Set the maximum number of queued log messages.
*
* Defaults to SyslogService::MAX_LOG_MESSAGES.
*
* @since 2.0.0
*/
void maximum_log_messages(size_t count);
/**
* Get the server to send messages to.
*
* @since 2.0.0
* @return IP address and UDP port of the syslog server.
*/
std::pair<IPAddress, uint16_t> destination() const;
/**
* Set the server to send messages to.
*
* To disable sending messages, set the host to `0.0.0.0` and the
* log level to uuid::log::Level::OFF (otherwise they will be
* queued but not sent).
*
* @param[in] host IP address of the syslog server.
* @param[in] port UDP port to send messages to.
* @since 2.0.0
*/
void destination(IPAddress host, uint16_t port = DEFAULT_PORT);
/**
* Get local hostname.
*
* @since 2.0.0
* @return Hostname of this device.
*/
std::string hostname() const;
/**
* Set local hostname.
*
* @param[in] hostname Hostname of this device.
* @since 2.0.0
*/
void hostname(std::string hostname);
/**
* Get mark interval.
*
* @since 2.0.0
* @return Mark interval in seconds (0 = disable).
*/
unsigned long mark_interval() const;
/**
* Set mark interval.
*
* When no messages have been sent for this period of time, a
* `-- MARK --` message will be generated automatically.
*
* @param[in] interval Mark interval in seconds (0 = disable).
* @since 2.0.0
*/
void mark_interval(unsigned long interval);
/**
* Dispatch queued log messages.
*
* @since 1.0.0
*/
void loop();
/**
* Add a new log message.
*
* This will be put in a queue for output at the next loop()
* process. The queue has a maximum size of
* get_maximum_log_messages() and will discard the oldest message
* first.
*
* @param[in] message New log message, shared by all handlers.
* @since 1.0.0
*/
virtual void operator<<(std::shared_ptr<uuid::log::Message> message);
private:
/**
* Log message that has been queued.
*
* Contains an identifier sequence to indicate when log messages
* could not be output because the queue discarded one or more
* messages.
*
* @since 1.0.0
*/
class QueuedLogMessage {
public:
/**
* Create a queued log message.
*
* @param[in] id Identifier to use for the log message on the queue.
* @param[in] content Log message content.
* @since 1.0.0
*/
QueuedLogMessage(unsigned long id, std::shared_ptr<uuid::log::Message> && content);
~QueuedLogMessage() = default;
unsigned long id_; /*!< Sequential identifier for this log message. @since 1.0.0 */
struct timeval time_; /*!< Time message was received. @since 1.0.0 */
const std::shared_ptr<const uuid::log::Message> content_; /*!< Log message content. @since 1.0.0 */
private:
static bool time_good_; /*!< System time appears to be valid. @since 1.0.0 */
};
/**
* Remove messages that were queued before the log level was set.
*
* @param[in] level New log level
* @since 1.0.0
*/
void remove_queued_messages(uuid::log::Level level);
/**
* Check if it is possible to transmit to the server.
*
* @return True if it is safe to transmit a message to the server,
* otherwise false.
* @since 1.0.0
*/
bool can_transmit();
/**
* Attempt to transmit one message to the server.
*
* @param[in] message Log message to be sent.
* @return True if the message was successfully set, otherwise
* false.
* @since 1.0.0
*/
bool transmit(const QueuedLogMessage & message);
static uuid::log::Logger logger_; /*!< uuid::log::Logger instance for syslog services. @since 1.0.0 */
bool started_ = false; /*!< Flag to indicate that messages have started being transmitted. @since 1.0.0 */
WiFiUDP udp_; /*!< UDP client. @since 1.0.0 */
IPAddress host_; /*!< Host to send messages to. @since 1.0.0 */
uint16_t port_ = DEFAULT_PORT; /*!< Port to send messages to. @since 1.0.0 */
uint64_t last_transmit_ = 0; /*!< Last transmit time. @since 1.0.0 */
std::string hostname_{'-'}; /*!< Local hostname. @since 1.0.0 */
size_t maximum_log_messages_ = MAX_LOG_MESSAGES; /*!< Maximum number of log messages to buffer before they are output. @since 1.0.0 */
unsigned long log_message_id_ = 0; /*!< The next identifier to use for queued log messages. @since 1.0.0 */
std::list<QueuedLogMessage> log_messages_; /*!< Queued log messages, in the order they were received. @since 1.0.0 */
std::atomic<bool> log_messages_overflow_{false}; /*!< Check if log messages have overflowed the buffer. @since 1.0.0 */
uint64_t mark_interval_ = 0; /*!< Mark interval in milliseconds. @since 2.0.0 */
uint64_t last_message_ = 0; /*!< Last message/mark time. @since 2.0.0 */
};
} // namespace syslog
} // namespace uuid
#endif

674
lib/uuid-telnet/COPYING Normal file
View File

@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program 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.
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

View File

@@ -0,0 +1,24 @@
mcu-uuid-telnet |Build Status|
==============================
Description
-----------
Microcontroller telnet service
Purpose
-------
Provides access to a console shell as a telnet server (using the
`RFC 854 protocol <https://tools.ietf.org/html/rfc854>`_).
Documentation
-------------
`Read the documentation <https://mcu-uuid-telnet.readthedocs.io/>`_
generated from the docs_ directory.
.. _docs: docs/
.. |Build Status| image:: https://travis-ci.org/nomis/mcu-uuid-telnet.svg?branch=master
:target: https://travis-ci.org/nomis/mcu-uuid-telnet

View File

@@ -0,0 +1,36 @@
{
"name": "uuid-telnet",
"description": "Telnet service",
"keywords": "communication,telnet",
"authors": [
{
"name": "Simon Arlott",
"maintainer": true
}
],
"repository": {
"type": "git",
"url": "https://github.com/nomis/mcu-uuid-telnet.git"
},
"version": "0.1.0",
"license": "GPL-3.0-or-later",
"homepage": "https://mcu-uuid-telnet.readthedocs.io/",
"export": {
"exclude": [
".travis.yml",
"test/*"
]
},
"frameworks": [
"arduino"
],
"dependencies": {
"uuid-common": "^1.1.0",
"uuid-log": "^2.1.1",
"uuid-console": "^0.7.0"
},
"build": {
"flags": "-Wall -Wextra",
"libLDFMode": "chain+"
}
}

View File

@@ -0,0 +1,358 @@
/*
* uuid-telnet - Telnet service
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "uuid/telnet.h"
#include <Arduino.h>
#include <algorithm>
#include <string>
#include <vector>
namespace uuid {
namespace telnet {
TelnetStream::TelnetStream(WiFiClient &client)
: client_(client) {
output_buffer_.reserve(BUFFER_SIZE);
}
void TelnetStream::start() {
raw_write({
IAC, WILL, OPT_ECHO,
IAC, WILL, OPT_BINARY,
IAC, WILL, OPT_SGA,
IAC, DONT, OPT_ECHO,
IAC, DO, OPT_BINARY,
IAC, DO, OPT_SGA
});
}
int TelnetStream::available() {
if (peek() == -1) {
return 0;
} else {
return 1;
}
}
int TelnetStream::read() {
if (peek_ != -1) {
int data = peek_;
peek_ = -1;
return data;
}
buffer_flush();
restart:
int data = raw_read();
if (data == -1) {
return -1;
}
unsigned char c = data;
if (sub_negotiation_) {
if (previous_raw_in_ == IAC) {
switch (c) {
case SE:
sub_negotiation_ = false;
previous_raw_in_ = 0;
goto restart;
case IAC:
previous_raw_in_ = 0;
goto restart;
}
} else {
switch (c) {
case IAC:
previous_raw_in_ = c;
goto restart;
default:
previous_raw_in_ = 0;
goto restart;
}
}
} else {
if (previous_raw_in_ == IAC) {
switch (c) {
case IP:
// Interrupt (^C)
previous_raw_in_ = 0;
c = '\x03';
break;
case EC:
// Backspace (^H)
previous_raw_in_ = 0;
c = '\x08';
break;
case EL:
// Delete line (^U)
previous_raw_in_ = 0;
c = '\x15';
break;
case IAC:
previous_raw_in_ = 0;
break;
case SB:
case WILL:
case WONT:
case DO:
case DONT:
previous_raw_in_ = c;
goto restart;
case SE:
case DM:
case BRK:
case AO:
case AYT:
case GA:
case NOP:
default:
previous_raw_in_ = 0;
goto restart;
}
} else if (previous_raw_in_ == SB) {
sub_negotiation_ = true;
previous_raw_in_ = 0;
goto restart;
} else if (previous_raw_in_ == WILL || previous_raw_in_ == WONT) {
switch (c) {
case OPT_ECHO:
// Don't do these
raw_write({IAC, DONT, c});
break;
case OPT_BINARY:
case OPT_SGA:
// Do these
raw_write({IAC, DO, c});
break;
default:
// Don't do anything else
raw_write({IAC, DONT, c});
break;
}
previous_raw_in_ = 0;
goto restart;
} else if (previous_raw_in_ == DO) {
switch (c) {
case OPT_ECHO:
case OPT_BINARY:
case OPT_SGA:
// These are always enabled
break;
default:
// Refuse to do anything else
raw_write({IAC, WONT, c});
break;
}
previous_raw_in_ = 0;
goto restart;
} else if (previous_raw_in_ == DONT) {
switch (c) {
case OPT_ECHO:
case OPT_BINARY:
case OPT_SGA:
// Insist that we do these
raw_write({IAC, WILL, c});
break;
default:
// Everything else is always disabled
break;
}
previous_raw_in_ = 0;
goto restart;
} else {
switch (c) {
case IAC:
previous_raw_in_ = c;
goto restart;
default:
previous_raw_in_ = 0;
break;
}
}
}
if (previous_in_ == CR) {
if (c == NUL) {
previous_in_ = 0;
goto restart;
}
}
previous_in_ = c;
return c;
}
int TelnetStream::peek() {
buffer_flush();
// It's too complicated to implement this by calling peek()
// on the original stream, especially if the original stream
// doesn't actually support peeking.
if (peek_ == -1) {
peek_ = read();
}
return peek_;
}
size_t TelnetStream::write(uint8_t data) {
if (previous_out_ == CR && data != LF) {
previous_out_ = data;
if (raw_write({NUL, data}) != 2) {
return 0;
}
} else {
previous_out_ = data;
}
if (data == IAC) {
if (raw_write({IAC, IAC}) != 2) {
return 0;
}
} else {
if (raw_write(data) != 1) {
return 0;
}
}
return 1;
}
size_t TelnetStream::write(const uint8_t *buffer, size_t size) {
std::vector<unsigned char> data;
data.reserve(size);
while (size-- > 0) {
unsigned char c = *buffer++;
if (previous_out_ == CR && c != LF) {
data.push_back((unsigned char)NUL);
}
if (c == IAC) {
data.push_back((unsigned char)IAC);
}
previous_out_ = c;
data.push_back(c);
}
size_t len = raw_write(data);
if (len < size) {
len = 0;
}
return len;
}
void TelnetStream::flush() {
// This is a pure virtual function in Arduino's Stream class, which
// makes no sense because that class is for input and this is an
// output function. Later versions move it to Print as an empty
// virtual function so this is here for backward compatibility.
}
int TelnetStream::raw_available() {
return client_.available();
}
int TelnetStream::raw_read() {
return client_.read();
}
void TelnetStream::buffer_flush() {
if (!output_buffer_.empty()) {
size_t len = client_.write(reinterpret_cast<const unsigned char*>(output_buffer_.data()), output_buffer_.size());
if (len != output_buffer_.size()) {
client_.stop();
}
output_buffer_.clear();
output_buffer_.shrink_to_fit();
}
}
size_t TelnetStream::raw_write(unsigned char data) {
output_buffer_.push_back(data);
if (output_buffer_.size() >= BUFFER_SIZE) {
buffer_flush();
}
return 1;
}
size_t TelnetStream::raw_write(const std::vector<unsigned char> &data) {
return raw_write(reinterpret_cast<const unsigned char*>(data.data()), data.size());
}
size_t TelnetStream::raw_write(const uint8_t *buffer, size_t size) {
size_t offset = 0;
size_t remaining = size;
if (!output_buffer_.empty()) {
// Fill the rest of the buffer
size_t block = std::min(remaining, BUFFER_SIZE - output_buffer_.size());
output_buffer_.insert(output_buffer_.end(), buffer, buffer + block);
offset += block;
remaining -= block;
if (output_buffer_.size() >= BUFFER_SIZE) {
buffer_flush();
}
}
if (remaining >= BUFFER_SIZE) {
// Output directly if it won't fit in the buffer
size_t len = client_.write(buffer + offset, remaining);
if (len != remaining) {
client_.stop();
return offset + len;
}
} else if (remaining > 0) {
// Put the rest in the buffer
output_buffer_.insert(output_buffer_.end(), buffer + offset, buffer + offset + remaining);
}
return size;
}
} // namespace telnet
} // namespace uuid

View File

@@ -0,0 +1,237 @@
/*
* uuid-telnet - Telnet service
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "uuid/telnet.h"
#include <Arduino.h>
#if defined(ESP8266)
#include <ESP8266WiFi.h>
#elif defined(ESP32)
#include <WiFi.h>
#endif
#include <WiFiUdp.h>
#include <algorithm>
#include <list>
#include <memory>
#include <string>
#include <uuid/common.h>
#include <uuid/log.h>
#ifndef UUID_TELNET_HAVE_WIFICLIENT_REMOTE
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
#define UUID_TELNET_HAVE_WIFICLIENT_REMOTE 1
#else
#define UUID_TELNET_HAVE_WIFICLIENT_REMOTE 0
#endif
#endif
#ifndef UUID_TELNET_HAVE_WIFICLIENT_NODELAY
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
#define UUID_TELNET_HAVE_WIFICLIENT_NODELAY 1
#else
#define UUID_TELNET_HAVE_WIFICLIENT_NODELAY 0
#endif
#endif
#ifndef UUID_TELNET_HAVE_WIFICLIENT_KEEPALIVE
#if defined(ARDUINO_ARCH_ESP8266)
#define UUID_TELNET_HAVE_WIFICLIENT_KEEPALIVE 1
#else
#define UUID_TELNET_HAVE_WIFICLIENT_KEEPALIVE 0
#endif
#endif
static const char __pstr__logger_name[] __attribute__((__aligned__(sizeof(int)))) PROGMEM = "telnet";
namespace uuid {
namespace telnet {
uuid::log::Logger TelnetService::logger_{FPSTR(__pstr__logger_name), uuid::log::Facility::DAEMON};
TelnetService::TelnetService(std::shared_ptr<uuid::console::Commands> commands, unsigned int context, unsigned int flags)
: TelnetService(DEFAULT_PORT, commands, context, flags) {
}
TelnetService::TelnetService(uint16_t port, std::shared_ptr<uuid::console::Commands> commands, unsigned int context, unsigned int flags)
: TelnetService(port,
[commands, context, flags](Stream & stream, const IPAddress & addr __attribute__((unused)), uint16_t port __attribute__((unused)))
-> std::shared_ptr<uuid::console::Shell> { return std::make_shared<uuid::console::StreamConsole>(commands, stream, context, flags); }) {
}
TelnetService::TelnetService(shell_factory_function shell_factory)
: TelnetService(DEFAULT_PORT, shell_factory) {
}
TelnetService::TelnetService(uint16_t port, shell_factory_function shell_factory)
: server_(port)
, shell_factory_(shell_factory) {
}
void TelnetService::start() {
server_.begin();
}
void TelnetService::close_all() {
while (!connections_.empty()) {
connections_.front().stop();
connections_.pop_front();
}
}
void TelnetService::stop() {
server_.stop();
}
size_t TelnetService::maximum_connections() const {
return maximum_connections_;
}
void TelnetService::maximum_connections(size_t count) {
maximum_connections_ = std::max((size_t)1, count);
while (connections_.size() > maximum_connections_) {
for (auto it = connections_.begin(); it != connections_.end();) {
if (it->active()) {
it->stop();
it = connections_.erase(it);
break;
} else {
it = connections_.erase(it);
}
}
}
}
unsigned long TelnetService::initial_idle_timeout() const {
return initial_idle_timeout_;
}
void TelnetService::initial_idle_timeout(unsigned long timeout) {
initial_idle_timeout_ = timeout;
}
unsigned long TelnetService::default_write_timeout() const {
return write_timeout_;
}
void TelnetService::default_write_timeout(unsigned long timeout) {
write_timeout_ = timeout;
}
void TelnetService::loop() {
for (auto it = connections_.begin(); it != connections_.end();) {
if (!it->loop()) {
it = connections_.erase(it);
} else {
it++;
}
}
WiFiClient client = server_.available();
if (client) {
if (connections_.size() >= maximum_connections_) {
#if UUID_TELNET_HAVE_WIFICLIENT_REMOTE
logger_.info(F("New connection from [%s]:%u rejected (connection limit reached)"),
uuid::printable_to_string(client.remoteIP()).c_str(),
client.remotePort());
#else
logger_.info(F("New connection rejected (connection limit reached)"));
#endif
client.println(F("Maximum connection limit reached"));
client.stop();
} else {
#if UUID_TELNET_HAVE_WIFICLIENT_REMOTE
logger_.info(F("New connection from [%s]:%u accepted"), uuid::printable_to_string(client.remoteIP()).c_str(), client.remotePort());
#endif
connections_.emplace_back(shell_factory_, std::move(client), initial_idle_timeout_, write_timeout_);
#if !(UUID_TELNET_HAVE_WIFICLIENT_REMOTE)
logger_.info(F("New connection %p accepted"), &connections_.back());
#endif
}
}
}
TelnetService::Connection::Connection(shell_factory_function & shell_factory, WiFiClient && client, unsigned long idle_timeout, unsigned long write_timeout)
: client_(std::move(client))
, stream_(client_) {
#if UUID_TELNET_HAVE_WIFICLIENT_REMOTE
// These have to be copied because they're not accessible on closed connections
addr_ = client_.remoteIP();
port_ = client_.remotePort();
#else
port_ = 0;
#endif
#if UUID_TELNET_HAVE_WIFICLIENT_NODELAY
client_.setNoDelay(true);
#endif
#if UUID_TELNET_HAVE_WIFICLIENT_KEEPALIVE
// Disconnect after 30 seconds without a response
client_.keepAlive(5, 5, 5);
#endif
if (write_timeout > 0) {
client_.setTimeout(write_timeout);
}
stream_.start();
if (client_.connected()) {
std::shared_ptr<uuid::console::Shell> shell = shell_factory(stream_, addr_, port_);
shell->idle_timeout(idle_timeout);
shell->start();
shell_ = shell;
} else {
shell_ = nullptr;
}
}
bool TelnetService::Connection::active() {
return shell_.use_count() > 1;
}
bool TelnetService::Connection::loop() {
if (active()) {
if (!client_.connected()) {
shell_->stop();
}
return true;
} else {
#if UUID_TELNET_HAVE_WIFICLIENT_REMOTE
logger_.info(F("Connection from [%s]:%u closed"), uuid::printable_to_string(addr_).c_str(), port_);
#else
logger_.info(F("Connection %p closed"), this);
#endif
return false;
}
}
void TelnetService::Connection::stop() {
if (shell_) {
shell_->stop();
}
}
} // namespace telnet
} // namespace uuid

View File

@@ -0,0 +1,441 @@
/*
* uuid-telnet - Telnet service
* Copyright 2019 Simon Arlott
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef UUID_TELNET_H_
#define UUID_TELNET_H_
#include <Arduino.h>
#ifdef ARDUINO_ARCH_ESP8266
# include <ESP8266WiFi.h>
#else
# include <WiFi.h>
#endif
#include <WiFiUdp.h>
#include <functional>
#include <list>
#include <memory>
#include <string>
#include <vector>
#include <uuid/console.h>
namespace uuid {
/**
* Telnet service.
*
* - <a href="https://github.com/nomis/mcu-uuid-telnet/">Git Repository</a>
* - <a href="https://mcu-uuid-telnet.readthedocs.io/">Documentation</a>
*/
namespace telnet {
/**
* Stream wrapper that performs telnet protocol handling, option
* negotiation and output buffering.
*
* @since 0.1.0
*/
class TelnetStream: public ::Stream {
public:
/**
* Create a new telnet stream wrapper.
*
* @param[in] client Client connection.
* @since 0.1.0
*/
explicit TelnetStream(WiFiClient &client);
virtual ~TelnetStream() = default;
/**
* Perform initial negotiation.
*
* @since 0.1.0
*/
void start();
/**
* Check for available input.
*
* @return The number of bytes available to read.
* @since 0.1.0
*/
int available() override;
/**
* Read one byte from the available input.
*
* @return An unsigned char if input is available, otherwise -1.
* @since 0.1.0
*/
int read() override;
/**
* Read one byte from the available input without advancing to the
* next one.
*
* @return An unsigned char if input is available, otherwise -1.
* @since 0.1.0
*/
int peek() override;
/**
* Write one byte to the output stream.
*
* Disconnect the client if the socket buffer is full.
*
* @param[in] data Data to be output.
* @return The number of bytes that were output.
* @since 0.1.0
*/
size_t write(uint8_t data) override;
/**
* Write an array of bytes to the output stream.
*
* Disconnect the client if the socket buffer is full.
*
* @param[in] buffer Buffer to be output.
* @param[in] size Length of the buffer.
* @return The number of bytes that were output.
* @since 0.1.0
*/
size_t write(const uint8_t *buffer, size_t size) override;
/**
* Does nothing.
*
* This is a pure virtual function in Arduino's Stream class, which
* makes no sense because that class is for input and this is an
* output function. Later versions move it to Print as an empty
* virtual function so this is here for backward compatibility.
*
* @since 0.1.0
*/
void flush() override;
private:
static constexpr const unsigned char NUL = 0; /*!< No operation. @since 0.1.0 */
static constexpr const unsigned char BEL = 7; /*!< Produces an audible or visible signal. @since 0.1.0 */
static constexpr const unsigned char BS = 8; /*!< Moves the print head one character position towards the left margin. @since 0.1.0 */
static constexpr const unsigned char HT = 9; /*!< Moves the printer to the next horizontal tab stop. @since 0.1.0 */
static constexpr const unsigned char LF = 10; /*!< Line Feed. @since 0.1.0 */
static constexpr const unsigned char VT = 11; /*!< Moves the printer to the next vertical tab stop. @since 0.1.0 */
static constexpr const unsigned char FF = 12; /*!< Moves the printer to the top of the next page, keeping the same horizontal position. @since 0.1.0 */
static constexpr const unsigned char CR = 13; /*!< Carriage Return. @since 0.1.0 */
static constexpr const unsigned char SE = 240; /*!< End of sub-negotiation parameters. @since 0.1.0 */
static constexpr const unsigned char NOP = 241; /*!< No operation. @since 0.1.0 */
static constexpr const unsigned char DM = 242; /*!< The data stream portion of a Synch. @since 0.1.0 */
static constexpr const unsigned char BRK = 243; /*!< NVT character BRK. @since 0.1.0 */
static constexpr const unsigned char IP = 244; /*!< Interrupt Process function. @since 0.1.0 */
static constexpr const unsigned char AO = 245; /*!< Abort Output function. @since 0.1.0 */
static constexpr const unsigned char AYT = 246; /*!< Are You There function. @since 0.1.0 */
static constexpr const unsigned char EC = 247; /*!< Erase Character function. @since 0.1.0 */
static constexpr const unsigned char EL = 248; /*!< Erase Line function. @since 0.1.0 */
static constexpr const unsigned char GA = 249; /*!< Go Ahead signal. @since 0.1.0 */
static constexpr const unsigned char SB = 250; /*!< Sub-negotiation of the indicated option. @since 0.1.0 */
static constexpr const unsigned char WILL = 251; /*!< Indicates the desire to begin performing, or confirmation that you are now performing, the indicated option. @since 0.1.0 */
static constexpr const unsigned char WONT = 252; /*!< Indicates the refusal to perform, or continue performing, the indicated option. @since 0.1.0 */
static constexpr const unsigned char DO = 253; /*!< Indicates the request that the other party perform, or confirmation that you are expecting the other party to perform, the indicated option. @since 0.1.0 */
static constexpr const unsigned char DONT = 254; /*!< Indicates the demand that the other party stop performing, or confirmation that you are no longer expecting the other party to perform, the indicated option. @since 0.1.0 */
static constexpr const unsigned char IAC = 255; /*!< Interpret As Command escape character. @since 0.1.0 */
static constexpr const unsigned char OPT_BINARY = 0; /*!< Binary (8-bit) transmission mode. (RFC 856). @since 0.1.0 */
static constexpr const unsigned char OPT_ECHO = 1; /*!< Remote Echo (RFC 857). @since 0.1.0 */
static constexpr const unsigned char OPT_SGA = 3; /*!< Suppress Go Ahead (RFC 858). @since 0.1.0 */
static constexpr const size_t BUFFER_SIZE = 536; /*!< Output buffer size. @since 0.1.0 */
TelnetStream(const TelnetStream&) = delete;
TelnetStream& operator=(const TelnetStream&) = delete;
/**
* Directly check for available input.
*
* @return The number of bytes available to read.
* @since 0.1.0
*/
int raw_available();
/**
* Read one byte directly from the available input.
*
* @return An unsigned char if input is available, otherwise -1.
* @since 0.1.0
*/
int raw_read();
/**
* Flush output stream buffer.
*
* Disconnect the client if the socket buffer is full.
*
* @since 0.1.0
*/
void buffer_flush();
/**
* Write one byte directly to the output stream.
*
* Disconnect the client if the socket buffer is full.
*
* @param[in] data Data to be output.
* @return The number of bytes that were output.
* @since 0.1.0
*/
size_t raw_write(unsigned char data);
/**
* Write a vector of bytes directly to the output stream.
*
* Disconnect the client if the socket buffer is full.
*
* @param[in] data Data to be output.
* @return The number of bytes that were output.
* @since 0.1.0
*/
size_t raw_write(const std::vector<unsigned char> &data);
/**
* Write an array of bytes directly to the output stream.
*
* Disconnect the client if the socket buffer is full.
*
* @param[in] buffer Buffer to be output.
* @param[in] size Length of the buffer.
* @return The number of bytes that were output.
* @since 0.1.0
*/
size_t raw_write(const uint8_t *buffer, size_t size);
WiFiClient &client_; /*!< Client connection. @since 0.1.0 */
unsigned char previous_raw_in_ = 0; /*!< Previous raw character that was received. Used to detect commands. @since 0.1.0 */
bool sub_negotiation_ = false; /*!< Sub-negotiation mode. @since 0.1.0 */
unsigned char previous_in_ = 0; /*!< Previous character that was received. Used to detect CR NUL. @since 0.1.0 */
unsigned char previous_out_ = 0; /*!< Previous character that was sent. Used to insert NUL after CR without LF. @since 0.1.0 */
int peek_ = -1; /*!< Previously read data cached by peek(). @since 0.1.0 */
std::vector<char> output_buffer_; /*!< Buffer data to be output until a read function is called. @since 0.1.0 */
};
/**
* Provides access to a console shell as a telnet server.
*
* @since 0.1.0
*/
class TelnetService {
public:
static constexpr size_t MAX_CONNECTIONS = 3; /*!< Maximum number of concurrent open connections. @since 0.1.0 */
static constexpr uint16_t DEFAULT_PORT = 23; /*!< Default TCP port to listen on. @since 0.1.0 */
static constexpr unsigned long DEFAULT_IDLE_TIMEOUT = 600; /*!< Default initial idle timeout (in seconds). @since 0.1.0 */
static constexpr unsigned long DEFAULT_WRITE_TIMEOUT = 0; /*!< Default write timeout (in milliseconds). @ since 0.1.0 */
/**
* Function to handle the creation of a shell.
*
* @param[in] stream Stream for the telnet connection.
* @param[in] addr Remote IP address.
* @param[in] port Remote port.
* @since 0.1.0
*/
using shell_factory_function = std::function<std::shared_ptr<uuid::console::Shell>(Stream &stream, const IPAddress &addr, uint16_t port)>;
/**
* Create a new telnet service listening on the default port.
*
* @param[in] commands Commands available for execution in shells.
* @param[in] context Default context for shells.
* @param[in] flags Initial flags for shells.
* @since 0.1.0
*/
TelnetService(std::shared_ptr<uuid::console::Commands> commands, unsigned int context = 0, unsigned int flags = 0);
/**
* Create a new telnet service listening on a specific port.
*
* @param[in] port TCP listening port.
* @param[in] commands Commands available for execution in shells.
* @param[in] context Default context for shells.
* @param[in] flags Initial flags for shells.
* @since 0.1.0
*/
TelnetService(uint16_t port, std::shared_ptr<uuid::console::Commands> commands, unsigned int context = 0, unsigned int flags = 0);
/**
* Create a new telnet service listening on the default port.
*
* @param[in] shell_factory Function to create a shell for new connections.
* @since 0.1.0
*/
explicit TelnetService(shell_factory_function shell_factory);
/**
* Create a new telnet service listening on a specific port.
*
* @param[in] port TCP listening port.
* @param[in] shell_factory Function to create a shell for new connections.
* @since 0.1.0
*/
TelnetService(uint16_t port, shell_factory_function shell_factory);
~TelnetService() = default;
/**
* Start listening for connections on the configured port.
*
* @since 0.1.0
*/
void start();
/**
* Close all connections.
*
* The listening status is not affected.
*
* @since 0.1.0
*/
void close_all();
/**
* Stop listening for connections.
*
* Existing connections are not affected.
*
* @since 0.1.0
*/
void stop();
/**
* Get the maximum number of concurrent open connections.
*
* @return The maximum number of concurrent open connections.
* @since 0.1.0
*/
size_t maximum_connections() const;
/**
* Set the maximum number of concurrent open connections.
*
* Defaults to TelnetService::MAX_CONNECTIONS.
*
* @since 0.1.0
*/
void maximum_connections(size_t count);
/**
* Get the initial idle timeout for new connections.
*
* @return The initial idle timeout in seconds (or 0 for disabled).
* @since 0.1.0
*/
unsigned long initial_idle_timeout() const;
/**
* Set the initial idle timeout for new connections.
*
* Defaults to TelnetService::DEFAULT_IDLE_TIMEOUT.
*
* @param[in] timeout Idle timeout in seconds (or 0 to disable).
* @since 0.1.0
*/
void initial_idle_timeout(unsigned long timeout);
/**
* Get the default socket write timeout for new connections.
*
* @return The default socket write timeout in seconds (or 0 for
* platform default).
* @since 0.1.0
*/
unsigned long default_write_timeout() const;
/**
* Set the default socket write timeout for new connections.
*
* Defaults to TelnetService::DEFAULT_WRITE_TIMEOUT (platform
* default).
*
* @param[in] timeout Socket write timeout in seconds (or 0 for
* platform default).
* @since 0.1.0
*/
void default_write_timeout(unsigned long timeout);
/**
* Accept new connections.
*
* @since 0.1.0
*/
void loop();
private:
/**
* Telnet connection.
*
* Holds the client and stream instance for the lifetime of the shell.
*
* @since 0.1.0
*/
class Connection {
public:
/**
* Create a telnet connection shell.
*
* @param[in] shell_factory Function to create a shell for new connections.
* @param[in] client Client connection.
* @param[in] idle_timeout Idle timeout in seconds.
* @param[in] write_timeout Idle timeout in milliseconds.
* @since 0.1.0
*/
Connection(shell_factory_function &shell_factory, WiFiClient &&client, unsigned long idle_timeout, unsigned long write_timeout);
~Connection() = default;
/**
* Check if the shell is still active.
*
* @return Active status of the shell.
* @since 0.1.0
*/
bool active();
/**
* Stop the shell if the client is not connected.
*
* @return Active status of the shell.
* @since 0.1.0
*/
bool loop();
/**
* Stop the shell.
*
* @since 0.1.0
*/
void stop();
private:
Connection(const Connection&) = delete;
Connection& operator=(const Connection&) = delete;
WiFiClient client_; /*!< Client connection. @since 0.1.0 */
TelnetStream stream_; /*!< Telnet stream for the connection. @since 0.1.0 */
std::shared_ptr<uuid::console::Shell> shell_; /*!< Shell for connection. @since 0.1.0 */
IPAddress addr_; /*!< Remote address of connection. @since 0.1.0 */
uint16_t port_; /*!< Remote port of connection. @since 0.1.0 */
};
TelnetService(const TelnetService&) = delete;
TelnetService& operator=(const TelnetService&) = delete;
static uuid::log::Logger logger_; /*!< uuid::log::Logger instance for telnet services. @since 0.1.0 */
WiFiServer server_; /*!< TCP server. @since 0.1.0 */
size_t maximum_connections_ = MAX_CONNECTIONS; /*!< Maximum number of concurrent open connections. @since 0.1.0 */
std::list<Connection> connections_; /*!< Open connections. @since 0.1.0 */
shell_factory_function shell_factory_; /*!< Function to create a shell. @since 0.1.0 */
unsigned long initial_idle_timeout_ = DEFAULT_IDLE_TIMEOUT; /*!< Initial idle timeout (in seconds). @since 0.1.0 */
unsigned long write_timeout_ = DEFAULT_WRITE_TIMEOUT; /*!< Write timeout (in milliseconds). @since 0.1.0 */
};
} // namespace telnet
} // namespace uuid
#endif