#include "spit_client.hpp" #include "esp_log.h" #include "Queue.hpp" namespace spit { static const char *TAG = "SPIT"; static fos::Queue> incommingSpit(64); /** * 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), mName(name), mProjName(projName), mProcessor(name, projName, getClientHostName(), createGUID(), calls, { .delay = [&](int ms){ return fos::Task::delayMs(ms); }, .sendMessage = [&](Message::OPPart &msg){ return sendMessage(msg); }, .sendTypeInternal = [&](SType &sType) { return sendTypeInternal(sType);}, .sendObjectInternal = [&](SObject *sObject) { return sendObjectInternal(sObject);} } ), mSocket(socket) { mID = createGUID(); mComputerName = getClientHostName(); } SClient::~SClient() { stopServerAutoDetect(); disconnectInternal(Connection::None); } void SClient::clientProjectClose() { if (mConnectionStatus == Connection::Ok) { ESP_LOGI(TAG, " ... tidy up ressources"); Message::CloseProject closeSPITProject(mProjName); 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 SType::setConfirmedFalse(mProcessor.mSpitTypes); SObject::setConfirmedFalse(mProcessor.mSpitObjects); if (mConnectionStatus == Connection::Ok) { // send ReportProject to the server Message::ReportProject reportSPITProject(mProjName, mProjName); 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"); } } } /** * 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 SClient::setActionSPITType(string typeId, bool actionAssigned) { SType *spitType = mProcessor.mSpitTypes.getEntry(typeId); if (spitType == nullptr) return; if (spitType->ActionAssigned.set(actionAssigned)) { sendTypeInternal(*spitType); for (auto& [key, value] : mProcessor.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 SClient::setActionSPITObject(string spitObjId, bool actionAssigned) { SObject *spitObject = mProcessor.mSpitObjects.getEntry(spitObjId); if (spitObject == nullptr) return; if (spitObject->setActionAssigned(actionAssigned) == true) { sendObjectInternal(spitObject); } } //---------- 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::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(mID, 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(receiveSpitMessage, *this, "receiveTask", fos::Task::DefaultStackSize, fos::Task::MAX_PRIORITY-3); mThreadProcess = new fos::TaskT(processSpitMessage, *this, "processTask", fos::Task::DefaultStackSize); return true; } void SClient::stopReceiveSPITMessages() { mStopReceiveMessages = true; } void SClient::receiveSpitMessage(SClient &rM) { char buffer[MAXSPITMESSAGEBYTECOUNT + 1]; int rdLen; while (rM.mStopReceiveMessages == false) { rdLen = rM.mSocket.receiveMessage(buffer, MAXSPITMESSAGEBYTECOUNT, nullptr); if (rdLen < 0) { if (rM.mStopReceiveMessages) { return; } continue; } auto vec = make_unique>(buffer, buffer + rdLen); incommingSpit.enqueue(std::move(vec)); } } void SClient::processSpitMessage(SClient &rM) { while (true) { auto data = *incommingSpit.dequeue(); auto spitMsg = make_unique(data); rM.parseMessage(move(spitMsg)); } } void SClient::resetMessageNumberReceived() { mMessageNumberReceived = -1; mOvertakingMessages.clear(); } void SClient::checkParseMessage(unique_ptr 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(std::move(p_SPITMessage)); return; } if (mMessageNumberReceived < 0) { mMessageNumberReceived = msgNum; parseMessage(std::move(p_SPITMessage)); return; } int intNextMessageNumber = mMessageNumberReceived + 1; if (intNextMessageNumber > 255 || intNextMessageNumber < 1) intNextMessageNumber = 1; if (msgNum == intNextMessageNumber) { mMessageNumberReceived = msgNum; parseMessage(std::move(p_SPITMessage)); // check overtaking messages auto it = mOvertakingMessages.begin(); while (mOvertakingMessages.size() > 0) { intNextMessageNumber = mMessageNumberReceived + 1; if (intNextMessageNumber > 255 || intNextMessageNumber < 1) intNextMessageNumber = 1; auto &ptrOvertakingSPIT_Message = *it; msgNum = (int)(ptrOvertakingSPIT_Message->getHeader()->getMessageNumber() & 0xFF); if (msgNum == intNextMessageNumber) { mMessageNumberReceived = msgNum; parseMessage(move(ptrOvertakingSPIT_Message)); mOvertakingMessages.erase(it); it = mOvertakingMessages.begin(); } else { break; } } } else { // p_SPITMessage has overtaken another message addOvertakingMessage(move(p_SPITMessage)); } } void SClient::addOvertakingMessage(unique_ptr 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)); } void SClient::parseMessage(unique_ptr message) { answerWatchdog(); if (!message || !message->getHeader() || !message->getOPPart()) { ESP_LOGD(TAG, "Client::parseMessage incomplete message"); return; } switch (message->getType()) { //----- Connection ----- case Message::ConnectAnswer::OPFLAG: parseConnectAnswer(message->getAs()); break; case Message::Disconnect::OPFLAG: parseDisconnect(message->getAs()); break; case Message::ReportProject::OPFLAG: mProcessor.parseReportProject(message->getAs()); break; case Message::Watchdog::OPFLAG: parseWatchdog(message->getAs()); break; case Message::WatchdogAnswer::OPFLAG: parseWatchdogAnswer(message->getAs()); break; //----- SPIT Project ----- case Message::StartProjectTransfer::OPFLAG: mProcessor.parseStartProjectTransfer(message->getAs()); break; case Message::CloseProject::OPFLAG: mProcessor.parseCloseProject(message->getAs()); break; case Message::RequestProjectTransfer::OPFLAG: mProcessor.parseRequestProjectTransfer(message->getAs()); break; case Message::EndProjectTransfer::OPFLAG: mProcessor.parseEndProjectTransfer(message->getAs()); break; //----- SPIT Types ------ case Message::ReportType::OPFLAG: mProcessor.parseReportType(message->getAs()); break; case Message::RemoveType::OPFLAG: mProcessor. parseRemoveType(message->getAs()); //----- SPIT Objects ------ break; case Message::ReportObject::OPFLAG: mProcessor.parseReportObject(message->getAs()); break; case Message::RemoveObject::OPFLAG: mProcessor.parseRemoveObject(message->getAs()); break; //----- Play SPIT Objects ------ case Message::PlayStart::OPFLAG: mProcessor.parsePlayStart(message->getAs()); break; case Message::PlayObject::OPFLAG: mProcessor.parsePlayObject(message->getAs()); break; case Message::PlayEnd::OPFLAG: mProcessor.parsePlayEnd(message->getAs()); break; case Message::ReportPlayStatus::OPFLAG: mProcessor.parseReportPlayStatus(message->getAs()); break; default: ESP_LOGD(TAG, "Client::parseMessage: Unknown 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; } Message::Connect sMConnect(mComputerName, mName); sMConnect.setLoginName(loginName); sMConnect.setLoginPassword(loginPassword); sMConnect.setServerIP(serverIP); if (serverPort.length() <= 0) { serverPort = "0"; } int intServerPort = stoi(serverPort); sMConnect.setServerPort(intServerPort); 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(sMConnect) < 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() != mID) return; mServerID = message.getServerID(); mServerComputerName = message.getServerComputerName(); mServerName = message.getServerName(); mServerStepTime = message.getStepTime(); if (message.getConnected() == false) { disconnectInternal(Connection::NoneServer); } else { message.getStepTime(); 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; /* function o_Callable = bind(&SClient::disconnectInternal, this, intCauseDisconnect); SPIT_Handler::addCallable(o_Callable); */ 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) { // ESP_LOGD(TAG, "parse Watchdog "); 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); } } string SClient::getServerProjectID() { return mProcessor.getServerProjectID(); } string SClient::getServerProjectName() { return mProcessor.getServerProjectName(); } int SClient::getServerFramesPerSecond() { return mProcessor.getServerFramesPerSecond(); } 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 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 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 } /** * 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 p_SPITType the pointer to the changed SPITType * @return 0 = is sended, -1 = is not sended */ int SClient::sendTypeInternal(SType &p_SPITType) { Message::ReportType sMReportSPITType; p_SPITType.fillMessageOP(sMReportSPITType); return sendMessage(sMReportSPITType); } SType *SClient::createTypeFixed(string iD, string p_GroupName, string p_Name, uint64_t p_DefaultDelay, uint64_t p_DefaultDuration) { return createType(iD, p_GroupName, p_Name, p_DefaultDelay, p_DefaultDuration, true, true); } SType *SClient::createType(string p_ID, string p_GroupName, string p_Name, uint64_t p_DefaultDelay, uint64_t p_DefaultDuration, bool p_FIxed, bool p_ProjectIndepended) { bool boolNew; SType *ptrSPITType = mProcessor.mSpitTypes.getEntry(p_ID); if (ptrSPITType == nullptr) { auto spitType = make_unique(p_ID, p_GroupName, p_Name, p_FIxed, p_ProjectIndepended, p_DefaultDelay, p_DefaultDuration); ptrSPITType = spitType.get(); mProcessor.mSpitTypes.addEntry(move(spitType)); ptrSPITType->setCreatedByClient(true); boolNew = true; } else { ptrSPITType->GroupName.set(p_GroupName); ptrSPITType->Name.set(p_Name); ptrSPITType->Fixed.set(p_FIxed); ptrSPITType->ProjectIndepended.set(p_ProjectIndepended); ptrSPITType->DefaultDelay.set(p_DefaultDelay); ptrSPITType->DefaultDuration.set(p_DefaultDuration); boolNew = false; } auto eventType = boolNew ? ObjectEvent::Created : ObjectEvent::Changed; mCallbacks.onSpitType(eventType, false, *ptrSPITType); sendTypeInternal(*ptrSPITType); return ptrSPITType; } int SClient::sendObjectInternal(SObject *ptrSPITObject) { if (ptrSPITObject == nullptr) return -1; Message::ReportObject sMReportSPITObject; ptrSPITObject->fillMessageOP(sMReportSPITObject); return sendMessage(sMReportSPITObject); } /** * 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. * CAUTION: The SPITObject must already exist. * @return true = SPITObject is changed, false = not changed, maybe obj is NULL or the SPITObject doesn't exists */ bool SClient::changeSPITObject(SObject *obj) { if (obj == nullptr) return false; SObject *ptrSPITObject; SType *ptrSPITType; { ptrSPITObject = mProcessor.mSpitObjects.getEntry(obj->getID()); if (ptrSPITObject == nullptr) return false; ptrSPITType = mProcessor.mSpitTypes.getEntry(obj->getID()); if (ptrSPITObject == nullptr) return false; ptrSPITObject->setActionAssigned(obj->getActionAssigned()); ; ptrSPITObject->Delay.set(obj->Delay.get()); ptrSPITObject->Duration.set(obj->Delay.get()); ptrSPITObject->Fixed.set(obj->Fixed.get()); ptrSPITObject->FrameFadeInLength.set(obj->FrameFadeInLength.get()); ptrSPITObject->FrameFadeOutLength.set(obj->FrameFadeOutLength.get()); ptrSPITObject->FrameLength.set(obj->FrameLength.get()); ptrSPITObject->FrameStart.set(obj->FrameStart.get()); ptrSPITObject->Name.set(obj->Name.get()); ptrSPITObject->Remark.set(obj->Remark.get()); ptrSPITObject->SPITTypeID.set(obj->SPITTypeID.get()); ptrSPITObject->ServerParams.set(obj->ServerParams.get()); ptrSPITObject->Track.set(obj->Track.get()); ptrSPITObject->ValueFactor.set(obj->ValueFactor.get()); ptrSPITObject->ValueUse.set(obj->ValueUse.get()); mCallbacks.onSpitObject(ObjectEvent::Changed, false, *ptrSPITType, *ptrSPITObject); sendObjectInternal(ptrSPITObject); } return true; } //--------- 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 p_SPITObjectID 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 SClient::sendRemoteObject(string p_SPITObjectID, int valuePpm, char valueFlag) { SObject *ptrRemoteSPITObject = mProcessor.mRemoteSpitObjects.getEntry(p_SPITObjectID); if (ptrRemoteSPITObject == nullptr) return; Message::PlayStart playSPITStart(mID, mProjName, PLAYERTYPE_DISCRETE); playSPITStart.setPlayerIsRunning(false); playSPITStart.setTime(0); sendMessage(playSPITStart); Message::PlayObject playSPITObject; playSPITObject.setID(ptrRemoteSPITObject->getID()); playSPITObject.setPlayPrePosition(false); playSPITObject.setPlayerID(mID); playSPITObject.setValue(valuePpm); playSPITObject.setValueFlag(valueFlag); sendMessage(playSPITObject); Message::PlayEnd playSPITEnd(mID); sendMessage(playSPITEnd); } fos::Semaphore SClient::sStartSad; unordered_map> 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( 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; } } } }