aboutsummaryrefslogtreecommitdiff
path: root/Mussel/Mussel.cpp
blob: a05f329cea9dfce4c8442dcbd6e54274d773860b (plain)
  1. // SPDX-FileCopyrightText: 2025 Amal Mazrah <mazrah@ruc.dk>
  2. // SPDX-FileCopyrightText: 2025 Jonas Smedegaard <dr@jones.dk>
  3. // SPDX-FileCopyrightText: 2025 Mennatullah Hatim Kassim <stud-mennatulla@ruc.dk>
  4. // SPDX-FileCopyrightText: 2025 Noor Ahmad <noora@ruc.dk>
  5. // SPDX-FileCopyrightText: 2025 Tanishka Suwalka <tanishkas@ruc.dk>
  6. // SPDX-License-Identifier: GPL-3.0-or-later
  7. /// Mussel - a small library for Arduino to emulate a mussel biosensor
  8. ///
  9. /// * v0.0.2
  10. /// * rewrite attitude #2 to also handle button press
  11. /// * add voting functions and example
  12. ///
  13. /// * v0.0.1
  14. /// * initial release to radicle
  15. ///
  16. /// @version 0.0.2
  17. /// @see <https://app.radicle.xyz/nodes/seed.radicle.garden/rad:z2tFBF4gN7ziG9oXtUytVQNYe3VhQ/tree/Mussel/README.md>
  18. /// @see <https://moodle.ruc.dk/course/view.php?id=23504>
  19. #include "Mussel.h"
  20. #include "Arduino.h"
  21. /// Main constructor
  22. ///
  23. /// @param attitude behavioral profile as integer
  24. Mussel::Mussel(int attitude) {
  25. _attitude = attitude;
  26. }
  27. /// Constructor for attitudes using an input pin
  28. ///
  29. /// @param attitude behavioral profile as integer
  30. /// @param pin Used pin as uint8_t
  31. Mussel::Mussel(int attitude, uint8_t pin) {
  32. _attitude = attitude;
  33. _pin = pin;
  34. }
  35. /// Constructor for attitudes using an input pin and a sensor type
  36. ///
  37. /// @param attitude behavioral profile as integer
  38. /// @param pin Used pin as uint8_t
  39. /// @param type type of sensor as uint8_t
  40. Mussel::Mussel(int attitude, uint8_t pin, uint8_t type)
  41. #ifdef DHT_H
  42. : mussel_dht(pin, type)
  43. #endif
  44. {
  45. _attitude = attitude;
  46. _pin = pin;
  47. }
  48. /// Setup function
  49. void Mussel::begin() {
  50. switch(_attitude) {
  51. case 1:
  52. _boolState = HIGH;
  53. // reset timer
  54. _time = millis();
  55. // use INPUT_PULLDOWN to signal when the button is held down
  56. // (not PULLUP which signals when it is released)
  57. pinMode(_pin, INPUT_PULLDOWN);
  58. break;
  59. #ifdef DHT_H
  60. case 3:
  61. mussel_dht.begin();
  62. break;
  63. #endif
  64. case 4:
  65. // use INPUT_PULLDOWN to signal when the button is held down
  66. // (not PULLUP which signals when it is released)
  67. pinMode(_pin, INPUT_PULLDOWN);
  68. break;
  69. case 5:
  70. _boolState = HIGH;
  71. _count = 0;
  72. _time = 0;
  73. // Enable internal pull-up resistor for button
  74. pinMode(_pin, INPUT_PULLUP);
  75. break;
  76. case 6:
  77. _boolState = HIGH;
  78. break;
  79. }
  80. }
  81. /// Description of mussel
  82. ///
  83. /// @return name and attitude of mussel as String
  84. String Mussel::desc() {
  85. String _str;
  86. switch(_attitude) {
  87. case 1:
  88. _str = "closed 10 seconds every 50 seconds and on button push";
  89. break;
  90. case 2:
  91. _str = "closed 4 seconds every 8 seconds";
  92. break;
  93. case 3:
  94. _str = "closed when cold";
  95. break;
  96. case 4:
  97. _str = "closed when button is pushed";
  98. break;
  99. case 5:
  100. _str = "changes state when button is pushed";
  101. break;
  102. case 6:
  103. _str = "changes state on ON/OFF command";
  104. break;
  105. case 10:
  106. _str = "handles voting";
  107. break;
  108. default:
  109. _str = "undefined [" + static_cast<String>(_attitude) + "]";
  110. break;
  111. }
  112. return _str;
  113. }
  114. /// Sensor reading
  115. ///
  116. /// * Values 0-99 is a measured relative mussel gape size
  117. /// * Values 100-254 are reserved for future use
  118. /// * Value 255 is an internal error
  119. ///
  120. /// @return relative gape size as value 0-255 encoded as int
  121. int Mussel::read() {
  122. int _read;
  123. switch(_attitude) {
  124. case 1:
  125. if (digitalRead(_pin)
  126. // TODO: account for rollover
  127. or (millis() - _time) > MUSSEL_NORMAL_PACE * 1000
  128. ) {
  129. _read = 2;
  130. _boolState = HIGH;
  131. // reset timer
  132. _time = millis();
  133. } else if (_boolState == HIGH
  134. and (millis() - _time) > MUSSEL_STRESS_PACE * 1000
  135. ) {
  136. _read = 42;
  137. _boolState = LOW;
  138. // reset timer
  139. _time = millis();
  140. }
  141. break;
  142. case 2:
  143. // 42 if current second modulo 12 is below 9, else 2
  144. _read = (static_cast<unsigned long>(millis() / 1000) % 12) < 9
  145. ? 42
  146. : 2;
  147. break;
  148. case 3:
  149. #ifdef DHT_H
  150. // temperature in Celsius
  151. _read = mussel_dht.readTemperature();
  152. #else
  153. _read = 255;
  154. #endif
  155. break;
  156. case 4:
  157. // 2 if button is pressed, else 42
  158. _read = digitalRead(_pin) == HIGH
  159. ? 2
  160. : 42;
  161. break;
  162. case 5: {
  163. bool _reading = digitalRead(_pin); // Read button state
  164. // Debounce logic:
  165. // Ensures a single press isn't detected multiple times
  166. if (_reading != _boolState) {
  167. _time = millis(); // Reset debounce timer
  168. }
  169. if ((millis() - _time) > MUSSEL_DEBOUNCE_DELAY) {
  170. // Check for button press (transition from HIGH to LOW)
  171. if (_reading == LOW && _boolState == HIGH) {
  172. _count++; // Increment click count
  173. if (_count > 3) {
  174. _count = 0; // Reset cycle after 3 clicks
  175. }
  176. switch (_count) {
  177. case 1: _read = 2; break; // State: Angry
  178. case 2: _read = 42; break; // State: Happy
  179. case 3: _read = 15; break; // State: Unsure
  180. case 0: _read = 99; break; // State: Off
  181. }
  182. }
  183. }
  184. _boolState = _reading; // Update button state
  185. break;
  186. }
  187. case 6:
  188. if (Serial.available() > 0) {
  189. String command = Serial.readStringUntil('\n');
  190. command.trim();
  191. if (command.equalsIgnoreCase("ON")) {
  192. _boolState = HIGH;
  193. }
  194. else if (command.equalsIgnoreCase("OFF")) {
  195. _boolState = LOW;
  196. }
  197. }
  198. _read = _boolState == HIGH
  199. ? 42
  200. : 2;
  201. break;
  202. default:
  203. _read = 255;
  204. break;
  205. }
  206. return _read;
  207. }
  208. /// Function to push data onto the stack
  209. bool Mussel::push(String id, unsigned long timestamp, int measure) {
  210. // Check if stack is full
  211. if (top >= STACK_SIZE - 1) {
  212. Serial.println("Stack Full");
  213. // Return false if stack is full
  214. return false;
  215. }
  216. top++;
  217. idStack[top] = id;
  218. timeStack[top] = timestamp;
  219. measureStack[top] = measure;
  220. // Return true on successful push
  221. return true;
  222. }
  223. /// Function to print stack contents
  224. void Mussel::printStack() {
  225. for (int i = top; i >= 0; i--) {
  226. Serial.print("ID: "); Serial.print(idStack[i]);
  227. Serial.print(", Time: "); Serial.print(timeStack[i]);
  228. Serial.print(", measure: "); Serial.println(measureStack[i]);
  229. }
  230. }
  231. bool qualifyVote(Vote vote, unsigned long currentTime) {
  232. // If the measure is 42 (YES), check timestamp validity
  233. if (vote.measure == 42) {
  234. // If the vote's timestamp is within 1 minute, count it as YES
  235. if (currentTime - vote.timestamp <= MUSSEL_VOTE_TIME_AHEAD) {
  236. return true;
  237. }
  238. // If the vote's timestamp is older than 2 minutes, count it as NO
  239. else if (currentTime - vote.timestamp > MUSSEL_VOTE_TIME_BEHIND) {
  240. return false;
  241. }
  242. }
  243. // If the measure is 2, always count the vote as NO
  244. if (vote.measure == 2) {
  245. return false;
  246. }
  247. // Default case: vote is invalid if no conditions are met
  248. return false;
  249. }
  250. /// Dump internal variables, formatted for use with Serial Plotter
  251. ///
  252. /// @return internal variables as String
  253. String Mussel::debug() {
  254. return static_cast<String>(
  255. "pin:") + _pin
  256. + "\tboolState:" + _boolState
  257. + "\ttime:" + _time
  258. + "\tcount:" + _count;
  259. }