~krystianch/tiny-weather

7115db56de886cf9183fa1ab16900ea058c1883b — Krystian Chachuła 4 months ago e978339
WIP: experimental dynamic refresh
3 files changed, 217 insertions(+), 157 deletions(-)

M receiver/main.c
M receiver/ui-first.c
M receiver/ui.h
M receiver/main.c => receiver/main.c +48 -40
@@ 18,9 18,8 @@
#include "virtualwire-rx.h"

enum {
	DISPLAY_INTERVAL = 60000,
	VW_BAUD = 1000,
	FULLREF_PERIOD_S = 180,
	UI_INDICATOR_SCALER = 16,
	LED_PIN = 2,
};



@@ 28,7 27,7 @@ extern unsigned char _binary_font_ttf_start[];
extern unsigned char _binary_font_ttf_end[];
extern unsigned char _binary_font_ttf_size[];

bool fullref_flag = false;
size_t ddirty_x1, ddirty_y1, ddirty_x2, ddirty_y2;
queue_t rx_queue;

static uint16_t


@@ 40,6 39,12 @@ crc_ccitt_update (uint16_t crc, uint8_t data)
            ^ ((uint16_t)data << 3));
}

uint32_t
get_ms_since_boot()
{
	return to_ms_since_boot(get_absolute_time());
}

static bool
vw_rx_read()
{


@@ 86,7 91,7 @@ on_msg(uint8_t *msg)
		"pres. %f hPa, humi. %f %", payload.serial, temperature,
		pressure, humidity);

	ui_on_packet(payload.serial, temperature, pressure, humidity);
	ui_on_packet(get_ms_since_boot(), payload.serial, temperature, pressure, humidity);
}

static bool


@@ 98,21 103,37 @@ update_vw(repeating_timer_t *t)
	return true;
}

