src/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
95 / 96
99.0%
Branches:
73 / 78
93.6%
Functions:
15 / 15
100.0%

RadioScheduler.cpp
Line Branch Exec Source
1 #include "RadioScheduler.h"
2 #include "esp_log.h"
3
4 // Logged as part of the radio code it came from.
5 static const char *TAG = "Somfy";
6
7 #define TX_EVENT_QUEUE_SIZE (TX_JOB_QUEUE_SIZE * 3)
8
9 // Whether a job fits with spaces slots free. Normal jobs leave the reserved
10 // slots free for stops.
11 277 static bool admits(uint32_t spaces, bool priority) {
12
2/2
✓ Branch 0 taken 48 times.
✓ Branch 1 taken 229 times.
277 return spaces > (priority ? 0u : (uint32_t)TX_RESERVED_SLOTS);
13 }
14 276 uint32_t RadioScheduler::submit(somfy_tx_job_t &job, bool priority, uint16_t delayMs, uint32_t now) {
15 276 job.ticket = 0;
16
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 273 times.
276 if(this->suspendMask) {
17 ESP_LOGW(TAG, "Radio TX suspended, dropping transmission");
18 3 return 0;
19 }
20
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 270 times.
273 if(!admits(TX_JOB_QUEUE_SIZE - this->count, priority)) {
21 ESP_LOGE(TAG, "Radio TX queue full, dropping transmission");
22 3 return 0;
23 }
24
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 268 times.
270 if(!this->outcomesFit()) {
25 ESP_LOGE(TAG, "Radio TX outcomes not collected, dropping transmission");
26 2 return 0;
27 }
28 // 0 means no job so it is skipped on wrap.
29
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 268 times.
268 if(++this->lastTicket == 0) ++this->lastTicket;
30 268 job.ticket = this->lastTicket;
31 268 job.priority = priority;
32
5/6
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 222 times.
✓ Branch 2 taken 46 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 13 times.
✓ Branch 6 taken 33 times.
268 job.front = priority && !job.repeater && !this->waitingFor(job.frame.remoteAddress);
33 268 job.queuedAt = now;
34 268 job.delayMs = delayMs;
35 // Front jobs go out in the order they came, ahead of the back.
36 268 uint8_t at = this->count;
37
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 255 times.
268 if(job.front) {
38 13 at = 0;
39
4/4
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 10 times.
14 while(at < this->count && this->jobs[at].front) at++;
40 }
41
2/2
✓ Branch 0 taken 65 times.
✓ Branch 1 taken 268 times.
333 for(uint8_t i = this->count; i > at; i--) this->jobs[i] = this->jobs[i - 1];
42 268 this->jobs[at] = job;
43 268 this->count++;
44 268 return job.ticket;
45 }
46 5 bool RadioScheduler::hasRoom() const {
47
6/6
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 time.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 1 time.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 1 time.
5 return !this->suspendMask && admits(TX_JOB_QUEUE_SIZE - this->count, false) && this->outcomesFit();
48 }
49 273 bool RadioScheduler::outcomesFit() const {
50 // Each job not yet ended reports at most two outcomes: started, then how it
51 // ended. Counting repeater frames too keeps this simple and errs safe.
52
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 269 times.
273 const uint32_t unended = this->count + (this->taken ? 1u : 0u) + 1u;
53 273 return this->eventCount + 2u * unended <= TX_EVENT_QUEUE_SIZE;
54 }
55 46 bool RadioScheduler::waitingFor(uint32_t remoteAddress) const {
56
2/2
✓ Branch 0 taken 99 times.
✓ Branch 1 taken 13 times.
112 for(uint8_t i = 0; i < this->count; i++) {
57
4/4
✓ Branch 0 taken 98 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 33 times.
✓ Branch 3 taken 65 times.
99 if(!this->jobs[i].repeater && this->jobs[i].frame.remoteAddress == remoteAddress) return true;
58 }
59 13 return false;
60 }
61 112 void RadioScheduler::remove(uint8_t i) {
62
2/2
✓ Branch 0 taken 55 times.
✓ Branch 1 taken 112 times.
167 for(; i + 1 < this->count; i++) this->jobs[i] = this->jobs[i + 1];
63 112 this->count--;
64 112 }
65 126 bool RadioScheduler::next(uint32_t now, somfy_tx_job_t &job, uint32_t &waitMs) {
66 126 waitMs = RADIO_WAIT_FOREVER;
67
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 122 times.
126 if(this->count == 0) return false;
68 // Jobs go out in order, so a delayed job holds up the ones behind it. A
69 // front job queued meanwhile is ahead of it.
70 122 const somfy_tx_job_t &head = this->jobs[0];
71
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 104 times.
122 if(head.delayMs != 0) {
72 // The delay lets the motor settle, so it counts from the job ahead of it
73 // going off air when that came after the job was queued. Ages are unsigned
74 // so this works across a millis() wrap.
75 18 uint32_t waited = now - head.queuedAt;
76
4/4
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 10 times.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 2 times.
18 if(this->endedOnce && now - this->lastEnded < waited) waited = now - this->lastEnded;
77
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 8 times.
18 if(waited < head.delayMs) {
78 10 waitMs = head.delayMs - waited;
79 10 return false;
80 }
81 }
82 112 job = head;
83 112 this->remove(0);
84 112 this->taken = job.ticket;
85 112 this->takenFlushed = false;
86 112 this->takenJob = job;
87 112 return true;
88 }
89 108 bool RadioScheduler::start(uint32_t ticket, uint32_t now) {
90
3/4
✓ Branch 0 taken 108 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 107 times.
108 if(ticket == 0 || ticket != this->taken) return false;
91
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 106 times.
107 if(this->takenFlushed) {
92 // Flushed after the radio task took it.
93 1 this->post(tx_event_t::dropped, this->takenJob, now);
94 1 this->taken = 0;
95 1 return false;
96 }
97 106 this->post(tx_event_t::started, this->takenJob, now);
98 106 return true;
99 }
100 5 bool RadioScheduler::keepSending(uint32_t ticket) const {
101
6/6
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 2 times.
5 return ticket != 0 && ticket == this->taken && !this->takenFlushed;
102 }
103 107 void RadioScheduler::ended(uint32_t ticket, bool complete, uint32_t now) {
104
3/4
✓ Branch 0 taken 107 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 106 times.
107 if(ticket == 0 || ticket != this->taken) return;
105
2/2
✓ Branch 0 taken 103 times.
✓ Branch 1 taken 3 times.
106 this->post(complete ? tx_event_t::finished : tx_event_t::aborted, this->takenJob, now);
106 106 this->taken = 0;
107 106 this->lastEnded = now;
108 106 this->endedOnce = true;
109 }
110 9 void RadioScheduler::flush(uint32_t now) {
111 // Every discarded job is reported as dropped.
112
2/2
✓ Branch 1 taken 36 times.
✓ Branch 2 taken 9 times.
45 for(uint8_t i = 0; i < this->count; i++) this->post(tx_event_t::dropped, this->jobs[i], now);
113 9 this->count = 0;
114
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 6 times.
9 if(this->taken) this->takenFlushed = true;
115 9 }
116 3 void RadioScheduler::suspend(uint8_t reason, uint32_t now) {
117 3 this->suspendMask |= reason;
118 3 this->flush(now);
119 3 }
120 2 void RadioScheduler::resume(uint8_t reason) { this->suspendMask &= ~reason; }
121 249 void RadioScheduler::post(tx_event_t type, const somfy_tx_job_t &job, uint32_t at) {
122 // Repeater frames don't belong to any of our remotes.
123
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 244 times.
249 if(job.repeater) return;
124
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 244 times.
244 if(this->eventCount >= TX_EVENT_QUEUE_SIZE) {
125 // submit() leaves room for every outcome so this can't happen short of a bug.
126 ESP_LOGE(TAG, "Radio TX event queue full, outcome lost");
127 ✗ return;
128 }
129 244 somfy_tx_event_t &evt = this->events[(this->eventHead + this->eventCount) % TX_EVENT_QUEUE_SIZE];
130 244 evt.type = type;
131 244 evt.cmd = job.frame.cmd;
132 244 evt.remoteAddress = job.frame.remoteAddress;
133 244 evt.ticket = job.ticket;
134 244 evt.at = at;
135 244 this->eventCount++;
136 }
137 200 bool RadioScheduler::popEvent(somfy_tx_event_t &evt) {
138
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 188 times.
200 if(this->eventCount == 0) return false;
139 188 evt = this->events[this->eventHead];
140 188 this->eventHead = (uint8_t)((this->eventHead + 1) % TX_EVENT_QUEUE_SIZE);
141 188 this->eventCount--;
142 188 return true;
143 }
144