Merge pull request #51 from Genaker/testable

Rework graphs
This commit is contained in:
Sassa-nf
2024-10-11 23:18:33 +01:00
committed by GitHub
18 changed files with 1484 additions and 466 deletions
+3
View File
@@ -12,4 +12,7 @@
},
"files.insertFinalNewline": true,
"files.autoSave": "onFocusChange",
"files.associations": {
"cstdint": "cpp"
},
}
+18 -8
View File
@@ -8,6 +8,9 @@
#include <Arduino.h>
#endif
#include <charts.h>
#include <scan.h>
// #include <heltec_unofficial.h>
// (optional) major and minor tick-marks at x MHz
@@ -31,14 +34,21 @@
#define SCREEN_HEIGHT 64 // ???? not used
// publish functions
#ifdef Vision_Master_E290
extern void UI_Init(DEPG0290BxS800FxX_BW *);
#else
extern void UI_Init(SSD1306Wire *);
#endif
extern void UI_displayDecorate(int, int, bool);
extern void UI_setLedFlag(bool);
extern void UI_Init(Display_t *);
extern void UI_clearPlotter(void);
extern void UI_clearTopStatus(void);
extern void UI_drawCursor(int16_t);
struct StatusBar : Chart
{
Scan &r;
bool ui_initialized;
uint16_t scan_progress_count;
StatusBar(Display_t &d, uint16_t x, uint16_t y, uint16_t w, Scan &r)
: Chart(d, x, y, w, LABEL_HEIGHT), r(r), ui_initialized(false),
scan_progress_count(0) {};
virtual void clearStatus();
virtual void draw() override;
};
+194
View File
@@ -0,0 +1,194 @@
#include "charts.h"
void BarChart::reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
{
if (w != width)
{
delete[] ys;
delete[] changed;
ys = new float[w];
changed = new bool[w];
}
memset(ys, 0, w * sizeof(float));
memset(changed, false, w * sizeof(bool));
redraw_all = true;
Chart::reset(x, y, w, h);
}
int BarChart::updatePoint(float x, float y)
{
if (x < min_x || x >= max_x)
{
return -1;
}
size_t idx = width * (x - min_x) / (max_x - min_x);
if (idx >= width)
{
idx = width - 1;
}
if (!changed[idx] || ys[idx] < y)
{
ys[idx] = y;
changed[idx] = true;
}
return idx;
}
void BarChart::draw()
{
for (int x = 0; x < width; x++)
{
if (!changed[x] && !redraw_all)
continue;
drawOne(x);
}
redraw_all = false;
}
void BarChart::drawOne(int x)
{
if (x < 0)
return;
int y = y2pos(ys[x]);
if (y < height)
{
display.setColor(BLACK);
display.drawVerticalLine(pos_x + x, pos_y, y);
display.setColor(WHITE);
display.drawVerticalLine(pos_x + x, pos_y + y, height - y);
}
else
{
display.setColor(BLACK);
display.drawVerticalLine(pos_x + x, pos_y, height);
}
if (x % 2 == 0)
{
display.setColor(INVERSE);
display.setPixel(pos_x + x, pos_y + y2pos(level_y));
}
changed[x] = false;
}
int BarChart::x2pos(float x)
{
if (x < min_x)
x = min_x;
if (x > max_x)
x = max_x;
return width * (x - min_x) / (max_x - min_x);
}
int BarChart::y2pos(float y)
{
if (y < min_y)
y = min_y;
if (y > max_y)
y = max_y;
return height - height * (y - min_y) / (max_y - min_y);
}
void BarChart::onEvent(Event &e)
{
if (e.type != DETECTED)
{
return;
}
level_y = e.emitter.trigger_level;
int u = updatePoint(e.emitter.current_frequency, e.detected.rssi);
if (e.emitter.animated)
{
drawOne(u);
}
}
void DecoratedBarChart::reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
{
Chart::reset(x, y, w, h);
bar.reset(x, y + LABEL_HEIGHT, w, h - LABEL_HEIGHT - AXIS_HEIGHT);
}
void DecoratedBarChart::draw()
{
bool draw_axis = bar.redraw_all;
bar.draw();
display.setColor(BLACK);
display.fillRect(pos_x, pos_y, width, bar.pos_y - pos_y);
display.setColor(WHITE);
display.setTextAlignment(TEXT_ALIGN_LEFT);
uint16_t first_untouched = 0;
for (uint16_t x = 0; x < width; x++)
{
float y = bar.ys[x];
if (y >= bar.level_y)
{
String s = String(bar.ys[x], 0);
uint16_t w = display.getStringWidth(s);
uint16_t x1 = x;
for (; x < x1 + w && x < width; x++)
{
if (bar.ys[x] > y)
{
y = bar.ys[x];
s = String(y, 0);
w = max(w, display.getStringWidth(s));
}
}
if (x > width && first_untouched <= width - w)
{
x1 = width - w;
}
first_untouched = x;
if (x1 + w <= width)
display.drawString(pos_x + x1, pos_y, s);
}
}
if (draw_axis)
{
display.setColor(WHITE);
uint16_t y = pos_y + height - AXIS_HEIGHT + 2;
display.fillRect(pos_x, y - 1, width, X_AXIS_WEIGHT);
// Start and end ticks
display.fillRect(pos_x, y - 1, 2, AXIS_HEIGHT);
display.fillRect(pos_x + width - 2, y - 1, 2, AXIS_HEIGHT);
for (float step = 0; bar.min_x + step * MAJOR_TICKS < bar.max_x; step += 1)
{
int tick_pos = bar.x2pos(bar.min_x + step * MAJOR_TICKS);
display.drawVerticalLine(pos_x + tick_pos, y, MAJOR_TICK_LENGTH);
}
for (float step = 0; bar.min_x + step * MINOR_TICKS < bar.max_x; step += 1)
{
int tick_pos = bar.x2pos(bar.min_x + step * MINOR_TICKS);
display.drawVerticalLine(pos_x + tick_pos, y, MINOR_TICK_LENGTH);
}
}
}
+95
View File
@@ -0,0 +1,95 @@
#include "charts.h"
uint16_t trim_w(uint16_t pos, uint16_t width, uint16_t w)
{
return min(width, (uint16_t)(max(w, pos) - pos));
}
size_t StackedChart::addChart(Chart *c)
{
Chart **cc = new Chart *[charts_sz + 1];
memcpy(cc, charts, charts_sz * sizeof(Chart *));
cc[charts_sz] = c;
free(charts);
c->reset(pos_x + c->pos_x, pos_y + c->pos_y, trim_w(c->pos_x, c->width, width),
c->height);
charts = cc;
return charts_sz++;
}
uint16_t StackedChart::setHeight(size_t c, uint16_t h)
{
if (h < height)
{
charts[c]->reset(charts[c]->pos_x, charts[c]->pos_y, charts[c]->width, h);
uint16_t used_space = 0;
for (int i = 0; i < charts_sz; i++)
{
used_space += charts[i]->height;
}
return used_space;
}
// this chart gets special treatment - pack all other charts,
// and make this one as big as possible
uint16_t used_space = 0;
for (int i = 0; i < c; i++)
{
charts[i]->reset(charts[i]->pos_x, pos_y + used_space, charts[i]->width,
charts[i]->height);
used_space += charts[i]->height;
}
uint16_t more_used_space = used_space;
for (int i = c + 1; i < charts_sz; i++)
{
more_used_space += charts[i]->height;
}
if (more_used_space < height)
{
charts[c]->reset(charts[c]->pos_x, pos_y + used_space, charts[c]->width,
height - more_used_space);
used_space += charts[c]->height;
}
for (int i = c + 1; i < charts_sz; i++)
{
charts[i]->reset(charts[i]->pos_x, pos_y + used_space, charts[i]->width,
charts[i]->height);
used_space += charts[i]->height;
}
return used_space;
}
void StackedChart::reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
{
for (int i = 0; i < charts_sz; i++)
{
uint16_t rel_x = charts[i]->pos_x - pos_x;
uint16_t rel_y = charts[i]->pos_y - pos_y;
charts[i]->reset(x + rel_x, y + rel_y, trim_w(rel_x, charts[i]->width, w),
charts[i]->height);
}
Chart::reset(x, y, w, h);
}
void StackedChart::draw()
{
for (int i = 0; i < charts_sz; i++)
charts[i]->draw();
}
void StackedChart::onEvent(Event &e)
{
if (e.type != SCAN_TASK_COMPLETE)
{
return;
}
draw();
}
+28
View File
@@ -0,0 +1,28 @@
#include "charts.h"
void UptimeClock::draw(uint64_t t)
{
t1 = t;
draw();
}
void UptimeClock::draw()
{
uint64_t uptime = t1 - t0;
int mils = uptime % 1000;
int seconds = (uptime / 1000) % 60;
int minutes = (uptime / 60000) % 60;
int hours = uptime / 3600000;
String s = String(hours) + (minutes < 10 ? ":0" : ":") + String(minutes) +
(seconds < 10 ? ":0" : ":") + String(seconds) +
(mils < 10 ? ".00"
: mils < 100 ? ".0"
: ".") +
String(mils);
int w = display.getStringWidth(s);
display.setColor(BLACK);
display.fillRect((display.width() - w) / 2, display.height() / 2 - 3, w, 7);
display.setColor(WHITE);
display.setTextAlignment(TEXT_ALIGN_CENTER_BOTH);
display.drawString(display.width() / 2, display.height() / 2, s);
}
+69
View File
@@ -0,0 +1,69 @@
#include "charts.h"
#include <cstdint>
void WaterfallChart::reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
{
Chart::reset(x, y, w, h);
model->reset(model->times[0], w);
update_to = model->buckets;
}
void WaterfallChart::updatePoint(uint64_t t, float x, float y)
{
if (x < min_x || x >= max_x)
{
return;
}
update_to = max(update_to, model->updateModel(t, x2pos(x), y >= level_y));
}
void WaterfallChart::draw()
{
size_t h = min(update_to, (size_t)height);
for (int y = 0; y < h; y++)
{
for (int x = 0; x < width; x++)
{
bool b = model->counts[y][x] > 0 &&
(model->events[y][x] >= model->counts[y][x] * threshold);
if (b)
{
display.setColor(WHITE);
}
else
{
display.setColor(BLACK);
}
display.setPixel(pos_x + x, pos_y + y);
}
}
update_to = 0;
}
int WaterfallChart::x2pos(float x)
{
if (x < min_x)
x = min_x;
if (x > max_x)
x = max_x;
return width * (x - min_x) / (max_x - min_x);
}
void WaterfallChart::onEvent(Event &e)
{
if (e.type != DETECTED)
{
return;
}
level_y = e.emitter.trigger_level;
updatePoint(e.time_ms, e.detected.freq, e.detected.rssi);
}
+178
View File
@@ -0,0 +1,178 @@
#ifndef CHARTS_H
#define CHARTS_H
#ifdef Vision_Master_E290
#include "HT_DEPG0290BxS800FxX_BW.h"
typedef DEPG0290BxS800FxX_BW Display_t;
#else
#include <OLEDDisplay.h>
typedef OLEDDisplay Display_t;
#endif
#include <cstdint>
#include <events.h>
#include <models.h>
#include <stdlib.h>
struct Chart
{
uint16_t pos_x, pos_y;
uint16_t width, height;
Display_t &display;
Chart(Display_t &d, uint16_t x, uint16_t y, uint16_t w, uint16_t h)
: display(d), pos_x(x), pos_y(y), width(w), height(h) {};
/*
* This method resets the state and sets the reference time.
*/
virtual void reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
{
pos_x = x;
pos_y = y;
width = w;
height = h;
}
/*
* Redraw everything that needs redrawing.
*/
virtual void draw() {};
};
/*
* ProgressChart supports updates with progressive redraw of just the affected area.
*/
struct ProgressChart : Chart
{
ProgressChart(Display_t &d, uint16_t x, uint16_t y, uint16_t w, uint16_t h)
: Chart(d, x, y, w, h) {};
/*
* Update one data point, and return what column needs redrawing.
*/
virtual int updatePoint(float x, float y) = 0;
/*
* If you fancy animated progress, then pass the output of updatePoint to here.
*/
virtual void drawOne(int x) = 0;
};
struct BarChart : ProgressChart, Listener
{
float min_x, max_x, min_y, max_y;
float level_y;
float *ys;
bool *changed;
bool redraw_all;
BarChart(Display_t &d, uint16_t x, uint16_t y, uint16_t w, uint16_t h, float min_x,
float max_x, float min_y, float max_y, float level_y)
: ProgressChart(d, x, y, w, h), min_x(min_x), max_x(max_x), min_y(min_y),
max_y(max_y), level_y(level_y), redraw_all(true)
{
ys = new float[w];
changed = new bool[w];
memset(ys, 0, w * sizeof(float));
memset(changed, 0, w * sizeof(bool));
};
void reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h) override;
int updatePoint(float x, float y) override;
void drawOne(int x) override;
void draw() override;
void onEvent(Event &) override;
int x2pos(float x);
int y2pos(float y);
};
#define LABEL_HEIGHT 7
#define X_AXIS_WEIGHT 1
#define MAJOR_TICK_LENGTH 2
#define MAJOR_TICKS 10
#define MINOR_TICK_LENGTH 1
#define MINOR_TICKS 5
#define AXIS_HEIGHT (X_AXIS_WEIGHT + MAJOR_TICK_LENGTH + 2)
struct DecoratedBarChart : Chart
{
BarChart bar;
DecoratedBarChart(Display_t &d, uint16_t x, uint16_t y, uint16_t w, uint16_t h,
float min_x, float max_x, float min_y, float max_y, float level_y)
: Chart(d, x, y, w, h),
bar(d, x, y + LABEL_HEIGHT, w, h - LABEL_HEIGHT - AXIS_HEIGHT, min_x, max_x,
min_y, max_y, level_y) {};
void reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h) override;
void draw() override;
};
struct StackedChart : Chart, Listener
{
Chart **charts;
size_t charts_sz;
StackedChart(Display_t &d, uint16_t x, uint16_t y, uint16_t w, uint16_t h)
: Chart(d, x, y, w, h), charts(NULL), charts_sz(0) {};
/*
* addChart adds c to the StackedChart, treats pos_x and pos_y of the chart
* as relative to this chart's origin, and trims width to fit. Adjust the
* height and pack charts using setHeight.
*/
size_t addChart(Chart *c);
/*
* Adjust the height of the chart and return the resulting required height.
* If h is >= height, the chart gets a special treatment: packs all other
* charts, and uses up the rest of space.
*/
uint16_t setHeight(size_t c, uint16_t h);
void reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h) override;
void draw() override;
void onEvent(Event &e) override;
};
struct WaterfallChart : Chart, Listener
{
float min_x, max_x;
float level_y, threshold;
size_t update_to;
WaterfallModel *model;
WaterfallChart(Display_t &d, uint16_t x, uint16_t y, uint16_t w, uint16_t h,
float min_x, float max_x, float level_y, float threshold,
WaterfallModel *m)
: Chart(d, x, y, w, h), model(m), min_x(min_x), max_x(max_x), level_y(level_y),
threshold(threshold), update_to(m->buckets) {};
void updatePoint(uint64_t t, float x, float y);
void reset(uint16_t x, uint16_t y, uint16_t w, uint16_t h) override;
void draw() override;
void onEvent(Event &e) override;
int x2pos(float x);
};
struct UptimeClock : Chart
{
uint64_t t0;
uint64_t t1;
UptimeClock(Display_t &d, uint64_t t0) : Chart(d, 0, 0, 0, 0), t0(t0), t1(t0) {};
void draw(uint64_t t);
virtual void draw() override;
};
#endif
+45
View File
@@ -0,0 +1,45 @@
#ifndef LORASA_EVENTS_H
#define LORASA_EVENTS_H
struct Event;
enum EventType
{
DETECTED = 0,
SCAN_TASK_COMPLETE,
_MAX_EVENT_TYPE // unused as event type
};
struct Listener;
#include <cstdint>
#include <scan.h>
struct Event
{
EventType type;
uint64_t epoch;
uint64_t time_ms;
Scan &emitter;
union
{
struct
{
float rssi;
float freq;
bool trigger;
bool detected;
size_t detected_at;
} detected;
};
Event(Scan &emitter, EventType type, uint64_t time_ms)
: emitter(emitter), type(type), epoch(emitter.epoch), time_ms(time_ms) {};
};
struct Listener
{
virtual void onEvent(Event &event) = 0;
};
#endif
+153
View File
@@ -0,0 +1,153 @@
#include "models.h"
#include <cstring>
WaterfallModel::WaterfallModel(size_t w, uint64_t base_dt, size_t m_sz,
const size_t *multiples)
{
width = w;
size_t dt_sz = 0;
for (int i = 0; i < m_sz; i++)
dt_sz += multiples[i];
buckets = dt_sz;
dt = new uint64_t[buckets];
events = new uint32_t *[buckets];
counts = new uint32_t *[buckets];
times = new uint64_t[buckets];
uint64_t m = base_dt;
for (int i = 0, j = 0; i < m_sz; i++)
{
for (int k = 0; k < multiples[i]; k++, j++)
{
dt[j] = m;
events[j] = new uint32_t[width];
counts[j] = new uint32_t[width];
}
m *= multiples[i];
}
}
void WaterfallModel::reset(uint64_t t0, size_t w)
{
if (w != width)
{
width = w;
for (int i = 0; i < buckets; i++)
{
delete[] counts[i];
delete[] events[i];
counts[i] = new uint32_t[w];
events[i] = new uint32_t[w];
}
}
for (int i = 0; i < buckets; i++)
{
memset(counts[i], 0, width * sizeof(uint32_t));
memset(events[i], 0, width * sizeof(uint32_t));
times[i] = t0 + dt[i];
}
}
/*
* The model is literally a stack of counters:
* - incomplete second
* - n complete seconds
* - incomplete minute
* - n complete minutes
* - ...
*
* updateModel updates incomplete second. When the second becomes complete, it
* gets pushed to complete seconds, and the last complete second is rotated out
* and it gets added to incomplete minute. This gets repeated for incomplete
* minutes, etc.
*/
size_t WaterfallModel::updateModel(uint16_t t, size_t x, uint16_t y)
{
size_t changed = 1;
while (t > times[0])
{
changed = push();
}
counts[0][x]++;
events[0][x] += y;
return changed;
}
size_t WaterfallModel::push()
{
size_t i = 1;
for (; i < buckets; i++)
{
if (dt[i - 1] == dt[i])
continue;
if (times[i - 1] <= times[i])
break;
times[i - 1] = times[i] + dt[i];
}
uint64_t t0 = times[0];
uint32_t *cc = counts[i - 1];
uint32_t *ee = events[i - 1];
memmove(times + 1, times, (i - 1) * sizeof(uint64_t));
memmove(counts + 1, counts, (i - 1) * sizeof(uint32_t *));
memmove(events + 1, events, (i - 1) * sizeof(uint32_t *));
if (i < buckets)
{
for (int j = 0; j < width; j++)
{
counts[i][j] += cc[j];
events[i][j] += ee[j];
}
i++;
}
memset(cc, 0, width * sizeof(uint32_t));
memset(ee, 0, width * sizeof(uint32_t));
counts[0] = cc;
events[0] = ee;
times[0] = t0 + dt[0];
return i;
}
#ifdef TO_STRING
#include <sstream>
#include <string>
#endif
char *WaterfallModel::toString()
{
#ifdef TO_STRING
std::stringstream r;
r << "w:" << width << " b:" << buckets << " [";
for (int i = 0; i < buckets; i++)
{
r << "dt:" << dt[i] << " t:" << times[i] << " [";
for (int j = 0; j < width; j++)
r << " c:" << counts[i][j] << " e:" << events[i][j];
r << " ]";
}
r << " ]";
char *ret = new char[r.str().length() + 1];
strncpy(ret, r.str().c_str(), r.str().length());
#else
char *ret = NULL;
#endif
return ret;
}
+27
View File
@@ -0,0 +1,27 @@
#ifndef CHARTS_MODELS_H
#define CHARTS_MODELS_H
#include <cstdint>
#include <cstring>
#include <stdlib.h>
struct WaterfallModel
{
uint32_t **events;
uint32_t **counts;
uint64_t *times;
uint64_t *dt;
size_t buckets;
size_t width;
WaterfallModel(size_t w, uint64_t base_dt, size_t m_sz, const size_t *multiples);
void reset(uint64_t t0, size_t width);
size_t updateModel(uint16_t t, size_t x, uint16_t y);
size_t push();
char *toString();
};
#endif
+58 -4
View File
@@ -66,10 +66,10 @@ uint16_t Scan::rssiMethod(size_t samples, uint16_t *result, size_t res_size)
return max_signal;
}
size_t Scan::detect(uint16_t *result, bool *filtered_result, size_t result_size,
int samples)
Event Scan::detect(uint16_t *result, bool *filtered_result, size_t result_size,
int samples)
{
size_t max_rssi_x = 999;
size_t max_rssi_x = result_size;
for (int y = 0; y < result_size; y++)
{
@@ -122,7 +122,61 @@ size_t Scan::detect(uint16_t *result, bool *filtered_result, size_t result_size,
}
}
return max_rssi_x;
Event event(*this, EventType::DETECTED, 0);
event.epoch = epoch;
event.detected.detected = max_rssi_x < result_size;
event.detected.freq = current_frequency;
event.detected.rssi =
event.detected.detected ? -(float)result[max_rssi_x] : LO_RSSI_THRESHOLD;
event.detected.detected_at = max_rssi_x;
event.detected.trigger =
event.detected.detected && event.detected.rssi >= trigger_level;
detection_count++;
return event;
}
size_t Scan::addEventListener(EventType t, Listener &l)
{
size_t c = listener_count[(size_t)t];
Listener **new_list = new Listener *[c + 1];
new_list[c] = &l;
listener_count[(size_t)t] = c + 1;
if (c > 0)
{
Listener **old_list = eventListeners[(size_t)t];
memcpy(new_list, old_list, c * sizeof(Listener *));
delete[] old_list;
}
eventListeners[(size_t)t] = new_list;
return c;
}
struct CallbackFunction : Listener
{
void (*cb)(void *arg, Event &e);
void *arg;
CallbackFunction(void cb(void *arg, Event &e), void *arg) : cb(cb), arg(arg) {}
void onEvent(Event &e) { cb(arg, e); }
};
size_t Scan::addEventListener(EventType t, void cb(void *arg, Event &e), void *arg)
{
return addEventListener(t, *(new CallbackFunction(cb, arg)));
}
void Scan::fireEvent(Event &event)
{
Listener **list = eventListeners[(size_t)event.type];
size_t c = listener_count[(size_t)event.type];
for (int i = 0; i < c; i++)
{
list[i]->onEvent(event);
}
}
#endif
+28 -2
View File
@@ -1,4 +1,5 @@
#include <cstdint>
#include <events.h>
#include <stdlib.h>
#ifndef LORASA_CORE_H
@@ -33,6 +34,27 @@ constexpr float LO_RSSI_THRESHOLD = HI_RSSI_THRESHOLD - 66;
struct Scan
{
uint64_t epoch;
float current_frequency;
uint64_t fr_begin;
uint64_t fr_end;
uint64_t drone_detection_level;
bool sound_on;
bool led_flag;
uint64_t detection_count;
bool animated;
float trigger_level;
Listener **eventListeners[(size_t)EventType::_MAX_EVENT_TYPE];
size_t listener_count[(size_t)EventType::_MAX_EVENT_TYPE];
Scan()
: epoch(0), current_frequency(0), fr_begin(0), fr_end(0),
drone_detection_level(0), sound_on(false), led_flag(false), detection_count(0),
animated(false), trigger_level(0), listener_count{
0,
} {};
virtual float getRSSI() = 0;
// rssiMethod gets the data similar to the scan method,
@@ -43,8 +65,12 @@ struct Scan
// those values that represent a detection event.
// It returns index that represents strongest signal at which a detection event
// occurred.
static size_t detect(uint16_t *result, bool *filtered_result, size_t result_size,
int samples);
Event detect(uint16_t *result, bool *filtered_result, size_t result_size,
int samples);
size_t addEventListener(EventType t, Listener &l);
size_t addEventListener(EventType t, void cb(void *, Event &), void *arg);
void fireEvent(Event &e);
};
// Remove reading without neighbors
+3 -1
View File
@@ -59,6 +59,8 @@ board_build.f_cpu = 240000000
lib_deps =
ropg/Heltec_ESP32_LoRa_v3@^0.9.1
RadioLib
U8g2
XPowersLib
build_flags =
-DLILYGO
-DT3_S3_V1_2_SX1262
@@ -72,7 +74,7 @@ build_flags =
-DARDUINO_USB_MODE=1
[env:lilygo-T3S3-v1-2-xs1280]
[env:lilygo-T3S3-v1-2-sx1280]
platform = espressif32
board = t3_s3_v1_x
framework = arduino
+264 -242
View File
@@ -24,7 +24,9 @@
// #define HELTEC_NO_DISPLAY
#include <Arduino.h>
#ifdef HELTEC
#include <ArduinoJson.h>
#endif
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
@@ -39,6 +41,8 @@
// library internals.
#define RADIOLIB_GODMODE (1)
#include <charts.h>
#include <events.h>
#include <scan.h>
#ifndef LILYGO
@@ -169,13 +173,11 @@ constexpr int WINDOW_SIZE = 15;
#define SINGLE_STEP (float)(RANGE / (STEPS * SCAN_RBW_FACTOR))
uint64_t range = (int)(FREQ_END - FREQ_BEGIN);
uint64_t fr_begin = FREQ_BEGIN;
uint64_t fr_end = FREQ_BEGIN;
uint64_t iterations = RANGE / RANGE_PER_PAGE;
// uint64_t range_frequency = FREQ_END - FREQ_BEGIN;
uint64_t median_frequency = FREQ_BEGIN + FREQ_END - FREQ_BEGIN / 2;
uint64_t median_frequency = (FREQ_BEGIN + FREQ_END) / 2;
// #define DISABLE_PLOT_CHART false // unused
@@ -187,8 +189,7 @@ bool filtered_result[RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE];
int max_bins_array_value[MAX_POWER_LEVELS];
int max_step_range = 32;
// Waterfall array
bool waterfall[STEPS], detected_y[STEPS]; // 20 - ??? steps of the waterfall
bool detected_y[STEPS]; // 20 - ??? steps
// global variable
@@ -197,12 +198,10 @@ bool first_run, new_pixel, detected_x = false;
// drone detection flag
bool detected = false;
uint64_t drone_detection_level = DEFAULT_DRONE_DETECTION_LEVEL;
uint64_t show_db_after = 80;
#define TRIGGER_LEVEL -80.0
uint64_t drone_detected_frequency_start = 0;
uint64_t drone_detected_frequency_end = 0;
uint64_t detection_count = 0;
bool single_page_scan = false;
bool SOUND_ON = false;
// #define PRINT_DEBUG
#define PRINT_PROFILE_TIME
@@ -222,7 +221,6 @@ uint64_t x, y, range_item, w = WATERFALL_START, i = 0;
int osd_x = 1, osd_y = 2, col = 0, max_bin = 32;
uint64_t ranges_count = 0;
float freq = 0;
int rssi = 0;
int state = 0;
@@ -235,7 +233,6 @@ constexpr int samples = SAMPLES_RSSI;
uint8_t result_index = 0;
uint8_t button_pressed_counter = 0;
uint64_t loop_cnt = 0;
#ifndef LILYGO
// #define JOYSTICK_ENABLED
@@ -363,6 +360,34 @@ void osdProcess()
}
#endif
struct RadioScan : Scan
{
float getRSSI() override;
};
float RadioScan::getRSSI()
{
#ifdef USING_SX1280PA
// radio.startReceive();
// get instantaneous RSSI value
// When PR will be merged we can use radi.getRSSI(false);
uint8_t data[3] = {0, 0, 0}; // RssiInst, Status, RFU
radio.mod->SPIreadStream(RADIOLIB_SX128X_CMD_GET_RSSI_INST, data, 3);
return ((float)data[0] / (-2.0));
#else
return radio.getRSSI(false);
#endif
}
RadioScan r;
#define WATERFALL_SENSITIVITY 0.05
DecoratedBarChart *bar;
WaterfallChart *waterChart;
StackedChart stacked(display, 0, 0, 0, 0);
UptimeClock *uptime;
void init_radio()
{
// initialize SX1262 FSK modem at the initial frequency
@@ -450,7 +475,9 @@ struct frequency_scan_result
void logToSerialTask(void *parameter)
{
#ifdef HELTEC
JsonDocument doc;
#endif
char jsonOutput[200];
for (;;)
@@ -475,17 +502,26 @@ void logToSerialTask(void *parameter)
continue;
}
#ifdef HELTEC
doc["low_range_freq"] = frequency_scan_result.begin;
doc["high_range_freq"] = frequency_scan_result.end;
doc["value"] = max_result;
serializeJson(doc, jsonOutput);
Serial.println(jsonOutput);
#else
Serial.printf("{\"low_range_freq\": %ull, \"high_range_freq\": %ull, "
"\"value\": \"%s\"}\n",
frequency_scan_result.begin, frequency_scan_result.end,
max_result);
#endif
}
vTaskDelay(LOG_DATA_JSON_INTERVAL / portTICK_PERIOD_MS);
}
}
void drone_sound_alarm(void *arg, Event &e);
void setup(void)
{
#ifdef LILYGO
@@ -513,7 +549,6 @@ void setup(void)
#endif
float vbat;
float resolution;
loop_cnt = 0;
bt_start = millis();
wf_start = millis();
@@ -532,7 +567,7 @@ void setup(void)
delay(10);
if (button.pressed())
{
SOUND_ON = !SOUND_ON;
r.sound_on = !r.sound_on;
tone(BUZZER_PIN, 205, 100);
delay(50);
tone(BUZZER_PIN, 205, 100);
@@ -642,6 +677,45 @@ void setup(void)
#ifdef LOG_DATA_JSON
xTaskCreate(logToSerialTask, "LOG_DATA_JSON", 2048, NULL, 1, NULL);
#endif
r.trigger_level = TRIGGER_LEVEL;
stacked.reset(0, 0, display.width(), display.height());
bar = new DecoratedBarChart(display, 0, 0, display.width(), 0, FREQ_BEGIN, FREQ_END,
LO_RSSI_THRESHOLD, HI_RSSI_THRESHOLD, r.trigger_level);
size_t b = stacked.addChart(bar);
Chart *statusBar = new StatusBar(display, 0, 0, display.width(), r);
#if (WATERFALL_ENABLED == true)
size_t *multiples = new size_t[6]{5, 3, 4, 15, 4, 3};
WaterfallModel *model =
new WaterfallModel((size_t)display.width(), 1000, 6, multiples);
model->reset(millis(), display.width());
delete[] multiples;
waterChart =
new WaterfallChart(display, 0, WATERFALL_START, display.width(), 0, FREQ_BEGIN,
FREQ_END, r.trigger_level, WATERFALL_SENSITIVITY, model);
size_t c = stacked.addChart(waterChart);
stacked.setHeight(c, stacked.height - WATERFALL_START - statusBar->height);
r.addEventListener(DETECTED, *waterChart);
#endif
size_t d = stacked.addChart(statusBar);
stacked.setHeight(b, stacked.height);
r.addEventListener(DETECTED, bar->bar);
r.addEventListener(DETECTED, drone_sound_alarm, &r);
r.addEventListener(SCAN_TASK_COMPLETE, stacked);
#ifdef UPTIME_CLOCK
uptime = new UptimeClock(display, millis());
#endif
}
// Formula to translate 33 bin to approximate RSSI value
@@ -651,10 +725,9 @@ int binToRSSI(int bin)
return 11 + (bin * 4);
}
// return true if continue the code is false break the loop
bool buttonPressHandler(float freq)
// is there an input using Hot Button or joystick
bool buttonInputRequested()
{
// Detection level button short press
if (button.pressedFor(100)
#ifdef JOYSTICK_ENABLED
|| joy_btn_click()
@@ -662,73 +735,80 @@ bool buttonPressHandler(float freq)
)
{
button.update();
button_pressed_counter = 0;
// if long press stop
while (button.pressedNow()
if (button.pressedNow()
#ifdef JOYSTICK_ENABLED
|| joy_btn_click()
|| joy_btn_click()
#endif
)
{
// Print Curent frequency once
if (button_pressed_counter == 0)
{
display.setTextAlignment(TEXT_ALIGN_CENTER);
display.drawString(128 / 2, 0, String(freq));
display.display();
}
delay(10);
button_pressed_counter++;
if (button_pressed_counter > 150)
{
digitalWrite(LED, HIGH);
delay(150);
digitalWrite(LED, LOW);
}
}
if (button_pressed_counter > 150)
{
// Remove Curent Frequency Text
display.setTextAlignment(TEXT_ALIGN_CENTER);
display.setColor(BLACK);
display.drawString(128 / 2, 0, String(freq));
display.setColor(WHITE);
display.display();
return false;
}
if (button_pressed_counter > 50 && button_pressed_counter < 150)
{
if (!joy_btn_clicked)
{
// Visually confirm it's off so user releases button
display.displayOff();
// Deep sleep (has wait for release so we don't wake up
// immediately)
heltec_deep_sleep();
}
return false;
}
button.update();
display.setTextAlignment(TEXT_ALIGN_RIGHT);
// erase old drone detection level value
display.setColor(BLACK);
display.fillRect(128 - 13, 0, 13, 13);
display.setColor(WHITE);
drone_detection_level++;
// print new value
display.drawString(128, 0, String(drone_detection_level));
tone(BUZZER_PIN, 104, 150);
if (drone_detection_level > 30)
{
drone_detection_level = 1;
return true;
}
}
return true;
return false;
}
void drone_sound_alarm(int drone_detection_level, int detection_count,
int tone_freq_db = 205)
enum ButtonEvent
{
NONE = 0,
LONG_PRESS,
SHORT_PRESS,
TOO_SHORT,
SUSPEND
};
ButtonEvent buttonPressEvent()
{
button_pressed_counter = 0;
// if long press stop
while (button.pressedNow()
#ifdef JOYSTICK_ENABLED
|| joy_btn_click()
#endif
)
{
delay(10);
button_pressed_counter++;
if (button_pressed_counter > 150)
{
digitalWrite(LED, HIGH);
delay(150);
digitalWrite(LED, LOW);
}
}
if (button_pressed_counter > 150)
{
return LONG_PRESS;
}
if (button_pressed_counter > 50)
{
if (!joy_btn_clicked)
{
return SUSPEND;
}
return SHORT_PRESS;
}
button.update();
return TOO_SHORT;
}
void drone_sound_alarm(void *arg, Event &e)
{
if (e.type != DETECTED)
{
return;
}
Scan &r = *((Scan *)arg);
if (!r.sound_on)
return;
int tone_freq_db = e.detected.detected_at * 2;
int drone_detection_level = r.drone_detection_level;
int detection_count = r.detection_count;
// If level is set to sensitive,
// start beeping every 10th frequency and shorter
// it improves performance less short beep delays...
@@ -740,12 +820,12 @@ void drone_sound_alarm(int drone_detection_level, int detection_count,
tone_freq_db = 285 - tone_freq_db;
}
if (detection_count == 1 && SOUND_ON)
if (r.detection_count == 1 && r.sound_on)
{
tone(BUZZER_PIN, tone_freq_db,
10); // same action ??? but first time
}
if (detection_count % 5 == 0 && SOUND_ON)
if (r.detection_count % 5 == 0 && r.sound_on)
{
tone(BUZZER_PIN, tone_freq_db,
10); // same action ??? but every 5th time
@@ -753,7 +833,7 @@ void drone_sound_alarm(int drone_detection_level, int detection_count,
}
else
{
if (detection_count % 20 == 0 && SOUND_ON)
if (r.detection_count % 20 == 0 && r.sound_on)
{
tone(BUZZER_PIN, 205,
10); // same action ??? but every 20th detection
@@ -767,7 +847,7 @@ void joystickMoveCursor(int joy_x_pressed)
if (joy_x_pressed > 0)
{
cursor_x_position--;
display.drawString(cursor_x_position, 0, String((int)freq));
display.drawString(cursor_x_position, 0, String((int)r.current_frequency));
display.drawLine(cursor_x_position, 1, cursor_x_position, 10);
display.display();
delay(10);
@@ -775,7 +855,7 @@ void joystickMoveCursor(int joy_x_pressed)
else if (joy_x_pressed < 0)
{
cursor_x_position++;
display.drawString(cursor_x_position, 0, String((int)freq));
display.drawString(cursor_x_position, 0, String((int)r.current_frequency));
display.drawLine(cursor_x_position, 1, cursor_x_position, 10);
display.display();
delay(10);
@@ -783,7 +863,7 @@ void joystickMoveCursor(int joy_x_pressed)
if (cursor_x_position > DISPLAY_WIDTH || cursor_x_position < 0)
{
cursor_x_position = 0;
display.drawString(cursor_x_position, 0, String((int)freq));
display.drawString(cursor_x_position, 0, String((int)r.current_frequency));
display.drawLine(cursor_x_position, 1, cursor_x_position, 10);
display.display();
delay(10);
@@ -821,45 +901,23 @@ void check_ranges()
}
}
struct RadioScan : Scan
{
float getRSSI() override;
};
float RadioScan::getRSSI()
{
#ifdef USING_SX1280PA
// radio.startReceive();
// get instantaneous RSSI value
// When PR will be merged we can use radi.getRSSI(false);
uint8_t data[3] = {0, 0, 0}; // RssiInst, Status, RFU
radio.mod->SPIreadStream(RADIOLIB_SX128X_CMD_GET_RSSI_INST, data, 3);
return ((float)data[0] / (-2.0));
#else
return radio.getRSSI(false);
#endif
}
// MAX Frequency RSSI BIN value of the samples
int max_rssi_x = 999;
RadioScan r;
void loop(void)
{
UI_displayDecorate(0, 0, false); // some default values
r.led_flag = false;
detection_count = 0;
r.detection_count = 0;
drone_detected_frequency_start = 0;
ranges_count = 0;
// reset scan time
#ifdef PRINT_PROFILE_TIME
scan_time = 0;
// general purpose loop counter
loop_cnt++;
loop_start = millis();
#endif
r.epoch++;
if (!ANIMATED_RELOAD || !single_page_scan)
{
@@ -875,8 +933,8 @@ void loop(void)
RANGE_PER_PAGE = range;
}
fr_begin = FREQ_BEGIN;
fr_end = fr_begin;
r.fr_begin = FREQ_BEGIN;
r.fr_end = r.fr_begin;
// 50 is a single-screen range
// TODO: Make 50 a variable with the option to show the full range
@@ -898,14 +956,14 @@ void loop(void)
range = RANGE_PER_PAGE;
if (ranges_count == 0)
{
fr_begin = (range_item == 0) ? fr_begin : fr_begin += range;
fr_end = fr_begin + RANGE_PER_PAGE;
r.fr_begin = (range_item == 0) ? r.fr_begin : r.fr_begin + range;
r.fr_end = r.fr_begin + RANGE_PER_PAGE;
}
else
{
fr_begin = SCAN_RANGES[range_item] / 1000;
fr_end = SCAN_RANGES[range_item] % 1000;
range = fr_end - fr_begin;
r.fr_begin = SCAN_RANGES[range_item] / 1000;
r.fr_end = SCAN_RANGES[range_item] % 1000;
range = r.fr_end - r.fr_begin;
}
#ifdef DISABLED_CODE
@@ -916,11 +974,6 @@ void loop(void)
}
#endif
if (single_page_scan == false)
{
UI_displayDecorate(fr_begin, fr_end, true);
}
drone_detected_frequency_start = 0;
display.setTextAlignment(TEXT_ALIGN_RIGHT);
@@ -951,26 +1004,29 @@ void loop(void)
// Because of the SCAN_RBW_FACTOR x is not a display coordinate anymore
// x > STEPS on SCAN_RBW_FACTOR
int display_x = x / SCAN_RBW_FACTOR;
waterfall[display_x] = false;
float step = (range * ((float)x / (STEPS * SCAN_RBW_FACTOR)));
freq = fr_begin + step;
LOG("setFrequency:%f\n", freq);
r.current_frequency = r.fr_begin + step;
LOG("setFrequency:%f\n", r.current_frequency);
#ifdef USING_SX1280PA
state = radio.setFrequency(freq); // 1280 doesn't have calibration
state =
radio.setFrequency(r.current_frequency); // 1280 doesn't have calibration
radio.startReceive(RADIOLIB_SX128X_RX_TIMEOUT_INF);
#elif USING_SX1276
state = radio.setFrequency(freq);
#else
state = radio.setFrequency(freq, false); // false = no calibration need here
state = radio.setFrequency(r.current_frequency,
false); // false = no calibration need here
#endif
int radio_error_count = 0;
if (state != RADIOLIB_ERR_NONE)
{
display.drawString(
0, 64 - 10, "E(" + String(state) + "):setFrequency:" + String(freq));
Serial.println("E(" + String(state) + "):setFrequency:" + String(freq));
display.drawString(0, 64 - 10,
"E(" + String(state) +
"):setFrequency:" + String(r.current_frequency));
Serial.println("E(" + String(state) +
"):setFrequency:" + String(r.current_frequency));
display.display();
delay(2);
radio_error_count++;
@@ -978,7 +1034,7 @@ void loop(void)
continue;
}
LOG("Step:%d Freq: %f\n", x, freq);
LOG("Step:%d Freq: %f\n", x, r.current_frequency);
// SpectralScan Method
#ifdef METHOD_SPECTRAL
{
@@ -1012,6 +1068,7 @@ void loop(void)
LOG("METHOD RSSI");
uint16_t max_rssi = r.rssiMethod(SAMPLES_RSSI, result,
RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE);
if (max_x_rssi[display_x] > max_rssi)
{
max_x_rssi[display_x] = max_rssi;
@@ -1036,65 +1093,38 @@ void loop(void)
display.setColor(WHITE);
}
#endif
size_t detected_at = r.detect(
result, filtered_result, RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE, samples);
Event event = r.detect(result, filtered_result,
RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE, samples);
event.time_ms = millis();
size_t detected_at = event.detected.detected_at;
if (max_rssi_x > detected_at)
{
// MAx bin Value not RSSI
max_rssi_x = detected_at;
}
detected = detected_at < RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE;
detected = event.detected.detected;
detected_y[display_x] = false;
#if FILTER_SPECTRUM_RESULTS
for (int y = 0; y < RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE; y++)
{
// calculating max window x RSSI after filters
x_window = (int)(display_x / WINDOW_SIZE);
int abs_result = abs(result[y]);
if (filtered_result[y] == 1 && result[y] != 0 && result[y] != 1 &&
max_x_window[x_window] > abs_result)
{
max_x_window[x_window] = abs_result;
LOG("MAX x window: %i %i\n", x_window, abs_result);
}
}
#endif
float rr = event.detected.rssi;
r.drone_detection_level = drone_detection_level;
if (detected_at <= drone_detection_level)
if (event.detected.trigger)
{
// check if we should alarm about a drone presence
if (detected_y[display_x] == false) // detection threshold match
{
// Set LED to ON (filtered in UI component)
UI_setLedFlag(true);
#if (WATERFALL_ENABLED == true)
if (single_page_scan)
{
// Drone detection true for waterfall
if (!waterfall[display_x])
{
waterfall[display_x] = true;
display.setColor(WHITE);
display.setPixel(display_x, w);
}
}
#endif
r.led_flag = true;
if (drone_detected_frequency_start == 0)
{
// mark freq start
drone_detected_frequency_start = freq;
drone_detected_frequency_start = r.current_frequency;
}
// mark freq end ... will shift right to last detected range
drone_detected_frequency_end = freq;
if (SOUND_ON == true)
{
drone_sound_alarm(drone_detection_level, detection_count,
max_rssi_x * 2);
}
drone_detected_frequency_end = r.current_frequency;
#ifdef LOG_DATA_JSON
frequency_scan_result.begin = drone_detected_frequency_start;
@@ -1113,64 +1143,15 @@ void loop(void)
#endif
}
}
#if (WATERFALL_ENABLED == true)
if ((single_page_scan) && (waterfall[display_x] != true) && new_pixel)
{
// If drone not found set dark pixel on the waterfall
// TODO: make something like scrolling up if possible
waterfall[display_x] = false;
display.setColor(BLACK);
display.setPixel(display_x, w);
display.setColor(WHITE);
}
#endif
}
#ifdef PRINT_DEBUG
for (int y = 0; y < RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE; y++)
{
if (filtered_result[y] == 1)
{
LOG("Pixel:%i(%i):%i,", display_x, x, y);
}
}
#endif
for (int y = 0; y < min(RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE,
MAX_POWER_LEVELS - START_LOW);
y++)
{
if (filtered_result[y] == 1)
{
// Set MAIN signal level pixel
display.setPixelColor(display_x, y + START_LOW, WHITE);
}
}
// -------------------------------------------------------------
// Draw "Detection Level line" every 2 pixel
// -------------------------------------------------------------
if (display_x % 2 == 0)
{
if (filtered_result[drone_detection_level] == 1)
{
display.setColor(INVERSE);
}
else
{
display.setColor(WHITE);
}
display.setPixel(display_x, drone_detection_level + START_LOW);
// display.setPixel(display_x, y + START_LOW - 1); // 2 px wide
display.setColor(WHITE);
}
r.fireEvent(event);
#ifdef JOYSTICK_ENABLED
// Draw joystick cursor and Frequency RSSI value
if (display_x == cursor_x_position)
{
display.drawString(display_x - 1, 0, String((int)freq));
display.drawString(display_x - 1, 0, String((int)r.current_frequency));
display.drawLine(display_x, 1, display_x, 12);
// if method scan RSSI we can get exact RSSI value
display.drawString(display_x + 17, 0, "-" + String((int)max_rssi_x * 4));
@@ -1180,22 +1161,76 @@ void loop(void)
#ifdef PRINT_PROFILE_TIME
scan_time += (millis() - scan_start_time);
#endif
// count detected
if (detected)
{
detection_count++;
}
#ifdef PRINT_DEBUG
Serial.println("....\n");
#endif
if (first_run || ANIMATED_RELOAD)
if (r.animated)
{
display.display();
}
if (buttonPressHandler(freq) == false)
break;
if (buttonInputRequested())
{
display.setTextAlignment(TEXT_ALIGN_CENTER);
display.drawString(display.width() / 2, 0, String(r.current_frequency));
display.display();
ButtonEvent e = buttonPressEvent();
if (e == LONG_PRESS)
{
// Remove Curent Frequency Text
display.setTextAlignment(TEXT_ALIGN_CENTER);
display.setColor(BLACK);
display.drawString(display.width() / 2, 0,
String(r.current_frequency));
display.setColor(WHITE);
display.display();
break;
}
if (e == SUSPEND)
{
// Visually confirm it's off so user releases button
display.displayOff();
// Deep sleep (has wait for release so we don't wake up
// immediately)
heltec_deep_sleep();
break;
}
if (e == SHORT_PRESS)
break;
if (e == TOO_SHORT)
{
String v = String(r.trigger_level) + " dB";
uint16_t w = display.getStringWidth(v);
display.setTextAlignment(TEXT_ALIGN_RIGHT);
// erase old drone detection level value
display.setColor(BLACK);
display.fillRect(display.width() - w, 0, 13, w);
display.setColor(WHITE);
// dt is roughly single-pixel increment
float dt =
bar->bar.height == 0
? 0.0
: (LO_RSSI_THRESHOLD - HI_RSSI_THRESHOLD) / bar->bar.height;
r.trigger_level += dt;
if (r.trigger_level <= LO_RSSI_THRESHOLD)
{
r.trigger_level = HI_RSSI_THRESHOLD;
}
// print new value
display.drawString(display.width(), 0, v);
tone(BUZZER_PIN, 104, 150);
bar->bar.redraw_all = true;
}
}
// wait a little bit before the next scan,
// otherwise the SX1262 hangs
@@ -1243,29 +1278,16 @@ void loop(void)
{
w = WATERFALL_START;
}
#if (WATERFALL_ENABLED == true)
// Draw waterfall position cursor
if (single_page_scan)
{
display.setColor(BLACK);
display.drawHorizontalLine(0, w, STEPS);
display.setColor(WHITE);
}
#endif
#ifdef METHOD_RSSI
// Printing Max Window DB.
for (int x2 = 0; x2 < STEPS / WINDOW_SIZE; x2++)
{
if (max_x_window[x2] < show_db_after && max_x_window[x2] != 0)
{
display.drawString(x2 * WINDOW_SIZE + WINDOW_SIZE, 0,
"-" + String(max_x_window[x2]));
}
max_x_window[x2] = 999;
Event event(r, SCAN_TASK_COMPLETE, millis());
r.fireEvent(event);
}
#endif
// Render display data here
#ifdef UPTIME_CLOCK
uptime->draw(millis());
#endif
display.display();
#ifdef OSD_ENABLED
// Sometimes OSD prints entire screen with the digits.
+75 -193
View File
@@ -2,6 +2,8 @@
#include "RadioLib.h"
#include "global_config.h"
#include "images.h"
#include <charts.h>
#include <scan.h>
// -------------------------------------------------
// LOCAL DEFINES
@@ -12,24 +14,9 @@
//
#define SCALE_TEXT_TOP (HEIGHT + X_AXIS_WEIGHT + MAJOR_TICK_LENGTH)
static unsigned int start_scan_text = (128 / 2) - 3;
// initialized flag
static bool ui_initialized = false;
static bool led_flag = false;
static unsigned short int scan_progress_count = 0;
#ifdef Vision_Master_E290
static DEPG0290BxS800FxX_BW *display_instance;
#else
//(0x3c, SDA_OLED, SCL_OLED, DISPLAY_GEOMETRY);
static SSD1306Wire *display_instance;
#endif
// temporary dirty import ... to be solved durring upcoming refactoring
extern unsigned int drone_detection_level;
extern unsigned int RANGE_PER_PAGE;
extern unsigned int median_frequency;
extern unsigned int detection_count;
extern bool SOUND_ON;
extern unsigned int drone_detected_frequency_start;
extern unsigned int drone_detected_frequency_end;
extern unsigned int ranges_count;
@@ -40,233 +27,138 @@ extern unsigned int range_item;
extern uint64_t loop_time;
#ifndef Vision_Master_E290
void UI_Init(SSD1306Wire *display_ptr)
void UI_Init(Display_t *display_ptr)
{
// init pointer to display instance.
display_instance = display_ptr;
// check for null ???
display_instance->clear();
display_ptr->clear();
// draw the UCOG welcome logo
display_instance->drawXbm(0, 2, 128, 64, epd_bitmap_ucog);
display_instance->display();
display_ptr->drawXbm(0, 2, 128, 64, epd_bitmap_ucog);
display_ptr->display();
}
#endif
#ifdef Vision_Master_E290
void UI_Init(DEPG0290BxS800FxX_BW *display_ptr)
{
// init pointer to display instance.
display_instance = display_ptr;
// check for null ???
display_instance->clear();
// draw the UCOG welcome logo
display_instance->drawXbm(0, 2, 128, 64, epd_bitmap_ucog);
display_instance->display();
}
#endif
void UI_setLedFlag(bool new_status) { led_flag = new_status; }
void clearStatus(void)
void StatusBar::clearStatus(void)
{
// clear status line
display_instance->setColor(BLACK);
display_instance->fillRect(0, ROW_STATUS_TEXT + 2, 128, 13);
display_instance->setColor(WHITE);
display.setColor(BLACK);
display.fillRect(pos_x, pos_y, width, height);
}
void UI_clearPlotter(void)
{
// clear the scan plot rectangle (top part)
display_instance->setColor(BLACK);
display_instance->fillRect(0, 10, STEPS, HEIGHT - 10);
display_instance->setColor(WHITE);
// display_instance->setColor(BLACK);
// display_instance->fillRect(0, 10, STEPS, HEIGHT - 10);
// display_instance->setColor(WHITE);
}
void UI_clearTopStatus(void)
{
// clear the scan plot rectangle (top part)
display_instance->setColor(BLACK);
display_instance->fillRect(0, 0, STEPS, 10);
display_instance->setColor(WHITE);
}
/**
* @brief Draws ticks on the display at regular whole intervals.
*
* @param every The interval between ticks in MHz.
* @param length The length of each tick in pixels.
*/
void drawTicks(float every, int length)
{
int first_tick;
bool correction;
int pixels_per_step;
int correction_number;
int tick;
int tick_minor;
int median;
first_tick = 0;
//+ (every - (fr_begin - (int)(fr_begin / every) * every));
/*if (first_tick < fr_begin)
{
first_tick += every;
}*/
correction = false;
pixels_per_step = STEPS / (RANGE_PER_PAGE / every);
if (STEPS / RANGE_PER_PAGE != 0)
{
correction = true;
}
correction_number = STEPS - (int)(pixels_per_step * (RANGE_PER_PAGE / every));
tick = 0;
tick_minor = 0;
median = (RANGE_PER_PAGE / every) / 2;
// TODO: (RANGE_PER_PAGE / every)
// * 2 has twice extra steps we need to figureout correct logic or minor
// ticks is not showing to the end
for (int t = 0; t <= (RANGE_PER_PAGE / every) * 2; t++)
{
// fix if pixels per step is not int and we have shift
if (correction && t % 2 != 0 && correction_number > 1)
{
// pixels_per_step++;
correction_number--;
}
tick += pixels_per_step;
tick_minor = tick / 2;
if (tick <= 128 - 3)
{
display_instance->drawLine(tick, HEIGHT + X_AXIS_WEIGHT, tick,
HEIGHT + X_AXIS_WEIGHT + length);
// Central tick
if (tick > (128 / 2) - 3 && tick < (128 / 2) + 3)
{
display_instance->drawLine(tick + 1, HEIGHT + X_AXIS_WEIGHT, tick + 1,
HEIGHT + X_AXIS_WEIGHT + length);
}
}
#ifdef MINOR_TICKS
// Fix two ticks together
if ((tick_minor + 1 != tick) && (tick_minor - 1 != tick) &&
(tick_minor + 2 != tick) && (tick_minor - 2 != tick))
{
display_instance->drawLine(tick_minor, HEIGHT + X_AXIS_WEIGHT, tick_minor,
HEIGHT + X_AXIS_WEIGHT + MINOR_TICK_LENGTH);
}
// Central tick
if (tick_minor > (128 / 2) - 3 && tick_minor < (128 / 2) + 3)
{
display_instance->drawLine(tick_minor + 1, HEIGHT + X_AXIS_WEIGHT,
tick_minor + 1,
HEIGHT + X_AXIS_WEIGHT + MINOR_TICK_LENGTH);
}
#endif
}
// display_instance->setColor(BLACK);
// display_instance->fillRect(0, 0, STEPS, 10);
// display_instance->setColor(WHITE);
}
void UI_drawCursor(int16_t possition)
{
// Draw animated vertical cursor on reload process
display_instance->setColor(BLACK);
display_instance->drawVerticalLine(possition, 0, HEIGHT);
display_instance->drawVerticalLine(possition + 1, 0, HEIGHT);
display_instance->drawVerticalLine(possition + 2, 0, HEIGHT);
display_instance->setColor(WHITE);
// display_instance->setColor(BLACK);
// display_instance->drawVerticalLine(possition, 0, HEIGHT);
// display_instance->drawVerticalLine(possition + 1, 0, HEIGHT);
// display_instance->drawVerticalLine(possition + 2, 0, HEIGHT);
// display_instance->setColor(WHITE);
}
/**
* @brief Decorates the display: everything but the plot itself.
*/
void UI_displayDecorate(int begin = 0, int end = 0, bool redraw = false)
void StatusBar::draw()
{
uint16_t text_y = pos_y + height - 10;
if (!ui_initialized)
{
// Start and end ticks
display_instance->fillRect(0, HEIGHT + X_AXIS_WEIGHT, 2, MAJOR_TICK_LENGTH + 1);
display_instance->fillRect(126, HEIGHT + X_AXIS_WEIGHT, 2, MAJOR_TICK_LENGTH + 1);
// Drone detection level
display_instance->setTextAlignment(TEXT_ALIGN_RIGHT);
display_instance->drawString(128, 0, String(drone_detection_level));
display.setTextAlignment(TEXT_ALIGN_RIGHT);
display.drawString(width, 0, String(r.drone_detection_level));
}
if (!ui_initialized || redraw)
if (!ui_initialized)
{
// Clear something
display_instance->setColor(BLACK);
display_instance->fillRect(0, SCALE_TEXT_TOP + 1, 128, 12);
display_instance->setColor(WHITE);
/* display_instance->setColor(BLACK);
display_instance->fillRect(0, SCALE_TEXT_TOP + 1, 128, 12);
display_instance->setColor(WHITE);
*/
// Drone detection level
display_instance->setTextAlignment(TEXT_ALIGN_RIGHT);
display_instance->drawString(128, 0, String(drone_detection_level));
display.setTextAlignment(TEXT_ALIGN_RIGHT);
display.drawString(pos_x + width, 0, String(r.drone_detection_level));
// Frequency start
display_instance->setTextAlignment(TEXT_ALIGN_LEFT);
display_instance->drawString(0, ROW_STATUS_TEXT,
(begin == 0) ? String(FREQ_BEGIN) : String(begin));
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.drawString(pos_x, text_y,
(r.fr_begin == 0) ? String(FREQ_BEGIN) : String(r.fr_begin));
// Frequency detected
display_instance->setTextAlignment(TEXT_ALIGN_CENTER);
display_instance->drawString(128 / 2, ROW_STATUS_TEXT,
(begin == 0) ? String(median_frequency)
: String(begin + ((end - begin) / 2)));
display.setTextAlignment(TEXT_ALIGN_CENTER);
display.drawString(pos_x + width / 2, text_y,
(r.fr_begin == 0)
? String(median_frequency)
: String(r.fr_begin + ((r.fr_end - r.fr_begin) / 2)));
// Frequency end
display_instance->setTextAlignment(TEXT_ALIGN_RIGHT);
display_instance->drawString(128, ROW_STATUS_TEXT,
(end == 0) ? String(FREQ_END) : String(end));
display.setTextAlignment(TEXT_ALIGN_RIGHT);
display.drawString(pos_x + width, text_y,
(r.fr_end == 0) ? String(FREQ_END) : String(r.fr_end));
}
// Status text block
if (led_flag) // 'drone' detected
if (r.led_flag) // 'drone' detected
{
display_instance->setTextAlignment(TEXT_ALIGN_CENTER);
display.setTextAlignment(TEXT_ALIGN_CENTER);
// clear status line
clearStatus();
display_instance->drawString(start_scan_text + 2, ROW_STATUS_TEXT,
String(drone_detected_frequency_start) + ">RF<" +
String(drone_detected_frequency_end));
display.setColor(WHITE);
display.drawString(pos_x + width / 2, text_y,
String(drone_detected_frequency_start) + ">RF<" +
String(drone_detected_frequency_end));
}
else
{
// "Scanning"
display_instance->setTextAlignment(TEXT_ALIGN_CENTER);
display.setTextAlignment(TEXT_ALIGN_CENTER);
// clear status line
clearStatus();
if (scan_progress_count == 0)
String s = "Scan \\";
if (scan_progress_count == 1)
{
display_instance->drawString(start_scan_text, ROW_STATUS_TEXT, "Scan \\");
}
else if (scan_progress_count == 1)
{
display_instance->drawString(start_scan_text, ROW_STATUS_TEXT, "Scan |");
s = "Scan |";
}
else if (scan_progress_count == 2)
{
display_instance->drawString(start_scan_text, ROW_STATUS_TEXT, "Scan /");
s = "Scan /";
}
else if (scan_progress_count == 3)
{
display_instance->drawString(start_scan_text, ROW_STATUS_TEXT, "Scan -");
s = "Scan -";
}
scan_progress_count++;
if (scan_progress_count >= 4)
{
scan_progress_count = 0;
}
display.setColor(WHITE);
display.drawString(pos_x + width / 2 - 3, text_y, s);
}
if (led_flag == true && detection_count >= 5)
if (r.led_flag && r.detection_count >= 5)
{
digitalWrite(LED, HIGH);
if (SOUND_ON)
if (r.sound_on)
{
tone(BUZZER_PIN, 104, 100);
}
digitalWrite(REB_PIN, HIGH);
led_flag = false;
r.led_flag = false;
}
else if (!redraw)
else if (!r.led_flag)
{
digitalWrite(LED, LOW);
}
@@ -274,39 +166,29 @@ void UI_displayDecorate(int begin = 0, int end = 0, bool redraw = false)
if (ranges_count == 0)
{
#ifdef DEBUG
display_instance->setTextAlignment(TEXT_ALIGN_LEFT);
display_instance->drawString(0, ROW_STATUS_TEXT, String(loop_time));
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.drawString(pos_x, text_y, String(loop_time));
#else
display_instance->setTextAlignment(TEXT_ALIGN_LEFT);
display_instance->drawString(0, ROW_STATUS_TEXT, String(FREQ_BEGIN));
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.drawString(pos_x, text_y, String(FREQ_BEGIN));
#endif
display_instance->setTextAlignment(TEXT_ALIGN_RIGHT);
display_instance->drawString(128, ROW_STATUS_TEXT, String(FREQ_END));
display.setTextAlignment(TEXT_ALIGN_RIGHT);
display.drawString(pos_x + width, text_y, String(FREQ_END));
}
else if (ranges_count > 0)
{
display_instance->setTextAlignment(TEXT_ALIGN_LEFT);
display_instance->drawString(0, ROW_STATUS_TEXT,
String(SCAN_RANGES[range_item] / 1000) + "-" +
String(SCAN_RANGES[range_item] % 1000));
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.drawString(pos_x, text_y,
String(SCAN_RANGES[range_item] / 1000) + "-" +
String(SCAN_RANGES[range_item] % 1000));
if (range_item + 1 < iterations)
{
display_instance->setTextAlignment(TEXT_ALIGN_RIGHT);
display_instance->drawString(128, ROW_STATUS_TEXT,
String(SCAN_RANGES[range_item + 1] / 1000) +
"-" +
String(SCAN_RANGES[range_item + 1] % 1000));
display.setTextAlignment(TEXT_ALIGN_RIGHT);
display.drawString(pos_x + width, text_y,
String(SCAN_RANGES[range_item + 1] / 1000) + "-" +
String(SCAN_RANGES[range_item + 1] % 1000));
}
}
if (ui_initialized == false)
{
// X-axis
display_instance->fillRect(0, HEIGHT, STEPS, X_AXIS_WEIGHT);
// ticks
#ifdef MAJOR_TICKS
drawTicks(MAJOR_TICKS, MAJOR_TICK_LENGTH);
#endif
}
ui_initialized = true;
}
+5 -16
View File
@@ -3,10 +3,6 @@
#include "../lib/scan/scan.cpp"
#include <unity.h>
void setUp(void) {}
void tearDown(void) {}
struct TestScan : Scan
{
TestScan(float *ctx, int sz) : ctx(ctx), sz(sz), idx(0) {}
@@ -52,27 +48,20 @@ void test_detect()
uint16_t samples[test_sz] = {20, 50, 55, 60, 0, 70, 75, 80, 0, 90, 0, 100, 110};
bool result[test_sz];
size_t r = Scan::detect(samples, result, test_sz, 1);
TestScan test_scan({}, 0);
Event e = test_scan.detect(samples, result, test_sz, 1);
size_t r = e.detected.detected_at;
bool expect[test_sz] = {1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1};
TEST_ASSERT_EQUAL_INT16(0, r);
TEST_ASSERT_EQUAL_INT8_ARRAY(expect, result, test_sz);
r = Scan::detect(samples, result, test_sz, 2);
Event e2 = test_scan.detect(samples, result, test_sz, 2);
r = e2.detected.detected_at;
bool expect2[test_sz] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0};
TEST_ASSERT_EQUAL_INT16(1, r);
TEST_ASSERT_EQUAL_INT8_ARRAY(expect2, result, test_sz);
}
int main(int argc, char **argv)
{
UNITY_BEGIN();
RUN_TEST(test_rssi);
RUN_TEST(test_detect);
UNITY_END();
}
+220
View File
@@ -0,0 +1,220 @@
#define TO_STRING
#include "../lib/models/WaterfallModel.cpp"
#include <stdio.h>
#include <unity.h>
void test_push()
{
size_t *ms = new size_t[6]{5, 3, 4, 15, 4, 3};
WaterfallModel m(1, 1, 6, ms);
delete ms;
char *r = m.toString();
TEST_ASSERT_EQUAL_STRING("w:1 b:34 "
"[dt:1 t:0 [ c:0 e:0 ]"
"dt:1 t:0 [ c:0 e:0 ]"
"dt:1 t:0 [ c:0 e:0 ]"
"dt:1 t:0 [ c:0 e:0 ]"
"dt:1 t:0 [ c:0 e:0 ]"
"dt:5 t:0 [ c:0 e:0 ]"
"dt:5 t:0 [ c:0 e:0 ]"
"dt:5 t:0 [ c:0 e:0 ]"
"dt:15 t:0 [ c:0 e:0 ]"
"dt:15 t:0 [ c:0 e:0 ]"
"dt:15 t:0 [ c:0 e:0 ]"
"dt:15 t:0 [ c:0 e:0 ]"
"dt:60 t:0 [ c:0 e:0 ]"
"dt:60 t:0 [ c:0 e:0 ]"
"dt:60 t:0 [ c:0 e:0 ]"
"dt:60 t:0 [ c:0 e:0 ]"
"dt:60 t:0 [ c:0 e:0 ]"
"dt:60 t:0 [ c:0 e:0 ]"
"dt:60 t:0 [ c:0 e:0 ]"
"dt:60 t:0 [ c:0 e:0 ]"
"dt:60 t:0 [ c:0 e:0 ]"
"dt:60 t:0 [ c:0 e:0 ]"
"dt:60 t:0 [ c:0 e:0 ]"
"dt:60 t:0 [ c:0 e:0 ]"
"dt:60 t:0 [ c:0 e:0 ]"
"dt:60 t:0 [ c:0 e:0 ]"
"dt:60 t:0 [ c:0 e:0 ]"
"dt:900 t:0 [ c:0 e:0 ]"
"dt:900 t:0 [ c:0 e:0 ]"
"dt:900 t:0 [ c:0 e:0 ]"
"dt:900 t:0 [ c:0 e:0 ]"
"dt:3600 t:0 [ c:0 e:0 ]"
"dt:3600 t:0 [ c:0 e:0 ]"
"dt:3600 t:0 [ c:0 e:0 ] ]",
r);
delete r;
m.reset(0, 1);
uint64_t i = 0;
for (; i < 10; i++)
m.updateModel(i, 0, 1);
r = m.toString();
TEST_ASSERT_EQUAL_STRING("w:1 b:34 "
"[dt:1 t:9 [ c:1 e:1 ]"
"dt:1 t:8 [ c:1 e:1 ]"
"dt:1 t:7 [ c:1 e:1 ]"
"dt:1 t:6 [ c:1 e:1 ]"
"dt:1 t:5 [ c:1 e:1 ]"
"dt:5 t:5 [ c:5 e:5 ]"
"dt:5 t:5 [ c:0 e:0 ]"
"dt:5 t:5 [ c:0 e:0 ]"
"dt:15 t:15 [ c:0 e:0 ]"
"dt:15 t:15 [ c:0 e:0 ]"
"dt:15 t:15 [ c:0 e:0 ]"
"dt:15 t:15 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:900 t:900 [ c:0 e:0 ]"
"dt:900 t:900 [ c:0 e:0 ]"
"dt:900 t:900 [ c:0 e:0 ]"
"dt:900 t:900 [ c:0 e:0 ]"
"dt:3600 t:3600 [ c:0 e:0 ]"
"dt:3600 t:3600 [ c:0 e:0 ]"
"dt:3600 t:3600 [ c:0 e:0 ] ]",
r);
delete r;
for (; i < 100; i += 10)
m.updateModel(i, 0, 1);
r = m.toString();
TEST_ASSERT_EQUAL_STRING("w:1 b:34 "
"[dt:1 t:90 [ c:1 e:1 ]"
"dt:1 t:89 [ c:0 e:0 ]"
"dt:1 t:88 [ c:0 e:0 ]"
"dt:1 t:87 [ c:0 e:0 ]"
"dt:1 t:86 [ c:0 e:0 ]"
"dt:5 t:85 [ c:0 e:0 ]"
"dt:5 t:80 [ c:1 e:1 ]"
"dt:5 t:75 [ c:0 e:0 ]"
"dt:15 t:75 [ c:1 e:1 ]"
"dt:15 t:60 [ c:2 e:2 ]"
"dt:15 t:45 [ c:1 e:1 ]"
"dt:15 t:30 [ c:2 e:2 ]"
"dt:60 t:60 [ c:11 e:11 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:60 t:60 [ c:0 e:0 ]"
"dt:900 t:900 [ c:0 e:0 ]"
"dt:900 t:900 [ c:0 e:0 ]"
"dt:900 t:900 [ c:0 e:0 ]"
"dt:900 t:900 [ c:0 e:0 ]"
"dt:3600 t:3600 [ c:0 e:0 ]"
"dt:3600 t:3600 [ c:0 e:0 ]"
"dt:3600 t:3600 [ c:0 e:0 ] ]",
r);
delete r;
for (; i < 10000; i++)
m.updateModel(i, 0, 1);
r = m.toString();
TEST_ASSERT_EQUAL_STRING("w:1 b:34 "
"[dt:1 t:9999 [ c:1 e:1 ]"
"dt:1 t:9998 [ c:1 e:1 ]"
"dt:1 t:9997 [ c:1 e:1 ]"
"dt:1 t:9996 [ c:1 e:1 ]"
"dt:1 t:9995 [ c:1 e:1 ]"
"dt:5 t:9995 [ c:4 e:4 ]"
"dt:5 t:9990 [ c:5 e:5 ]"
"dt:5 t:9985 [ c:5 e:5 ]"
"dt:15 t:9990 [ c:5 e:5 ]"
"dt:15 t:9975 [ c:15 e:15 ]"
"dt:15 t:9960 [ c:15 e:15 ]"
"dt:15 t:9945 [ c:15 e:15 ]"
"dt:60 t:9960 [ c:30 e:30 ]"
"dt:60 t:9900 [ c:60 e:60 ]"
"dt:60 t:9840 [ c:60 e:60 ]"
"dt:60 t:9780 [ c:60 e:60 ]"
"dt:60 t:9720 [ c:60 e:60 ]"
"dt:60 t:9660 [ c:60 e:60 ]"
"dt:60 t:9600 [ c:60 e:60 ]"
"dt:60 t:9540 [ c:60 e:60 ]"
"dt:60 t:9480 [ c:60 e:60 ]"
"dt:60 t:9420 [ c:60 e:60 ]"
"dt:60 t:9360 [ c:60 e:60 ]"
"dt:60 t:9300 [ c:60 e:60 ]"
"dt:60 t:9240 [ c:60 e:60 ]"
"dt:60 t:9180 [ c:60 e:60 ]"
"dt:60 t:9120 [ c:60 e:60 ]"
"dt:900 t:9900 [ c:60 e:60 ]"
"dt:900 t:9000 [ c:900 e:900 ]"
"dt:900 t:8100 [ c:900 e:900 ]"
"dt:900 t:7200 [ c:900 e:900 ]"
"dt:3600 t:7200 [ c:2700 e:2700 ]"
"dt:3600 t:3600 [ c:3520 e:3520 ]"
"dt:3600 t:3600 [ c:0 e:0 ] ]",
r);
delete r;
for (; i < 5000; i++)
m.updateModel(i, 0, 1);
r = m.toString();
TEST_ASSERT_EQUAL_STRING("w:1 b:34 "
"[dt:1 t:9999 [ c:1 e:1 ]"
"dt:1 t:9998 [ c:1 e:1 ]"
"dt:1 t:9997 [ c:1 e:1 ]"
"dt:1 t:9996 [ c:1 e:1 ]"
"dt:1 t:9995 [ c:1 e:1 ]"
"dt:5 t:9995 [ c:4 e:4 ]"
"dt:5 t:9990 [ c:5 e:5 ]"
"dt:5 t:9985 [ c:5 e:5 ]"
"dt:15 t:9990 [ c:5 e:5 ]"
"dt:15 t:9975 [ c:15 e:15 ]"
"dt:15 t:9960 [ c:15 e:15 ]"
"dt:15 t:9945 [ c:15 e:15 ]"
"dt:60 t:9960 [ c:30 e:30 ]"
"dt:60 t:9900 [ c:60 e:60 ]"
"dt:60 t:9840 [ c:60 e:60 ]"
"dt:60 t:9780 [ c:60 e:60 ]"
"dt:60 t:9720 [ c:60 e:60 ]"
"dt:60 t:9660 [ c:60 e:60 ]"
"dt:60 t:9600 [ c:60 e:60 ]"
"dt:60 t:9540 [ c:60 e:60 ]"
"dt:60 t:9480 [ c:60 e:60 ]"
"dt:60 t:9420 [ c:60 e:60 ]"
"dt:60 t:9360 [ c:60 e:60 ]"
"dt:60 t:9300 [ c:60 e:60 ]"
"dt:60 t:9240 [ c:60 e:60 ]"
"dt:60 t:9180 [ c:60 e:60 ]"
"dt:60 t:9120 [ c:60 e:60 ]"
"dt:900 t:9900 [ c:60 e:60 ]"
"dt:900 t:9000 [ c:900 e:900 ]"
"dt:900 t:8100 [ c:900 e:900 ]"
"dt:900 t:7200 [ c:900 e:900 ]"
"dt:3600 t:7200 [ c:2700 e:2700 ]"
"dt:3600 t:3600 [ c:3520 e:3520 ]"
"dt:3600 t:3600 [ c:0 e:0 ] ]",
r);
delete r;
}
+21
View File
@@ -0,0 +1,21 @@
#include <unity.h>
void test_rssi();
void test_detect();
void test_push();
void setUp(void) {}
void tearDown(void) {}
int main(int argc, char **argv)
{
UNITY_BEGIN();
RUN_TEST(test_rssi);
RUN_TEST(test_detect);
RUN_TEST(test_push);
UNITY_END();
}