shot_control.cpp 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. #include "shot_control.h"
  2. #include "diagnostic_queue.h"
  3. #include "heater_controller.h"
  4. #include "power_schedule.h"
  5. #include "program_memory.h"
  6. #include "serial_output.h"
  7. #if defined(MECOFFEE_RUST_SHOT)
  8. #include <avr/io.h>
  9. #include <avr/interrupt.h>
  10. #include <stddef.h>
  11. struct RustShotSettings {uint16_t enabled,closed,pump,pause,maximum;};
  12. extern "C" {
  13. void mecoffee_rust_shot_event(meCoffee::ShotState*,uint8_t,uint32_t,uint32_t,void*);
  14. void mecoffee_rust_shot_active(meCoffee::ShotState*,const RustShotSettings*,uint8_t,uint32_t,uint32_t,void*);
  15. void mecoffee_rust_poll_input(meCoffee::DebouncedInput*,uint8_t,uint8_t,uint32_t,void*);
  16. uint16_t mecoffee_rust_shot_layout(uint8_t);
  17. }
  18. static_assert(sizeof(meCoffee::ShotState)==5,"shot state ABI");
  19. static_assert(sizeof(meCoffee::DebouncedInput)==15,"input state ABI");
  20. static_assert(offsetof(meCoffee::DebouncedInput,beginTimestamp)==3,"input begin ABI");
  21. #endif
  22. namespace meCoffee {
  23. namespace {
  24. #if defined(MECOFFEE_RUST_TELEMETRY)
  25. extern "C" void mecoffee_rust_telemetry(void*,uint8_t);
  26. void reportShotBegin(FirmwareState& state) { mecoffee_rust_telemetry(&state,3); }
  27. void reportShotEnd(FirmwareState& state) { mecoffee_rust_telemetry(&state,4); }
  28. #else
  29. void reportShotBegin(FirmwareState& state) {
  30. if (state.alternateModeSelection != 0) {
  31. return;
  32. }
  33. writeProgramMemoryText(MECOFFEE_PROGRAM_TEXT("sht "));
  34. writeUnsignedDecimalText(state.sensor.aggregateGeneration);
  35. writeProgramMemoryText(MECOFFEE_PROGRAM_TEXT(" 0 OK"));
  36. writeCarriageReturnLineFeed();
  37. }
  38. void reportShotEnd(FirmwareState& state) {
  39. if (state.alternateModeSelection != 0) {
  40. return;
  41. }
  42. writeProgramMemoryText(MECOFFEE_PROGRAM_TEXT("sht "));
  43. writeUnsignedDecimalText(state.sensor.aggregateGeneration);
  44. writeSpace();
  45. writeUnsignedDecimalText(state.shot.lastShotDuration);
  46. writeProgramMemoryText(MECOFFEE_PROGRAM_TEXT(" OK"));
  47. writeCarriageReturnLineFeed();
  48. }
  49. #endif
  50. void beginShotEvent(FirmwareState* state, uint32_t timestamp) {
  51. #if defined(MECOFFEE_RUST_SHOT)
  52. mecoffee_rust_shot_event(&state->shot,0,timestamp,0,state);
  53. #else
  54. // Observed in the original: a shot begin counts as user activity for the inactivity timer.
  55. state->powerSchedule.lastActivityTimestamp = timestamp;
  56. state->shot.isTimeoutEventLatched = false;
  57. beginShotFeedForward(state, timestamp);
  58. enqueueDiagnosticCallback(&state->diagnosticQueue, reportShotBegin);
  59. #endif
  60. }
  61. void endShotEvent(FirmwareState* state, uint32_t endTimestamp, uint32_t beginTimestamp) {
  62. #if defined(MECOFFEE_RUST_SHOT)
  63. mecoffee_rust_shot_event(&state->shot,1,endTimestamp,beginTimestamp,state);
  64. #else
  65. setGroupEnabled(&groupForRole(&state->outputChannels, OutputGroupRole::Pump), false);
  66. setGroupEnabled(&groupForRole(&state->outputChannels, OutputGroupRole::Valve), false);
  67. state->shot.lastShotDuration = endTimestamp - beginTimestamp;
  68. endShotFeedForward(state, endTimestamp);
  69. enqueueDiagnosticCallback(&state->diagnosticQueue, reportShotEnd);
  70. #endif
  71. }
  72. void runShotActiveCallback(FirmwareState* state, uint32_t foregroundSnapshotMilliseconds, uint32_t beginTimestamp) {
  73. #if defined(MECOFFEE_RUST_SHOT)
  74. const auto& s=state->settings;
  75. const RustShotSettings settings={s.preinfusionEnabled,s.preinfusionValveClosed,s.preinfusionPumpDuration,s.preinfusionPauseDuration,s.maximumShotDuration};
  76. mecoffee_rust_shot_active(&state->shot,&settings,state->alternateModeSelection,foregroundSnapshotMilliseconds,beginTimestamp,state);
  77. #else
  78. const uint32_t elapsed = foregroundSnapshotMilliseconds - beginTimestamp;
  79. const ShotOutputRequest request = evaluateShotOutputRequest(state->settings, state->alternateModeSelection, elapsed);
  80. OutputGroup& pump = groupForRole(&state->outputChannels, OutputGroupRole::Pump);
  81. OutputGroup& valve = groupForRole(&state->outputChannels, OutputGroupRole::Valve);
  82. if (request.isTimedOut) {
  83. setGroupEnabled(&pump, false);
  84. setGroupEnabled(&valve, false);
  85. if (!state->shot.isTimeoutEventLatched) {
  86. state->shot.isTimeoutEventLatched = true;
  87. endShotEvent(state, foregroundSnapshotMilliseconds, beginTimestamp);
  88. }
  89. return;
  90. }
  91. state->shot.isTimeoutEventLatched = false;
  92. setGroupEnabled(&pump, request.isPumpRequested);
  93. setGroupEnabled(&valve, request.isValveRequested);
  94. #endif
  95. }
  96. // Begin (argument 1) and end (argument 0) of an input burst.
  97. void runInputStateChange(FirmwareState* state, InputRole role, uint8_t argument, uint32_t timestamp, uint32_t beginTimestamp) {
  98. switch (role) {
  99. case InputRole::Power:
  100. handlePowerInputChange(state, argument, timestamp);
  101. break;
  102. case InputRole::Mode:
  103. handleModeInputChange(state, argument, timestamp);
  104. break;
  105. case InputRole::Shot:
  106. if (argument != 0) {
  107. beginShotEvent(state, timestamp);
  108. } else {
  109. endShotEvent(state, timestamp, beginTimestamp);
  110. }
  111. break;
  112. }
  113. }
  114. } // namespace
  115. bool isInputQualified(const DebouncedInput& input) {
  116. return input.risingEdgeCount >= kRequiredEdgesPerLevel && input.fallingEdgeCount >= kRequiredEdgesPerLevel;
  117. }
  118. void initializeDebouncedInput(DebouncedInput* input) {
  119. #if defined(MECOFFEE_RUST_SHOT)
  120. if(mecoffee_rust_shot_layout(0)!=5 || mecoffee_rust_shot_layout(1)!=15 || mecoffee_rust_shot_layout(2)!=10 || mecoffee_rust_shot_layout(3)!=3) {cli();for(;;){}}
  121. #endif
  122. *input = DebouncedInput{};
  123. input->isLastLevelHigh = true;
  124. }
  125. ShotOutputRequest evaluateShotOutputRequest(const SettingsValues& settings, uint8_t alternateMode, uint32_t elapsedMilliseconds) {
  126. const uint16_t gapEnd = static_cast<uint16_t>(settings.preinfusionPumpDuration + settings.preinfusionPauseDuration);
  127. const bool isInGap = settings.preinfusionEnabled != 0 && elapsedMilliseconds > settings.preinfusionPumpDuration &&
  128. elapsedMilliseconds < gapEnd;
  129. const uint16_t timeoutThreshold = static_cast<uint16_t>(static_cast<uint32_t>(settings.maximumShotDuration) * 1000u);
  130. ShotOutputRequest request;
  131. request.isTimedOut = settings.maximumShotDuration != 0 && elapsedMilliseconds > timeoutThreshold;
  132. request.isPumpRequested = !isInGap && !request.isTimedOut;
  133. request.isValveRequested = !request.isTimedOut && alternateMode == 0 && (!isInGap || settings.preinfusionValveClosed == 0);
  134. return request;
  135. }
  136. void pollDebouncedInput(FirmwareState* state, DebouncedInput* input, InputRole role, bool isPinHigh,
  137. uint32_t foregroundSnapshotMilliseconds) {
  138. #if defined(MECOFFEE_RUST_SHOT)
  139. mecoffee_rust_poll_input(input,static_cast<uint8_t>(role),isPinHigh,foregroundSnapshotMilliseconds,state);
  140. #else
  141. if (isPinHigh != input->isLastLevelHigh) {
  142. input->isLastLevelHigh = isPinHigh;
  143. input->lastChangeTimestamp = foregroundSnapshotMilliseconds;
  144. uint8_t& edgeCount = isPinHigh ? input->risingEdgeCount : input->fallingEdgeCount;
  145. if (edgeCount < 255) {
  146. ++edgeCount;
  147. }
  148. if (!isInputQualified(*input)) {
  149. return;
  150. }
  151. // The begin guard and the active callback run in the same call, so the first qualified
  152. // falling edge both begins the burst and requests the outputs.
  153. if (!isPinHigh && input->beginTimestamp == 0) {
  154. input->beginTimestamp = foregroundSnapshotMilliseconds;
  155. runInputStateChange(state, role, 1, foregroundSnapshotMilliseconds, 0);
  156. }
  157. if (role == InputRole::Shot) {
  158. runShotActiveCallback(state, foregroundSnapshotMilliseconds, input->beginTimestamp);
  159. }
  160. return;
  161. }
  162. if (input->lastChangeTimestamp != 0 &&
  163. static_cast<uint32_t>(foregroundSnapshotMilliseconds - input->lastChangeTimestamp) >= kInputStableDuration) {
  164. const bool wasQualified = isInputQualified(*input);
  165. const uint32_t beginTimestamp = input->beginTimestamp;
  166. input->risingEdgeCount = 0;
  167. input->fallingEdgeCount = 0;
  168. input->beginTimestamp = 0;
  169. input->lastChangeTimestamp = 0;
  170. if (wasQualified) {
  171. input->endTimestamp = foregroundSnapshotMilliseconds;
  172. runInputStateChange(state, role, 0, foregroundSnapshotMilliseconds, beginTimestamp);
  173. }
  174. }
  175. #endif
  176. }
  177. #if defined(MECOFFEE_RUST_SHOT)
  178. extern "C" void mecoffee_shot_effect(void* context,uint8_t operation,uint32_t value) {
  179. auto* state=static_cast<FirmwareState*>(context);
  180. switch(operation) {
  181. case 0: state->powerSchedule.lastActivityTimestamp=value;break;
  182. case 1: {
  183. const uint8_t saved=SREG;cli();
  184. setGroupEnabled(&groupForRole(&state->outputChannels,static_cast<OutputGroupRole>(value&255)),(value&256)!=0);
  185. SREG=saved;break;
  186. }
  187. case 2: beginShotFeedForward(state,value);break;
  188. case 3: endShotFeedForward(state,value);break;
  189. case 4: enqueueDiagnosticCallback(&state->diagnosticQueue,reportShotBegin);break;
  190. case 5: enqueueDiagnosticCallback(&state->diagnosticQueue,reportShotEnd);break;
  191. }
  192. }
  193. extern "C" void mecoffee_input_event(void* context,uint8_t role,uint8_t active,uint32_t now,uint32_t begin) {
  194. auto* state=static_cast<FirmwareState*>(context);
  195. // Use the supplied timestamp, never read the Rust-borrowed DebouncedInput.
  196. if(active==2) runShotActiveCallback(state,now,begin);
  197. else runInputStateChange(state,static_cast<InputRole>(role),active,now,begin);
  198. }
  199. #endif
  200. } // namespace meCoffee