diff options
author | Tanishka Suwalka <tanishkas@ruc.dk> | 2025-04-16 22:51:19 +0200 |
---|---|---|
committer | Jonas Smedegaard <dr@jones.dk> | 2025-04-16 22:51:19 +0200 |
commit | fb48424cadc57170d39de0ac0b0450f373ef74fc (patch) | |
tree | 01a491bd8a43d412661ebf81255270a23d42c6b6 /vote | |
parent | 120ac51294ddae7b479c916382cd8be96f39cf2d (diff) |
cleanup stack; simplify qualify() limits; change onclude()
Diffstat (limited to 'vote')
-rw-r--r-- | vote/vote.ino | 135 |
1 files changed, 98 insertions, 37 deletions
diff --git a/vote/vote.ino b/vote/vote.ino index bce78ef..6d63b71 100644 --- a/vote/vote.ino +++ b/vote/vote.ino @@ -37,8 +37,7 @@ #define BALLOT_MAX 5 // Validity timing thresholds -const unsigned long VOTE_TIME_AHEAD = 1 * 60 * 1000; // 1 minute -const unsigned long VOTE_TIME_BEHIND = 2 * 60 * 1000; // 2 minutes +const unsigned long VOTE_TIME_INVALID = 1 * 60 * 1000; // 1 minute // Classify gape state enum MusselGapState { @@ -107,6 +106,43 @@ void storeVoteForMussel( timestamp, id.c_str(), gape_measure); } +/// Clean outdated votes from each mussel and remove mussels with no valid votes +void cleanOldVotes() { + unsigned long now = millis(); + + for (int i = 0; i < voterCount; ) { + Voter &voter = voters[i]; + int newCount = 0; + + // Shift valid votes to the front + for (int j = 0; j < voter.voteCount; j++) { + unsigned long age = now - voter.votes[j].timestamp; + + if (age < VOTE_TIME_INVALID) { + voter.votes[newCount++] = voter.votes[j]; + } else { + log_i("Dropped old vote for Mussel %s | Age: %lu ms", + voter.id.c_str(), age); + } + } + + voter.voteCount = newCount; + + // If all votes are dropped, remove the mussel + if (voter.voteCount == 0) { + log_i("Removing Mussel %s - No valid votes left", voter.id.c_str()); + + for (int k = i; k < voterCount - 1; k++) { + voters[k] = voters[k + 1]; + } + voterCount--; + continue; // Do not increment i since the list shifted + } + + i++; // Only increment if we didn't remove current voter + } +} + /// Classify mussel state based on topmost vote void alignVotes() { for (int i = 0; i < voterCount; i++) { @@ -132,48 +168,71 @@ void alignVotes() { } } -/// Decide whether a vote is valid based on gape and age +/// Decide whether a vote is valid based on age const char* qualifyMusselVote( - int gape, unsigned long voteTimestamp, unsigned long now + unsigned long voteTimestamp, unsigned long now ) { - - // Determine state based on gape - MusselGapState gapState = gape >= 40 && gape <= 90 ? Open : Closed; - const char* gapStateStr = (gapState == Open) ? "Open" : "Closed"; unsigned long age = now - voteTimestamp; - // Log the state - log_i("Qualifying vote | Time since vote: %lu ms | Gape: %d (%s)", - age, gape, gapStateStr); + // Log vote age and gape + log_i("Qualifying vote | Time since vote: %lu ms", age); - // Invalid if mussel is closed - if (gapState == Closed) { - log_i("→ INVALID: Mussel is Closed"); + if (age <= VOTE_TIME_INVALID) { + log_i("→ VALID: Vote is within 1 minute"); + return "valid"; + } else { + log_i("→ INVALID: Vote is older than 1 minute"); return "invalid"; } +} - // Invalid if vote is too old - if (age > VOTE_TIME_BEHIND) { - log_i("→ INVALID: Vote is too old (>2 minutes)"); - return "invalid"; - } +/// Output the final vote decision for mussels +void concludeMusselVote() { + int openVotes = 0; + int totalValidVotes = 0; - // Valid if within 1 minute and mussel is open - if (age <= VOTE_TIME_AHEAD) { - log_i("→ VALID: Mussel is Open and vote is recent"); - return "valid"; + // First, align votes and qualify them + for (int i = 0; i < voterCount; i++) { + Voter &voter = voters[i]; + + // Skip mussels with no data + if (voter.voteCount == 0) continue; + + // Use latest vote to determine state + Vote latest = voter.votes[voter.voteCount - 1]; + unsigned long now = millis(); + + // Check if the vote is valid using the qualify function + const char* validity = qualifyMusselVote(latest.timestamp, now); + + if (strcmp(validity, "valid") == 0) { + totalValidVotes++; // Count valid votes + + // Align and classify mussel state + String state = (latest.measure >= 0 && latest.measure < 40) + ? "Closed" + : (latest.measure >= 40 && latest.measure <= 90) + ? "Open" + : "Invalid reading"; + + // Count valid "Open" votes + if (state == "Open") { + openVotes++; + } + } } - // Catch-all for anything in between - log_i("→ INVALID: Vote is in uncertain window time"); - return "invalid"; -} + // Determine the threshold (half of total valid votes, rounded down) + int threshold = totalValidVotes / 2; -/// Output the final vote decision for a mussel -void concludeMusselVote(const String& musselId, const char* validity) { - const char* result = strcmp(validity, "valid") == 0 ? "YES" : "NO"; - log_i("Final Vote from Mussel %s → %s (Vote was %s)", - musselId.c_str(), result, validity); + // If the number of "Open" votes is greater than or equal to the threshold, water is drinkable + if (openVotes >= threshold) { + log_i("Final Vote: YES (Water is drinkable) - Open Votes: %d, Total Valid Votes: %d", + openVotes, totalValidVotes); + } else { + log_i("Final Vote: NO (Water is not drinkable) - Open Votes: %d, Total Valid Votes: %d", + openVotes, totalValidVotes); + } } // Bluetooth beacon discovery callbacks @@ -191,8 +250,7 @@ class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks { String id_mangled = advertisedDevice.getName(); id_mangled.replace(' ', '_'); id_mangled.replace(':', '='); - Serial.printf("%s:%d\n", - id_mangled.c_str(), EddystoneTLM.getTemp()); + Serial.println(id_mangled + ":" + EddystoneTLM.getTemp()); #endif unsigned long now = millis(); @@ -206,10 +264,11 @@ class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks { alignVotes(); // 3. Qualify - const char* validity = qualifyMusselVote(gape, now, millis()); + const char* validity = qualifyMusselVote( now, millis()); // 4. Conclude - concludeMusselVote(musselID, validity); + concludeMusselVote(); + } } }; @@ -222,7 +281,7 @@ void setup() { // setup Bluetooth BLEDevice::init(""); pBLEScan = BLEDevice::getScan(); - pBLEScan->setAdvertisedDeviceCallbacks( + pBLEScan->setAdvertisedDeviceCallbacks( new MyAdvertisedDeviceCallbacks()); pBLEScan->setActiveScan(true); pBLEScan->setInterval(SCAN_INTERVAL); @@ -234,5 +293,7 @@ void loop() { pBLEScan->start(SCAN_TIME_SEC, false); pBLEScan->clearResults(); + cleanOldVotes(); // Keeps the voter stack tidy + delay(500); } |