#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 SProcessor::createConnectMessage(string serverIP, string serverPort, string loginName, string loginPassword) { auto msg = make_unique(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 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 data) { auto message = make_unique(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) { 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()); break; //----- SPIT Project ----- case Message::StartProjectTransfer::OPFLAG: parseStartProjectTransfer(message.getAs()); break; case Message::CloseProject::OPFLAG: parseCloseProject(message.getAs()); break; case Message::RequestProjectTransfer::OPFLAG: parseRequestProjectTransfer(message.getAs()); break; case Message::EndProjectTransfer::OPFLAG: parseEndProjectTransfer(message.getAs()); break; //----- SPIT Types ------ case Message::ReportType::OPFLAG: parseReportType(message.getAs()); break; case Message::RemoveType::OPFLAG: parseRemoveType(message.getAs()); //----- SPIT Objects ------ break; case Message::ReportObject::OPFLAG: parseReportObject(message.getAs()); break; case Message::RemoveObject::OPFLAG: parseRemoveObject(message.getAs()); break; //----- Play SPIT Objects ------ case Message::PlayStart::OPFLAG: parsePlayStart(message.getAs()); break; case Message::PlayObject::OPFLAG: parsePlayObject(message.getAs()); break; case Message::PlayEnd::OPFLAG: parsePlayEnd(message.getAs()); break; case Message::ReportPlayStatus::OPFLAG: parseReportPlayStatus(message.getAs()); 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(message.getID()); sType = spitType.get(); mSpitTypes.addEntry(move(spitType)); isNew = true; } } else { sType = mRemoteSpitTypes.getEntry(message.getID()); if (sType == nullptr) { auto spitType = make_unique(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(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(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(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