aboutsummaryrefslogtreecommitdiff
path: root/vote
diff options
context:
space:
mode:
authorTanishka Suwalka <tanishkas@ruc.dk>2025-04-16 20:17:34 +0200
committerJonas Smedegaard <dr@jones.dk>2025-04-16 20:45:07 +0200
commit8354610bcdc3267b8628394c3c2d9b368a659b25 (patch)
tree85135c8c3c8d050ec21426dcd362c0c53ff04f6a /vote
parent8a34f5c4359e074bc895dc5ed69c394748b44757 (diff)
store, align, qualify and conclude
Diffstat (limited to 'vote')
-rw-r--r--vote/vote.ino182
1 files changed, 133 insertions, 49 deletions
diff --git a/vote/vote.ino b/vote/vote.ino
index 8036408..13b425a 100644
--- a/vote/vote.ino
+++ b/vote/vote.ino
@@ -32,25 +32,141 @@
#define SCAN_WINDOW 99
#define SCAN_TIME_SEC 1
+// stack sizes for voters and ballots-per-voter
+#define VOTER_MAX 10
+#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
-// Limited size NOW!! can be transformed into infinite array
-#define STACK_SIZE 1000
+// Classify gape state
+enum MusselGapState {
+ Closed,
+ Open
+};
+// Data structures
struct Vote {
- String id;
unsigned long timestamp;
int measure;
};
-// Index of the top element in the stack, -1 means stack is empty
-int top = -1;
+struct Voter {
+ String id; // Mussel ID
+ Vote votes[BALLOT_MAX]; // Last 5 sensor readings
+ int voteCount = 0; // Number of readings stored
+};
+
+// Global array of mussel voters
+Voter voters[VOTER_MAX];
+int voterCount = 0;
// pointer to control Bluetooth networking
BLEScan *pBLEScan;
+/// Find index of mussel ID in the voters array
+int findVoterIndex(const String& id) {
+ for (int i = 0; i < voterCount; i++) {
+ if (voters[i].id == id) return i;
+ }
+ return -1; // Not found
+}
+
+/// Add or update vote for a mussel ID
+void storeVoteForMussel(const String& id, unsigned long timestamp, int gape_measure) {
+ int index = findVoterIndex(id);
+
+ // If mussel not found, add new
+ if (index == -1) {
+ if (voterCount >= VOTER_MAX) {
+ Serial.printf("Ignored: Max mussel limit reached (%s)\n", id.c_str());
+ return;
+ }
+ voters[voterCount].id = id;
+ voters[voterCount].voteCount = 0;
+ index = voterCount++;
+ }
+
+ Voter &voter = voters[index];
+
+ // Maintain a fixed number of stored votes (FIFO logic)
+ if (voter.voteCount >= BALLOT_MAX) {
+ for (int i = 1; i < BALLOT_MAX; i++) {
+ voter.votes[i - 1] = voter.votes[i];
+ }
+ voter.voteCount = BALLOT_MAX - 1;
+ }
+
+ // Store the new vote at the end
+ voter.votes[voter.voteCount++] = {timestamp, gape_measure};
+ Serial.printf("Vote stored: Time: %lu, Mussel: %s, Gape: %d\n", timestamp, id.c_str(), gape_measure);
+}
+
+/// Classify mussel state based on topmost vote
+void alignVotes() {
+ Serial.println("---- Mussel Alignment Status ----");
+ for (int i = 0; i < voterCount; i++) {
+ Voter &voter = voters[i];
+
+ // Skip mussels with no data
+ if (voter.voteCount == 0) {
+ Serial.printf("Mussel ID: %s - No data\n", voter.id.c_str());
+ continue;
+ }
+
+ // Use latest vote to determine state
+ Vote latest = voter.votes[voter.voteCount - 1];
+ String state = (latest.measure >= 0 && latest.measure < 40) ? "Closed" :
+ (latest.measure >= 40 && latest.measure <= 90) ? "Open" :
+ "Invalid reading";
+
+ Serial.printf("Mussel ID: %s | Latest Gape: %d | State: %s\n",
+ voter.id.c_str(), latest.measure, state.c_str());
+ }
+ Serial.println("---------------------------------");
+}
+
+/// Decide whether a vote is valid based on gape and age
+const char* qualifyMusselVote(int gape, 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
+ Serial.printf("Qualifying vote | Time since vote: %lu ms | Gape: %d (%s)\n", age, gape, gapStateStr);
+
+ // Invalid if mussel is closed
+ if (gapState == Closed) {
+ Serial.println("→ INVALID: Mussel is Closed.");
+ return "invalid";
+ }
+
+ // Invalid if vote is too old
+ if (age > VOTE_TIME_BEHIND) {
+ Serial.println("→ INVALID: Vote is too old (>2 minutes).");
+ return "invalid";
+ }
+
+ // Valid if within 1 minute and mussel is open
+ if (age <= VOTE_TIME_AHEAD) {
+ Serial.println("→ VALID: Mussel is Open and vote is recent.");
+ return "valid";
+ }
+
+ // Catch-all for anything in between
+ Serial.println("→ INVALID: Vote is in uncertain window time");
+ return "invalid";
+}
+
+/// 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";
+ Serial.printf("Final Vote from Mussel %s → %s (Vote was %s)\n", musselId.c_str(), result, validity);
+}
+
// Bluetooth beacon discovery callbacks
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
@@ -69,56 +185,24 @@ class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
Serial.println(id_mangled + ":" + EddystoneTLM.getTemp());
#endif
- push(
- advertisedDevice.getName(),
- millis(),
- EddystoneTLM.getTemp()
- );
- }
- }
-};
-
-/// Function to push data onto the stack
-bool push(String id, unsigned long timestamp, int measure) {
+ unsigned long now = millis();
+ String musselID = advertisedDevice.getName();
+ int gape = EddystoneTLM.getTemp(); // Referring to gape_measure
- // Check if stack is full
- if (top >= STACK_SIZE - 1) {
- Serial.println("Stack Full");
+ // 1. Store vote
+ storeVoteForMussel(musselID, now, gape);
- // Return false if stack is full
- return false;
- }
- top++;
- idStack[top] = id;
- timeStack[top] = timestamp;
- measureStack[top] = measure;
-
- // Return true on successful push
- return true;
-}
+ // 2. Align
+ alignVotes();
-bool qualifyVote(Vote vote, unsigned long currentTime) {
+ // 3. Qualify
+ const char* validity = qualifyMusselVote(gape, now, millis());
- // If the measure is 42 (YES), check timestamp validity
- if (vote.measure == 42) {
- // If the vote's timestamp is within 1 minute, count it as YES
- if (currentTime - vote.timestamp <= VOTE_TIME_AHEAD) {
- return true;
+ // 4. Conclude
+ concludeMusselVote(musselID, validity);
}
- // If the vote's timestamp is older than 2 minutes, count it as NO
- else if (currentTime - vote.timestamp > VOTE_TIME_BEHIND) {
- return false;
- }
- }
-
- // If the measure is 2, always count the vote as NO
- if (vote.measure == 2) {
- return false;
}
-
- // Default case: vote is invalid if no conditions are met
- return false;
-}
+};
void setup() {
// enable logging to serial