diff --git a/.DS_Store b/.DS_Store index 5008ddf..b887057 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.gitignore b/.gitignore index b699600..830fb5a 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,5 @@ Defs.h *.cpp~ *.h~ +*.zip +*.gz diff --git a/EServer.cpp b/EServer.cpp index 80b010a..af49ebe 100644 --- a/EServer.cpp +++ b/EServer.cpp @@ -9,6 +9,7 @@ #include "HTTPSP.h" //#include //#include +#include #include "EServer.h" #include "Defs.h" #include "Oled.h" @@ -257,6 +258,7 @@ bool EmlaServer::WPost(const String url, const String request, String & payload, return result; } +/* // ------------------------------------------------------------------------ bool EmlaServer::WDelete(const String url, const String request, String & payload) { @@ -339,6 +341,7 @@ bool EmlaServer::WDelete(const String url, const String request, String & payloa } return result; } +*/ // ------------------------------------------------------------------------ @@ -368,4 +371,12 @@ String EmlaServer::UrlEncode(const char *src) return String(d); } + +// ------------------------------------------------------------------------ +bool ePing(const char *hostname) +{ + bool success = Ping.ping("192.168.1.67", 10); + return success; +} + // diff --git a/EServer.h b/EServer.h index ad687b4..2451575 100644 --- a/EServer.h +++ b/EServer.h @@ -12,6 +12,9 @@ #include "Oled.h" +bool ePing(const char *hostname); + + class EmlaServer { private: @@ -34,7 +37,7 @@ class EmlaServer void SetCookieFromHeader(String rawCookie); bool WGet(const String url, String & payload); bool WPost(const String url, const String request, String & payload, bool getSessionCookie = false); - bool WDelete(const String url, const String request, String & payload); +// bool WDelete(const String url, const String request, String & payload); String UrlEncode(const char *src); }; diff --git a/Main.cpp b/Main.cpp index e78f25e..4d3e343 100644 --- a/Main.cpp +++ b/Main.cpp @@ -70,6 +70,14 @@ void setup() // Pins pinMode(SHOCK_PIN, OUTPUT); + pinMode(SHOCK1_PIN, OUTPUT); + digitalWrite(SHOCK1_PIN, HIGH); + pinMode(SHOCK2_PIN, OUTPUT); + digitalWrite(SHOCK2_PIN, HIGH); + pinMode(SHOCK3_PIN, OUTPUT); + digitalWrite(SHOCK3_PIN, HIGH); + pinMode(SHOCK4_PIN, OUTPUT); + digitalWrite(SHOCK4_PIN, HIGH); // pin controlling the cover state pinMode(COVER_OPEN_PIN, INPUT_PULLUP); @@ -142,7 +150,6 @@ void setup() tasklist.Init(); verification.Init(); message.Init(); - session.InfoChastikey(); session.SetTimeOfLast5sInterval(timeFunc.GetTimeInSeconds()); // session.SetTimeOfLast5sInterval(timeFunc.GetTimeInSeconds()); @@ -164,7 +171,7 @@ void loop() { oledDisplay.PrintDisplay(); message.MessageCoverStateChange(); - message.WriteCommandsAndSettings(); + message.WriteCommandsAndSettings("loop() coverStateChanged"); oldCoverState = coverState; } @@ -185,6 +192,25 @@ void loop() // every 1 minute if (timeFunc.GetTimeInSeconds() >= (session.GetTimeOfLast1minInterval() + 60)) { + if (ePing("")) + { + // Wearer is at home + if (session.GetAwayCounter() > AWAY_LIMIT) + { + message.SendMessage("Wearer returned home.", GROUP_CHAT_ID); + message.WriteCommandsAndSettings("loop() Wearer returned home"); + } + session.SetAwayCounter(0); + } + else + { + // Wearer cannot be reached + if (session.GetAwayCounter() == AWAY_LIMIT) + message.SendMessage("Wearer is away from home since " + String(AWAY_LIMIT, DEC) + " minutes.", GROUP_CHAT_ID); + if (session.GetAwayCounter() == (AWAY_LIMIT + 1)) + message.WriteCommandsAndSettings("loop() Wearer is away"); + session.SetAwayCounter(session.GetAwayCounter() + 1); + } verification.ProcessVerification(GROUP_CHAT_ID); timeFunc.ProcessSleepTime(); // this method calls tasklist.ProcessTasks() in the morning and evening @@ -202,9 +228,13 @@ void loop() { session.SetEmergencyReleaseCounter(session.GetEmergencyReleaseCounter() + 1); if (session.GetEmergencyReleaseCounter() < 2) - message.SendMessageAll("The first emergency release request has been accepted, but it needs to be raised again for confirmation."); + message.SendMessageAll("The first emergency release request has been accepted, but it needs to be raised again for confirmation. If the second request is raised successfully, the safe will be unlocked and the holder role will be removed."); else + { message.UnlockAction(users.GetWearer()->GetId(), GROUP_CHAT_ID, FORCE); + message.GuestAction(users.GetHolder()->GetId(), GROUP_CHAT_ID, FORCE); + session.SetEmergencyReleaseCounterRequest(false); + } } else { diff --git a/Message.cpp b/Message.cpp index f33d175..6afd291 100644 --- a/Message.cpp +++ b/Message.cpp @@ -32,15 +32,15 @@ UniversalTelegramBot bot(BOT_TOKEN, clientsec); // Checks for new messages every 1 second. -#define BOT_COMMANDS_GENERAL "/start - Start communication\n/state - Report the cover state\n/roles - List roles\n/users - List users in chat\n/play_6 - Throw dice (any number of possibilities with corresponding number)\n" -#define BOT_COMMANDS_SHOCKS "/shock_1 - Shock for 1 second (other intervals work with corresponding numbers)\n/shock_5 - Shock for 5 seconds (other intervals work with corresponding numbers)\n/shock_10 - Shock for 10 seconds (other intervals work with corresponding numbers)\n/shock_30 - Shock for 30 seconds (other intervals work with corresponding numbers)\n" -#define BOT_COMMANDS_RANDOM "/random_5 - Switch on random shock mode with 5 shocks per hour (other intervals work with corresponding numbers)\n/random_off - Switch off random shock mode\n" +#define BOT_COMMANDS_GENERAL "/start - Start communication\n/menu - Activate the button menu\n/state - Report the session state\n/roles - List and select roles\n/users - List users in chat\n/play_6 - Throw dice (any number of possibilities with corresponding number)\n" +#define BOT_COMMANDS_SHOCKS "/shock - Electro shock\n" +#define BOT_COMMANDS_RANDOM "/random - Random shock mode\n" #define BOT_COMMANDS_TEASING "/teasing_on - Enable teasing\n/teasing_off - Disable teasing\n/verify_1 - Enable verification mode with 1 check-in per day\n/verify_2 - Enable verification mode with 2 check-ins per day\n/verify_off - Disable verification mode\n" #define BOT_COMMANDS_TASKS "" #define BOT_COMMANDS_UNLOCK "/unlock - Unlock key safe\n/play4unlock - Throw dice to determine if unlocking should be possible\n" #define BOT_COMMANDS_ROLES "/holder - Adopt holder role\n/teaser - Adopt teaser role\n/guest - Adopt guest role\n" -#define BOT_COMMANDS_WAITING "/waiting - Make wearer waiting to be captured by the holder\n/free - Make wearer free again (stops waiting for capture)\n" -#define BOT_COMMANDS_CAPTURE "/capture - Capture wearer as a sub\n/release - Release wearer as a sub\n" +#define BOT_COMMANDS_WAITING "" +#define BOT_COMMANDS_CAPTURE "" #define BOT_COMMANDS_EMERGENCY "/thisisanemergency - Release the wearer in case of an emergency\n" String botCommandsAll = BOT_COMMANDS_GENERAL BOT_COMMANDS_SHOCKS BOT_COMMANDS_RANDOM BOT_COMMANDS_TEASING BOT_COMMANDS_UNLOCK BOT_COMMANDS_ROLES BOT_COMMANDS_WAITING BOT_COMMANDS_CAPTURE ; @@ -72,9 +72,9 @@ String botCommandsAllHelp = BOT_COMMANDS_GENERAL BOT_COMMANDS_SHOCKS BOT_COMMAND */ -#define COMMON_MSG_WAITING " The holder can capture him with the /capture command." -#define COMMON_MSG_CAPTURE " He is now securely locked and lost his permissions to open the key safe. The key safe can only be operated by the holder using the /unlock command.\nFurthermore, the wearer can now be punished with shocks." -#define COMMON_MSG_TEASING_ON "Teasing mode is activated. Users with teaser role can now support the holder with treatments like /shock_5." +#define COMMON_MSG_WAITING " The holder can capture him with the /holder command." +#define COMMON_MSG_CAPTURE " He is now securely locked and lost his permissions to open the key safe. The key safe can only be operated by the holder using the /unlock command.\nFurthermore, the wearer can now be treated with shocks." +#define COMMON_MSG_TEASING_ON "Teasing mode is activated. Users with teaser role can now support the holder with shock treatments." #define COMMON_MSG_TEASING_OFF "Teasing mode is switched off. Only the holder has the permission to treat the wearer with shocks." @@ -88,8 +88,10 @@ Message::Message() void Message::Init() { Serial.println("*** Message::Init()"); + SendMessage("*** Message::Init(), free heap: " + String(ESP.getFreeHeap(), DEC), WEARER_CHAT_ID); // UserSet::Init() must have already be run! // populate values with defaults... + session.SetTimeOfLastShock(timeFunc.GetTimeInSeconds()); session.SetTimeOfLastUnlock(timeFunc.GetTimeInSeconds()); session.SetTimeOfLastOpening(timeFunc.GetTimeInSeconds()); @@ -103,6 +105,8 @@ void Message::Init() users.GetUser(index)->SetBot(true); AdoptSettings(); + + SendMessage("*** Message::Init() completed", WEARER_CHAT_ID); } @@ -119,6 +123,11 @@ void Message::MessageWearerState(String chatId) SendMessage(SYMBOL_FINGER_UP " Wearer is available to be captured by a holder with command: /holder!!!", chatId); else SendMessage("Wearer is free.", chatId); + + if (session.GetAwayCounter() == 0) + SendMessage("Wearer is currently at home.", chatId); + else + SendMessage("Wearer has been last seen at home " + String(session.GetAwayCounter(), DEC) + " minutes ago.", chatId); } } @@ -150,13 +159,13 @@ void Message::MessageCoverStateChange(String chatId) if (coverState == COVER_CLOSED) { session.SetTimeOfLastClosing(timeFunc.GetTimeInSeconds()); - WriteCommandsAndSettings(); + WriteCommandsAndSettings("Message-MessageCoverStateChange(COVER_CLOSED)"); SendMessage(SYMBOL_LOCK_CLOSED " Key safe has just been safely closed.", chatId); } else { session.SetTimeOfLastOpening(timeFunc.GetTimeInSeconds()); - WriteCommandsAndSettings(); + WriteCommandsAndSettings("Message-MessageCoverStateChange(COVER_OPENED)"); SendMessage(SYMBOL_LOCK_OPEN " Key safe has just been opened.", chatId); } } @@ -174,23 +183,32 @@ void Message::MessageCoverState(String chatId) // ------------------------------------------------------------------------ -void Message::MessageSendEarnedCredits(int creditsEarned, String chatId) -{ +/* + void Message::MessageSendEarnedCredits(int creditsEarned, String chatId) + { SendMessage(String(SYMBOL_CREDIT) + " Wearer " + users.GetWearer()->GetName() + " has earned " + creditsEarned + " credits. His new credit balance is " + session.GetCredits() + ".", chatId); -} + } -// ------------------------------------------------------------------------ -void Message::MessageSendEarnedVouchers(int vouchersEarned, String chatId) -{ + // ------------------------------------------------------------------------ + void Message::MessageSendEarnedVouchers(int vouchersEarned, String chatId) + { SendMessage(String(SYMBOL_VOUCHER) + " Wearer " + users.GetWearer()->GetName() + " has earned " + vouchersEarned + " unlock vouchers. His new voucher balance is " + session.GetVouchers() + ".", chatId); -} + } +*/ // ------------------------------------------------------------------------ void Message::MessageModes(String chatId) { String msg; + unsigned long lockTimerRemaining = session.GetLockTimerRemaining(); + if (lockTimerRemaining > 0) + msg = SYMBOL_CLOCK_3 " Lock timer is set to run for " + timeFunc.Time2String(lockTimerRemaining, IS_DURATION) + ". The key safe cannot be opened before " + timeFunc.GetTimeString(WITH_DATE, session.GetLockTimerEnd()); + else + msg = "Lock timer is inactive."; + SendMessage(msg, chatId); + if (session.IsTeasingMode()) msg = COMMON_MSG_TEASING_ON; else @@ -198,8 +216,8 @@ void Message::MessageModes(String chatId) SendMessage(msg, chatId); if (session.IsRandomMode()) - msg = SYMBOL_DEVIL_SMILE " Random mode is activated. The wearer receives " + String(session.GetRandomModeShocksPerHour(), DEC) + - " random shocks per hour since " + timeFunc.Time2String(timeFunc.GetTimeInSeconds() - session.GetTimeOfRandomModeStart()) + + msg = SYMBOL_DEVIL_SMILE " Random mode is activated. The wearer receives " + String(session.GetRandomModeShocksPerHour(), DEC) + + " random shocks per hour since " + timeFunc.Time2String(timeFunc.GetTimeInSeconds() - session.GetTimeOfRandomModeStart()) + ". The random mode will be automatically deactivated after " + timeFunc.Time2String(RANDOM_SHOCK_AUTO_OFF_SECONDS - timeFunc.GetTimeInSeconds() + session.GetTimeOfRandomModeStart()) + "."; else msg = "Random mode is switched off."; @@ -207,22 +225,22 @@ void Message::MessageModes(String chatId) if (verification.IsEnabled()) { - msg = SYMBOL_WATCHING_EYES " Verification mode is activated. The wearer must send " + String(verification.GetRequiredCountPerDay(), DEC) + + msg = SYMBOL_WATCHING_EYES " Verification mode is activated. The wearer must send " + String(verification.GetRequiredCountPerDay(), DEC) + " verification photos per day as requested. He has provided " + String(verification.GetActualToday(), DEC) + " photos today."; } else msg = "Verification mode is switched off."; SendMessage(msg, chatId); - SendMessage(SYMBOL_CREDIT " Wearer has collected " + String(session.GetCredits(), DEC) + " credits from shock treatments.", chatId); - if (session.GetVouchers() > 0) - SendMessage(SYMBOL_VOUCHER " Wearer has collected " + String(session.GetVouchers(), DEC) + " unlock vouchers.", chatId); + // SendMessage(SYMBOL_CREDIT " Wearer has collected " + String(session.GetCredits(), DEC) + " credits from shock treatments.", chatId); + // if (session.GetVouchers() > 0) + // SendMessage(SYMBOL_VOUCHER " Wearer has collected " + String(session.GetVouchers(), DEC) + " unlock vouchers.", chatId); if (session.GetDeviations() > 0) SendMessage(SYMBOL_SMILE_OHOH " Wearer has missed expectations " + String(session.GetDeviations(), DEC) + " times.", chatId); if (session.GetFailures() > 0) SendMessage(SYMBOL_FORBIDDEN " Wearer has failed " + String(session.GetFailures(), DEC) + " times to complete his tasks.", chatId); - if (users.GetWearer()->IsSleeping()) + if (users.GetWearer() && users.GetWearer()->IsSleeping()) SendMessage(SYMBOL_SLEEPING " Wearer is currently allowed to sleep and shocks are disabled during that time.", chatId); } @@ -238,7 +256,7 @@ void Message::MessageUsers(String chatId) String name = users.GetUser(i)->GetName(); Serial.println(String("\"") + name + String("\"")); if (name.length() > 0) - msg += "- " + name + " (" + users.GetUser(i)->GetRoleStr() + ")\n"; + msg += "- " + name + " (" + users.GetUser(i)->GetRoleStr() + ") [" + timeFunc.GetTimeString(NO_DATE, timeFunc.GetTimeInSeconds() - users.GetUser(i)->GetLastMessageTime(), IS_RELATIVE) + "]\n"; ++i; Serial.println(msg); } @@ -269,26 +287,17 @@ void Message::MessageRoles(String chatId) // ------------------------------------------------------------------------ -void Message::MessageChastikeyState(String chatId) +void Message::MessageState(String chatId) { String msg; - if (session.IsActiveChastikeySession()) - msg = "Wearer " + users.GetWearer()->GetName() + " is locked in a ChastiKey session and controlled by holder " + session.GetChastikeyHolder() + ". The key safe remains securely locked as long as this session is active."; -// else -// msg = "Wearer " + users.GetWearer()->GetName() + " is not locked in a ChastiKey session."; - SendMessage(msg, chatId); -} - -// ------------------------------------------------------------------------ -void Message::MessageState(String chatId) -{ + Serial.println("*** Message::MessageState()"); + SendMessage("*** Message::MessageState(), free heap: " + String(ESP.getFreeHeap(), DEC), WEARER_CHAT_ID); MessageWearerState(chatId); MessageLastShock(chatId); MessageCoverState(chatId); MessageModes(chatId); MessageUsers(chatId); - MessageChastikeyState(chatId); } @@ -303,6 +312,7 @@ void Message::MessageTasks(String chatId) // ------------------------------------------------------------------------ void Message::ShockAction(String durationStr, int count, String fromId, String chatId) { + Serial.println("*** Message::ShockAction(String)"); if (timeFunc.IsSleepingTime()) { SendMessage("Wearer " + users.GetWearer()->GetName() + " is allowed to sleep and cannot be shocked now.", chatId); @@ -311,9 +321,9 @@ void Message::ShockAction(String durationStr, int count, String fromId, String c { unsigned long milliseconds; if (durationStr.length() > 0) - milliseconds = durationStr.toInt() * 1000L; + milliseconds = durationStr.toInt() * 1000UL; else - milliseconds = 3000; + milliseconds = 3000UL; ShockAction(milliseconds, count, fromId, chatId); } @@ -323,38 +333,40 @@ void Message::ShockAction(String durationStr, int count, String fromId, String c //------------------------------------------------------------------------ void Message::ShockAction(unsigned long milliseconds, int count, String fromId, String chatId) { + Serial.println("*** Message::ShockAction(int)"); User *u = users.GetUserFromId(fromId); String msg; - if (u) + if (users.MayShock(fromId)) { - if (u->IsHolder() || - u->IsBot() || - (u->IsTeaser() && session.IsTeasingMode()) || - (u->IsWearer() && session.IsTeasingMode())) - { - String msg = SYMBOL_COLLISION SYMBOL_COLLISION SYMBOL_COLLISION " Shock processing for " + String((milliseconds / 1000L), DEC) + " s begins..."; - SendMessage(msg, chatId); - SendMessage(msg, USER_ID_WEARER); - session.SetTimeOfLastShock(timeFunc.GetTimeInSeconds()); - session.SetCreditFractions(session.GetCreditFractions() + (milliseconds / 1000), chatId); - WriteCommandsAndSettings(); - - session.Shock(count, milliseconds); - - msg = "Shock processing completed. :-)"; + String msg1 = SYMBOL_COLLISION SYMBOL_COLLISION SYMBOL_COLLISION " Shock processing for " + String((milliseconds / 1000UL), DEC) + " s begins..."; + String msg2 = SYMBOL_COLLISION SYMBOL_COLLISION SYMBOL_COLLISION " Shock processing begins..."; + if (chatId != GROUP_CHAT_ID) + SendMessage(msg1, chatId); + // SendMessage(msg1, USER_ID_WEARER); + SendMessage(msg2, GROUP_CHAT_ID); + session.SetTimeOfLastShock(timeFunc.GetTimeInSeconds()); + // session.SetCreditFractions(session.GetCreditFractions() + (milliseconds / 1000), chatId); + // WriteCommandsAndSettings("Message-ShockAction() begin"); + + session.Shock(count, milliseconds); + + msg = "Shock processing completed. " SYMBOL_DEVIL_SMILE; + if (chatId != GROUP_CHAT_ID) SendMessage(msg, chatId); - SendMessage(msg, USER_ID_BOT); - WriteCommandsAndSettings(); - } +// SendMessage(msg, USER_ID_BOT); + SendMessage(msg, GROUP_CHAT_ID); + WriteCommandsAndSettings("Message-ShockAction() end"); + } + else + { + String msg = SYMBOL_NO_ENTRY " Request denied. Only the holder"; + if (session.IsTeasingMode()) + msg += " and the teasers are"; else - { - String msg = SYMBOL_NO_ENTRY " Request denied. Only the holder"; - if (session.IsTeasingMode()) - msg += " and the teasers"; - msg += " are allowed to execute shock punishments."; - SendMessage(msg, chatId); - } + msg += " is"; + msg += " currently allowed to execute shock treatments."; + SendMessage(msg, chatId); } } @@ -372,9 +384,15 @@ void Message::WaitingAction(String fromId, String chatId) if (u->IsFreeWearer()) { users.GetWearer()->UpdateRoleId(ROLE_WEARER_WAITING); - msg = SYMBOL_WATCHING_EYES " Wearer " + users.GetWearerName() + " is now exposed and waiting to be captured by the holder." COMMON_MSG_WAITING; - SendMessage(msg, chatId); - WriteCommandsAndSettings(); + // if there is a holder already, directly proceed to capturing the wearer + if (users.GetHolder()) + CaptureAction(fromId, chatId); + else + { + msg = SYMBOL_WATCHING_EYES " Wearer " + users.GetWearerName() + " is now exposed and waiting to be captured by the holder." COMMON_MSG_WAITING; + SendMessage(msg, chatId); + WriteCommandsAndSettings("Message-WaitingAction()"); + } } // does request comes from waiting wearer? else if (u->IsWaitingWearer()) @@ -402,12 +420,15 @@ void Message::FreeAction(String fromId, String chatId) String msg; // does request comes from waiting wearer? + if (! users.GetUserFromId(fromId)) + return; + if (users.GetUserFromId(fromId)->IsWaitingWearer()) { users.GetWearer()->UpdateRoleId(ROLE_WEARER_FREE); msg = "Wearer " + users.GetWearerName() + " is now free and cannot be captured by the holder."; SendMessage(msg, chatId); - WriteCommandsAndSettings(); + WriteCommandsAndSettings("Message-FreeAction()"); } // does request comes from free wearer? else if (users.GetUserFromId(fromId)->IsFreeWearer()) @@ -433,8 +454,12 @@ void Message::FreeAction(String fromId, String chatId) void Message::CaptureAction(String fromId, String chatId) { String msg; + // - requesting user is holder or + // - requesting user is not holder, but may become one or + // - requestung user is wearer and there is a holder if (users.GetUserFromId(fromId)->IsHolder() || - (! users.GetHolder() && users.GetUserFromId(fromId)->MayBecomeHolder())) + (! users.GetHolder() && users.GetUserFromId(fromId)->MayBecomeHolder()) || + (users.GetUserFromId(fromId)->IsWearer() && users.GetHolder())) { // request is from holder // or from other user when there is no holder present yet @@ -448,7 +473,7 @@ void Message::CaptureAction(String fromId, String chatId) msg += " User " + users.GetUserFromId(fromId)->GetName() + " is now holder."; users.GetUserFromId(fromId)->SetHolder(); } - WriteCommandsAndSettings(); + WriteCommandsAndSettings("Message-CaptureAction()"); } else if (users.GetWearer()->IsCapturedWearer()) { @@ -481,7 +506,7 @@ void Message::ReleaseAction(String fromId, String chatId) { users.GetWearer()->UpdateRoleId(ROLE_WEARER_WAITING); msg = SYMBOL_LOCK_OPEN " Releasing wearer " + users.GetWearerName() + ". He is now released and received back his permissions to open the key safe.\n"; - WriteCommandsAndSettings(); + WriteCommandsAndSettings("Message-ReleaseAction()"); } else { @@ -499,6 +524,74 @@ void Message::ReleaseAction(String fromId, String chatId) } +// ------------------------------------------------------------------------ +void Message::LockTimerAction(String durationStr, String fromId, String chatId) +{ + String msg; + unsigned long timerChange = 0; + bool isSubstract = (durationStr[0] == '-'); + + Serial.println("*** Message::LockTimerAction(String)"); + Serial.println(" - durationStr: " + durationStr); + durationStr = durationStr.substring(1); + const char timerUnit = durationStr[durationStr.length() - 1]; + durationStr = durationStr.substring(0, durationStr.length() - 1); + switch (timerUnit) + { + case 'm': + Serial.println(" - minutes"); + timerChange = durationStr.toInt() * 60; + break; + case 'h': + Serial.println(" - hours"); + timerChange = durationStr.toInt() * 3600; + break; + case 'd': + Serial.println(" - days"); + timerChange = durationStr.toInt() * 86400; + break; + default: + timerChange = durationStr.toInt(); + break; + } + + if (isSubstract) + { + Serial.println(" - timerChange: sub " + timerChange); + // For testing purposes + // if ((users.GetUserFromId(fromId)->IsHolder()) || + // (users.GetUserFromId(fromId)->IsTeaser())) + if (users.GetUserFromId(fromId)->IsHolder()) + { + // request is from holder + session.SubLockTimerEnd(timerChange); + msg = SYMBOL_CLOCK_3 " Subtracting " + timeFunc.Time2String(timerChange, IS_DURATION) + " from the lock timer. "; + if (session.IsLockTimerActive()) + msg += "New total lock-down time is " + timeFunc.Time2String(session.GetLockTimerRemaining(), IS_DURATION) + ". " + "The key safe cannot be opened before " + timeFunc.GetTimeString(WITH_DATE, session.GetLockTimerEnd()) + ".\n"; + else + msg += "Lock-down timer is now cleared. " + "The key safe may be unlocked.\n"; + } + else + { + msg = "You have no permission to reduce the lock timer. Only the holder has permission to do this."; + } + } + else + { + Serial.println(" - timerChange: add " + timerChange); + if (session.AddLockTimerEnd(timerChange)) + SendMessage(SYMBOL_WARNING " The maximum for the lock-down timer is reached. The timer is limited to 7 days.\n", chatId); + msg = SYMBOL_CLOCK_3 " Adding " + timeFunc.Time2String(timerChange, IS_DURATION) + " to the lock timer. " + "New total lock-down time is " + timeFunc.Time2String(session.GetLockTimerRemaining(), IS_DURATION) + ". " + "The key safe cannot be unlocked before " + timeFunc.GetTimeString(WITH_DATE, session.GetLockTimerEnd()) + ".\n"; + } + SendMessage(msg, chatId); + WriteCommandsAndSettings("Message-LockTimerAction()"); +} + + // ------------------------------------------------------------------------ void Message::UnlockAction(String fromId, String chatId, bool force) { @@ -510,29 +603,28 @@ void Message::UnlockAction(String fromId, String chatId, bool force) Serial.print("- holder: "); Serial.println(users.GetUserFromId(fromId)->IsHolder()); - // from wearer & wearer is free - // from holder - session.InfoChastikey(); - - if (session.IsActiveChastikeySession() && ! force) - { - SendMessage(SYMBOL_NO_ENTRY " Unlock request is denied! Wearer is locked in an active ChastiKey session.", chatId); - } - else if (users.GetUserFromId(fromId)->MayUnlock() || force) + if (users.GetUserFromId(fromId)->MayUnlock() || force) { if (force) SendMessageAll("An emergency release request has been granted.", chatId); - SendMessageAll(SYMBOL_LOCK_OPEN " Key safe is unlocking now for 4 seconds and may be opened during this period.", chatId); - session.Unlock(); - // check state of the safe afterwards - coverState = digitalRead(COVER_OPEN_PIN); - if (coverState == COVER_OPEN) + if (session.IsLockTimerActive()) { - SendMessageAll(SYMBOL_KEY " Key safe has been opened.", chatId); - WriteCommandsAndSettings(); + SendMessage(SYMBOL_NO_ENTRY " The lock timer is active! No unlocking possible (the holder may reduce the lock timer to enable unlocking).", chatId); } else - SendMessageAll(SYMBOL_LOCK_CLOSED " Key safe is locked again and has not been opened.", chatId); + { + SendMessageAll(SYMBOL_LOCK_OPEN " Key safe is unlocking now for 4 seconds and may be opened during this period.", chatId); + session.Unlock(); + // check state of the safe afterwards + coverState = digitalRead(COVER_OPEN_PIN); + if (coverState == COVER_OPEN) + { + SendMessageAll(SYMBOL_KEY " Key safe has been opened.", chatId); + WriteCommandsAndSettings("Message-UnlockAction()"); + } + else + SendMessageAll(SYMBOL_LOCK_CLOSED " Key safe is locked again and has not been opened.", chatId); + } } else { @@ -541,9 +633,10 @@ void Message::UnlockAction(String fromId, String chatId, bool force) } -// ------------------------------------------------------------------------ -void Message::PlayAction(String max, String fromId, String chatId) -{ +/* + // ------------------------------------------------------------------------ + void Message::PlayAction(String max, String fromId, String chatId) + { int maxCount = max.toInt(); if (maxCount > 100) maxCount = 100; @@ -562,14 +655,15 @@ void Message::PlayAction(String max, String fromId, String chatId) while (i <= result) { progress += "."; - EditMessage(num[i / 10] + num[i % 10] + " " + progress, String(lastMessageId, DEC), chatId); + EditMessage(num[i / 10] + num[i % 10] + " " + progress, chatId, lastMessageId); if (maxCount < 10) i++; else i = i + maxCount/10; } - EditMessage(num[result / 10] + num[result % 10] + " " + SYMBOL_STAR_GLOWING, String(lastMessageId, DEC), chatId); -} + EditMessage(num[result / 10] + num[result % 10] + " " + SYMBOL_STAR_GLOWING, chatId, lastMessageId); + } +*/ // ------------------------------------------------------------------------ @@ -578,9 +672,9 @@ void Message::Play4UnlockAction(String fromId, String chatId) String num[10] = { SYMBOL_DIGIT0, SYMBOL_DIGIT1, SYMBOL_DIGIT2, SYMBOL_DIGIT3, SYMBOL_DIGIT4, SYMBOL_DIGIT5, SYMBOL_DIGIT6, SYMBOL_DIGIT7, SYMBOL_DIGIT8, SYMBOL_DIGIT9 }; Serial.println("*** Message::Play4UnlockAction()"); unsigned long duration = timeFunc.GetTimeInSeconds() - session.GetTimeOfLastClosing(); - int winPoints = session.GetCredits() + timeFunc.GetNumberOfDays(duration); + int winPoints = /*session.GetCredits() + */ timeFunc.GetNumberOfDays(duration); SendMessage(String(SYMBOL_DICE) + " Playing for unlock. Wearer wins for values smaller than: " + num[winPoints / 10] + num[winPoints % 10] + - " (" + timeFunc.GetNumberOfDays(duration) + " days locked + " + session.GetCredits() + " credits)", chatId); + " (" + timeFunc.GetNumberOfDays(duration) + " days locked" + /*" + " + session.GetCredits() + " credits"*/ ")", chatId); SendMessage(SYMBOL_DIGIT0 SYMBOL_DIGIT0 " " SYMBOL_DRUM, chatId); delay(300); int lastMessageId = GetLastSentMessageId(); @@ -591,10 +685,10 @@ void Message::Play4UnlockAction(String fromId, String chatId) while (i < result) { progress += "."; - EditMessage(num[i / 10] + num[i % 10] + " " + progress, String(lastMessageId, DEC), chatId); + EditMessage(num[i / 10] + num[i % 10] + " " + progress, chatId, lastMessageId); i = i + 5; } - EditMessage(num[result / 10] + num[result % 10] + " " + SYMBOL_STAR_GLOWING, String(lastMessageId, DEC), chatId); + EditMessage(num[result / 10] + num[result % 10] + " " + SYMBOL_STAR_GLOWING, chatId, lastMessageId); if (result <= winPoints) SendMessage(SYMBOL_LOCK_OPEN " Wearer has won! " SYMBOL_SMILY_SMILE SYMBOL_SMILY_SMILE SYMBOL_SMILY_SMILE, chatId); else @@ -610,11 +704,18 @@ void Message::HolderAction(String fromId, String chatId) if (u && u->MayBecomeHolder()) { - u->UpdateRoleId(ROLE_HOLDER); - msg = SYMBOL_QUEEN " User " + users.GetUserFromId(fromId)->GetName() + " has now holder permissions."; - msg += "The holder can use the following commands:\n" + botCommandsHolder; - SendMessage(msg, chatId); - WriteCommandsAndSettings(); + if (! session.IsLockTimerActive()) + { + u->UpdateRoleId(ROLE_HOLDER); + msg = SYMBOL_QUEEN " User " + users.GetUserFromId(fromId)->GetName() + " has now holder permissions."; + msg += "The holder can use the following commands:\n" + botCommandsHolder; + SendMessage(msg, chatId); + WriteCommandsAndSettings("Message-HolderAction()"); + } + else + { + SendMessage(SYMBOL_NO_ENTRY " The lock timer is active! No one may become holder until the lock timer has expired.", chatId); + } } else { @@ -648,7 +749,7 @@ void Message::TeaserAction(String fromId, String chatId) } u->UpdateRoleId(ROLE_TEASER); SendMessage(msg, chatId); - WriteCommandsAndSettings(); + WriteCommandsAndSettings("Message-TeaserAction()"); } else { @@ -662,14 +763,17 @@ void Message::TeaserAction(String fromId, String chatId) void Message::TeasingModeAction(bool mode, String fromId, String chatId) { Serial.println("*** Message::TeasingModeAction()"); - if (users.GetHolder() && (users.GetHolder()->GetId() == fromId)) + // Only the holder controls the teasing mode unless + // the wearer is free + if ((users.GetHolder() && (users.GetHolder()->GetId() == fromId)) || + (users.GetWearer()->IsFreeWearer())) { session.SetTeasingMode(mode); if (mode) SendMessage(COMMON_MSG_TEASING_ON, chatId); else SendMessage(COMMON_MSG_TEASING_OFF, chatId); - WriteCommandsAndSettings(); + WriteCommandsAndSettings("Message-TeasingModeAction()"); } else { @@ -679,7 +783,7 @@ void Message::TeasingModeAction(bool mode, String fromId, String chatId) // ------------------------------------------------------------------------ -void Message::GuestAction(String fromId, String chatId) +void Message::GuestAction(String fromId, String chatId, bool force) { User *u = users.GetUserFromId(fromId); String msg; @@ -687,21 +791,24 @@ void Message::GuestAction(String fromId, String chatId) if (u) { msg = "User " + users.GetUserFromId(fromId)->GetName() + " has now guest permissions."; + if (force) + msg += " This is an enforced change. "; if (u->IsHolder()) { users.GetWearer()->UpdateRoleId(ROLE_WEARER_WAITING); - msg += " Since user " + users.GetUserFromId(fromId)->GetName() + " has given up the holder role, the wearer " + users.GetWearer()->GetName() + " is no longer captured. He is now released and received back his permissions to open the key safe."; + msg += " Since user " + users.GetUserFromId(fromId)->GetName() + " has no longer the holder role, the wearer " + users.GetWearer()->GetName() + " is no longer captured. He is now released and received back his permissions to open the key safe."; } u->UpdateRoleId(ROLE_GUEST); SendMessage(msg, chatId); - WriteCommandsAndSettings(); + WriteCommandsAndSettings("Message-GuestAction()"); } } -// ------------------------------------------------------------------------ -void Message::RestrictUserAction(String fromId, String chatId) -{ +/* + // ------------------------------------------------------------------------ + void Message::RestrictUserAction(String fromId, String chatId) + { User *u = users.GetUserFromId(fromId); String msg; @@ -721,7 +828,8 @@ void Message::RestrictUserAction(String fromId, String chatId) } SendMessage(msg, chatId); } -} + } +*/ // ------------------------------------------------------------------------ @@ -736,7 +844,7 @@ void Message::VerificationModeAction(String commandParameter, String fromId, Str int verificationsPerDay = commandParameter.toInt(); if (u->IsWearer() && (verificationsPerDay >= minimumVerificationCount)) wearerMayUseCommand = true; - + if (u) { if (force || @@ -760,7 +868,7 @@ void Message::VerificationModeAction(String commandParameter, String fromId, Str verification.SetVerificationMode(true, verificationsPerDay); SendMessage(msg, chatId); } - WriteCommandsAndSettings(); + WriteCommandsAndSettings("Message-VerificationModeAction()"); } else { @@ -795,10 +903,11 @@ void Message::RandomShockModeAction(String commandParameter, String fromId, Stri { if (commandParameter == "off") { - int creditsEarned = session.SetRandomMode(false); + // int creditsEarned = session.SetRandomMode(false); SendMessage("Switching off random mode.", chatId); - MessageSendEarnedCredits(creditsEarned, chatId); - WriteCommandsAndSettings(); + session.SetRandomMode(false, 0); + // MessageSendEarnedCredits(creditsEarned, chatId); + WriteCommandsAndSettings("Message-RandomShockModeAction(off)"); } else { @@ -809,12 +918,12 @@ void Message::RandomShockModeAction(String commandParameter, String fromId, Stri msg += "Maximum setting of random shocks per hour reached."; shocksPerHour = RANDOM_SHOCKS_PER_HOUR_MAX; } - msg += SYMBOL_COLLISION " Switching on random punishment mode with " + commandParameter + " shocks per hour on average. " - "This punishment remains active until it is switched off by a privileged user, " + String(RANDOM_SHOCK_AUTO_OFF_SECONDS/3600, DEC) + + msg += SYMBOL_COLLISION " Switching on random treatment mode with " + commandParameter + " shocks per hour on average. " + "This treatment remains active until it is switched off by a privileged user, " + String(RANDOM_SHOCK_AUTO_OFF_SECONDS / 3600, DEC) + " hours have passed or sleeping time of the wearer " + users.GetWearer()->GetName() + " begins."; session.SetRandomMode(true, shocksPerHour); SendMessage(msg, chatId); - WriteCommandsAndSettings(); + WriteCommandsAndSettings("Message-RandomShockModeAction(on)"); } else { @@ -930,37 +1039,53 @@ void Message::ProcessNewMessages() // Chat id of the requester String chat_id = String(bot.messages[i].chat_id); - Serial.print("Chat ID: "); - Serial.println(chat_id); + // Serial.print("Chat ID: "); + // Serial.println(chat_id); // check for photo bool hasPhoto = bot.messages[i].hasPhoto; String caption = bot.messages[i].file_caption; - Serial.print("- hasPhoto: "); - Serial.println(hasPhoto); - Serial.print("- caption: "); - Serial.println(caption); - + /* Serial.print("- hasPhoto: "); + Serial.println(hasPhoto); + Serial.print("- caption: "); + Serial.println(caption); + */ // Print the received message String from_name = bot.messages[i].from_name; String from_id = bot.messages[i].from_id; - Serial.print(from_name); - Serial.print(": "); + // Serial.print(from_name); + // Serial.print(": "); String text = bot.messages[i].text; - Serial.println(text); + // Serial.println(text); if (text.substring(text.length() - 14) == "@shockcell_bot") text = text.substring(0, text.length() - 14); - Serial.println(text); + // Serial.println(text); users.AddUser(from_id, from_name); - // execute commands + // pre-process the commands + if ((text.substring(0, 1) == "/") || (text.substring(0, 1) == ".")) + { + int sPos = text.indexOf(" "); + if (sPos > -1) + text = text.substring(0, sPos); + } + // Serial.println(String("###> '") + text + "'"); + + User *userFrom = users.GetUserFromId(from_id); + if (userFrom) + { + Serial.println("- user " + userFrom->GetName() + " at " + timeFunc.GetTimeString(WITH_DATE, userFrom->GetLastMessageTime())); + userFrom->SetLastMessageTime(); + Serial.println("- user " + userFrom->GetName() + " at " + timeFunc.GetTimeString(WITH_DATE, userFrom->GetLastMessageTime()) + " (updated)"); + } + // execute public commands if (text == "/start") { String welcome = "Welcome, " + from_name + ", use the following commands to control " + users.GetWearer()->GetName() + "'s ShockCell.\n\n"; welcome += "Commands for the "; - switch (users.GetUserFromId(from_id)->GetRoleId()) + switch (userFrom->GetRoleId()) { case ROLE_WEARER_CAPTURED: case ROLE_WEARER_WAITING: @@ -979,28 +1104,59 @@ void Message::ProcessNewMessages() } SendMessage(welcome, chat_id); } - else if (text.substring(0, 6) == "/shock") + // --- MAIN MENU ----------------------------------------------------------------- + else if (text.substring(0, 5) == "/menu") { - if (text[6] == '_') - ShockAction(text.substring(7), 1, from_id, chat_id); - else - ShockAction(text.substring(6), 1, from_id, chat_id); - } - else if (text == "/state") - { - session.InfoChastikey(); - MessageState(chat_id); + String keyboardJson = "[[\"/state - Session state\", \"/roles - Select role\", \"/users - List users\"]"; + switch (userFrom->GetRoleId()) + { + case ROLE_HOLDER: + case ROLE_TEASER: + case ROLE_GUEST: + case ROLE_WEARER_CAPTURED: + case ROLE_WEARER_WAITING: + case ROLE_WEARER_FREE: + keyboardJson += ",[\"/unlock - Unlock safe\", \"/lock - Lock timer\"]"; + keyboardJson += ",[\"/shock - Send shock\", \"/random - Setup random shocks\", \"/verify - Verification settings\"]"; + // keyboardJson += ",[\"/verify - Verification settings\", \"/game - Play game\"]"; + break; + } + keyboardJson += "]"; + Serial.println(keyboardJson); + // keyboardJson = "[[\"/state - Session state\", \"/roles - Select role\", \"/users - List users\"],[\"/shock - Send shock\", \"/random - Setup random shocks\"],[\"/game - Play game\"]]"; + SendMessageWithKeybaord(chat_id, "Please select the menu option:", keyboardJson); } - else if (text == "/unlock") - UnlockAction(from_id, chat_id); - else if (text == "/play4unlock") - Play4UnlockAction(from_id, chat_id); - else if (text.substring(0, 5) == "/play") - PlayAction(text.substring(6), from_id, chat_id); - else if (text == "/users") - MessageUsers(chat_id); + else if (text == "/state") MessageState(chat_id); + // submenu /roles + else if (text == "/users") MessageUsers(chat_id); + else if (text == "/unlock") UnlockAction(from_id, chat_id); + // submenu /lock + // submenu /shock + // submenu /random + // submenu /verify + + // --- SUB MENU roles ----------------------------------------------------------------- else if (text == "/roles") + { MessageRoles(chat_id); + String keyboardJson; + switch (userFrom->GetRoleId()) + { + case ROLE_HOLDER: + case ROLE_TEASER: + case ROLE_GUEST: + keyboardJson = "[[\"/holder - Full control of the wearer\", \"/teaser - Limited control, supporting the holder\", \"/guest - No control\"]" + ",[\"." SYMBOL_LOCK_KEY " - Take keys from wearer\", \"." SYMBOL_KEY " - Return keys to wearer\", \"/teasing - Enable/disable teasing\"]"; + break; + case ROLE_WEARER_CAPTURED: + case ROLE_WEARER_WAITING: + case ROLE_WEARER_FREE: + keyboardJson = "[[\"." SYMBOL_LOCK_KEY " - Offer keys\", \"." SYMBOL_KEY " - Get keys back\", \"/teasing - Enable/disable teasing\"]"; + break; + } + keyboardJson += ",[\"/menu - Menu options\"]]"; + SendMessageWithKeybaord(chat_id, "Please select your new role:", keyboardJson); + } else if (text == "/holder") { HolderAction(from_id, chat_id); @@ -1012,32 +1168,129 @@ void Message::ProcessNewMessages() ReleaseAction(from_id, chat_id); TeaserAction(from_id, chat_id); } - else if (text == "/teasing_on") - TeasingModeAction(true, from_id, chat_id); - else if (text == "/teasing_off") - TeasingModeAction(false, from_id, chat_id); else if (text == "/guest") { if (users.IdIsHolder(from_id)) ReleaseAction(from_id, chat_id); GuestAction(from_id, chat_id); } - else if (text == "/waiting") - WaitingAction(from_id, chat_id); - else if (text == "/capture") - CaptureAction(from_id, chat_id); - else if (text == "/release") - ReleaseAction(from_id, chat_id); - else if (text == "/free") - FreeAction(from_id, chat_id); + else if (text == "." SYMBOL_LOCK_KEY) + { + switch (userFrom->GetRoleId()) + { + case ROLE_HOLDER: + case ROLE_TEASER: + case ROLE_GUEST: + CaptureAction(from_id, chat_id); + break; + case ROLE_WEARER_CAPTURED: + case ROLE_WEARER_WAITING: + case ROLE_WEARER_FREE: + WaitingAction(from_id, chat_id); + break; + } + } + else if (text == "." SYMBOL_KEY) + { + switch (userFrom->GetRoleId()) + { + case ROLE_HOLDER: + case ROLE_TEASER: + case ROLE_GUEST: + ReleaseAction(from_id, chat_id); + break; + case ROLE_WEARER_CAPTURED: + case ROLE_WEARER_WAITING: + case ROLE_WEARER_FREE: + FreeAction(from_id, chat_id); + break; + } + } + + // ------ SUB-SUB MENU teasing ----------------------------------------------------------------- + else if (text == "/teasing") + { + String keyboardJson; + keyboardJson = "[[\"/teasing_on - Allow others to send shocks\", \"/teasing_off - Only holder can shock\"]" + ",[\"/roles - Back to role options\"]]"; + SendMessageWithKeybaord(chat_id, "Please select:", keyboardJson); + } + else if (text == "/teasing_on") + TeasingModeAction(true, from_id, chat_id); + else if (text == "/teasing_off") + TeasingModeAction(false, from_id, chat_id); + + // --- SUB MENU lock ----------------------------------------------------------------- + else if (text.substring(0, 5) == "/lock") + { + String keyboardJson = "[[\"." SYMBOL_CLOCK_3 "+10m - lock +10m\", \"." SYMBOL_CLOCK_3 "+1h - lock +1h\", \"." SYMBOL_CLOCK_3 "+1d - lock +1d\"]," + "[\"." SYMBOL_CLOCK_3 "-10m - lock -10m\", \"." SYMBOL_CLOCK_3 "-1h - lock -1h\", \"." SYMBOL_CLOCK_3 "-1d - lock -1d\"]," + "[\"/menu - Menu options\"]]"; + SendMessageWithKeybaord(chat_id, "Please select the time to add/subtract keeping the safe secured against any unlocking:", keyboardJson); + } + else if (text.substring(0, 5) == "." SYMBOL_CLOCK_3) + LockTimerAction(text.substring(5), from_id, chat_id); + + // --- SUB MENU shock ----------------------------------------------------------------- + else if (text.substring(0, 6) == "/shock") + { + String keyboardJson = "[[\"." SYMBOL_COLLISION "1 - shock 1s\", \"." SYMBOL_COLLISION "3 - shock 3s\", \"." SYMBOL_COLLISION "5 - shock 5s\"]," + "[\"." SYMBOL_COLLISION "10 - shock 10s\", \"." SYMBOL_COLLISION "20 - shock 20s\", \"." SYMBOL_COLLISION "30 - shock 30s\"]," + "[\"." SYMBOL_COLLISION "40 - shock 40s\", \"." SYMBOL_COLLISION "50 - shock 50s\", \"." SYMBOL_COLLISION "60 - shock 60s\"]," + "[\"/menu - Menu options\"]]"; + SendMessageWithKeybaord(chat_id, "Please select shock treatment duration for one immediate shock:", keyboardJson); + } + else if (text.substring(0, 5) == "." SYMBOL_COLLISION) + ShockAction(text.substring(5), 1, from_id, chat_id); + + // --- SUB MENU random ----------------------------------------------------------------- + else if (text.substring(0, 7) == "/random") + { + String keyboardJson = "[[\"." SYMBOL_DIRECT_HIT "1 - 1 shock\", \"." SYMBOL_DIRECT_HIT "2 - 2 shocks\", \"." SYMBOL_DIRECT_HIT "3 - 3 shocks\"]," + "[\"." SYMBOL_DIRECT_HIT "5 - 5 shocks\", \"." SYMBOL_DIRECT_HIT "7 - 7 shocks\", \"." SYMBOL_DIRECT_HIT "10 - 10 shocks\"]," + "[\"." SYMBOL_DIRECT_HIT "off - No random shocks\", \"/menu - Menu options\"]]"; + SendMessageWithKeybaord(chat_id, "Please select the number of random shocks per hour with random length between 1 and 10 seconds that you want to schedule for the next 3 hours:", keyboardJson); + } + else if (text.substring(0, 5) == "." SYMBOL_DIRECT_HIT) + RandomShockModeAction(text.substring(5), from_id, chat_id); + + // --- SUB MENU verify ----------------------------------------------------------------- + else if (text.substring(0, 7) == "/verify") + { + String keyboardJson = "[[\"." SYMBOL_POLICEMAN "1 - 1 verification\", \"." SYMBOL_POLICEMAN "2 - 2 verifications\"]," + "[\"." SYMBOL_POLICEMAN "3 - 3 verifications\", \"." SYMBOL_POLICEMAN "4 - 4 verifications\"]," + "[\"." SYMBOL_POLICEMAN "off - No verifications\", \"/menu - Menu options\"]]"; + SendMessageWithKeybaord(chat_id, "Please select the number of random verifications to be requested from the wearer daily:", keyboardJson); + } + else if (text.substring(0, 5) == "." SYMBOL_POLICEMAN) + VerificationModeAction(text.substring(5), from_id, chat_id); + + else if (text == "/game") + { + String keyboardJson = "[[\"." SYMBOL_DICE SYMBOL_DICE "6 - Throw dice 1..6\", \"." SYMBOL_DICE SYMBOL_DICE "10 - Dice 1..10\", \"." SYMBOL_DICE SYMBOL_DICE "100 - Dice 1..100\"]," + "[\"." SYMBOL_DICE SYMBOL_LOCK_OPEN " - Play for unlock\"]," + "[\"/menu - Menu options\"]]"; + SendMessageWithKeybaord(chat_id, "Please select the game that you wish to play:", keyboardJson); + } + + + + + + + // --- COMMANDS ----------------------------------------------------------------- + // else if (text.substring(0, 9) == "." SYMBOL_DICE SYMBOL_DICE) + // PlayAction(text.substring(9), from_id, chat_id); + else if (text.substring(0, 9) == "." SYMBOL_DICE SYMBOL_LOCK_OPEN) + Play4UnlockAction(from_id, chat_id); + + + + + // else if (text.substring(0, 5) == "/play") + // PlayAction(text.substring(6), from_id, chat_id); else if (text == "/tasklist") MessageTasks(); - else if (text.substring(0, 7) == "/verify") - VerificationModeAction(text.substring(8), from_id, chat_id); - else if (text.substring(0, 13) == "/verification") - VerificationModeAction(text.substring(14), from_id, chat_id); - else if (text.substring(0, 7) == "/random") - RandomShockModeAction(text.substring(8), from_id, chat_id); // else if (text == "/restrict") // RestrictUserAction(from_id, chat_id); else if (text == "/thisisanemergency") @@ -1045,7 +1298,7 @@ void Message::ProcessNewMessages() else if (text == "/readsettings") AdoptSettings(); else if (text == "/writesettings") - WriteCommandsAndSettings(); + WriteCommandsAndSettings("Message-ProcessNewMessages() writesettings"); else if (text == "/restartshockcell") RestartRequest(from_id, chat_id); else if (text.substring(0, 1) == "/") @@ -1068,7 +1321,7 @@ void Message::SendMessage(String msg, String chatId) Serial.print(chatId); Serial.print("> "); Serial.println(msg); - bot.sendMessage(chatId, msg, ""); + bot.sendMessage(chatId, msg); } @@ -1082,12 +1335,19 @@ void Message::SendMessageAll(String msg, String chatId) // ------------------------------------------------- -void Message::EditMessage(String msg, String messageId, String chatId) +void Message::EditMessage(String msg, String chatId, int messageId) { Serial.print(chatId); Serial.print("> "); Serial.println(msg); - bot.editMessage(chatId, messageId, msg); + bot.sendMessage(chatId, msg, PARSE_MODE, messageId); +} + + +// ------------------------------------------------- +void Message::SendMessageWithKeybaord(String chatId, String message, String keyboardJson) +{ + bot.sendMessageWithReplyKeyboard(chatId, message, PARSE_MODE, keyboardJson, RESIZE_KEYBOARD, MULTI_TIME_KEYBOARD, SELECTIVE_KEYBOARD); } @@ -1195,7 +1455,7 @@ void Message::AdoptChatDescription() { String descr; Serial.println("*** AdoptChatDescription()"); - + if (bot.getChatDescription(GROUP_CHAT_ID, descr)) { String response = bot.getMyCommands(); @@ -1291,6 +1551,8 @@ void Message::AdoptSettings() session.SetTimeOfLastOpening(ReadParamLong(descr, LAST_OPENING_TAG)); session.SetTimeOfLastClosing(ReadParamLong(descr, LAST_CLOSING_TAG)); session.SetTimeOfLastShock(ReadParamLong(descr, LAST_SHOCK_TAG)); + unsigned int loTi = ReadParamLong(descr, LOCK_TIMER_END_TAG); + session.SetLockTimerEnd((loTi > 0) ? loTi : timeFunc.GetTimeInSeconds()); session.SetTimeOfRandomModeStart(ReadParamLong(descr, RANDOM_MODE_START_TAG)); verification.Reset(); verification.SetVerificationModeInt(ReadParamLong(descr, VERIFICATION_MODE_TAG)); @@ -1310,6 +1572,7 @@ void Message::AdoptSettings() if ((i > 0) && (i < (verification.GetRequiredCountPerDay() - 1))) verification.GetEvent(i)->SetRandom(true); } + verification.SetNeedsSchedule(false); verification.Print(); session.SetTeasingModeInt(ReadParamLong(descr, TEASING_MODE_TAG)); @@ -1318,32 +1581,43 @@ void Message::AdoptSettings() Serial.println(session.IsRandomMode()); Serial.print("- random Mode cycle: "); Serial.println(session.GetRandomModeShocksPerHour()); - // session.SetRandomMode(false, 5); - session.SetCredits(ReadParamLong(descr, CREDITS_TAG)); - session.SetVouchers(ReadParamLong(descr, VOUCHER_TAG)); + // session.SetRandomMode(false, 5); + // session.SetCredits(ReadParamLong(descr, CREDITS_TAG)); + // session.SetVouchers(ReadParamLong(descr, VOUCHER_TAG)); session.SetDeviations(ReadParamLong(descr, DEVIATIONS_TAG)); session.SetFailures(ReadParamLong(descr, FAILURES_TAG)); + session.SetAwayCounter(ReadParamLong(descr, AWAY_TAG)); + Serial.println("- SW version current: " + String(SW_VERSION, DEC)); + + SendMessage("Application software version " + String(SW_VERSION, DEC), WEARER_CHAT_ID); + if (SW_VERSION != ReadParamLong(descr, SWVERSION_TAG)) + { + Serial.println(String("- SW version previous: ") + String(ReadParamLong(descr, SWVERSION_TAG), DEC)); +// SendMessage(String("A software-update has been installed: version ") + String(SW_VERSION, DEC), GROUP_CHAT_ID); + } + // session.SetClientIPAddress(ReadParamLong(descr, IPADDRESS_TAG)); // info += users.GetUsersInfo(); AdoptUserInfos(descr); } else { // this is only needed for the initial setup of a chat group - WriteCommandsAndSettings(); + //WriteCommandsAndSettings("Message-AdoptSettings()"); } } /* -/start - Start communication\n -[{"command":"start","description":"Start communication"},{"command":"roles","description":"List roles"},{"command":"shock1","description":"Shock for 1 seconds"},{"command":"shock3","description":"Shock for 3 seconds"},{"command":"shock5","description":"Shock for 5 seconds"},{"command":"shock10","description":"Shock for 10 seconds"},{"command":"shock30","description":"Shock for 30 seconds"},{"command":"state","description":"Report the cover state"},{"command":"unlock","description":"Unlock key safe"},{"command":"users","description":"Lists users in chat"},{"command":"holder","description":"Adopt holder role"},{"command":"teaser","description":"Adopt teaser role"},{"command":"guest","description":"Adopt guest role"},{"command":"waiting","description":"Make wearer waiting to be captured by the holder"},{"command":"free","description":"Make wearer free again (stops waiting for capturing by the holder)"},{"command":"capture","description":"Capture wearer as a sub"},{"command":"release","description":"Release wearer as a sub"},{"command":"_lasttimes","description":"LastOpening:1604473444; LastClosing:1604473509; LastShock:1604853044; RandomModeStart:1604847654;"},{"command":"_modes","description":"TeasingMode:1; RandomMode:0; Credits:0;"},{"command":"_userlist","description":"Charly:1157999292:Wearer/captured; Ruler:1264046045:ShockCell; S:919525040:Guest; roberta:1209481168:Holder; Spark:780464021:Teaser"}] + /start - Start communication\n + [{"command":"start","description":"Start communication"},{"command":"roles","description":"List roles"},{"command":"shock1","description":"Shock for 1 seconds"},{"command":"shock3","description":"Shock for 3 seconds"},{"command":"shock5","description":"Shock for 5 seconds"},{"command":"shock10","description":"Shock for 10 seconds"},{"command":"shock30","description":"Shock for 30 seconds"},{"command":"state","description":"Report the cover state"},{"command":"unlock","description":"Unlock key safe"},{"command":"users","description":"Lists users in chat"},{"command":"holder","description":"Adopt holder role"},{"command":"teaser","description":"Adopt teaser role"},{"command":"guest","description":"Adopt guest role"},{"command":"waiting","description":"Make wearer waiting to be captured by the holder"},{"command":"free","description":"Make wearer free again (stops waiting for capturing by the holder)"},{"command":"capture","description":"Capture wearer as a sub"},{"command":"release","description":"Release wearer as a sub"},{"command":"_lasttimes","description":"LastOpening:1604473444; LastClosing:1604473509; LastShock:1604853044; RandomModeStart:1604847654;"},{"command":"_modes","description":"TeasingMode:1; RandomMode:0; Credits:0;"},{"command":"_userlist","description":"Charly:1157999292:Wearer/captured; Ruler:1264046045:ShockCell; S:919525040:Guest; roberta:1209481168:Holder; Spark:780464021:Teaser"}] */ // ------------------------------------------------- -void Message::WriteCommandsAndSettings() +void Message::WriteCommandsAndSettings(String reference) { // This is the old way of storing the settings // Redundant saving... Serial.println("*** WriteCommandsAndSettings()"); + SendMessage("*** Message::WriteCommandsAndSettings(" + reference + "), free heap: " + String(ESP.getFreeHeap(), DEC), WEARER_CHAT_ID); String info; info = verification.GetHash(); info += " " USERS_PREFIX " "; @@ -1375,7 +1649,7 @@ void Message::WriteCommandsAndSettings() Serial.println(posDash); if ((posNL = botCommandsAll.indexOf("\n", pos)) != -1) { - commands += "{\"command\":\"" + botCommandsAll.substring(pos+1, posDash) + "\",\"description\":\"" + botCommandsAll.substring(posDash+3, posNL) + "\"},"; + commands += "{\"command\":\"" + botCommandsAll.substring(pos + 1, posDash) + "\",\"description\":\"" + botCommandsAll.substring(posDash + 3, posNL) + "\"},"; Serial.print(" - posNL="); Serial.println(posNL); Serial.println(commands); @@ -1391,6 +1665,7 @@ void Message::WriteCommandsAndSettings() LAST_OPENING_TAG ":" + String(session.GetTimeOfLastOpening(), DEC) + "; " LAST_CLOSING_TAG ":" + String(session.GetTimeOfLastClosing(), DEC) + "; " LAST_SHOCK_TAG ":" + String(session.GetTimeOfLastShock(), DEC) + "; " + LOCK_TIMER_END_TAG ":" + String(session.GetLockTimerEnd(), DEC) + "; " RANDOM_MODE_START_TAG ":" + String(session.GetTimeOfRandomModeStart(), DEC) + "; " "\"},"; commands += "{\"command\":\"_verification\",\"description\":\""; @@ -1404,10 +1679,20 @@ void Message::WriteCommandsAndSettings() VERIFICATION_MODE_TAG ":" + String(verification.GetVerificationModeInt(), DEC) + "; " ACTUAL_VERIFICATIONS_TAG ":" + String(verification.GetActualToday(), DEC) + "; " DAY_OF_WEEK_TAG ":" + String(verification.GetDayOfWeek(), DEC) + "; " - CREDITS_TAG ":" + String(session.GetCredits(), DEC) + "; " - VOUCHER_TAG ":" + String(session.GetVouchers(), DEC) + "; " + // CREDITS_TAG ":" + String(session.GetCredits(), DEC) + "; " + CREDITS_TAG ":0; " + // VOUCHER_TAG ":" + String(session.GetVouchers(), DEC) + "; " + VOUCHER_TAG ":0; " DEVIATIONS_TAG ":" + String(session.GetDeviations(), DEC) + "; " FAILURES_TAG ":" + String(session.GetFailures(), DEC) + "; " + AWAY_TAG ":" + String(session.GetAwayCounter(), DEC) + "; " + "\"},"; + commands += "{\"command\":\"_reference\",\"description\":\"" " " REFERENCE_PREFIX " " + + reference + " " + timeFunc.GetTimeString(WITH_DATE) + + "\"},"; + commands += "{\"command\":\"_version\",\"description\":\"" + SWVERSION_TAG ":" + String(SW_VERSION, DEC) + "; " + // IPADDRESS_TAG ":" + String(session.GetWearerIPAddress(), DEC) + "; " "\"},"; commands += "{\"command\":\"_users\",\"description\":\"" " " USERS_PREFIX " " + users.GetUsersInfo() + diff --git a/Message.h b/Message.h index c9c8ba7..ac09e16 100644 --- a/Message.h +++ b/Message.h @@ -9,6 +9,7 @@ #define LAST_CLOSING_TAG "LACL" #define LAST_SHOCK_TAG "LASH" //#define LAST_SHOCK_TAG "LastShock" +#define LOCK_TIMER_END_TAG "LOTE" #define RANDOM_MODE_START_TAG "RAMS" //#define RANDOM_MODE_START_TAG "RandomModeStart" #define NEXT_VERIFICATION_BEGIN_TAG "VSTA" @@ -25,8 +26,12 @@ #define VOUCHER_TAG "VOUC" #define DEVIATIONS_TAG "DEVN" #define FAILURES_TAG "FAIL" +#define SWVERSION_TAG "SWVERSION" #define USERS_PREFIX "USERS:" #define CHATS_PREFIX "CHATS:" +#define REFERENCE_PREFIX "REFERENCE:" +#define AWAY_TAG "AWAY:" +#define IPADDRESS_TAG "IPCLIENT:" // general #define SYMBOL_WARNING "\xe2\x9a\xa0" @@ -38,6 +43,7 @@ #define SYMBOL_KEY "\xf0\x9f\x94\x91" #define SYMBOL_TWO_CHAIN_LINKS "\xf0\x9f\x94\x97" #define SYMBOL_CHAINS "\xe2\x9b\x93" +#define SYMBOL_LOCKED_LUGGAGE "\xf0\x9f\x9b\x85" // time #define SYMBOL_MOON "\xf0\x9f\x8c\x99" #define SYMBOL_SUN1 "\xe2\x98\x80" @@ -46,6 +52,7 @@ // control #define SYMBOL_WATCHING_EYES "\xf0\x9f\x91\x80" #define SYMBOL_POLICEMAN "\xf0\x9f\x91\xae" +#define SYMBOL_PASSPORT_CONTROL "\xf0\x9f\x9b\x83" #define SYMBOL_CUSTOMS "\xf0\x9f\x9b\x82" #define SYMBOL_EYE "\xf0\x9f\x91\x81" // holder @@ -66,6 +73,7 @@ #define SYMBOL_STAR_GLOWING "\xf0\x9f\x8c\x9f" #define SYMBOL_GEM "\xf0\x9f\x92\x8e" // games +#define SYMBOL_DIRECT_HIT "\xf0\x9f\x8e\xaf" #define SYMBOL_DICE "\xf0\x9f\x8e\xb2" #define SYMBOL_DIGIT0 "\x30\xe2\x83\xa3" #define SYMBOL_DIGIT1 "\x31\xe2\x83\xa3" @@ -92,6 +100,13 @@ #define SYMBOL_CREDIT SYMBOL_STAR #define SYMBOL_VOUCHER SYMBOL_LOCK_KEY +#define PARSE_MODE "" +#define RESIZE_KEYBOARD true +#define ONE_TIME_KEYBOARD true +#define MULTI_TIME_KEYBOARD false +#define SELECTIVE_KEYBOARD false +#define REMOVE_KEYBOARD true + #include "Arduino.h" #include @@ -120,7 +135,8 @@ class Message void ProcessNewMessages(); void SendMessage(String msg, String chatId=GROUP_CHAT_ID); void SendMessageAll(String msg, String chatId=GROUP_CHAT_ID); - void EditMessage(String msg, String messageId, String chatId); + void EditMessage(String msg, String chatId, int messageId); + void SendMessageWithKeybaord(String chatId, String message, String keyboardJson); int GetLastSentMessageId(); void SendRichMessage(String id, String msg); void MessageWearerState(String chatId); @@ -129,12 +145,9 @@ class Message void MessageIsOpen(String chatId); void MessageCoverStateChange(String chatId=GROUP_CHAT_ID); void MessageCoverState(String chatId=GROUP_CHAT_ID); - void MessageSendEarnedCredits(int creditsEarned, String chatId=GROUP_CHAT_ID); - void MessageSendEarnedVouchers(int vouchersEarned, String chatId=GROUP_CHAT_ID); void MessageModes(String chatId=GROUP_CHAT_ID); void MessageUsers(String chatId=GROUP_CHAT_ID); void MessageRoles(String chatId=GROUP_CHAT_ID); - void MessageChastikeyState(String chatId=GROUP_CHAT_ID); void MessageState(String chatId=GROUP_CHAT_ID); void MessageTasks(String chatId=GROUP_CHAT_ID); void ShockAction(String durationStr, int count, String fromId, String chatId=GROUP_CHAT_ID); @@ -142,15 +155,16 @@ class Message void HolderAction(String fromId, String chatId=GROUP_CHAT_ID); void TeaserAction(String fromId, String chatId=GROUP_CHAT_ID); void TeasingModeAction(bool mode, String fromId, String chatId=GROUP_CHAT_ID); - void GuestAction(String fromId, String chatId=GROUP_CHAT_ID); + void GuestAction(String fromId, String chatId=GROUP_CHAT_ID, bool force=false); void WaitingAction(String fromId, String chatId=GROUP_CHAT_ID); void FreeAction(String fromId, String chatId=GROUP_CHAT_ID); void CaptureAction(String fromId, String chatId=GROUP_CHAT_ID); void UnlockAction(String fromId, String chatId=GROUP_CHAT_ID, bool force=false); + void LockTimerAction(String durationStr, String fromId, String chatId); void Play4UnlockAction(String fromId, String chatId=GROUP_CHAT_ID); - void PlayAction(String max, String fromId, String chatId=GROUP_CHAT_ID); +// void PlayAction(String max, String fromId, String chatId=GROUP_CHAT_ID); void ReleaseAction(String fromId, String chatId=GROUP_CHAT_ID); - void RestrictUserAction(String fromId, String chatId=GROUP_CHAT_ID); +// void RestrictUserAction(String fromId, String chatId=GROUP_CHAT_ID); void RandomShockModeAction(String commandParameter, String fromId, String chatId=GROUP_CHAT_ID, bool force=false); void VerificationModeAction(String commandParameter, String fromId, String chatId=GROUP_CHAT_ID, bool force=false); void CheckVerificationAction(String caption, String fromId, String chatId=GROUP_CHAT_ID); @@ -166,7 +180,7 @@ class Message void AdoptChatDescription(); void AdoptSettings(); void UnknownCommand(String chatId=GROUP_CHAT_ID); - void WriteCommandsAndSettings(); + void WriteCommandsAndSettings(String reference); }; #endif diff --git a/Oled.cpp b/Oled.cpp index 745ba83..5499214 100644 --- a/Oled.cpp +++ b/Oled.cpp @@ -107,7 +107,7 @@ void OledDisplay::PrintDisplay(String statusMsg) line[i] = ""; } // line[5] = statusMsg; - line[6] = timeFunc.GetTimeString(); + line[6] = timeFunc.GetTimeString(WITH_DATE); Show(line); } diff --git a/Session.cpp b/Session.cpp index 8f31bfe..cd6f5ea 100644 --- a/Session.cpp +++ b/Session.cpp @@ -23,6 +23,7 @@ extern Task tasks; String Task::GetStatusMessage() { String statusStr; + Serial.println(ESP.getFreeHeap()); if (IsActive() && (! IsFulfilled())) { // active task @@ -45,6 +46,7 @@ String Task::GetStatusMessage() // pending task statusStr = "pending"; } + Serial.println(" . return"); return "[" + String(GetIndex(), DEC) + "] " + GetMessage() + " (" + statusStr + ")\n"; } } @@ -144,13 +146,15 @@ int Tasklist::Add(String msg, int tType, unsigned long tBegin, unsigned long tEn const String Tasklist::GetTaskList() { String str = "List of open tasks:\n"; - Serial.println("*** Tasklist::MessageTasks()"); + /* + Serial.println("*** Tasklist::GetTaskList()"); for (int i = 0; i < MAX_TASKS; i++) { Serial.println("- index:" + String(i, DEC)); str += task[i].GetStatusMessage(); } Print(); +*/ return str; } @@ -219,6 +223,7 @@ void Verification::Init() event[i].SetIndex(i); event[i].Reset(); } + needsSchedule = true; } @@ -330,7 +335,7 @@ void Verification::SetVerificationMode(bool onOff, int count) SetEnabled(onOff); requiredCountPerDay = count; Schedule(true); - message.WriteCommandsAndSettings(); + message.WriteCommandsAndSettings("Verification-SetVerificationMode()"); } @@ -347,8 +352,9 @@ void Verification::Schedule(bool force) if (IsEnabled()) { Serial.println("- IsEnabled"); - // schedule new verification events if there is a new day - if (force || (timeFunc.GetDayOfWeek() != GetDayOfWeek())) + // schedule new verification events at 5:00 + if (force || + (needsSchedule && (timeFunc.GetHours() == 5))) { Serial.println(""); // new day --> update day of week @@ -390,7 +396,8 @@ void Verification::Schedule(bool force) Serial.println("0:"); break; } - message.WriteCommandsAndSettings(); + needsSchedule = false; + message.WriteCommandsAndSettings("Verification-Schedule()"); Print(); } } @@ -430,7 +437,7 @@ void Verification::CheckIn(String chatId) session.SetDeviations(session.GetDeviations() + 1); message.SendMessage(SYMBOL_DEVIL_SMILE " Verification is not acceptable. It comes early.", chatId); } - message.WriteCommandsAndSettings(); + message.WriteCommandsAndSettings("Verification-CheckIn()"); } } @@ -469,7 +476,7 @@ void Verification::ProcessVerification(String chatId) session.SetFailures(session.GetFailures() + 1); message.SendMessage(SYMBOL_DEVIL_ANGRY SYMBOL_DEVIL_ANGRY SYMBOL_DEVIL_ANGRY " Verification request has expired!!! Wearer " + users.GetWearer()->GetName() + " has failed to provide a verification in time!", chatId); WindowCompleted(); - message.WriteCommandsAndSettings(); + message.WriteCommandsAndSettings("Verification-ProcessVerification() verification expired"); } } } @@ -483,7 +490,7 @@ void Verification::ProcessVerification(String chatId) message.SendMessage(SYMBOL_DEVIL_SMILE " Verification request - wearer " + users.GetWearer()->GetName() + " must provide a verification showing the verification code " + GetCurrentEvent()->GetCode() + " within " + timeFunc.Time2String(GetTimeOfNextEnd() - timeFunc.GetTimeInSeconds()) + " from now!", chatId); GetCurrentEvent()->SetAnnouncedBegin(true); - message.WriteCommandsAndSettings(); + message.WriteCommandsAndSettings("Verification-ProcessVerification() verification request"); } } } @@ -492,18 +499,18 @@ void Verification::ProcessVerification(String chatId) // ------------------------------------------------------------------------ -void Session::Shock(int count, long milliseconds) +void Session::Shock(int count, long milliseconds, int level) { Serial.print("*** Shock() milliseconds="); Serial.print(milliseconds); Serial.print(", count="); Serial.print(count); - String prefix = "#=" + String(count, DEC) + " t=" + String((milliseconds / 1000), DEC); + String prefix = "#=" + String(count, DEC) + " t=" + String((milliseconds / 1000), DEC) + " l=" + String(level, DEC); // IFTTT webhook handling { String payload; - String request = "value1=" + String(count, DEC) + "&value2=" + String(milliseconds, DEC) + "&value3=Telegram"; + String request = "value1=" + String(count, DEC) + "&value2=" + String(milliseconds, DEC) + "," + String(level, DEC) + "&value3=Telegram"; Serial.print("*** IFTTT webhook"); Serial.println(request); @@ -525,12 +532,47 @@ void Session::Shock(int count, long milliseconds) int firstBurst = 0; long now = timeFunc.GetTimeInSeconds(); + long delivered = 0; Serial.print("last shock ago: "); Serial.println(now - timeOfLastShock); if ((now - timeOfLastShock) > 250) firstBurst = 2500; + int shockPin = SHOCK_PIN; + bool turnOn, turnOff; + level = 10; + if (level < 20) + { + shockPin = SHOCK1_PIN; + turnOn = LOW; + turnOff = HIGH; + } + else if (level < 40) + { + shockPin = SHOCK_PIN; + turnOn = HIGH; + turnOff = LOW; + } + else if (level < 40) + { + shockPin = SHOCK2_PIN; + turnOn = LOW; + turnOff = HIGH; + } + else if (level < 40) + { + shockPin = SHOCK3_PIN; + turnOn = LOW; + turnOff = HIGH; + } + else + { + shockPin = SHOCK4_PIN; + turnOn = LOW; + turnOff = HIGH; + } + for (int i = 0; i < count; i++) { Serial.println("*** Shock processing: "); @@ -539,14 +581,38 @@ void Session::Shock(int count, long milliseconds) Serial.print(milliseconds); Serial.println(" ms"); - digitalWrite(SHOCK_PIN, HIGH); +// digitalWrite(shockPin, turnOn); + digitalWrite(4, HIGH); delay(100); - digitalWrite(SHOCK_PIN, LOW); +// digitalWrite(shockPin, turnOff); + digitalWrite(4, LOW); delay(200); - digitalWrite(SHOCK_PIN, HIGH); - delay(milliseconds + firstBurst); - digitalWrite(SHOCK_PIN, LOW); +// digitalWrite(shockPin, turnOn); + digitalWrite(4, HIGH); + delay(firstBurst); +// digitalWrite(shockPin, turnOff); + digitalWrite(4, LOW); + delay(50); + + while(milliseconds > delivered) + { +// digitalWrite(shockPin, turnOn); + digitalWrite(4, HIGH); + if ((milliseconds - delivered) > 10000L) + { + delay(10000L); + delivered += 10000L; + } + else + { + delay(milliseconds - delivered); + delivered += (milliseconds - delivered); + } +// digitalWrite(shockPin, turnOff); + digitalWrite(4, LOW); + delay(50); + } delay(SHOCK_BREAK_DURATION); } @@ -617,121 +683,71 @@ unsigned long Session::GetRemainingTime(bool forDisplay) // ------------------------------------------------------------------------ -void Session::SetCredits(int newVal, String chatId) +unsigned long Session::GetLockTimerRemaining() { - int creditCount = GetCredits(); - SetCredits(newVal); - if (GetCredits() > creditCount) - message.SendMessage(String(SYMBOL_CREDIT) + " Wearer received " + (GetCredits() > creditCount) + " credits.", chatId); + if (timeFunc.GetTimeInSeconds() > lockTimerEnd) + return 0; + else + return lockTimerEnd - timeFunc.GetTimeInSeconds(); } // ------------------------------------------------------------------------ -void Session::SetCreditFractions(int newVal) +bool Session::AddLockTimerEnd(unsigned long lockTime) { - creditFractions = newVal; - if (creditFractions >= 10) + bool limitReached = false; + // limit extension to 7 days + if (lockTime > 7*86400L) { - int newCredits = creditFractions / 10; - creditFractions = creditFractions % 10; - SetCredits(GetCredits() + newCredits); + lockTime = 7*86400L; + limitReached = true; } -} + if (IsLockTimerActive()) + { + // lock timer may not exceed 7 days + if ((GetLockTimerEnd() + lockTime) > (timeFunc.GetTimeInSeconds() + 7*86400L)) + { + SetLockTimerEnd(timeFunc.GetTimeInSeconds() + 7*86400L); + limitReached = true; + } + else + SetLockTimerEnd(GetLockTimerEnd() + lockTime); + } + else + SetLockTimerEnd(timeFunc.GetTimeInSeconds() + lockTime); -// ------------------------------------------------------------------------ -void Session::SetCreditFractions(int newVal, String chatId) -{ - int creditCount = GetCredits(); - SetCreditFractions(newVal); - if (GetCredits() > creditCount) - message.SendMessage(String(SYMBOL_CREDIT) + " Wearer received " + (GetCredits() > creditCount) + " credits.", chatId); + return limitReached; } // ------------------------------------------------------------------------ -void Session::SetVouchers(int newVal, String chatId) +void Session::SubLockTimerEnd(unsigned long lockTime) { - int voucherCount = GetVouchers(); - SetVouchers(newVal); - if (GetVouchers() > voucherCount) - message.SendMessage(String(SYMBOL_VOUCHER) + " Wearer received " + (GetVouchers() > voucherCount) + " unlock vouchers.", chatId); + if (IsLockTimerActive()) + { + if (GetLockTimerEnd() > lockTime) + SetLockTimerEnd(GetLockTimerEnd() - lockTime); + else + // we want the lockTimerEnd to be set to the current time if cleared to avoid underrun issues + SetLockTimerEnd(timeFunc.GetTimeInSeconds()); + } + else + SetLockTimerEnd(timeFunc.GetTimeInSeconds() - lockTime); } // ------------------------------------------------------------------------ bool Session::IsActiveSession() { - return (! users.GetWearer()->IsFreeWearer()) || activeChastikeySession; -} - - -// ------------------------------------------------------------------------ -void Session::InfoChastikey() -{ - String payload; - - Serial.println("*** Session::InfoChastikey()"); -/* - int trial = 0; - bool success = false; - while ((trial < 10) && ! success) - { - success = emlaServer.WGet("https://api.chastikey.com/v0.3/listlocks.php?username=cblock", payload); - trial++; - if (trial > 1) - { - Serial.print("Retry: "); - Serial.println(trial); - } - if (! success) - delay(1000); - } - // Serial.println(payload); - - if (payload.length() > 0) - { - // Extract values - const size_t capacity = JSON_ARRAY_SIZE(1) + JSON_ARRAY_SIZE(10) + JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(3) + 10*JSON_OBJECT_SIZE(7) + 1260; - DynamicJsonDocument doc(capacity); - - deserializeJson(doc, payload); - - JsonObject response_0 = doc["response"][0]; - int response_0_status = response_0["status"]; // 200 - const char* response_0_message = response_0["message"]; // "Success" - long response_0_timestampGenerated = response_0["timestampGenerated"]; // 1569859752 - - JsonArray locks = doc["locks"]; - - SetActiveChastikeySession(false); - SetChastikeyHolder(""); - for (int i = 0; i < 25; i++) - { - JsonObject lockInfo = locks[i]; - String lockStatus = lockInfo["status"]; // "UnlockedReal" - Serial.print("- Chastikey lock "); - Serial.print(i); - Serial.print(": "); - Serial.println(lockStatus); - if (lockStatus == "Locked") - { - SetActiveChastikeySession(true); - SetChastikeyHolder(lockInfo["lockedBy"]); - } -// const char* locks_combination = locks["combination"]; // "84725435" - } - } - Serial.print("- Chastikey session: "); - Serial.println(IsActiveChastikeySession()); - */ + return (! users.GetWearer()->IsFreeWearer()); } // ------------------------------------------------------------------------ void Session::Punishment(int level) { - message.SendMessage("Wearer " + users.GetWearer()->GetName() + "needs to be punished.", chatId); + message.SendMessage("Wearer " + users.GetWearer()->GetName() + "needs to be treated.", chatId); unsigned long rnd = random(10000); switch (rnd * level / 10000) { @@ -780,7 +796,7 @@ int Session::SetRandomMode(bool onOff, int shocksPerHour) unsigned long now = timeFunc.GetTimeInSeconds(); unsigned long duration = now - GetTimeOfRandomModeStart(); // credit is given for each hours minus 5 seconds in order to prevent small deviations from the 1 hour duration - int creditIncrement = duration / 3595; +// int creditIncrement = duration / 3595; bool oldRandomShockMode = randomShockMode; randomShockMode = onOff; @@ -799,14 +815,15 @@ int Session::SetRandomMode(bool onOff, int shocksPerHour) else if (oldRandomShockMode) { // if randomShockMode is switched off, but oldRandomShockMode was on... - Serial.print("- credit increment: "); - Serial.println(creditIncrement); - SetCredits(GetCredits() + creditIncrement); +// Serial.print("- credit increment: "); +// Serial.println(creditIncrement); +// SetCredits(GetCredits() + creditIncrement); } - message.WriteCommandsAndSettings(); + message.WriteCommandsAndSettings("Session-SetRandomMode()"); - return creditIncrement; +// return creditIncrement; + return 0; } @@ -848,6 +865,8 @@ void Session::ProcessRandomShocks() if (duration > RANDOM_SHOCK_AUTO_OFF_SECONDS) { message.SendMessage("Random mode will be turned off now, because it ran for " + String(RANDOM_SHOCK_AUTO_OFF_SECONDS/3600, DEC) + " hours."); + randomShockMode = false; + message.WriteCommandsAndSettings("Session-ProcessRandomShocks()"); message.RandomShockModeAction("off", USER_ID_BOT, GROUP_CHAT_ID, FORCE); return; } diff --git a/Session.h b/Session.h index 80d5130..776eedd 100644 --- a/Session.h +++ b/Session.h @@ -33,7 +33,6 @@ #include "User.h" - class Task { private: @@ -131,6 +130,7 @@ class Verification private: VerificationEvent event[MAX_VERIFICATIONS]; bool enabled = true; + bool needsSchedule = true; // indicates that a new set of verification requests have to be scheduled (for the next day) bool dayIsCompleted = false; int requiredCountPerDay = 0; int currentIndex = 0; @@ -140,6 +140,8 @@ class Verification void Init(); bool IsEnabled() { return enabled; } void SetEnabled(bool e) { enabled = e; } + bool NeedsSchedule() { return needsSchedule; } + void SetNeedsSchedule(bool n) { needsSchedule = n; } void SetVerificationMode(bool onOff, int count=1); // only for settings exchange: void SetVerificationModeInt(int mode) { enabled = (mode > 0); requiredCountPerDay = mode; } @@ -148,7 +150,7 @@ class Verification void SetActualToday(int a); int GetActualToday(); bool IsDayCompleted() { return dayIsCompleted; } - void SetDayCompleted(bool i) { dayIsCompleted = i; } + void SetDayCompleted(bool i) { dayIsCompleted = i; needsSchedule = true; } // int GetCurrentIndex() { return currentIndex; } void SetCurrentIndex(int c) { currentIndex = c; } @@ -176,18 +178,18 @@ class Session private: String id; String chatId; - bool activeChastikeySession = false; bool randomShockMode = false; bool ramdomShockOffMessage30 = false; bool ramdomShockOffMessage10 = false; unsigned long randomShocksPerHour = 1; bool teasingMode = true; - int credits = 0; - int unlockVouchers = 0; - int creditFractions = 0; + unsigned long awayCounter = 0; +// int credits = 0; +// int unlockVouchers = 0; +// int creditFractions = 0; int deviations = 0; int failures = 0; - String chastikeyHolder; + unsigned int wearerPresence = 0; unsigned long endTime; String endTimeStr; bool elapsedTimeDisplay; @@ -203,6 +205,8 @@ class Session unsigned long timeOfLast5sInterval = 0; unsigned long timeOfLast1minInterval = 0; unsigned long timeOfLast5minInterval = 0; + unsigned long lockTimerEnd = 0; + bool lockTimerWasActive = false; public: Session() {} @@ -220,11 +224,6 @@ class Session // User & GetSupervisor() { return supervisor; } // bool HasSupervisor() { return (supervisor.GetId().length() > 0); } bool IsActiveSession(); - bool IsActiveChastikeySession() { return activeChastikeySession; } - void SetActiveChastikeySession(bool is) { activeChastikeySession = is; } - - String GetChastikeyHolder() { return chastikeyHolder; } - void SetChastikeyHolder(String hname) { chastikeyHolder = hname; } unsigned long GetTimeOfLastUnlock() { return timeOfLastUnlock; } void SetTimeOfLastUnlock(unsigned long t) { timeOfLastUnlock = t; } @@ -241,6 +240,13 @@ class Session unsigned long GetTimeOfRandomModeStart() { return timeOfRandomModeStart; } void SetTimeOfRandomModeStart(unsigned long t) { timeOfRandomModeStart = t; } + unsigned long GetLockTimerEnd() { return lockTimerEnd; } + void SetLockTimerEnd(unsigned long l) { lockTimerEnd = l; } + bool AddLockTimerEnd(unsigned long l); + void SubLockTimerEnd(unsigned long l); + unsigned long GetLockTimerRemaining(); + bool IsLockTimerActive() { return (GetLockTimerRemaining() > 0); } + void SetTimeOfLast5sInterval(unsigned long setTime) { timeOfLast5sInterval = setTime; } unsigned long GetTimeOfLast5sInterval() { return timeOfLast5sInterval; } void SetTimeOfLast1minInterval(unsigned long setTime) { timeOfLast1minInterval = setTime; } @@ -266,15 +272,8 @@ class Session void SetRandomModeInt(int mode) { randomShockMode = (mode > 0); randomShocksPerHour = mode; } int GetRandomModeInt() { return randomShockMode ? randomShocksPerHour : 0; } - void SetVouchers(int newVal) { unlockVouchers = newVal; } - void SetVouchers(int newVal, String chatId); - int GetVouchers() { return unlockVouchers; } - void SetCredits(int newVal) { credits = newVal; } - void SetCredits(int newVal, String chatId); - int GetCredits() { return credits; } - void SetCreditFractions(int newVal); - void SetCreditFractions(int newVal, String chatId); - int GetCreditFractions() { return creditFractions; } + void SetAwayCounter(unsigned long newVal) { awayCounter = newVal; } + unsigned long GetAwayCounter() { return awayCounter; } void SetDeviations(int newVal) { deviations = newVal; } int GetDeviations() { return deviations; } void SetFailures(int newVal) { failures = newVal; } @@ -282,15 +281,14 @@ class Session void SetEmergencyReleaseCounter(int newVal) { emergencyReleaseCounter = newVal; emergencyReleaseCounterRequest = false; } int GetEmergencyReleaseCounter() { return emergencyReleaseCounter; } - void SetEmergencyReleaseCounterRequest(bool req) { emergencyReleaseCounterRequest = req; } + void SetEmergencyReleaseCounterRequest(bool req) { emergencyReleaseCounterRequest = req; if (! req) emergencyReleaseCounter = 0; } bool GetEmergencyReleaseCounterRequest() { return emergencyReleaseCounterRequest; } void Punishment(int level); - void Shock(int count, long milliseconds); + void Shock(int count, long milliseconds, int level=30); void Lock(); void ForcedUnlock(); void Unlock(); - void InfoChastikey(); void ProcessRandomShocks(); void ScheduleNextRandomShock(); diff --git a/ShockCell light.fzz b/ShockCell light.fzz new file mode 100644 index 0000000..91b6a5a Binary files /dev/null and b/ShockCell light.fzz differ diff --git a/ShockCell light_Schaltplan.png b/ShockCell light_Schaltplan.png new file mode 100644 index 0000000..d54e902 Binary files /dev/null and b/ShockCell light_Schaltplan.png differ diff --git a/ShockCell light_Steckplatine.png b/ShockCell light_Steckplatine.png new file mode 100644 index 0000000..56365fe Binary files /dev/null and b/ShockCell light_Steckplatine.png differ diff --git a/ShockCell.fzz b/ShockCell.fzz index 642ada7..69bdacc 100644 Binary files a/ShockCell.fzz and b/ShockCell.fzz differ diff --git a/ShockCell.ino b/ShockCell.ino index 6a41d89..46912a8 100644 --- a/ShockCell.ino +++ b/ShockCell.ino @@ -13,3 +13,23 @@ // BOARD // DOIT ESP32 DEVKIT V1 // Programmer: AVR ISP mkII +// +// +// PINOUT ESP32/30pin +// --------------------------------- +// EN | D23 +// VP | D22 +// VN | TX0 +// D34 | RX0 +// D35 | D21 +// D32 violet | D19 +// D33 blue | D18 +// D25 green | D5 +// D26 yellow | TX2 +// D27 orange | RX2 +// D14 red | D4 +// D12 brown | D2 +// D13 | D15 +// GND | GND +// VIN | 3V3 +// --------------------------------- diff --git a/TimeF.cpp b/TimeF.cpp index 8b3323f..9b01ec9 100644 --- a/TimeF.cpp +++ b/TimeF.cpp @@ -203,7 +203,7 @@ bool TimeFunctions::SleepingTimeJustChanged(bool started) // ------------------------------------------------------------------------ -String TimeFunctions::GetTimeString(unsigned long t) +String TimeFunctions::GetTimeString(bool withDate, unsigned long t, bool isRelative) { time_t nowSecs; @@ -211,12 +211,19 @@ String TimeFunctions::GetTimeString(unsigned long t) nowSecs = GetTimeInSeconds(); else nowSecs = t; + + // DST correction + if (! isRelative) + nowSecs = nowSecs + dstOffset*3600; + struct tm timeinfo; gmtime_r(&nowSecs, &timeinfo); - int hours = (timeinfo.tm_hour + dstOffset) % 24; char buf[100]; - sprintf(buf, "%02d:%02d.%02d", hours, timeinfo.tm_min, timeinfo.tm_sec); + if (withDate) + sprintf(buf, "%02d.%02d.%02d %02d:%02d.%02d", timeinfo.tm_mday, timeinfo.tm_mon+1, timeinfo.tm_year+1900, timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec); + else + sprintf(buf, "%02d:%02d.%02d", timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec); return String(buf); } @@ -262,16 +269,18 @@ void TimeFunctions::ProcessSleepTime() if (IsSleepingTime() && (! users.GetWearer()->IsSleeping())) { // Go to sleep - message.MessageTasks(); +// message.MessageTasks(); users.GetWearer()->SetSleeping(true); - message.SendMessage("Good night " + users.GetWearer()->GetName() + ", sleep well and frustrated."); + message.SendMessage("Good night " + users.GetWearer()->GetName() + " boi. Sleep well and frustrated."); + message.SendMessage("*** TimeFunctions::ProcessSleepTime(), free heap: " + String(ESP.getFreeHeap(), DEC), WEARER_CHAT_ID); } if ((! IsSleepingTime()) && users.GetWearer()->IsSleeping()) { // Wake up users.GetWearer()->SetSleeping(false); - message.SendMessage("Good morning " + users.GetWearer()->GetName() + ", wake up boy!"); - message.MessageTasks(); + message.SendMessage("Good morning " + users.GetWearer()->GetName() + " boi. Time to wake up!"); +// message.MessageTasks(); + message.SendMessage("*** TimeFunctions::ProcessSleepTime(), free heap: " + String(ESP.getFreeHeap(), DEC), WEARER_CHAT_ID); } } /* diff --git a/TimeF.h b/TimeF.h index 04aa65c..5f32b17 100644 --- a/TimeF.h +++ b/TimeF.h @@ -3,6 +3,11 @@ #define __TimeFunctions_h__ #define TIME_MAX 4102444799L +#define IS_DURATION true +#define WITH_DATE true +#define NO_DATE false +#define IS_ABSOLUTE false +#define IS_RELATIVE true #include "Arduino.h" @@ -27,7 +32,7 @@ class TimeFunctions unsigned long WakeUpTime(); unsigned long SleepTime(); bool SleepingTimeJustChanged(bool started); - String GetTimeString(unsigned long t = 0); + String GetTimeString(bool withDate, unsigned long t = 0, bool isRelative = false); void SetClock(); void ProcessSleepTime(); }; diff --git a/User.cpp b/User.cpp index b87fd5d..12e91e8 100644 --- a/User.cpp +++ b/User.cpp @@ -4,6 +4,7 @@ #include "User.h" #include "Defs.h" #include "Message.h" +#include "Session.h" RoleSet roles; @@ -11,6 +12,7 @@ RoleSet roles; extern TimeFunctions timeFunc; extern UniversalTelegramBot bot; extern Message message; +extern Session session; extern UserSet users; @@ -79,6 +81,19 @@ bool User::UpdateRoleId(int r, bool force) } +// ------------------------------------------------------------------------ +void User::SetLastMessageTime(unsigned int now) +{ + Serial.println("*** SetLastMessageTime(" + String(now, DEC) + ")"); + Serial.println("- before: " + GetName() + " (" + GetRoleStr() + ") [" + timeFunc.GetTimeString(WITH_DATE, GetLastMessageTime()) + "]"); + if (now == 0) + lastMessageTime = timeFunc.GetTimeInSeconds(); + else + lastMessageTime = now; + Serial.println("- after: " + GetName() + " (" + GetRoleStr() + ") [" + timeFunc.GetTimeString(WITH_DATE, GetLastMessageTime()) + "]"); +} + + // ------------------------------------------------------------------------ bool User::MayBecomeHolder() { @@ -233,6 +248,27 @@ String UserSet::GetUsersInfo() } +// ------------------------------------------------------------------------ +bool UserSet::MayShock(String fromId) +{ + bool may; + User *u = users.GetUserFromId(fromId); + if (u) + { + if (u->IsHolder() || + u->IsBot() || + (u->IsTeaser() && session.IsTeasingMode()) || + (u->IsWearer() && session.IsTeasingMode()) || + (users.GetWearer()->IsFreeWearer())) + return true; + else + return false; + } + else + return false; +} + + // ------------------------------------------------------------------------ void UserSet::Update() { diff --git a/User.h b/User.h index 99d5333..162fae5 100644 --- a/User.h +++ b/User.h @@ -102,8 +102,8 @@ class User bool MayUnlock() { return (IsFreeWearer() || IsWaitingWearer() || IsHolder()); } bool IsSleeping() { return sleeping; } void SetSleeping(bool s) { sleeping = s; } - unsigned long LastMessageTime() { return lastMessageTime; } - void SetLastMessageTime(unsigned int now) { lastMessageTime = now; } + unsigned long GetLastMessageTime() { return lastMessageTime; } + void SetLastMessageTime(unsigned int now=0); }; @@ -116,7 +116,7 @@ class UserSet int holderIndex; int botIndex; User user[USER_CACHE_SIZE]; - unsigned long lastMessageTime; +// unsigned long lastMessageTime; public: UserSet() : count(0), wearerIndex(-1), holderIndex(-1), botIndex(-1) {} @@ -138,9 +138,10 @@ class UserSet bool IdIsHolder(String id); void SetBotIndex(int i) { botIndex = i; } int AddUser(String id, String name="", int role=ROLE_GUEST, bool isBot=false); - unsigned long LastMessageTime() { return lastMessageTime; } - void SetLastMessageTime(unsigned long now) { lastMessageTime = now; } +// unsigned long GetLastMessageTime() { return lastMessageTime; } +// void SetLastMessageTime(unsigned long now) { lastMessageTime = now; } String GetUsersInfo(); + bool MayShock(String fromId); void Update(); };