Branching from Cathaswatch project

https://git.nerdyssey.de/exodus/1NiM-WatingRoom-WatchController
This commit is contained in:
2024-11-16 11:53:28 +01:00
parent 1a13428c9d
commit 8c49e2f11c
116 changed files with 14510 additions and 590 deletions
+1
View File
@@ -0,0 +1 @@
https://github.com/typester/esp32-uuid/tree/master
+7
View File
@@ -0,0 +1,7 @@
Copyright 2019 Daisuke Murase (typester)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+84
View File
@@ -0,0 +1,84 @@
#include "uuid.h"
#include <string.h>
#include "esp_system.h"
#include "esp_random.h"
void uuid_generate(uuid_t out)
{
esp_fill_random(out, sizeof(uuid_t));
/* uuid version */
out[6] = 0x40 | (out[6] & 0xF);
/* uuid variant */
out[8] = (0x80 | out[8]) & ~0x40;
}
static uint8_t unhex_char(unsigned char s)
{
if (0x30 <= s && s <= 0x39) { /* 0-9 */
return s - 0x30;
} else if (0x41 <= s && s <= 0x46) { /* A-F */
return s - 0x41 + 0xa;
} else if (0x61 <= s && s <= 0x69) { /* a-f */
return s - 0x61 + 0xa;
} else {
/* invalid string */
return 0xff;
}
}
static int unhex(unsigned char *s, size_t s_len, unsigned char *r)
{
int i;
for (i = 0; i < s_len; i += 2) {
uint8_t h = unhex_char(s[i]);
uint8_t l = unhex_char(s[i + 1]);
if (0xff == h || 0xff == l) {
return -1;
}
r[i / 2] = (h << 4) | (l & 0xf);
}
return 0;
}
int uuid_parse(const char *in, uuid_t uu)
{
const char *p = in;
uint8_t *op = (uint8_t *)uu;
if (0 != unhex((unsigned char *)p, 8, op)) {
return -1;
}
p += 8;
op += 4;
for (int i = 0; i < 3; i++) {
if ('-' != *p++ || 0 != unhex((unsigned char *)p, 4, op)) {
return -1;
}
p += 4;
op += 2;
}
if ('-' != *p++ || 0 != unhex((unsigned char *)p, 12, op)) {
return -1;
}
p += 12;
op += 6;
return 0;
}
void uuid_unparse(const uuid_t uu, char *out)
{
snprintf(out, UUID_STR_LEN,
"%02x%02x%02x%02x-%02x%02x-%02x%02x-"
"%02x%02x-%02x%02x%02x%02x%02x%02x",
uu[0], uu[1], uu[2], uu[3], uu[4], uu[5], uu[6], uu[7], uu[8], uu[9], uu[10], uu[11],
uu[12], uu[13], uu[14], uu[15]);
}
+17
View File
@@ -0,0 +1,17 @@
#pragma once
#include <stdint.h>
typedef uint8_t uuid_t[16];
#define UUID_STR_LEN 37
#ifdef __cplusplus
extern "C" {
#endif
void uuid_generate(uuid_t out);
int uuid_parse(const char *in, uuid_t uu);
void uuid_unparse(const uuid_t uu, char *out);
#ifdef __cplusplus
}
#endif
+650
View File
@@ -0,0 +1,650 @@
#include "spit_client.hpp"
#include "esp_log.h"
#include "Queue.hpp"
namespace spit {
static const char *TAG = "SPIT";
static fos::Queue<vector<char>> incommingSpit(128);
/**
* Create a new SPIT_client instance, should be done once by the user app.
* @param p_SPITClientInterface the user app should implement the SPIT_ClientInterface.
*/
SClient::SClient(string name, string projName, ISocket &socket, Callbacks calls)
: ISadClient(),
mCallbacks(calls),
mProcessor(name, projName, getClientHostName(), createGUID(), calls,
{
.delay = [&](int ms){ return fos::Task::delayMs(ms); },
.sendMessage = [&](Message::OPPart &msg){ return sendMessage(msg); },
.parseMessage = [&](Message& msg) { parseMessage(msg); }
}
),
mSocket(socket) {
}
SClient::~SClient() {
stopServerAutoDetect();
disconnectInternal(Connection::None);
}
void SClient::clientProjectClose() {
if (mConnectionStatus == Connection::Ok) {
ESP_LOGI(TAG, " ... tidy up ressources");
Message::CloseProject closeSPITProject(mProcessor.getProjName());
if (sendMessage(closeSPITProject) < 0) {
ESP_LOGD(TAG, "Client could not send Client's ReportProject");
}
}
}
void SClient::clientProjectLoaded() {
// set confirmed = false for all SPITTypes and SPITObjects
mProcessor.resetConfirmedFlags();
if (mConnectionStatus == Connection::Ok) {
// send ReportProject to the server
Message::ReportProject reportSPITProject(mProcessor.getProjName(), mProcessor.getProjName());
reportSPITProject.setFramesPerSecond(0);
if (sendMessage(reportSPITProject) < 0) {
ESP_LOGD(TAG, "SPIT_CLient::clientProjectLoaded could not send Client's ReportProject");
}
// send RequestProjectTransfer
Message::RequestProjectTransfer sMRequestProjectTransfer;
if (sendMessage(sMRequestProjectTransfer) < 0) {
ESP_LOGD(TAG, "SPIT_CLient::clientProjectLoaded could not send RequestProjectTransfer");
}
}
}
//---------- ServerAutoDetection SAD -----------
/** Starts the autodetection of SPITServers in the local network. If a connection is tried, the autodetection should be stopped before.
* If a SPITServer is detected in the locale network an eventServerDetected occurs.
*/
void SClient::startServerAutoDetect() {
if (mDisconnectedByServer == true) {
// Server has previously disconnected -> do not start ServerAutodetect directly
fos::Task::delayMs(1000); // sleep a little to give the SPITServer time to clean up its stuff
}
SadDetectedServer::clearDetectedServers();
if (!sThread) {
sThread = new fos::TaskT<SClient &>(SClient::processSocketTask, *this, "spitSock", fos::Task::DefaultStackSize * 2);
}
mAbortSearch = false;
sStartSad.give();
}
/** Stops the auto detection of SPIT servers. */
void SClient::stopServerAutoDetect() {
mAbortSearch = true;
}
/**
* If a new server was found by the ServerAutoDetect mechanism, this function is called.
* To do so the SAD_Client ServerAutoDetect has to be startet - see SAD_Client::startServerAutoDetect.
* If a server can be reached via several IP addresses, the IP address is selected according to the following preference:
*/
void SClient::eventServerDetected(SadDetectedServer &server) {
mCallbacks.onServerEvent(ServerEvent::Detect, server);
// If a connection has been established and the connection is lost -> reconnect
if (_doReconnect) {
if (mServerID == server.getID()) {
stopServerAutoDetect();
ESP_LOGI(TAG, "reconnect to %s / %s", server.getIP().data(), server.getPort().data());
if (connect(server.getIP(), server.getPort(), server.getClientIP(), "0")) {
return;
}
}
}
// Stop auto detection because a suitable server was found
stopServerAutoDetect();
ESP_LOGI(TAG, "connect to %s / %s", server.getIP().data(), server.getPort().data());
if (!connect(server.getIP(), server.getPort(), server.getClientIP(), "0" /*= any port*/)) {
// connection message could not be sent -> better to restart AutoDetection
startServerAutoDetect();
}
}
/**
* If a SPITServer is not more found, this function is called.
* To do so the SAD_Client ServerAutoDetect has to be startet - see SAD_Client::startServerAutoDetect.
*/
void SClient::eventServerLost(SadDetectedServer &server) {
mCallbacks.onServerEvent(ServerEvent::Lost, server);
}
void SClient::eventServerChanged(SadDetectedServer &server) {
mCallbacks.onServerEvent(ServerEvent::Changed, server);
// tryConnection(detectedServer);
}
// void MyApp::tryConnection(const SadDetectedServer &detected) {
// // fetch the infoemations from the detected
// bool boolSuccess = false;
// bool boolPossibleConnect = checkPossibleConnection(detected);
// if (boolPossibleConnect == false)
// return;
// // --------------------------------------
// // You have three Options to use the detected server
// // OPTION 1 Use the first server found
// // OPTION 2 Use the first server found that fulfills specific conditions
// // OPTION 3 take a copy of the detected and do connection later
// // SadDetectedServer o_DetectedServer = detected; //this will call the copy constructor
// // ---------------------------------------
// // Excample use the first detected LOOPBACK server, means the server runs on the same computer as this client
// // if (detected.getIpIsLoopback() == true) { //omit the if condition if you want to use the first server found
// boolSuccess = false;
// // Stop auto detection because a suitable server was found
// stopServerAutoDetect();
// if (mSPIT_Client != nullptr) {
// mServerProjectName = detected.getProjectName();
// mServerIP = detected.getIP();
// #if defined(DEBUGOUTPUT)
// ESP_LOGD(TAG, "MySPITApp: connect to %s ", detected.getIP().data());
// #endif
// // Use the mBoardNumber as login name
// string stringLoginName = "";
// string stringLoginPassword = "";
// boolSuccess = mSPIT_Client->connect(detected.getIP(), detected.getPort(), detected.getClientIP(), stringLoginName, stringLoginPassword); // ClientPort = "0" meeans use any port
// }
// if (boolSuccess == false) {
// // connection message could not be sent
// // perhaps restart AutoDetection
// startServerAutoDetect();
// } else {
// // WAITING FOR CONNECTION ANSWER
// // If the Server is closed in the meantime no answer will return
// //-> connect(..) has started the watchdog
// }
// // } //Excample
// }
int SClient::sendMessage(Message::OPPart &opPart) {
if (mMessageNumberSend == 255)
mMessageNumberSend = 1;
else
mMessageNumberSend++;
opPart.setMessageNumber(mMessageNumberSend);
opPart.setServerInfoInHeader(mProcessor.getID(), mServerID);
int opFlag = opPart.getHeader()->getType();
try {
auto spitMessage = Message::createMessage(opPart);
if (spitMessage.empty()) {
return -1;
}
int intResult = mSocket.sendMessage(spitMessage.data(), spitMessage.size());
if (intResult < 0) {
ESP_LOGD(TAG, "client could not send message OPFlage: %d", opFlag);
}
return intResult;
} catch (...) {
ESP_LOGD(TAG, "Exception client OPFlag: %d", opFlag);
}
return -1;
}
bool SClient::startReceiveSPITMessages() {
if (!mSocket.getIsBound())
return false;
mStopReceiveMessages = false;
mThreadReceive = new fos::TaskT<SClient &>(receiveSpitMessage, *this, "receiveTask", fos::Task::DefaultStackSize, fos::Task::MAX_PRIORITY-3);
mThreadProcess = new fos::TaskT<SClient &>(processSpitMessage, *this, "processTask", fos::Task::DefaultStackSize);
return true;
}
void SClient::stopReceiveSPITMessages() {
mStopReceiveMessages = true;
}
void SClient::receiveSpitMessage(SClient &rM) {
char buffer[Message::MAX_MESSAGE_SIZE];
int rdLen;
while (rM.mStopReceiveMessages == false) {
rdLen = rM.mSocket.receiveMessage(buffer, Message::MAX_MESSAGE_SIZE, nullptr);
if (rdLen < 0) {
if (rM.mStopReceiveMessages) {
return;
}
continue;
}
auto vec = make_unique<vector<char>>(buffer, buffer + rdLen);
incommingSpit.enqueue(std::move(vec));
}
}
void SClient::processSpitMessage(SClient &rM) {
while (true) {
auto data = *incommingSpit.dequeue();
auto spitMsg = make_unique<Message>(data);
rM.checkParseMessage(move(spitMsg));
}
}
void SClient::resetMessageNumberReceived() {
mMessageNumberReceived = -1;
mProcessor.resetMessageNumberReceived();
}
void SClient::checkParseMessage(unique_ptr<Message> p_SPITMessage) {
answerWatchdog();
if (!p_SPITMessage || !p_SPITMessage->getHeader() || !p_SPITMessage->getOPPart()) {
ESP_LOGD(TAG, "Client::parseMessage incomplete message");
return;
}
int msgNum = (int)(p_SPITMessage->getHeader()->getMessageNumber() & 0xFF);
if (msgNum == 0) {
parseMessage(*p_SPITMessage);
return;
}
if (mMessageNumberReceived < 0) {
mMessageNumberReceived = msgNum;
parseMessage(*p_SPITMessage);
return;
}
int intNextMessageNumber = mMessageNumberReceived + 1;
if (intNextMessageNumber > 255 || intNextMessageNumber < 1)
intNextMessageNumber = 1;
if (msgNum == intNextMessageNumber) {
mMessageNumberReceived = msgNum;
parseMessage(*p_SPITMessage);
mMessageNumberReceived = mProcessor.sendOvertakingMessage(mMessageNumberReceived);
} else {
// p_SPITMessage has overtaken another message
mProcessor.addOvertakingMessage(move(p_SPITMessage));
}
}
void SClient::parseMessage(Message& message) {
answerWatchdog();
if (!message.getHeader() || !message.getOPPart()) {
ESP_LOGD(TAG, "Client::parseMessage incomplete message");
return;
}
switch (message.getType()) {
//----- Connection -----
case Message::ConnectAnswer::OPFLAG:
parseConnectAnswer(message.getAs<Message::ConnectAnswer>());
break;
case Message::Disconnect::OPFLAG:
parseDisconnect(message.getAs<Message::Disconnect>());
break;
case Message::Watchdog::OPFLAG:
parseWatchdog(message.getAs<Message::Watchdog>());
break;
case Message::WatchdogAnswer::OPFLAG:
parseWatchdogAnswer(message.getAs<Message::WatchdogAnswer>());
break;
default:
// pass object to SubProcessor
mProcessor.parseMessage(message);
}
}
//----- Connection ------
/**
* Tries to connect to a SPITServer.
* @param serverIP the ip address of the SPIT server
* @param serverPort the port number of the SPIT server
* @param clientIP the local IP address of the client
* @param clientPort the port number of the Client, if "0" the OS uses any possible port number
* @param loginName login user name (liveSHOW does not use this -> mayby ""
* @param loginPassword login user password (liveSHOW does not use this -> mayby ""
* @return false = an error has occured, true = the connect was sent to the server
*/
bool SClient::connect(string serverIP, string serverPort, string clientIP, string clientPort, string loginName, string loginPassword) {
disconnect();
mSocket.setConnectionParams(serverIP, serverPort, clientIP, clientPort);
if (mSocket.bindSocket(clientIP, clientPort) < 0) {
mStopReceiveMessages = true;
return false;
}
auto connMsg = mProcessor.createConnectMessage(serverIP, serverPort, loginName, loginPassword);
if (startReceiveSPITMessages() == false) {
mStopReceiveMessages = true;
return false;
}
resetMessageNumberReceived();
Connection intOldConnectionStatus = mConnectionStatus;
mConnectionStatus = Connection::TryConnection;
mCallbacks.onConnectionStatus(mServerID, mServerComputerName, mServerName, mServerStepTime,
mConnectionStatus, intOldConnectionStatus);
// sleep a little to make sure that the reception of the messages is started
fos::Task::delayMs(500);
if (sendMessage(*connMsg) < 0) {
mConnectionStatus = Connection::None;
mStopReceiveMessages = true;
return false;
}
return true;
}
/**
* Terminates the connection to the SPITServer.
*/
void SClient::disconnect() {
disconnectInternal(Connection::None);
}
void SClient::disconnectInternal(Connection cause) {
Connection intOldConnectionStatus = mConnectionStatus;
mConnectionStatus = cause;
switch (mConnectionStatus) {
case Connection::Lost:
break;
case Connection::None: {
Message::Disconnect sMDisconnect;
sendMessage(sMDisconnect);
} break;
case Connection::NoneServer:
break;
default:
break;
}
stopReceiveSPITMessages();
mCallbacks.onConnectionStatus(mServerID, mServerComputerName, mServerName, mServerStepTime,
mConnectionStatus, intOldConnectionStatus);
if (intOldConnectionStatus != Connection::None && intOldConnectionStatus != Connection::NoneServer) {
ESP_LOGI(TAG, "eventConnectionStatus %d ", (int)mConnectionStatus);
switch (mConnectionStatus) {
case SClient::Connection::None:
ESP_LOGD(TAG, "not connected");
break;
case SClient::Connection::NoneServer:
ESP_LOGD(TAG, "connection terminated by server");
// call eventProjectClosed because this may not be sended by the server,
// when the connection is closed
mCallbacks.onProjectUpdate(ProjUpdate::ServerClosed, mProcessor.getServerProjectID());
// Server has terminated the connection -> start a new game
// _doReconnect = false;
startServerAutoDetect();
break;
case SClient::Connection::Lost:
ESP_LOGD(TAG, "connection lost");
// Maybe you want to reconnect, see eventServerDetected(...)
// We have lost the connection -> set the flag to true and start the autodetection to reconnect at eventServerDetected(...)
// _doReconnect = true;
startServerAutoDetect();
break;
default:
break;
}
}
}
void SClient::parseConnectAnswer(Message::ConnectAnswer &message) {
if (message.getClientID() != mProcessor.getID())
return;
mServerID = message.getServerID();
mServerComputerName = message.getServerComputerName();
mServerName = message.getServerName();
mServerStepTime = message.getStepTime();
if (message.getConnected() == false) {
disconnectInternal(Connection::NoneServer);
} else {
mConnectionStatus = Connection::Ok;
mDisconnectedByServer = false;
ESP_LOGD(TAG, "connection established");
stopServerAutoDetect(); // normally you should stop the autodetection before you try a connection
// This is redundand
mStopReceiveMessages = false;
// Maybe you want to reconnect, see eventServerDetected(...)
// Here the connection is established -> don't reconnect at eventServerDetected(...)
// _doReconnect = false;
}
}
/**
* The SPIT server was closed
* @param message
*/
void SClient::parseDisconnect(Message::Disconnect &message) {
Connection intCauseDisconnect = Connection::NoneServer;
mDisconnectedByServer = true;
SClient::disconnectInternal(intCauseDisconnect);
}
//----- Watchdog ------
/**
* The SPIT server sends a watchdog message at regular intervals. The client responds immediately with WatchdogAnswer
* Conversely, the client also sends a watchdog message at regular intervals and waits for a WatchdogAnswer message.
* This is used to check if the other side is still present.
* @param message
*/
void SClient::parseWatchdog(Message::Watchdog &message) {
Message::WatchdogAnswer o_WatchdogAnswer;
sendMessage(o_WatchdogAnswer);
}
/**
* The SPIT server sends a watchdog message at regular intervals. The client responds immediately with WatchdogAnswer
* Conversely, the client also sends a watchdog message at regular intervals and waits for a WatchdogAnswer message.
* This is used to check if the other side is still present.
* @param message
*/
void SClient::parseWatchdogAnswer(Message::WatchdogAnswer &message) {
answerWatchdog(); // is also done by parseMessage
}
void SClient::sendWatchdog() {
int intResult;
Message::Watchdog message;
intResult = sendMessage(message);
if (intResult < 0) {
ESP_LOGW("WDT", "Failed to send Watchdog msg");
}
}
void SClient::eventWatchdogLost() {
if (mConnectionStatus == Connection::TryConnection) {
disconnectInternal(Connection::NoneServer);
} else {
disconnectInternal(Connection::Lost);
}
}
void SClient::sendProject(SClient *p_PtrSPIT_Client) {
// int intResult;
// Message::StartProjectTransfer* ptrSMStartProjectTransfer;
// Message::EndProjectTransfer* ptrSMEndProjectTransfer;
// int intCount;
// SPITType* ptrSPITType;
// SObject* ptrSPITObject;
// //StartProjectTransfer
// NotifyStartProjectTransfer_Client* o_RunnableStart = new NotifyStartProjectTransfer_Client(p_PtrSPIT_Client->getSPITClientInterface());
// SPIT_Handler::addRunnable(o_RunnableStart);
// ptrSMStartProjectTransfer = new Message::StartProjectTransfer();
// intResult = p_PtrSPIT_Client->sendMessage(ptrSMStartProjectTransfer);
// if (intResult < 0) {
// return;
// }
// // mWatchdog->answerWatchdog();
// // {
// //send all listes SPITTypes
// // unique_lock<recursive_mutex> o_LockType(*SPITType::getMutex());
// intCount = SPITType::getSPITTypeCount();
// for (int i = 0; i < intCount; i++) {
// ptrSPITType = mSpitTypes.getEntry(i);
// if (ptrSPITType == nullptr) continue;
// if (ptrSPITType->getCreatedByClient() == false) continue;
// intResult = p_PtrSPIT_Client->sendTypeInternal(ptrSPITType);
// if (intResult < 0) return;
// // mWatchdog->answerWatchdog();
// this_thread::sleep_for(chrono::milliseconds(7));
// }
// // }
// // {
// // unique_lock<recursive_mutex> o_LockObject(*SObject::getMutex());
// intCount = mSpitObjects.getEntryCount();
// for (int i = 0; i < intCount; i++) {
// ptrSPITObject = mSpitObjects.getEntry(i);
// if (ptrSPITObject == nullptr) continue;
// if (ptrSPITObject->getCreatedByClient() == false) continue;
// intResult = p_PtrSPIT_Client->sendObjectInternal(ptrSPITObject);
// if (intResult < 0) return;
// // mWatchdog->answerWatchdog();
// this_thread::sleep_for(chrono::milliseconds(7));
// }
// // }
// //EndProjectTransfer
// ptrSMEndProjectTransfer = new Message::EndProjectTransfer();
// intResult = p_PtrSPIT_Client->sendMessage(ptrSMEndProjectTransfer);
// if (intResult < 0) {
// return;
// }
// // mWatchdog->answerWatchdog();
// NotifyEndProjectTransfer_Client* o_RunnableEnd = new NotifyEndProjectTransfer_Client(p_PtrSPIT_Client->getSPITClientInterface());
// SPIT_Handler::addRunnable(o_RunnableEnd);
// //Send act
}
SType *SClient::createTypeFixed(string iD, string group, string name, uint64_t delay, uint64_t duration) {
return mProcessor.createType(iD, group, name, delay, duration, true, true);
}
fos::Semaphore SClient::sStartSad;
unordered_map<string, unique_ptr<SadDetectedServer>> SadDetectedServer::sKnownServers;
void SClient::processSocketTask(SClient &instance) {
ESP_LOGI(TAG, "Starting Socket managing");
static const string SERVERAUTODETECT_PRECODE = "#SAD#";
static const string SERVERAUTODETECT_SEPERATOR = "#,#";
static const string SERVERAUTODETECT_TASKID = "#spit#"; // -> "#SAD##,##spit#"
static const string SERVERAUTODETECT_PORT = "15110";
static constexpr int WATCHDOGTIME = 3000;
while (true) {
sStartSad.take();
bool searching = true;
instance.mSocket.setBroadcast(true);
string stringDatagram = SERVERAUTODETECT_PRECODE + SERVERAUTODETECT_SEPERATOR + SERVERAUTODETECT_TASKID;
int intDatagramLength = stringDatagram.length();
while (searching) {
if (instance.mAbortSearch)
break;
SadDetectedServer::resetServerFlags();
string stringBroadcastIP4 = instance.mSocket.mOwnIp.getBroadcastIP4();
instance.mSocket.sendMessageTo(stringBroadcastIP4, SERVERAUTODETECT_PORT, stringDatagram.data(), intDatagramLength);
{
// wait for 3000 milliseconds or
fos::Task::delayMs(3000);
}
bool receiving = true;
while (receiving) {
if (instance.mAbortSearch) {
break;
}
constexpr int SIZE = 512;
char buffer[SIZE + 1];
string stringServerIP;
int intReceivedByteCount = instance.mSocket.receiveMessage(buffer, SIZE, &stringServerIP);
if (intReceivedByteCount <= 0) {
break;
}
buffer[intReceivedByteCount] = '\0';
string stringMessage = buffer;
// Check if precode is ok
if (stringMessage.find(SERVERAUTODETECT_PRECODE.data(), 0) != 0) {
break;
}
int intIndex = 0;
int intOldPos = 0;
string stringPart;
string strParts[10];
while (intIndex < SadDetectedServer::PARAM_IP) {
int intPos = stringMessage.find(SERVERAUTODETECT_SEPERATOR.data(), intOldPos);
if (intPos == string::npos) {
stringPart = "";
} else {
stringPart = stringMessage.substr(intOldPos, intPos - intOldPos);
intOldPos = intPos + SERVERAUTODETECT_SEPERATOR.length();
}
strParts[intIndex] = stringPart;
intIndex++;
}
auto server = make_unique<SadDetectedServer>(
strParts[SadDetectedServer::PARAM_NAME],
strParts[SadDetectedServer::PARAM_ID],
strParts[SadDetectedServer::PARAM_COMPUTERNAME],
stringServerIP,
strParts[SadDetectedServer::PARAM_PORT],
strParts[SadDetectedServer::PARAM_CLIENTIP],
strParts[SadDetectedServer::PARAM_ADDITIONS],
strParts[SadDetectedServer::PARAM_SERVERPROJECTNAME]);
SadDetectedServer::addDetectedServer(move(server));
}
fos::Task::delayMs(100);
SadDetectedServer::evaluateDetectedServers(instance);
instance.mAbortSearch = false;
// if(instance.mConnectionStatus == Connection::Ok) break;
fos::Task::delayMs(1000);
}
instance.mSocket.setBroadcast(false);
ESP_LOGW(TAG, "Finished searching");
ESP_LOGW(TAG, "Start working");
auto nextWdTrigger = fos::Timing::getMilliSec() + WATCHDOGTIME;
// auto nextWdCheck = nextWdTrigger + WATCHDOGTIME;
while (true) {
// auto time = fos::Timing::getMilliSec();
// if (time > nextWdTrigger) {
// instance.sAnswered = false;
// // instance.sendWatchdog();
// nextWdTrigger = nextWdCheck + WATCHDOGTIME;
// }
// if (time > nextWdCheck) {
// if (!instance.sAnswered) {
// ESP_LOGW(TAG, "TimeOut");
// instance.eventWatchdogLost();
// break;
// }
// nextWdCheck = nextWdTrigger + WATCHDOGTIME;
// }
fos::Task::delayMs(25);
// if (instance.mStopReceiveMessages)
// break;
}
}
}
}
File diff suppressed because it is too large Load Diff
+202
View File
@@ -0,0 +1,202 @@
#include "spit_object.hpp"
#include "spit_socket.hpp"
#include "esp_log.h"
namespace spit {
/**
* clears all unconfirmed SObjects not created by this SPIT_Client.
*/
void SObject::clearUnconfirmedObjects(SpitRepo<SObject> &repo) {
auto &entries = repo.getRepo();
for (auto i = entries.begin(); i != entries.end();) {
SObject *ptrSObject = i->second.get();
if (ptrSObject->getCreatedByClient() == false && ptrSObject->getConfirmed() == false) {
i = entries.erase(i);
} else {
i++;
}
}
}
void SObject::eventProjectClosed(SpitRepo<SObject> &repo, SpitRepo<SObject> &remoteRepo) {
auto &entries = repo.getRepo();
for (auto i = entries.begin(); i != entries.end(); ) {
SObject *ptrSObject = i->second.get();
if (ptrSObject->getCreatedByClient() == true) {
i++;
continue;
}
i = entries.erase(i);
}
// Remote
remoteRepo.clearEntries();
}
/**
* sets confirmed = false for all SObjects.
* This should be called when the client has loaded a project.
*/
void SObject::setConfirmedFalse(SpitRepo<SObject> &repo) {
auto &entries = repo.getRepo();
for (auto i = entries.begin(); i != entries.end(); i++) {
i->second->setConfirmed(false);
}
}
bool SObject::setMessageOP(Message::ReportObject *message) {
bool changeAllowed;
if ((mCreatedByClient == true) && (Fixed.mValue == true)) {
changeAllowed = false;
} else {
changeAllowed = true;
}
// Confirmation should be changable
if (mCreatedByClient) {
// Server reports a client created SPITType -> the type is confirmed
mConfirmed = true;
} else {
// type is NOT client created -> the client has to confirm the SPITtype
mConfirmed = message->getConfirmed();
}
if (changeAllowed == false) {
return false;
}
bool changed = false;
if (Delay.mValue != message->getDelay()) {
Delay.mValue = message->getDelay();
changed = true;
}
if (Duration.mValue != message->getDuration()) {
Duration.mValue = message->getDuration();
changed = true;
}
if (Fixed.mValue != message->getFixed()) {
Fixed.mValue = message->getFixed();
changed = true;
}
if (FrameFadeInLength.mValue != message->getFrameFadeInLength()) {
FrameFadeInLength.mValue = message->getFrameFadeInLength();
changed = true;
}
if (FrameFadeOutLength.mValue != message->getFrameFadeOutLength()) {
FrameFadeOutLength.mValue = message->getFrameFadeOutLength();
changed = true;
}
if (FrameLength.mValue != message->getFrameLength()) {
FrameLength.mValue = message->getFrameLength();
changed = true;
}
if (FrameStart.mValue != message->getFrameStart()) {
FrameStart.mValue = message->getFrameStart();
changed = true;
}
if (Remark.mValue != message->getRemark()) {
Remark.mValue = message->getRemark();
changed = true;
}
if (mID != message->getID()) {
mID = message->getID();
changed = true;
}
if (Name.mValue != message->getName()) {
Name.mValue = message->getName();
changed = true;
}
if (SPITTypeID.mValue != message->getSPITTypeID()) {
SPITTypeID.mValue = message->getSPITTypeID();
changed = true;
}
if (ServerParams.mValue != message->getServerParams()) {
ServerParams.mValue = message->getServerParams();
changed = true;
}
if (ServerRemote.mValue != message->getServerRemote()) {
ServerRemote.mValue = message->getServerRemote();
changed = true;
}
if (Track.mValue != message->getTrack()) {
Track.mValue = message->getTrack();
changed = true;
}
if (mValueFactor != message->getValueFactor()) {
mValueFactor = message->getValueFactor();
changed = true;
}
if (mValueUse != message->getValueUse()) {
mValueUse = message->getValueUse();
changed = true;
}
return changed;
}
void SObject::fillMessageOP(Message::ReportObject &message) {
message.setActionAssigned(getActionAssigned()); // Action can be assigned to type or to this object
message.setConfirmed(mConfirmed);
message.setDelay(Delay.mValue);
message.setDuration(Duration.mValue);
message.setFixed(Fixed.mValue);
message.setFrameFadeInLength(FrameFadeInLength.mValue);
message.setFrameFadeOutLength(FrameFadeOutLength.mValue);
message.setFrameLength(FrameLength.mValue);
message.setFrameStart(FrameStart.mValue);
message.setID(mID);
message.setName(Name.mValue);
message.setRemark(Remark.mValue);
message.setSPITTypeID(SPITTypeID.mValue);
message.setServerParams(ServerParams.mValue);
message.setServerRemote(ServerRemote.mValue);
message.setTrack(Track.mValue);
message.setValueFactor(mValueFactor);
message.setValueUse(mValueUse);
}
void SObject::fillMessageOP(Message::RemoveObject &message) {
message.setID(mID);
}
/**
* This is used by the server and client to tell if the other side has registered the changes.
* @param p_Confirmed
* @return if mConfirmed has changed
*/
bool SObject::setConfirmed(bool p_Confirmed) {
if (mConfirmed == p_Confirmed)
return false;
mConfirmed = p_Confirmed;
return true;
}
/**
* This is a virtual construct - ActionAssignedType is not implemented in the SPITProtocol.
* This is used when an action is associated with a SPITType.
* Then this action is done for every SObject with the SPITType.
* @return true = an action is assigned for the SPITType.
*/
bool SObject::setActionAssignedType(bool p_ActionAssigned) {
if (mActionAssignedType == p_ActionAssigned)
return false;
bool oldActionAssigend = getActionAssigned();
mActionAssignedType = p_ActionAssigned;
if (getActionAssigned() == oldActionAssigend)
return false;
mConfirmed = false;
return true;
}
bool SObject::setActionAssigned(bool p_ActionAssigned) {
if (mActionAssigned == p_ActionAssigned)
return false;
bool oldActionAssigend = getActionAssigned();
mActionAssigned = p_ActionAssigned;
if (getActionAssigned() == oldActionAssigend)
return false;
mConfirmed = false;
return true;
}
}
+93
View File
@@ -0,0 +1,93 @@
#include "spit_player.hpp"
#include "esp_log.h"
#include <memory>
#include <vector>
using namespace std;
namespace spit {
unordered_map<string, unique_ptr<SPlayer>> SPlayer::sSpitPlayers;
SPlayer &SPlayer::getSPITPlayer(string playerID) {
auto i = sSpitPlayers.find(playerID);
if (i == sSpitPlayers.end()) {
sSpitPlayers[playerID] = make_unique<SPlayer>(playerID);
}
return *sSpitPlayers.at(playerID);
}
SPlayer::SPlayer(string playerID) {
mId = playerID;
}
SPlayer::~SPlayer() {
mPlaySPITTypes.clear();
}
/**
*
* @param p_SPITObjectID
* @return true = new PlayObject added<br>
* false = PlayObject exists
*/
SPlayer::PlayType *SPlayer::addTypePlaying(string spitTypeID) {
PlayType *ptrPlaySPITType;
auto i = mPlaySPITTypes.find(spitTypeID);
if (i == mPlaySPITTypes.end()) {
auto playSpitType = make_unique<PlayType>(this, spitTypeID);
ptrPlaySPITType = playSpitType.get();
mPlaySPITTypes[spitTypeID] = move(playSpitType);
} else {
ptrPlaySPITType = i->second.get();
}
ptrPlaySPITType->setIsPlayed(true);
return ptrPlaySPITType;
}
void SPlayer::startPlaySequence() {
for (auto i = mPlaySPITTypes.begin(); i != mPlaySPITTypes.end(); i++) {
PlayType *ptrPlaySPITType = i->second.get();
ptrPlaySPITType->startPlaySequence();
}
}
void SPlayer::endPlaySequence() {
for (auto i = mPlaySPITTypes.begin(); i != mPlaySPITTypes.end();) {
if (i->second.get()->getIsPlayed() == false) {
i = mPlaySPITTypes.erase(i);
} else {
++i;
}
}
}
SPlayer::PlayType::PlayType(SPlayer *p_SPITPlayer, string spitTypeID) {
mIsNew = true;
mSpitTypeID = spitTypeID;
mPlayerID = p_SPITPlayer->getID();
mPlayerName = p_SPITPlayer->getName();
startPlaySequence();
mIsNew = true;
}
SPlayer::PlayType::PlayType(const PlayType &orig) {
mSpitTypeID = orig.getSPITTypeID();
mIsPlayed = orig.getIsPlayed();
mIsNew = orig.getIsNew();
mPlayerID = orig.getPlayerID();
mPlayerName = orig.getPlayerName();
mPlayerRuns = orig.getPlayerIsRunning();
mValue = orig.getValue();
}
void SPlayer::PlayType::startPlaySequence() {
mIsPlayed = false;
mIsNew = false;
mValue = 0;
}
}
+745
View File
@@ -0,0 +1,745 @@
#include "spit_processor.hpp"
#include "esp_log.h"
namespace spit {
static const char *TAG = "SPIT";
/**
* Create a new SPIT_client instance, should be done once by the user app.
* @param p_SPITClientInterface the user app should implement the SPIT_ClientInterface.
*/
SProcessor::SProcessor(string name, string projName, string hostName, string guid, Callbacks calls, InternalCallbacks intern)
: mCallbacks(calls),
mInternCallbacks(intern),
mName(name),
mGuid(guid),
mProjName(projName),
mComputerName(hostName) {
}
SProcessor::~SProcessor() {
}
unique_ptr<Message::Connect> SProcessor::createConnectMessage(string serverIP, string serverPort, string loginName, string loginPassword) {
auto msg = make_unique<Message::Connect>(getComputerName(), mName);
msg->setLoginName(loginName);
msg->setLoginPassword(loginPassword);
msg->setServerIP(serverIP);
if (serverPort.length() <= 0) {
serverPort = "0";
}
int intServerPort = stoi(serverPort);
msg->setServerPort(intServerPort);
return msg;
}
/**
* Virtual construct - means actionAssigned for SPITTypes is is not implemented in SPITProtocol.
* This can used to setActionAssigned to all SPITObjects with this SPITType.
* This may be used in the case when the same action should be done for every SPITObject with this SPITType associated.
* This tells the server not what action is done, but it tells the server that there is an action.
* @param typeId the id of the SPITType
* @param actionAssigned
*/
void SProcessor::setActionType(string typeId, bool actionAssigned) {
SType *spitType = mSpitTypes.getEntry(typeId);
if (spitType == nullptr)
return;
if (spitType->ActionAssigned.set(actionAssigned)) {
sendTypeInternal(*spitType);
for (auto &[key, value] : mSpitObjects.getRepo()) {
if (value->SPITTypeID.get() == spitType->getID()) {
if (value->setActionAssignedType(actionAssigned) == true) {
sendObjectInternal(value.get());
}
}
}
}
}
/** This should be called if the user wants to associate/remove an action with a SPITObject. */
void SProcessor::setActionObject(string spitObjId, bool actionAssigned) {
SObject *spitObject = mSpitObjects.getEntry(spitObjId);
if (spitObject == nullptr)
return;
if (spitObject->setActionAssigned(actionAssigned) == true) {
sendObjectInternal(spitObject);
}
}
std::vector<char> SProcessor::createMessage(Message::OPPart &opPart) {
if (mMessageNumberSend == 255)
mMessageNumberSend = 1;
else
mMessageNumberSend++;
opPart.setMessageNumber(mMessageNumberSend);
opPart.setServerInfoInHeader(mGuid, mServerID);
int opFlag = opPart.getHeader()->getType();
try {
return Message::createMessage(opPart);
} catch (...) {
ESP_LOGD(TAG, "Exception client OPFlag: %d", opFlag);
}
return {};
}
void SProcessor::resetMessageNumberReceived() {
mMessageNumberReceived = -1;
mOvertakingMessages.clear();
}
void SProcessor::checkParseMessageData(vector<char> data) {
auto message = make_unique<Message>(data);
// answerWatchdog();
if (!message->getHeader() || !message->getOPPart()) {
ESP_LOGD(TAG, "Client::parseMessage incomplete message");
return;
}
int msgNum = (int)(message->getHeader()->getMessageNumber() & 0xFF);
if (msgNum == 0) {
parseMessage(*message);
return;
}
if (mMessageNumberReceived < 0) {
mMessageNumberReceived = msgNum;
parseMessage(*message);
return;
}
int msgNumNext = mMessageNumberReceived + 1;
if (msgNumNext > 255 || msgNumNext < 1)
msgNumNext = 1;
if (msgNum == msgNumNext) {
mMessageNumberReceived = msgNum;
parseMessage(*message);
mMessageNumberReceived = sendOvertakingMessage(mMessageNumberReceived);
} else {
// message has overtaken another message
addOvertakingMessage(move(message));
}
}
void SProcessor::addOvertakingMessage(unique_ptr<Message> message) {
if (!message) {
return;
}
if (mOvertakingMessages.size() > MAX_OVERTAKING_SPIT_MESSAGES) {
ESP_LOGD(TAG, "Client receiveing: perhaps message lost !!!!!");
}
int intMessageNumber = (int)(message->getHeader()->getMessageNumber() & 0xFF);
for (auto it = mOvertakingMessages.begin(); it != mOvertakingMessages.end(); ++it) {
const auto &overtaking = *it;
int intOvertakingMessageNumber = (int)(overtaking->getHeader()->getMessageNumber() & 0xFF);
if (intMessageNumber < intOvertakingMessageNumber) {
mOvertakingMessages.insert(it, move(message));
return;
}
}
mOvertakingMessages.push_back(move(message));
}
int SProcessor::sendOvertakingMessage(int msgNumRec) {
// check overtaking messages
auto it = mOvertakingMessages.begin();
while (mOvertakingMessages.size() > 0) {
int msgNumNext = msgNumRec + 1;
if (msgNumNext > 255 || msgNumNext < 1)
msgNumNext = 1;
auto &overtakingMessage = *it;
int msgNum = (int)(overtakingMessage->getHeader()->getMessageNumber() & 0xFF);
if (msgNum == msgNumNext) {
msgNumRec = msgNum;
parseMessage(*overtakingMessage);
mOvertakingMessages.erase(it);
it = mOvertakingMessages.begin();
} else {
break;
}
}
return msgNumRec;
}
void SProcessor::parseMessage(Message &message) {
// answerWatchdog();
if (!message.getHeader() || !message.getOPPart()) {
ESP_LOGD(TAG, "Client::parseMessage incomplete message");
return;
}
switch (message.getType()) {
//----- Connection -----
case Message::ReportProject::OPFLAG:
parseReportProject(message.getAs<Message::ReportProject>());
break;
//----- SPIT Project -----
case Message::StartProjectTransfer::OPFLAG:
parseStartProjectTransfer(message.getAs<Message::StartProjectTransfer>());
break;
case Message::CloseProject::OPFLAG:
parseCloseProject(message.getAs<Message::CloseProject>());
break;
case Message::RequestProjectTransfer::OPFLAG:
parseRequestProjectTransfer(message.getAs<Message::RequestProjectTransfer>());
break;
case Message::EndProjectTransfer::OPFLAG:
parseEndProjectTransfer(message.getAs<Message::EndProjectTransfer>());
break;
//----- SPIT Types ------
case Message::ReportType::OPFLAG:
parseReportType(message.getAs<Message::ReportType>());
break;
case Message::RemoveType::OPFLAG:
parseRemoveType(message.getAs<Message::RemoveType>());
//----- SPIT Objects ------
break;
case Message::ReportObject::OPFLAG:
parseReportObject(message.getAs<Message::ReportObject>());
break;
case Message::RemoveObject::OPFLAG:
parseRemoveObject(message.getAs<Message::RemoveObject>());
break;
//----- Play SPIT Objects ------
case Message::PlayStart::OPFLAG:
parsePlayStart(message.getAs<Message::PlayStart>());
break;
case Message::PlayObject::OPFLAG:
parsePlayObject(message.getAs<Message::PlayObject>());
break;
case Message::PlayEnd::OPFLAG:
parsePlayEnd(message.getAs<Message::PlayEnd>());
break;
case Message::ReportPlayStatus::OPFLAG:
parseReportPlayStatus(message.getAs<Message::ReportPlayStatus>());
break;
default:
ESP_LOGD(TAG, "Client::parseMessage: Unknown Message");
}
}
//----- SPIT Project ------
void SProcessor::parseReportProject(Message::ReportProject &message) {
ESP_LOGD(TAG, "parse ReportProject");
mServerProjectID = message.getProjectID();
mServerProjectName = message.getProjectName();
mServerFramesPerSecond = message.getFramesPerSecond();
mCallbacks.onProjectUpdate(ProjUpdate::ServerLoaded, mServerProjectID);
// Set all SPITTypes and SPITObjects confirmed = false
SType::setConfirmedFalse(mSpitTypes);
SObject::setConfirmedFalse(mSpitObjects);
// Send RequestProjectTransfer
Message::RequestProjectTransfer sMRequestProjectTransfer;
if (mInternCallbacks.sendMessage(sMRequestProjectTransfer) < 0) {
ESP_LOGD(TAG, "Client::parseReportProject could not send RequestProjectTransfer");
}
// todo Don't send the ReportProject here it should be sended after parseEndProjectTransfer
// Send client's ReportProject
Message::ReportProject reportProject(mProjName, mProjName);
reportProject.setFramesPerSecond(0);
if (mInternCallbacks.sendMessage(reportProject) < 0) {
ESP_LOGD(TAG, "Client::parseReportProject could not send Client's ReportProject");
}
}
void SProcessor::parseCloseProject(Message::CloseProject &p_CloseSPITProject) {
ESP_LOGD(TAG, "parse CloseProject");
string stringProjectID;
mCallbacks.onProjectUpdate(ProjUpdate::ServerClosed, p_CloseSPITProject.getProjectID());
SPlayer::clearPlayers();
}
void SProcessor::parseRequestProjectTransfer(Message::RequestProjectTransfer &message) {
ESP_LOGD(TAG, "parse RequestProjectTransfer");
// StartProjectTransfer
Message::StartProjectTransfer sMStartProjectTransfer;
if (mInternCallbacks.sendMessage(sMStartProjectTransfer) < 0) {
return;
}
// send all listes SPITTypes
for (const auto &[key, value] : mSpitTypes.getRepo()) {
sendTypeInternal(*value);
mInternCallbacks.delay(10);
}
for (auto &[key, value] : mSpitObjects.getRepo()) {
sendObjectInternal(value.get());
mInternCallbacks.delay(10);
}
// EndProjectTransfer
Message::EndProjectTransfer sMEndProjectTransfer;
mInternCallbacks.sendMessage(sMEndProjectTransfer);
}
void SProcessor::parseStartProjectTransfer(Message::StartProjectTransfer &message) {
mCallbacks.onProjectUpdate(ProjUpdate::StartTransfer, mServerProjectID);
}
void SProcessor::parseEndProjectTransfer(Message::EndProjectTransfer &message) {
SObject::clearUnconfirmedObjects(mSpitObjects);
mCallbacks.onProjectUpdate(ProjUpdate::EndTransfer, mServerProjectID);
// Send Message::ReportProject after the mSPITClientInterface is notfied of EndProjectTransfer
Message::ReportProject reportSPITProject(mProjName, mProjName);
reportSPITProject.setFramesPerSecond(0);
if (mInternCallbacks.sendMessage(reportSPITProject) < 0) {
ESP_LOGD(TAG, "Client::parseReportProject could not send Client's ReportProject");
}
}
void SProcessor::parseReportType(Message::ReportType &message) {
ESP_LOGD(TAG, "parse ReportType ID %s Name %s", message.getID().c_str(), message.getName().c_str());
SType *sType;
bool isNew = false;
bool isChanged = false;
if (message.getServerRemote() == false) {
sType = mSpitTypes.getEntry(message.getID());
if (sType == nullptr) {
// The user's app may veto the SPITType
bool createdByClient = false;
if (sType) {
createdByClient = sType->getCreatedByClient();
}
if (mCallbacks.onCheckSpitTypeVeto(false, createdByClient) == false) {
ESP_LOGW(TAG, "TypeVeto %s / %s", message.getID().c_str(), message.getName().c_str());
return;
}
auto spitType = make_unique<SType>(message.getID());
sType = spitType.get();
mSpitTypes.addEntry(move(spitType));
isNew = true;
}
} else {
sType = mRemoteSpitTypes.getEntry(message.getID());
if (sType == nullptr) {
auto spitType = make_unique<SType>(message.getID());
sType = spitType.get();
mRemoteSpitTypes.addEntry(move(spitType));
isNew = true;
}
}
if (!sType->setMessageOP(message)) {
// SPITType is not changable -> send back to the server
sendTypeInternal(*sType);
return;
}
auto eventType = isNew ? ObjectEvent::Created : ObjectEvent::Changed;
// add or change SPITType
mCallbacks.onSpitType(eventType, sType->ServerRemote.get(), *sType);
if (sType != nullptr) {
if (sType->setConfirmed(true) == true || isChanged == true) {
sendTypeInternal(*sType);
}
}
}
void SProcessor::parseRemoveType(Message::RemoveType &message) {
ESP_LOGD(TAG, "parse RemoveType %s", message.getID().c_str());
bool isRemote = false;
string sTypeID = message.getID();
SType *sType = mRemoteSpitTypes.getEntry(sTypeID);
if (!sType) {
isRemote = true;
} else {
sType = mSpitTypes.getEntry(sTypeID);
if (!sType) {
isRemote = false;
} else {
// No SType with sTypeID is listet
return;
}
}
if (sType->getCreatedByClient() && sType->Fixed.get()) {
// the SPITyte is created by client himself and it is fixed -> don't remove the SType!!
// Send the SPITTyp back to the server
sendTypeInternal(*sType);
return;
}
mCallbacks.onSpitType(ObjectEvent::Deleted, isRemote, *sType);
if (isRemote == false) {
mSpitTypes.removeEntry(*sType);
} else {
mRemoteSpitTypes.removeEntry(*sType);
}
}
/**
* Change an existing SPIT_Type.
* @param p_SPITType the SPITType vont
* @return true = changed, false = not changed maybe p_SPITType is NULL or the SPITType doesn't exists
*/
bool SProcessor::changeType(SType *sType) {
if (sType == nullptr)
return false;
SType *mod = mSpitTypes.getEntry(sType->getID());
if (mod == nullptr)
return false;
mod->DefaultDelay.set(sType->DefaultDelay.get());
mod->DefaultDuration.set(sType->DefaultDuration.get());
mod->Fixed.set(sType->Fixed.get());
mod->GroupName.set(sType->GroupName.get());
mod->Name.set(sType->Name.get());
mod->ProjectIndepended.set(sType->ProjectIndepended.get());
mCallbacks.onSpitType(ObjectEvent::Changed, false, *mod);
sendTypeInternal(*mod);
return true;
}
/**
* If a client has removed a SPITType where id == p_SPITTypeID, it should send it to the server.
* The corresponding SPITType if listet in SPITType will be deleted.
* An eventRemoveSPITType will occur.
* If a connection to a SPITServer is established, the SPITServer may dicard the deletion an an eventSPITType will occur
* -> the SPITType will be listet again.
* @param p_SPITType the id of the SPITType ro remove
* @return 0 = is sended, -1 = is not sended
*/
int SProcessor::removeType(string sType) {
if (sType.length() <= 0)
return -1;
SType *mod = mSpitTypes.getEntry(sType);
if (mod == nullptr)
return -1;
Message::RemoveType sMRemoveSPITType;
mod->fillMessageOP(sMRemoveSPITType);
mCallbacks.onSpitType(ObjectEvent::Deleted, false, *mod);
mSpitTypes.removeEntry(*mod);
return mInternCallbacks.sendMessage(sMRemoveSPITType);
}
SType *SProcessor::createType(string id, string groupName, string name, uint64_t delay, uint64_t duration, bool fixed, bool projIndipendent) {
bool boolNew;
SType *sType = mSpitTypes.getEntry(id);
if (sType == nullptr) {
auto spitType = make_unique<SType>(id, groupName, name, fixed, projIndipendent, delay, duration);
sType = spitType.get();
mSpitTypes.addEntry(move(spitType));
sType->setCreatedByClient(true);
boolNew = true;
} else {
sType->GroupName.set(groupName);
sType->Name.set(name);
sType->Fixed.set(fixed);
sType->ProjectIndepended.set(projIndipendent);
sType->DefaultDelay.set(delay);
sType->DefaultDuration.set(duration);
boolNew = false;
}
auto eventType = boolNew ? ObjectEvent::Created : ObjectEvent::Changed;
mCallbacks.onSpitType(eventType, false, *sType);
sendTypeInternal(*sType);
return sType;
}
/**
* If a client has changed a SPITType, it should send it to the server to transmit the changes.
* If the SPITType is a fixed one and not created by the client himself, then an eventSPITType can be sent from the server, which cancels the changes.
* @param sType the pointer to the changed SPITType
* @return 0 = is sended, -1 = is not sended
*/
int SProcessor::sendTypeInternal(SType &sType) {
Message::ReportType reportType;
sType.fillMessageOP(reportType);
return mInternCallbacks.sendMessage(reportType);
}
//----- SPIT Objects ------
void SProcessor::parseReportObject(Message::ReportObject &message) {
ESP_LOGD(TAG, "parse ReportObject %s", message.getID().c_str());
SObject *sObject;
SType *sType;
bool isNew = false;
bool isRemote = false;
bool isChanged = false;
{
ESP_LOGD(TAG, "parse ReportObject %s / %s", message.getID().c_str(), message.getName().c_str());
if (message.getServerRemote() == false) {
isRemote = false;
sObject = mSpitObjects.getEntry(message.getID());
sType = mSpitTypes.getEntry(message.getSPITTypeID());
if (sType == nullptr) {
// No associated SPITType found
if (sObject != nullptr) {
// SpitObject exists but has no corresponding SPITType -> remove the SPITObject without sending a message
// Maybe the SPITObject has changed the SPITTypeID to a SPITTypeID of another board
mSpitObjects.removeEntry(*sObject);
}
return;
}
bool isKnownType = sType != nullptr;
if (sObject == nullptr) {
// The user's app my veto the SPITType
if (mCallbacks.onCheckSpitObjectVeto(isKnownType) == false) {
ESP_LOGW(TAG, "ObjectVeto %s / %s", message.getID().c_str(), message.getName().c_str());
return;
}
auto spitObj = make_unique<SObject>(message.getID());
sObject = spitObj.get();
mSpitObjects.addEntry(move(spitObj));
isNew = true;
} else {
// The user's app my veto the SPITType
if (mCallbacks.onCheckSpitObjectVeto(isKnownType) == false) {
// SpitObject exists and User APp denied the SPITObject -> remove the SPITObject without sending a message
mSpitObjects.removeEntry(*sObject);
ESP_LOGW(TAG, "TypeVeto %s / %s", message.getID().c_str(), message.getName().c_str());
return;
}
}
} else {
isRemote = true;
sObject = mRemoteSpitObjects.getEntry(message.getID());
sType = mRemoteSpitTypes.getEntry(message.getSPITTypeID());
if (sType == nullptr) {
// No associated SPITType found
return;
}
if (sObject == nullptr) {
auto spitObj = make_unique<SObject>(message.getID());
sObject = spitObj.get();
mRemoteSpitObjects.addEntry(move(spitObj));
isNew = true;
}
}
if (!sObject->setMessageOP(&message)) {
ESP_LOGE(TAG, "Change not allowed %s", sObject->getID().c_str());
// setMessageOP error - the SPITObject can't be changed, it may be fixed and crated by this client
sendObjectInternal(sObject);
return;
}
if (isRemote == false) {
// set the virtual action assignment
sObject->setActionAssignedType(sType->ActionAssigned.get());
}
auto eventType = isNew ? ObjectEvent::Created : ObjectEvent::Changed;
mCallbacks.onSpitObject(eventType, isRemote, *sType, *sObject);
if (sObject->setConfirmed(true) == true || isChanged == true) {
// Confirmation has changed
sendObjectInternal(sObject);
}
}
}
void SProcessor::parseRemoveObject(Message::RemoveObject &message) {
ESP_LOGD(TAG, "parse RemoveObject %s", message.getID().c_str());
SObject *sObject = mRemoteSpitObjects.getEntry(message.getID());
if (sObject != nullptr) {
SType *sType = mRemoteSpitTypes.getEntry(sObject->SPITTypeID.get());
if (sType != nullptr) {
mCallbacks.onSpitObject(ObjectEvent::Deleted, true, *sType, *sObject);
}
mRemoteSpitObjects.removeEntry(*sObject);
} else {
// call removeObject, this will notify the server that the object is removed
removeObject(message.getID());
}
}
int SProcessor::sendObjectInternal(SObject *sObject) {
if (sObject == nullptr)
return -1;
Message::ReportObject reportObject;
sObject->fillMessageOP(reportObject);
return mInternCallbacks.sendMessage(reportObject);
}
/**
* When the client changes an existing SPITObject (normally the client should not do this), then he should send the changes to the SPITServer.
* An eventSPITObject occurs.
* The SPITServer may discard the changes and a second eventSPITObject occurs to correct the changes.
* @param obj the pointer to the changed SPITObject.
* @return 0 = sended, -1 = not sended
*/
bool SProcessor::changeObject(SObject *sObject) {
if (sObject == nullptr)
return false;
SObject *mod = mSpitObjects.getEntry(sObject->getID());
if (mod == nullptr)
return false;
SType *sType = mSpitTypes.getEntry(sObject->getID());
if (mod == nullptr)
return false;
mod->setActionAssigned(sObject->getActionAssigned());
mod->Delay.set(sObject->Delay.get());
mod->Duration.set(sObject->Delay.get());
mod->Fixed.set(sObject->Fixed.get());
mod->FrameFadeInLength.set(sObject->FrameFadeInLength.get());
mod->FrameFadeOutLength.set(sObject->FrameFadeOutLength.get());
mod->FrameLength.set(sObject->FrameLength.get());
mod->FrameStart.set(sObject->FrameStart.get());
mod->Name.set(sObject->Name.get());
mod->Remark.set(sObject->Remark.get());
mod->SPITTypeID.set(sObject->SPITTypeID.get());
mod->ServerParams.set(sObject->ServerParams.get());
mod->Track.set(sObject->Track.get());
mod->ValueFactor.set(sObject->ValueFactor.get());
mod->ValueUse.set(sObject->ValueUse.get());
mCallbacks.onSpitObject(ObjectEvent::Changed, false, *sType, *mod);
sendObjectInternal(mod);
return true;
}
/**
* When the client wants to remove a SPITObject with id == p_SPITObjectID (normally the client should not do this), then he should send it to the SPITServer.
* The corresponding SPITObject if listet in SPITObject will be deleted.
* An eventRemoveSPITType will occur.
* If an connection to a SPITServer is established, the SPITServer may discard the deletion and an eventSPITObject occurs
* -> the SPITObject is listet again.
* @param objId the id of the SPITObject to remove.
* @return 0 = sended to the SPITServer, -1 = not sended to the SPITServer or nos SPITObject with id == objId exists.
*/
int SProcessor::removeObject(string objId) {
if (objId.length() <= 0)
return -1;
Message::RemoveObject remObj;
SObject *sObject = mSpitObjects.getEntry(objId);
if (sObject == nullptr)
return -1;
SType *sType = mSpitTypes.getEntry(sObject->SPITTypeID.get());
if (sType == nullptr)
return -1;
sObject->fillMessageOP(remObj);
mCallbacks.onSpitObject(ObjectEvent::Deleted, false, *sType, *sObject);
mSpitObjects.removeEntry(*sObject);
return mInternCallbacks.sendMessage(remObj);
}
// //----- Play SPIT Objects ------
void SProcessor::parsePlayStart(Message::PlayStart &message) {
ESP_LOGD(TAG, "parse PlayStart ID: %s Name: %s Running: %d", message.getPlayerID().c_str(), message.getPlayerName().c_str(), (int)message.getPlayerIsRunning());
SPlayer &player = SPlayer::getSPITPlayer(message.getPlayerID());
player.setIsRunning(message.getPlayerIsRunning());
player.setName(message.getPlayerName());
player.setType(message.getPlayerType());
player.setTime(message.getTime());
player.startPlaySequence();
mCallbacks.onPlay(player.getID(), player.getName(), player.getType(), player.getIsRunning(), player.getTime());
}
void SProcessor::parsePlayObject(Message::PlayObject &message) {
SPlayer &player = SPlayer::getSPITPlayer(message.getPlayerID());
SObject *sObject = mSpitObjects.getEntry(message.getSPITObjectID());
if (sObject == nullptr) {
return;
}
SPlayer::PlayType *playType = player.addTypePlaying(sObject->SPITTypeID.get());
if (playType == nullptr) {
return;
}
playType->setPlayerIsRunning(player.getIsRunning());
playType->addValue(((double)message.getValue() / 1000000), ((double)sObject->ValueFactor.get() / 1000000));
}
void SProcessor::parsePlayEnd(Message::PlayEnd &message) {
SPlayer &sPlayer = SPlayer::getSPITPlayer(message.getPlayerID());
auto &playTypes = sPlayer.getPlaySPITTypes();
for (auto i = playTypes.begin(); i != playTypes.end(); i++) {
SPlayer::PlayType *playType = i->second.get();
bool isStarted = playType->getIsNew();
bool iStopped = (playType->getIsPlayed() == false);
SType *ptrSPITType = mSpitTypes.getEntry(playType->getSPITTypeID());
if (ptrSPITType == nullptr) {
// set isplayed to false, the ptrPlayObject will be removed by sPlayer.endPlaySequence()
playType->setIsPlayed(false);
continue;
}
mCallbacks.onPlaySpitType(playType->getPlayerID(), playType->getSPITTypeID(), playType->getPlayerIsRunning(), isStarted, iStopped, playType->getValue());
}
sPlayer.endPlaySequence();
}
void SProcessor::parseReportPlayStatus(Message::ReportPlayStatus &message) {
SType *sType = NULL;
SObject *sObject = mRemoteSpitObjects.getEntry(message.getID());
if (sObject == nullptr) {
sObject = mSpitObjects.getEntry(message.getID());
if (sObject == nullptr) {
return;
}
}
if (sObject->ServerRemote.get() == false) {
sType = mSpitTypes.getEntry(sObject->SPITTypeID.get());
} else {
sType = mRemoteSpitTypes.getEntry(sObject->SPITTypeID.get());
}
if (sType == nullptr) {
return;
}
mCallbacks.onPlayRemoteObjectStatus(*sType, *sObject, message.getFrameInsideObject(), message.getValue());
}
// //--------- Remote ----------
/**
* Send a remote control command to the SPITServer.
* There may be different command types:
* a) Some don't use value e.g. go to next scene, olay a jingle
* b) Some use values e.g. the main sound fader
* @param sObjectId the id of the remote SPITObject (this represents the remote control command)
* @param valuePpm the value in ppm
* in case a) the value is ignored
* in case b) the value is used
* @param valueFlag indicates how the server should use valuePpm:
* VALUEFLAG_DIRECT (0x00) The server overwrites its existing value with p_Value
* VALUEFLAG_INCREASE (0x01) The server increases his existing value by valuePpm
* VALUEFLAG_DECREASE (0x01) The server decreases his existing value by valuePpm
*/
void SProcessor::sendRemoteObject(string sObjectId, int valuePpm, char valueFlag) {
SObject *remoteObject = mRemoteSpitObjects.getEntry(sObjectId);
if (remoteObject == nullptr)
return;
Message::PlayStart playSPITStart(mGuid, mProjName, SPlayer::PTYPE_DISCRETE);
playSPITStart.setPlayerIsRunning(false);
playSPITStart.setTime(0);
mInternCallbacks.sendMessage(playSPITStart);
Message::PlayObject playSPITObject;
playSPITObject.setID(remoteObject->getID());
playSPITObject.setPlayPrePosition(false);
playSPITObject.setPlayerID(mGuid);
playSPITObject.setValue(valuePpm);
playSPITObject.setValueFlag(valueFlag);
mInternCallbacks.sendMessage(playSPITObject);
Message::PlayEnd playSPITEnd(mGuid);
mInternCallbacks.sendMessage(playSPITEnd);
}
} // namespace spit
+213
View File
@@ -0,0 +1,213 @@
#include "spit_socket.hpp"
#include "Task.hpp"
#include "esp/uuid.h"
#include "esp_log.h"
#include "esp_netif.h"
#include <string>
#include <unistd.h>
using namespace std;
namespace spit {
/**
* Creates socket. If there exist some, they will be closed.
* @return
*/
bool Socket::createSocketUdp() {
if (mUdpSocketIp4 != INVALID_SOCKET) {
close(mUdpSocketIp4);
mUdpSocketIp4 = INVALID_SOCKET;
}
mUdpSocketIp4 = socket(AF_INET, SOCK_DGRAM, 0);
int intResult = -1;
bool success = true;
if (mUdpSocketIp4 == INVALID_SOCKET) {
success = false;
} else {
int intOptionValue4 = 1;
intResult = setsockopt(mUdpSocketIp4, SOL_SOCKET, SO_REUSEADDR, (void *)&intOptionValue4, sizeof(intOptionValue4));
}
if (intResult < 0) {
success = false;
}
if (success == true) {
struct timeval tv;
tv.tv_sec = 1; // seconds
tv.tv_usec = 0; // microseconds (0.0 sec)
// This sets the receive timeout
setsockopt(mUdpSocketIp4, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
// set the mUdpSocketIp4 in NON BLOCKING mode because
// if the UDPSockets are in blocking mode the receiveMessage does not return in every constellation
// int intFlags;
// intFlags = fcntl(mUdpSocketIp4, F_GETFL);
// fcntl(mUdpSocketIp4, F_SETFL, intFlags | O_NONBLOCK);
// int intBufferSize = 2046 * 512;
// intResult = setsockopt(mUdpSocketIp4, SOL_SOCKET, SO_RCVBUF, (void*)&intBufferSize, sizeof(intBufferSize));
// intResult = setsockopt(mUdpSocketIp4, SOL_SOCKET, SO_SNDBUF, (void*)&intBufferSize, sizeof(intBufferSize));
}
return success;
}
/**
* Binds the socket to the client's ip address and port. The socket option SO_REUSEADDR is set to true.
* @param clientIp ip address of the client
* @param clientPort port of the client.
* if p_Port == "0" any free port will be used
* @return -1 id bind fails, o if bind is ok
*
*/
int Socket::bindSocket(string clientIp, string clientPort) {
string stringClientPort;
if (clientPort.length() <= 0)
stringClientPort = "0";
else
stringClientPort = clientPort;
addrinfo *clientAI;
if (getaddrinfo(clientIp.data(), clientPort.data(), nullptr, &clientAI) != 0) {
return -1;
}
if (mUdpSocketIp4 == INVALID_SOCKET) {
return -1;
}
auto *socketAdr = (sockaddr_in *)(clientAI->ai_addr);
int intSocklen = sizeof(*socketAdr);
// copy the socketAdr, may be not filled correct by getaddrinfo
sockaddr_in o_IP4SocketAddress;
memset(&o_IP4SocketAddress, 0, intSocklen);
o_IP4SocketAddress.sin_addr = socketAdr->sin_addr;
o_IP4SocketAddress.sin_family = socketAdr->sin_family;
o_IP4SocketAddress.sin_port = socketAdr->sin_port;
int intResult = bind(mUdpSocketIp4, (const sockaddr *)(socketAdr), (socklen_t)intSocklen);
if (intResult < 0) {
return intResult;
}
if ((intResult == 0) &&
(getsockname(mUdpSocketIp4, (sockaddr *)socketAdr, (socklen_t *)&intSocklen) != -1)) {
setBoundClientPort(to_string((int)socketAdr->sin_port));
}
freeaddrinfo(clientAI);
setIsBound(intResult != -1);
return intResult;
}
/**
* Set whether the socket are broadcast sockets or not. Set the sockopt SO_BROADCAST to 0 or 1
* @param p_Broadcast true SO_BROADCAST is set
* false = SO_BROADCAST is not set
* @return 0 = ok, -1 = error
*/
int Socket::setBroadcast(bool broadcast) const {
int intOptionValue = broadcast ? 1 : 0;
int intResult = setsockopt(mUdpSocketIp4, SOL_SOCKET, SO_BROADCAST, &intOptionValue, sizeof(int));
return intResult;
}
/**
* @param ip the ip address of the server
* @param port the port of the server
* @param msg buffer containing the message
* @param msgLength the length of the message
* @return -1 send fails
* 0 send is done
*/
int Socket::sendMessageTo(const string &ip, const string &port, const char *message, int msgLength) {
if (mUdpSocketIp4 == INVALID_SOCKET) {
return -1;
}
// prepare AddressInfo
int intResult = -1;
addrinfo *ptrAddressInfo;
if (getaddrinfo(ip.data(), port.data(), nullptr, &ptrAddressInfo) != 0) {
return intResult;
}
auto *ptrIP4SocketAddress = (sockaddr_in *)(ptrAddressInfo->ai_addr);
int intSocklen = sizeof(*ptrIP4SocketAddress);
if (mUdpSocketIp4 != INVALID_SOCKET) {
// copy the ptrIP4SocketAddress, may be not filled correct by getaddrinfo
sockaddr_in o_IP4SocketAddress;
memset(&o_IP4SocketAddress, 0, intSocklen);
o_IP4SocketAddress.sin_addr = ptrIP4SocketAddress->sin_addr;
o_IP4SocketAddress.sin_family = ptrIP4SocketAddress->sin_family;
o_IP4SocketAddress.sin_port = ptrIP4SocketAddress->sin_port;
intResult = sendto(mUdpSocketIp4, message, msgLength, 0, (const sockaddr *)&o_IP4SocketAddress, (socklen_t)intSocklen);
if (intResult < 0) {
ESP_LOGE("SPIT", "sendMessageTo IP4 sendto %s FAILED %s\n", ip.data(), strerror(errno));
ESP_LOGE("SPIT", "sendMessageTo ret=%d %s / %s / %d\n", intResult, port.data(), message, msgLength);
}
freeaddrinfo(ptrAddressInfo);
}
if (intResult < 0) {
return -1;
}
return 0;
}
/**
* Call this for receiving a message
* This will block until a message is received or the underlying socket is closed
* @param p_Buffer the buffer containing the received message later.
* @param p_BufferLength the size of the buffer
* @param p_ServerIP a pointer to a string, when a messages is received the string will contain the ip address of the sender/server
* if p_ServerIP is NULL - no detection of the server will be done.
* @return
*/
int Socket::receiveMessage(char *p_Buffer, int p_BufferLength, string *serverIp) const {
int intResult = -1;
if (mUdpSocketIp4 == INVALID_SOCKET) {
return intResult;
}
sockaddr_in o_IP4SocketAddress;
int intSocklen = sizeof(o_IP4SocketAddress);
memset(&o_IP4SocketAddress, 0, intSocklen);
intResult = recvfrom(mUdpSocketIp4, (void *)p_Buffer, p_BufferLength, 0, (sockaddr *)&o_IP4SocketAddress, (socklen_t *)&intSocklen);
if (intResult >= 0 && serverIp != nullptr) {
char bufferIP4[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(o_IP4SocketAddress.sin_addr), bufferIP4, intSocklen);
*serverIp = bufferIP4;
}
return intResult;
}
ISocket::IpAddress Socket::getAdaptersInfo() {
// iterate over active interfaces, and print out IPs
esp_netif_t *netif = nullptr;
if ((netif = esp_netif_next_unsafe(netif)) != nullptr) {
ESP_LOGI("SPIT", "Connected to %s", esp_netif_get_desc(netif));
esp_netif_ip_info_t ip;
ESP_ERROR_CHECK(esp_netif_get_ip_info(netif, &ip));
ESP_LOGI("SPIT", "- IPv4 address: " IPSTR ",", IP2STR(&ip.ip));
ESP_LOGI("SPIT", "- IPv4 netmask: " IPSTR ",", IP2STR(&ip.netmask));
char buffer[64];
sprintf(buffer, IPSTR, IP2STR(&ip.ip));
string stringIP4 = buffer;
sprintf(buffer, IPSTR, IP2STR(&ip.netmask));
string stringSubnet = buffer;
return {stringIP4, stringSubnet};
}
return {"0.0.0.0", "0.0.0.0"};
}
string createGUID() {
uuid_t uu;
uuid_generate(uu);
char uu_str[UUID_STR_LEN];
uuid_unparse(uu, uu_str);
return {uu_str};
}
string getClientHostName() {
const char *hostname;
esp_netif_get_hostname(esp_netif_get_default_netif(), &hostname);
if (hostname != nullptr) {
return {hostname};
}
return "";
}
}