| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- #include "shot_control.h"
- #include "diagnostic_queue.h"
- #include "heater_controller.h"
- #include "power_schedule.h"
- #include "program_memory.h"
- #include "serial_output.h"
- #if defined(MECOFFEE_RUST_SHOT)
- #include <avr/io.h>
- #include <avr/interrupt.h>
- #include <stddef.h>
- struct RustShotSettings {uint16_t enabled,closed,pump,pause,maximum;};
- extern "C" {
- void mecoffee_rust_shot_event(meCoffee::ShotState*,uint8_t,uint32_t,uint32_t,void*);
- void mecoffee_rust_shot_active(meCoffee::ShotState*,const RustShotSettings*,uint8_t,uint32_t,uint32_t,void*);
- void mecoffee_rust_poll_input(meCoffee::DebouncedInput*,uint8_t,uint8_t,uint32_t,void*);
- uint16_t mecoffee_rust_shot_layout(uint8_t);
- }
- static_assert(sizeof(meCoffee::ShotState)==5,"shot state ABI");
- static_assert(sizeof(meCoffee::DebouncedInput)==15,"input state ABI");
- static_assert(offsetof(meCoffee::DebouncedInput,beginTimestamp)==3,"input begin ABI");
- #endif
- namespace meCoffee {
- namespace {
- #if defined(MECOFFEE_RUST_TELEMETRY)
- extern "C" void mecoffee_rust_telemetry(void*,uint8_t);
- void reportShotBegin(FirmwareState& state) { mecoffee_rust_telemetry(&state,3); }
- void reportShotEnd(FirmwareState& state) { mecoffee_rust_telemetry(&state,4); }
- #else
- void reportShotBegin(FirmwareState& state) {
- if (state.alternateModeSelection != 0) {
- return;
- }
- writeProgramMemoryText(MECOFFEE_PROGRAM_TEXT("sht "));
- writeUnsignedDecimalText(state.sensor.aggregateGeneration);
- writeProgramMemoryText(MECOFFEE_PROGRAM_TEXT(" 0 OK"));
- writeCarriageReturnLineFeed();
- }
- void reportShotEnd(FirmwareState& state) {
- if (state.alternateModeSelection != 0) {
- return;
- }
- writeProgramMemoryText(MECOFFEE_PROGRAM_TEXT("sht "));
- writeUnsignedDecimalText(state.sensor.aggregateGeneration);
- writeSpace();
- writeUnsignedDecimalText(state.shot.lastShotDuration);
- writeProgramMemoryText(MECOFFEE_PROGRAM_TEXT(" OK"));
- writeCarriageReturnLineFeed();
- }
- #endif
- void beginShotEvent(FirmwareState* state, uint32_t timestamp) {
- #if defined(MECOFFEE_RUST_SHOT)
- mecoffee_rust_shot_event(&state->shot,0,timestamp,0,state);
- #else
- // Observed in the original: a shot begin counts as user activity for the inactivity timer.
- state->powerSchedule.lastActivityTimestamp = timestamp;
- state->shot.isTimeoutEventLatched = false;
- beginShotFeedForward(state, timestamp);
- enqueueDiagnosticCallback(&state->diagnosticQueue, reportShotBegin);
- #endif
- }
- void endShotEvent(FirmwareState* state, uint32_t endTimestamp, uint32_t beginTimestamp) {
- #if defined(MECOFFEE_RUST_SHOT)
- mecoffee_rust_shot_event(&state->shot,1,endTimestamp,beginTimestamp,state);
- #else
- setGroupEnabled(&groupForRole(&state->outputChannels, OutputGroupRole::Pump), false);
- setGroupEnabled(&groupForRole(&state->outputChannels, OutputGroupRole::Valve), false);
- state->shot.lastShotDuration = endTimestamp - beginTimestamp;
- endShotFeedForward(state, endTimestamp);
- enqueueDiagnosticCallback(&state->diagnosticQueue, reportShotEnd);
- #endif
- }
- void runShotActiveCallback(FirmwareState* state, uint32_t foregroundSnapshotMilliseconds, uint32_t beginTimestamp) {
- #if defined(MECOFFEE_RUST_SHOT)
- const auto& s=state->settings;
- const RustShotSettings settings={s.preinfusionEnabled,s.preinfusionValveClosed,s.preinfusionPumpDuration,s.preinfusionPauseDuration,s.maximumShotDuration};
- mecoffee_rust_shot_active(&state->shot,&settings,state->alternateModeSelection,foregroundSnapshotMilliseconds,beginTimestamp,state);
- #else
- const uint32_t elapsed = foregroundSnapshotMilliseconds - beginTimestamp;
- const ShotOutputRequest request = evaluateShotOutputRequest(state->settings, state->alternateModeSelection, elapsed);
- OutputGroup& pump = groupForRole(&state->outputChannels, OutputGroupRole::Pump);
- OutputGroup& valve = groupForRole(&state->outputChannels, OutputGroupRole::Valve);
- if (request.isTimedOut) {
- setGroupEnabled(&pump, false);
- setGroupEnabled(&valve, false);
- if (!state->shot.isTimeoutEventLatched) {
- state->shot.isTimeoutEventLatched = true;
- endShotEvent(state, foregroundSnapshotMilliseconds, beginTimestamp);
- }
- return;
- }
- state->shot.isTimeoutEventLatched = false;
- setGroupEnabled(&pump, request.isPumpRequested);
- setGroupEnabled(&valve, request.isValveRequested);
- #endif
- }
- // Begin (argument 1) and end (argument 0) of an input burst.
- void runInputStateChange(FirmwareState* state, InputRole role, uint8_t argument, uint32_t timestamp, uint32_t beginTimestamp) {
- switch (role) {
- case InputRole::Power:
- handlePowerInputChange(state, argument, timestamp);
- break;
- case InputRole::Mode:
- handleModeInputChange(state, argument, timestamp);
- break;
- case InputRole::Shot:
- if (argument != 0) {
- beginShotEvent(state, timestamp);
- } else {
- endShotEvent(state, timestamp, beginTimestamp);
- }
- break;
- }
- }
- } // namespace
- bool isInputQualified(const DebouncedInput& input) {
- return input.risingEdgeCount >= kRequiredEdgesPerLevel && input.fallingEdgeCount >= kRequiredEdgesPerLevel;
- }
- void initializeDebouncedInput(DebouncedInput* input) {
- #if defined(MECOFFEE_RUST_SHOT)
- 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(;;){}}
- #endif
- *input = DebouncedInput{};
- input->isLastLevelHigh = true;
- }
- ShotOutputRequest evaluateShotOutputRequest(const SettingsValues& settings, uint8_t alternateMode, uint32_t elapsedMilliseconds) {
- const uint16_t gapEnd = static_cast<uint16_t>(settings.preinfusionPumpDuration + settings.preinfusionPauseDuration);
- const bool isInGap = settings.preinfusionEnabled != 0 && elapsedMilliseconds > settings.preinfusionPumpDuration &&
- elapsedMilliseconds < gapEnd;
- const uint16_t timeoutThreshold = static_cast<uint16_t>(static_cast<uint32_t>(settings.maximumShotDuration) * 1000u);
- ShotOutputRequest request;
- request.isTimedOut = settings.maximumShotDuration != 0 && elapsedMilliseconds > timeoutThreshold;
- request.isPumpRequested = !isInGap && !request.isTimedOut;
- request.isValveRequested = !request.isTimedOut && alternateMode == 0 && (!isInGap || settings.preinfusionValveClosed == 0);
- return request;
- }
- void pollDebouncedInput(FirmwareState* state, DebouncedInput* input, InputRole role, bool isPinHigh,
- uint32_t foregroundSnapshotMilliseconds) {
- #if defined(MECOFFEE_RUST_SHOT)
- mecoffee_rust_poll_input(input,static_cast<uint8_t>(role),isPinHigh,foregroundSnapshotMilliseconds,state);
- #else
- if (isPinHigh != input->isLastLevelHigh) {
- input->isLastLevelHigh = isPinHigh;
- input->lastChangeTimestamp = foregroundSnapshotMilliseconds;
- uint8_t& edgeCount = isPinHigh ? input->risingEdgeCount : input->fallingEdgeCount;
- if (edgeCount < 255) {
- ++edgeCount;
- }
- if (!isInputQualified(*input)) {
- return;
- }
- // The begin guard and the active callback run in the same call, so the first qualified
- // falling edge both begins the burst and requests the outputs.
- if (!isPinHigh && input->beginTimestamp == 0) {
- input->beginTimestamp = foregroundSnapshotMilliseconds;
- runInputStateChange(state, role, 1, foregroundSnapshotMilliseconds, 0);
- }
- if (role == InputRole::Shot) {
- runShotActiveCallback(state, foregroundSnapshotMilliseconds, input->beginTimestamp);
- }
- return;
- }
- if (input->lastChangeTimestamp != 0 &&
- static_cast<uint32_t>(foregroundSnapshotMilliseconds - input->lastChangeTimestamp) >= kInputStableDuration) {
- const bool wasQualified = isInputQualified(*input);
- const uint32_t beginTimestamp = input->beginTimestamp;
- input->risingEdgeCount = 0;
- input->fallingEdgeCount = 0;
- input->beginTimestamp = 0;
- input->lastChangeTimestamp = 0;
- if (wasQualified) {
- input->endTimestamp = foregroundSnapshotMilliseconds;
- runInputStateChange(state, role, 0, foregroundSnapshotMilliseconds, beginTimestamp);
- }
- }
- #endif
- }
- #if defined(MECOFFEE_RUST_SHOT)
- extern "C" void mecoffee_shot_effect(void* context,uint8_t operation,uint32_t value) {
- auto* state=static_cast<FirmwareState*>(context);
- switch(operation) {
- case 0: state->powerSchedule.lastActivityTimestamp=value;break;
- case 1: {
- const uint8_t saved=SREG;cli();
- setGroupEnabled(&groupForRole(&state->outputChannels,static_cast<OutputGroupRole>(value&255)),(value&256)!=0);
- SREG=saved;break;
- }
- case 2: beginShotFeedForward(state,value);break;
- case 3: endShotFeedForward(state,value);break;
- case 4: enqueueDiagnosticCallback(&state->diagnosticQueue,reportShotBegin);break;
- case 5: enqueueDiagnosticCallback(&state->diagnosticQueue,reportShotEnd);break;
- }
- }
- extern "C" void mecoffee_input_event(void* context,uint8_t role,uint8_t active,uint32_t now,uint32_t begin) {
- auto* state=static_cast<FirmwareState*>(context);
- // Use the supplied timestamp, never read the Rust-borrowed DebouncedInput.
- if(active==2) runShotActiveCallback(state,now,begin);
- else runInputStateChange(state,static_cast<InputRole>(role),active,now,begin);
- }
- #endif
- } // namespace meCoffee
|