mirror of
https://github.com/Genaker/LoraSA.git
synced 2026-08-06 08:53:15 +02:00
Vendored
+3
@@ -12,4 +12,7 @@
|
||||
},
|
||||
"files.insertFinalNewline": true,
|
||||
"files.autoSave": "onFocusChange",
|
||||
"files.associations": {
|
||||
"cstdint": "cpp"
|
||||
},
|
||||
}
|
||||
|
||||
+18
-8
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
Reference in New Issue
Block a user