aboutsummaryrefslogtreecommitdiff
path: root/Mussel/Mussel.cpp
blob: 568030af18a8aa90ce0ab3b767c0216123d7a4b2 (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 byte
  121. byte Mussel::read() {
  122. byte _byte;
  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. _byte = 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. _byte = 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. _byte = static_cast<byte>(
  145. (static_cast<unsigned long>(millis() / 1000) % 12) < 9
  146. ? 42
  147. : 2);
  148. break;
  149. case 3:
  150. #ifdef DHT_H
  151. // temperature in Celsius
  152. _byte = static_cast<byte>(
  153. mussel_dht.readTemperature());
  154. #else
  155. _byte = 255;
  156. #endif
  157. break;
  158. case 4:
  159. // 2 if button is pressed, else 42
  160. _byte = static_cast<byte>(
  161. digitalRead(_pin) == HIGH
  162. ? 2
  163. : 42);
  164. break;
  165. case 5: {
  166. bool _reading = digitalRead(_pin); // Read button state
  167. // Debounce logic:
  168. // Ensures a single press isn't detected multiple times
  169. if (_reading != _boolState) {
  170. _time = millis(); // Reset debounce timer
  171. }
  172. if ((millis() - _time) > MUSSEL_DEBOUNCE_DELAY) {
  173. // Check for button press (transition from HIGH to LOW)
  174. if (_reading == LOW && _boolState == HIGH) {
  175. _count++; // Increment click count
  176. if (_count > 3) {
  177. _count = 0; // Reset cycle after 3 clicks
  178. }
  179. switch (_count) {
  180. case 1: _byte = 2; break; // State: Angry
  181. case 2: _byte = 42; break; // State: Happy
  182. case 3: _byte = 15; break; // State: Unsure
  183. case 0: _byte = 99; break; // State: Off
  184. }
  185. }
  186. }
  187. _boolState = _reading; // Update button state
  188. break;
  189. }
  190. case 6:
  191. if (Serial.available() > 0) {
  192. String command = Serial.readStringUntil('\n');
  193. command.trim();
  194. if (command.equalsIgnoreCase("ON")) {
  195. _boolState = HIGH;
  196. }
  197. else if (command.equalsIgnoreCase("OFF")) {
  198. _boolState = LOW;
  199. }
  200. }
  201. _byte = _boolState == HIGH
  202. ? 42
  203. : 2;
  204. break;
  205. default:
  206. _byte = 255;
  207. break;
  208. }
  209. return _byte;
  210. }
  211. /// Function to push data onto the stack
  212. bool Mussel::push(String id, unsigned long timestamp, int measure) {
  213. // Check if stack is full
  214. if (top >= STACK_SIZE - 1) {
  215. Serial.println("Stack Full");
  216. // Return false if stack is full
  217. return false;
  218. }
  219. top++;
  220. idStack[top] = id;
  221. timeStack[top] = timestamp;
  222. measureStack[top] = measure;
  223. // Return true on successful push
  224. return true;
  225. }
  226. /// Function to print stack contents
  227. void Mussel::printStack() {
  228. for (int i = top; i >= 0; i--) {
  229. Serial.print("ID: "); Serial.print(idStack[i]);
  230. Serial.print(", Time: "); Serial.print(timeStack[i]);
  231. Serial.print(", measure: "); Serial.println(measureStack[i]);
  232. }
  233. }
  234. /// Dump internal variables, formatted for use with Serial Plotter
  235. ///
  236. /// @return internal variables as String
  237. String Mussel::debug() {
  238. return static_cast<String>(
  239. "pin:") + _pin
  240. + "\tboolState:" + _boolState
  241. + "\ttime:" + _time
  242. + "\tcount:" + _count;
  243. }