static bool
update_ui(repeating_timer_t *t)
void
ui_mark_dirty(size_t x1, size_t y1, size_t x2, size_t y2)
{
	(void) t;
	static unsigned int scaler = UI_INDICATOR_SCALER;
	if (--scaler == 0) {
		assert(!fullref_flag);
		fullref_flag = true;
		scaler = UI_INDICATOR_SCALER;
	} else {
		ui_refresh_time((float) (UI_INDICATOR_SCALER - scaler)
			/ UI_INDICATOR_SCALER);
	if (x1 < ddirty_x1 || ddirty_x1 == ddirty_x2) {
		ddirty_x1 = x1;
	}
	if (y1 < ddirty_y1 || ddirty_y1 == ddirty_y2) {
		ddirty_y1 = y1;
	}
	if (x2 > ddirty_x2 || ddirty_x1 == ddirty_x2) {
		ddirty_x2 = x2;
	}
	if (y2 > ddirty_y2 || ddirty_y1 == ddirty_y2) {
		ddirty_y2 = y2;
	}
}

	return true;
void
display_update()
{
	bool window = ddirty_x1 != 0 || ddirty_y1 != 0
		|| ddirty_x2 != ui_width || ddirty_y2 != ui_height;

	epaper_init();
	epaper_refresh_window(
		window ? &EPAPER_LUT_GRAYWDW : &EPAPER_LUT_GRAY,
		window ? false : true,
		ddirty_x1, ddirty_y1, ddirty_x2, ddirty_y2);
	epaper_sleep();

	ddirty_x1 = ddirty_y1 = ddirty_x2 = ddirty_y2 = 0;
}

int


@@ 160,18 181,15 @@ main()
	ui_pixbuf = epaper_pixbuf;
	ui_width = EPAPER_WIDTH;
	ui_height = EPAPER_HEIGHT;
	ui_update();
	epaper_init();
	epaper_refresh(&EPAPER_LUT_GRAY, 1);
	epaper_sleep();
	ui_init();

	/* Timers */
	repeating_timer_t vw_timer, ui_timer;
	repeating_timer_t vw_timer;
	alarm_pool_t *apool0 = alarm_pool_create(TIMER_IRQ_0, 2);
	alarm_pool_add_repeating_timer_us(apool0, -1000000 / VW_BAUD / 8,
		update_vw, NULL, &vw_timer);
	add_repeating_timer_ms(-1000 * FULLREF_PERIOD_S / UI_INDICATOR_SCALER,
		update_ui, NULL, &ui_timer);

	uint32_t last_upd = 0;

	while (true) {
		uint8_t msg[VW_MSG_LEN];


@@ 179,24 197,14 @@ main()
			on_msg(msg);
		}

		if (fullref_flag) {
			ui_update();
			log_(LOG_DEBUG, "Updated UI");

			epaper_init();
			epaper_refresh(&EPAPER_LUT_GRAY, 1);
			epaper_sleep();

			fullref_flag = false;
			ui_partref_flag = false;
			log_(LOG_DEBUG, "Performed full refresh");
		} else if (ui_partref_flag) {
			epaper_init();
			epaper_refresh(&EPAPER_LUT_PARTIAL, 1);
			epaper_sleep();
		uint32_t now = get_ms_since_boot();
		ui_update(now);

			ui_partref_flag = false;
			log_(LOG_DEBUG, "Performed partial refresh");
		bool dirty = ddirty_x1 != ddirty_x2 && ddirty_y1 != ddirty_y2;
		bool due = now - last_upd > DISPLAY_INTERVAL || last_upd == 0;
		if (dirty && due) {
			display_update();
			last_upd = get_ms_since_boot();
		}

		__wfe();

M receiver/ui-first.c => receiver/ui-first.c +164 -115
@@ 12,142 12,144 @@

enum {
	N_PROBES = 4,
	PROBE_TIMEOUT = 300000,  /* 5 min */
	FS_LABEL = 30,
	FS_TEMP = 60,
	FS_HUMI = 30,
	FS_PRES = 30,
	FS_WARN = 45,
	PAD = 8,
	M_DOT = 3,
	R_DOT = 2,
	N_DOT = 3,
};

const size_t ui_period_s = 60;

float bufs[3 * N_PROBES * 4]; /* 3 quantities, 4 probes, 4 samples each */
struct ringbuf
	temps[N_PROBES] = {
		{ .buf = bufs + 0, .cap = 4 },
		{ .buf = bufs + 5, .cap = 4 },
		{ .buf = bufs + 10, .cap = 4 },
		{ .buf = bufs + 15, .cap = 4 },
	},
	humis[N_PROBES] = {
		{ .buf = bufs + 20, .cap = 4 },
		{ .buf = bufs + 25, .cap = 4 },
		{ .buf = bufs + 30, .cap = 4 },
		{ .buf = bufs + 35, .cap = 4 },
	},
	press = { .buf = bufs + 40, .cap = N_PROBES * 4 };
size_t dots[N_PROBES];
float outside_temps[2] = { NAN, NAN }, outside_humis[2] = { NAN, NAN };
float pressures[5] = { NAN, NAN, NAN, NAN, NAN };
uint32_t last_updated[5] = {0};

static void
draw_dots(uint8_t c, uint8_t r, uint8_t w, uint8_t h, uint8_t idx, bool filled,
	size_t count)
cell_init(uint8_t c, uint8_t r, uint8_t w, uint8_t h, char label[])
{
	int x0 = c * ui_width / 12;
	int y0 = r * ui_height / 12;
	int w0 = w * ui_width / 12;
	int h0 = h * ui_height / 12;
	(void) w;

	int dx0 = x0 + w0 - PAD - (N_DOT - 1) * M_DOT - (2 * N_DOT - 1) * R_DOT;
	int dy0 = y0 + h0 - PAD - (N_DOT - 1) * M_DOT - (2 * N_DOT - 1) * R_DOT;

	while (count--) {
		if (dots[idx] < N_DOT * N_DOT - 1) {
			ui_draw_circle(
				dx0 + (dots[idx] % N_DOT) * (2 * R_DOT + M_DOT),
				dy0 + (dots[idx] / N_DOT) * (2 * R_DOT + M_DOT),
				R_DOT,
				0x00,
				filled ? 0x00 : 0xe0,
				filled ? 2.0f * M_PI : 0.0f
			);
		} else if (dots[idx] == N_DOT * N_DOT - 1) {
			/* ellipsis */
			ui_draw_circle(x0 + w0 - PAD - R_DOT,
				y0 + h0 - PAD - R_DOT, 0, 0x00, 0x00, 2 * M_PI);
			ui_draw_circle(x0 + w0 - PAD - R_DOT - 2,
				y0 + h0 - PAD - R_DOT, 0, 0x00, 0x00, 2 * M_PI);
			ui_draw_circle(x0 + w0 - PAD - R_DOT + 2,
				y0 + h0 - PAD - R_DOT, 0, 0x00, 0x00, 2 * M_PI);
		}
		++dots[idx];
	}
	text_draw_align(label, x0 + PAD, y0 + PAD, FS_LABEL, TEXT_TOP_LEFT);
	text_draw_align("---.- °C", x0 + PAD, y0 + h0 / 2, FS_TEMP, TEXT_MIDDLE_LEFT);
	text_draw_align("---.- %RH", x0 + PAD, y0 + h0 - PAD, FS_HUMI, TEXT_BOTTOM_LEFT);
}

static void
draw_cell(uint8_t c, uint8_t r, uint8_t w, uint8_t h, uint8_t idx,
		char label[])
cell_update_temp(uint8_t c, uint8_t r, uint8_t w, uint8_t h, float temp, float humi)
{
	char buf[128];

	int x0 = c * ui_width / 12;
	int y0 = r * ui_height / 12;
	int w0 = w * ui_width / 12;
	int h0 = h * ui_height / 12;

	text_draw_align(label, x0 + PAD, y0 + PAD, FS_LABEL, TEXT_TOP_LEFT);
	ui_draw_rect(x0 + 1 + PAD, y0 + 1 + PAD + FS_LABEL,
		x0 + w0 - PAD, y0 + h0 - PAD, 0xff);

	float temp = idx == 2 ? ringbuf_min(temps + idx)
		: ringbuf_median(temps + idx);
	ui_sprintf_or_nan(buf, "%.1f °C", temp, "---.- °C");
	sprintf(buf, "%.1f °C", temp);
	text_draw_align(buf, x0 + PAD, y0 + h0 / 2, FS_TEMP, TEXT_MIDDLE_LEFT);

	float humi = idx == 2 ? ringbuf_max(humis + idx)
		: ringbuf_median(humis + idx);
	ui_sprintf_or_nan(buf, "%.1f %%RH", humi, "---.- %RH");
	sprintf(buf, "%.1f %%RH", humi);
	text_draw_align(buf, x0 + PAD, y0 + h0 - PAD, FS_HUMI, TEXT_BOTTOM_LEFT);

	dots[idx] = 0;
	draw_dots(c, r, w, h, idx, true, temps[idx].size);
	ui_mark_dirty(x0 + 1, y0 + 1, x0 + w0, y0 + h0);
}

static void
cell_update_pres(uint8_t c, uint8_t r, uint8_t w, uint8_t h, float pres)
{
	char buf[128];

	while (temps[idx].size) { ringbuf_get(temps + idx); }
	while (humis[idx].size) { ringbuf_get(humis + idx); }
	int x0 = c * ui_width / 12;
	int y0 = r * ui_height / 12;
	int w0 = w * ui_width / 12;
	int h0 = h * ui_height / 12;

	ui_draw_rect(x0 + 1 + PAD, y0 + 1 + PAD,
		x0 + w0 - PAD, y0 + h0 - PAD, 0xff);

	sprintf(buf, "%.1f hPa", pres);
	text_draw_align(buf, PAD, y0 + h0 / 2, FS_PRES, TEXT_MIDDLE_LEFT);

	ui_mark_dirty(x0 + 1, y0 + 1, x0 + w0, y0 + h0);
}

void
ui_on_packet(uint16_t serial, float temperature, float pressure, float humidity)
static void
cell_update_warn(uint8_t c, uint8_t r, uint8_t w, uint8_t h)
{
	switch (serial) {
	case SERIAL1:
		ringbuf_put(temps + 0, temperature);
		ringbuf_put(&press, pressure);
		ringbuf_put(humis + 0, humidity);
		draw_dots(0, 0, 6, 5, 0, false, 1);
		break;
	case SERIAL2:
		ringbuf_put(temps + 1, temperature);
		ringbuf_put(&press, pressure);
		ringbuf_put(humis + 1, humidity);
		draw_dots(6, 0, 6, 5, 1, false, 1);
		break;
	case SERIAL3:
		ringbuf_put(temps + 2, temperature);
		ringbuf_put(&press, pressure);
		ringbuf_put(humis + 2, humidity);
		draw_dots(0, 5, 6, 5, 2, false, 1);
		break;
	case SERIAL4:
		ringbuf_put(temps + 3, temperature);
		ringbuf_put(&press, pressure);
		ringbuf_put(humis + 3, humidity);
		draw_dots(6, 5, 6, 5, 3, false, 1);
		break;
	case SERIAL5:
		ringbuf_put(temps + 2, temperature);
		ringbuf_put(&press, pressure);
		ringbuf_put(humis + 2, humidity);
		draw_dots(0, 5, 6, 5, 2, false, 1);
		break;
	default:
		return;
	int x0 = c * ui_width / 12;
	int y0 = r * ui_height / 12;
	int w0 = w * ui_width / 12;
	int h0 = h * ui_height / 12;

	text_draw_align("?", x0 + w0 - PAD, y0 + h0 - PAD, FS_WARN,
		TEXT_BOTTOM_RIGHT);
	ui_mark_dirty(x0 + 1, y0 + 1, x0 + w0, y0 + h0);
}

float
min(float arr[], size_t sz)
{
	float min_ = FLT_MAX;
	for (size_t i = 0; i < sz; ++i) {
		if (!isnan(arr[i]) && arr[i] < min_) {
			min_ = arr[i];
		}
	}

	ui_partref_flag = true;
	if (min_ == FLT_MAX) {
		return NAN;
	}

	return min_;
}

float
max(float arr[], size_t sz)
{
	float max_ = FLT_MIN;
	for (size_t i = 0; i < sz; ++i) {
		if (!isnan(arr[i]) && arr[i] > max_) {
			max_ = arr[i];
		}
	}

	if (max_ == FLT_MIN) {
		return NAN;
	}

	return max_;
}

float
avg(float arr[], size_t sz)
{
	float avg_ = 0;
	size_t n = 0;
	for (size_t i = 0; i < sz; ++i) {
		if (!isnan(arr[i])) {
			avg_ += arr[i];
			++n;
		}
	}

	if (n == 0) {
		return NAN;
	}

	return avg_ / n;
}

void
ui_update()
ui_init()
{
	memset(ui_pixbuf, 0xff, ui_width * ui_height);



@@ 155,30 157,77 @@ ui_update()
	ui_draw_hline(5 * ui_height / 12, 0, ui_width);
	ui_draw_hline(5 * ui_height / 6, 0, ui_width);

	draw_cell(0, 0, 6, 5, 0, "SALON");
	draw_cell(6, 0, 6, 5, 1, "SYPIALNIA");
	draw_cell(0, 5, 6, 5, 2, "NA ZEWNĄTRZ");
	draw_cell(6, 5, 6, 5, 3, "ŁAZIENKA");
	cell_init(0, 0, 6, 5, "SALON");
	cell_init(6, 0, 6, 5, "SYPIALNIA");
	cell_init(0, 5, 6, 5, "NA ZEWNĄTRZ");
	cell_init(6, 5, 6, 5, "ŁAZIENKA");

	char buf[128];
	int y0 = 10 * ui_height / 12;
	int h0 = 2 * ui_height / 12;
	ui_sprintf_or_nan(buf, "%.1f hPa", ringbuf_median(&press), "----.- hPa");
	text_draw_align(buf, PAD, y0 + h0 / 2, FS_PRES, TEXT_MIDDLE_LEFT);
	while (press.size) { ringbuf_get(&press); }
	text_draw_align("----.- hPa", PAD, 5 * ui_height / 6 + ui_height / 12,
		FS_PRES, TEXT_MIDDLE_LEFT);

	/* XXX: BUGGY
	ui_draw_circle(ui_width - ui_height / 12, ui_height - ui_height / 12,
		10, 0x00, 0x00, 0);
	*/
	ui_mark_dirty(0, 0, ui_width, ui_height);
}

void
ui_refresh_time(float fraction)
ui_on_packet(uint32_t timestamp, uint16_t serial, float temperature, float pressure, float humidity)
{
	/* XXX: BUGGY
	ui_draw_circle(ui_width - ui_height / 12, ui_height - ui_height / 12,
		10, 0x00, 0x00, fraction * 2 * M_PI);
	*/
	ui_partref_flag = true;
	switch (serial) {
	case SERIAL1:
		pressures[0] = pressure;
		cell_update_temp(0, 0, 6, 5, temperature, humidity);
		cell_update_pres(0, 10, 12, 2, avg(pressures, 5));
		last_updated[0] = timestamp;
		break;
	case SERIAL2:
		pressures[1] = pressure;
		cell_update_temp(6, 0, 6, 5, temperature, humidity);
		cell_update_pres(0, 10, 12, 2, avg(pressures, 5));
		last_updated[1] = timestamp;
		break;
	case SERIAL3:
		outside_temps[0] = temperature;
		outside_humis[0] = humidity;
		pressures[2] = pressure;
		cell_update_temp(0, 5, 6, 5, min(outside_temps, 2), max(outside_humis, 2));
		cell_update_pres(0, 10, 12, 2, avg(pressures, 5));
		last_updated[2] = timestamp;
		break;
	case SERIAL4:
		pressures[3] = pressure;
		cell_update_temp(6, 5, 6, 5, temperature, humidity);
		cell_update_pres(0, 10, 12, 2, avg(pressures, 5));
		last_updated[3] = timestamp;
		break;
	case SERIAL5:
		outside_temps[1] = temperature;
		outside_humis[1] = humidity;
		pressures[4] = pressure;
		cell_update_temp(0, 5, 6, 5, min(outside_temps, 2), max(outside_humis, 2));
		cell_update_pres(0, 10, 12, 2, avg(pressures, 5));
		last_updated[4] = timestamp;
		break;
	}
}

void
ui_update(uint32_t timestamp)
{
	bool all = true;
	for (size_t i = 0; i < 5; ++i) {
		if (timestamp - last_updated[i] > PROBE_TIMEOUT) {
			switch (i) {
				case 0: cell_update_warn(0, 0, 6, 5); break;
				case 1: cell_update_warn(6, 0, 6, 5); break;
				case 2: cell_update_warn(0, 5, 6, 5); break;
				case 3: cell_update_warn(6, 5, 6, 5); break;
				case 4: cell_update_warn(0, 5, 6, 5); break;
			}
		} else {
			all = false;
		}
	}

	if (all) {
		cell_update_warn(0, 10, 12, 2);
	}
}

M receiver/ui.h => receiver/ui.h +5 -2
@@ 6,12 6,15 @@

extern uint8_t *ui_pixbuf;
extern size_t ui_width, ui_height;
extern bool ui_partref_flag;

void ui_init();
void ui_update();
void ui_on_packet(uint16_t serial, float temperature, float pressure,
void ui_on_packet(uint32_t timestamp, uint16_t serial, float temperature, float pressure,
	float humidity);
void ui_refresh_time(float fraction);
void ui_mark_dirty(size_t x1, size_t y1, size_t x2, size_t y2);
void ui_do_periodic();
void ui_update(uint32_t timestamp);

void ui_setpix(size_t x, size_t y, uint8_t value);
void ui_draw_vline(size_t x, size_t y1, size_t y2);