- // SPDX-FileCopyrightText: 2025 Amal Mazrah <mazrah@ruc.dk>
- // SPDX-FileCopyrightText: 2025 Jonas Smedegaard <dr@jones.dk>
- // SPDX-FileCopyrightText: 2025 Mennatullah Hatim Kassim <stud-mennatulla@ruc.dk>
- // SPDX-FileCopyrightText: 2025 Noor Ahmad <noora@ruc.dk>
- // SPDX-FileCopyrightText: 2025 Tanishka Suwalka <tanishkas@ruc.dk>
- // SPDX-License-Identifier: GPL-3.0-or-later
- /// Mussel - a small library for Arduino to emulate a mussel biosensor
- ///
- /// * v0.0.2
- /// * rewrite attitude #2 to also handle button press
- /// * add voting functions and example
- ///
- /// * v0.0.1
- /// * initial release to radicle
- ///
- /// @version 0.0.2
- /// @see <https://app.radicle.xyz/nodes/seed.radicle.garden/rad:z2tFBF4gN7ziG9oXtUytVQNYe3VhQ/tree/Mussel/README.md>
- /// @see <https://moodle.ruc.dk/course/view.php?id=23504>
- #include "Mussel.h"
- #include "Arduino.h"
- /// Main constructor
- ///
- /// @param attitude behavioral profile as integer
- Mussel::Mussel(int attitude) {
- _attitude = attitude;
- }
- /// Constructor for attitudes using an input pin
- ///
- /// @param attitude behavioral profile as integer
- /// @param pin Used pin as uint8_t
- Mussel::Mussel(int attitude, uint8_t pin) {
- _attitude = attitude;
- _pin = pin;
- }
- /// Constructor for attitudes using an input pin and a sensor type
- ///
- /// @param attitude behavioral profile as integer
- /// @param pin Used pin as uint8_t
- /// @param type type of sensor as uint8_t
- Mussel::Mussel(int attitude, uint8_t pin, uint8_t type)
- #ifdef DHT_H
- : mussel_dht(pin, type)
- #endif
- {
- _attitude = attitude;
- _pin = pin;
- }
- /// Setup function
- void Mussel::begin() {
- switch(_attitude) {
- case 1:
- _boolState = HIGH;
- // reset timer
- _time = millis();
- // use INPUT_PULLDOWN to signal when the button is held down
- // (not PULLUP which signals when it is released)
- pinMode(_pin, INPUT_PULLDOWN);
- break;
- #ifdef DHT_H
- case 3:
- mussel_dht.begin();
- break;
- #endif
- case 4:
- // use INPUT_PULLDOWN to signal when the button is held down
- // (not PULLUP which signals when it is released)
- pinMode(_pin, INPUT_PULLDOWN);
- break;
- case 5:
- _boolState = HIGH;
- _count = 0;
- _time = 0;
- // Enable internal pull-up resistor for button
- pinMode(_pin, INPUT_PULLUP);
- break;
- case 6:
- _boolState = HIGH;
- break;
- }
- }
- /// Description of mussel
- ///
- /// @return name and attitude of mussel as String
- String Mussel::desc() {
- String _str;
- switch(_attitude) {
- case 1:
- _str = "closed 10 seconds every 50 seconds and on button push";
- break;
- case 2:
- _str = "closed 4 seconds every 8 seconds";
- break;
- case 3:
- _str = "closed when cold";
- break;
- case 4:
- _str = "closed when button is pushed";
- break;
- case 5:
- _str = "changes state when button is pushed";
- break;
- case 6:
- _str = "changes state on ON/OFF command";
- break;
- case 10:
- _str = "handles voting";
- break;
- default:
- _str = "undefined [" + static_cast<String>(_attitude) + "]";
- break;
- }
- return _str;
- }
- /// Sensor reading
- ///
- /// * Values 0-99 is a measured relative mussel gape size
- /// * Values 100-254 are reserved for future use
- /// * Value 255 is an internal error
- ///
- /// @return relative gape size as value 0-255 encoded as int
- int Mussel::read() {
- int _read;
- switch(_attitude) {
- case 1:
- if (digitalRead(_pin)
- // TODO: account for rollover
- or (millis() - _time) > MUSSEL_NORMAL_PACE * 1000
- ) {
- _read = 2;
- _boolState = HIGH;
- // reset timer
- _time = millis();
- } else if (_boolState == HIGH
- and (millis() - _time) > MUSSEL_STRESS_PACE * 1000
- ) {
- _read = 42;
- _boolState = LOW;
- // reset timer
- _time = millis();
- }
- break;
- case 2:
- // 42 if current second modulo 12 is below 9, else 2
- _read = (static_cast<unsigned long>(millis() / 1000) % 12) < 9
- ? 42
- : 2;
- break;
- case 3:
- #ifdef DHT_H
- // temperature in Celsius
- _read = mussel_dht.readTemperature();
- #else
- _read = 255;
- #endif
- break;
- case 4:
- // 2 if button is pressed, else 42
- _read = digitalRead(_pin) == HIGH
- ? 2
- : 42;
- break;
- case 5: {
- bool _reading = digitalRead(_pin); // Read button state
- // Debounce logic:
- // Ensures a single press isn't detected multiple times
- if (_reading != _boolState) {
- _time = millis(); // Reset debounce timer
- }
- if ((millis() - _time) > MUSSEL_DEBOUNCE_DELAY) {
- // Check for button press (transition from HIGH to LOW)
- if (_reading == LOW && _boolState == HIGH) {
- _count++; // Increment click count
- if (_count > 3) {
- _count = 0; // Reset cycle after 3 clicks
- }
- switch (_count) {
- case 1: _read = 2; break; // State: Angry
- case 2: _read = 42; break; // State: Happy
- case 3: _read = 15; break; // State: Unsure
- case 0: _read = 99; break; // State: Off
- }
- }
- }
- _boolState = _reading; // Update button state
- break;
- }
- case 6:
- if (Serial.available() > 0) {
- String command = Serial.readStringUntil('\n');
- command.trim();
- if (command.equalsIgnoreCase("ON")) {
- _boolState = HIGH;
- }
- else if (command.equalsIgnoreCase("OFF")) {
- _boolState = LOW;
- }
- }
- _read = _boolState == HIGH
- ? 42
- : 2;
- break;
- default:
- _read = 255;
- break;
- }
- return _read;
- }
- /// Function to push data onto the stack
- bool Mussel::push(String id, unsigned long timestamp, int measure) {
- // Check if stack is full
- if (top >= STACK_SIZE - 1) {
- Serial.println("Stack Full");
- // 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;
- }
- /// Function to print stack contents
- void Mussel::printStack() {
- for (int i = top; i >= 0; i--) {
- Serial.print("ID: "); Serial.print(idStack[i]);
- Serial.print(", Time: "); Serial.print(timeStack[i]);
- Serial.print(", measure: "); Serial.println(measureStack[i]);
- }
- }
- bool qualifyVote(Vote vote, unsigned long currentTime) {
- // 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 <= MUSSEL_VOTE_TIME_AHEAD) {
- return true;
- }
- // If the vote's timestamp is older than 2 minutes, count it as NO
- else if (currentTime - vote.timestamp > MUSSEL_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;
- }
- /// Dump internal variables, formatted for use with Serial Plotter
- ///
- /// @return internal variables as String
- String Mussel::debug() {
- return static_cast<String>(
- "pin:") + _pin
- + "\tboolState:" + _boolState
- + "\ttime:" + _time
- + "\tcount:" + _count;
- }
|