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