ESPSomfy-RTS/src/RadioScheduler.h
cjkas f0e8b1d896 fix: refuse a radio job when its outcomes might not fit in the event ring
A full event ring threw an outcome away, breaking the rule that every job
we submit ends with exactly one of finished, aborted or dropped.  A lost
outcome left a favorite uncommitted and a held shade waiting out its 30s
timeout.  submit() and hasRoom() now keep room for two outcomes of every
job not yet ended, so an event can't be lost; if loop() stops collecting
them, new jobs are refused instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:05:06 +02:00

78 lines
3.6 KiB
C++

#ifndef RADIO_SCHEDULER_H
#define RADIO_SCHEDULER_H
#include <stdint.h>
#include "TxBuffer.h"
// Reasons the radio must not transmit. TX resumes once every reason is cleared.
#define TX_SUSPEND_SCAN 0x01
#define TX_SUSPEND_OTA 0x02
// No job is waiting so the radio task sleeps until it is woken.
#define RADIO_WAIT_FOREVER 0xFFFFFFFFu
// The radio jobs waiting for, or on, the air (see GLOSSARY.md). It decides
// their order, when a delayed job is due, what a flush or suspend drops, and
// reports the outcome of every job. Kept free of FreeRTOS and the radio so it
// can be unit tested on the PC (pio test -e native).
//
// Not thread safe. The caller serializes every call and passes the time in.
class RadioScheduler {
public:
// Queues a job. Its ticket comes back and is also set in job.seq, 0 when
// the queue is full or suspended, or when outcomes aren't collected with
// popEvent() fast enough to leave room for its own. A priority job (a stop) may use the
// reserved slots and goes ahead of every job queued at the back, unless a
// job of its remote is already waiting: overtaking that would put the
// rolling codes out of order. A delayed job doesn't go on air until
// delayMs after it was queued and after the job ahead of it ended, and holds
// up the jobs queued behind it.
uint32_t submit(somfy_tx_job_t &job, bool priority, uint16_t delayMs, uint32_t now);
// Whether a job would be taken right now.
bool hasRoom(bool priority) const;
// For the radio task: takes the job to send next. False when there is
// none due, waitMs then says how long until there is (RADIO_WAIT_FOREVER
// when nothing waits). A job taken goes on air through start() and
// ended().
bool next(uint32_t now, somfy_tx_job_t &job, uint32_t &waitMs);
// The job taken is about to go on air. False when it was flushed since,
// it is then dropped and must not be sent.
bool start(uint32_t ticket, uint32_t now);
// Whether the job on air should send its next frame. A flush cuts it
// short.
bool keepSending(uint32_t ticket) const;
// The job on air stopped, after every frame when complete.
void ended(uint32_t ticket, bool complete, uint32_t now);
// Drops every waiting job and cuts short the one on air.
void flush(uint32_t now);
// Refuses new jobs until every reason (TX_SUSPEND_*) is resumed. Flushes.
void suspend(uint8_t reason, uint32_t now);
void resume(uint8_t reason);
bool suspended() const { return this->suspendMask != 0; }
// Takes the oldest job outcome not yet handed out. Every job submitted
// that isn't a repeater frame is started and then finished or aborted, or
// dropped.
bool popEvent(somfy_tx_event_t &evt);
private:
somfy_tx_job_t jobs[TX_JOB_QUEUE_SIZE];
uint8_t count = 0;
uint32_t lastSeq = 0;
uint8_t suspendMask = 0;
// The job taken by next() and not yet ended (0 for none), kept for its
// events.
uint32_t taken = 0;
somfy_tx_job_t takenJob;
bool takenFlushed = false;
// When a job last went off air, for the delay of the job after it.
uint32_t lastEnded = 0;
bool endedOnce = false;
somfy_tx_event_t events[TX_JOB_QUEUE_SIZE * 3];
uint8_t eventHead = 0;
uint8_t eventCount = 0;
// Whether the outcomes of every job not yet ended, and of one more, still
// fit in events. A job is refused otherwise so no outcome is ever lost.
bool outcomesFit() const;
// Whether a job of the remote is still waiting.
bool waitingFor(uint32_t remoteAddress) const;
void remove(uint8_t i);
void post(tx_event_t type, const somfy_tx_job_t &job, uint32_t at);
};
#endif