aboutsummaryrefslogtreecommitdiff
path: root/vote
diff options
context:
space:
mode:
authorTanishka Suwalka <tanishkas@ruc.dk>2025-04-16 22:51:19 +0200
committerJonas Smedegaard <dr@jones.dk>2025-04-16 22:51:19 +0200
commitfb48424cadc57170d39de0ac0b0450f373ef74fc (patch)
tree01a491bd8a43d412661ebf81255270a23d42c6b6 /vote
parent120ac51294ddae7b479c916382cd8be96f39cf2d (diff)
cleanup stack; simplify qualify() limits; change onclude()
Diffstat (limited to 'vote')
-rw-r--r--vote/vote.ino135
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);
}