aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--controller_webpage/index.html37
-rw-r--r--include/param.h4
-rw-r--r--include/state_params.i20
-rw-r--r--main/param/fraction.c139
-rw-r--r--main/param/ratio.c139
-rw-r--r--main/ws2812b_writer.c10
6 files changed, 334 insertions, 15 deletions
diff --git a/controller_webpage/index.html b/controller_webpage/index.html
index 1a308eb..9754759 100644
--- a/controller_webpage/index.html
+++ b/controller_webpage/index.html
@@ -181,6 +181,8 @@
params: {},
};
+ const fmt = (v) => Number.parseFloat(v).toFixed(2);
+
function setStatus(text) {
document.getElementById("status").textContent = text || "";
}
@@ -251,6 +253,37 @@
field.appendChild(colorInput);
field.appendChild(textInput);
+ } else if (value.type === "fraction" || value.type === "ratio") {
+ const input = document.createElement("input");
+ input.type = "number";
+ input.step = "0.01";
+ input.min = "0";
+ if (value.type === "fraction") {
+ input.max = "1";
+ }
+ input.id = attr;
+ input.value = fmt(value.value);
+ field.appendChild(input);
+
+ const actions = document.createElement("div");
+ actions.className = "actions";
+
+ const setBtn = document.createElement("button");
+ setBtn.textContent = "Set";
+ setBtn.onclick = () => sendRequest(attr, "set", fmt(input.value));
+ actions.appendChild(setBtn);
+
+ const incBtn = document.createElement("button");
+ incBtn.textContent = "+";
+ incBtn.onclick = () => sendRequest(attr, "add", input.step || "0.01");
+ actions.appendChild(incBtn);
+
+ const decBtn = document.createElement("button");
+ decBtn.textContent = "−";
+ decBtn.onclick = () => sendRequest(attr, "sub", input.step || "0.01");
+ actions.appendChild(decBtn);
+
+ field.appendChild(actions);
} else {
const input = document.createElement("input");
input.type = "number";
@@ -318,7 +351,9 @@
} else {
const newVal = value.type === "color"
? (value.value.startsWith("#") ? value.value : `#${value.value}`)
- : value.value;
+ : (value.type === "fraction" || value.type === "ratio")
+ ? fmt(value.value)
+ : value.value;
el.value = newVal;
if (value.type === "color" && el.nextElementSibling) {
el.nextElementSibling.value = newVal;
diff --git a/include/param.h b/include/param.h
index 5259e0a..d0b5bf0 100644
--- a/include/param.h
+++ b/include/param.h
@@ -6,6 +6,8 @@
typedef struct httpd_req httpd_req_t;
typedef uint32_t color_int_t;
+typedef uint8_t fraction_t;
+typedef uint16_t ratio_t;
#define DECL_PARAMETER_INSTANCE(ty) \
void handle_option__##ty(const char* attr, sockbuf_t* sockbuf, ty* ptr); \
@@ -24,5 +26,7 @@ DECL_PARAMETER_INSTANCE(uint8_t);
DECL_PARAMETER_INSTANCE(bool);
DECL_PARAMETER_INSTANCE(uint32_t);
DECL_PARAMETER_INSTANCE(color_int_t);
+DECL_PARAMETER_INSTANCE(fraction_t);
+DECL_PARAMETER_INSTANCE(ratio_t);
DECL_PARAMETER_INSTANCE(int);
DECL_PARAMETER_INSTANCE(int32_t);
diff --git a/include/state_params.i b/include/state_params.i
index e768768..854008d 100644
--- a/include/state_params.i
+++ b/include/state_params.i
@@ -4,16 +4,16 @@
// type name display name, default value
STATE_PARAM(int32_t, time, "Current Time", 0)
-STATE_PARAM(int, speed, "Speed", 1000)
-STATE_PARAM(uint8_t, brightness, "Brightness", 50)
+STATE_PARAM(ratio_t, speed, "Speed", 255)
+STATE_PARAM(fraction_t, brightness, "Brightness", 50)
STATE_PARAM(uint8_t, n_snow, "Snow Effect", 10)
-STATE_PARAM(uint8_t, n_red, "Desaturation", 32)
-STATE_PARAM(int, x_scale, "X Scale", 255)
-STATE_PARAM(bool, power, "Power", true)
-STATE_PARAM(bool, cool, "Cool", false)
-STATE_PARAM(bool, invert, "Invert", false)
+STATE_PARAM(fraction_t, n_red, "Desaturation", 32)
+STATE_PARAM(ratio_t, x_scale, "X Scale", 255)
+STATE_PARAM(bool, power, "Power", true)
+STATE_PARAM(bool, cool, "Cool", false)
+STATE_PARAM(bool, invert, "Invert", false)
STATE_PARAM(bool, use_static_color, "Use Static Color", false)
STATE_PARAM(color_int_t, static_color, "Static Color", 0xffddbb)
-STATE_PARAM(bool, twinkle, "Twinkle", true)
-STATE_PARAM(uint8_t, twink_amt, "Twinkle Amount", 0)
-STATE_PARAM(uint8_t, base_brightness, "Base Brightness", 0)
+STATE_PARAM(bool, twinkle, "Twinkle", true)
+STATE_PARAM(fraction_t, twink_amt, "Twinkle Amount", 0)
+STATE_PARAM(fraction_t, base_brightness, "Base Brightness", 0)
diff --git a/main/param/fraction.c b/main/param/fraction.c
new file mode 100644
index 0000000..adfc769
--- /dev/null
+++ b/main/param/fraction.c
@@ -0,0 +1,139 @@
+#include "param.h"
+#include "http_server.h"
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static inline uint8_t clamp_u8(int v)
+{
+ if (v < 0) return 0;
+ if (v > 255) return 255;
+ return (uint8_t)v;
+}
+
+static int parse_fraction(const char* s, fraction_t* out)
+{
+ if (!s) return -1;
+ char* end = NULL;
+ float f = strtof(s, &end);
+ if (end == s) {
+ return -1;
+ }
+ *out = clamp_u8((int)lroundf(f * 255.0f));
+ return 0;
+}
+
+void cmd_handle(fraction_t)(const char* attr, sockbuf_t* sockbuf, fraction_t* ptr)
+{
+ char op_[8];
+ char buf_[32];
+
+ char* op = read_token(sockbuf, op_, sizeof(op_));
+ char* value = read_token(sockbuf, buf_, sizeof(buf_));
+
+ fraction_t curval = *ptr;
+ fraction_t parsed = 0;
+
+ if (!strcmp(op, "+=") || !strcmp(op, "-=") || !strcmp(op, "=")) {
+ if (parse_fraction(value, &parsed) != 0) {
+ sockbuf_write(sockbuf, "Invalid value\n");
+ return;
+ }
+ if (!strcmp(op, "+=")) {
+ curval = clamp_u8((int)curval + (int)parsed);
+ } else if (!strcmp(op, "-=")) {
+ curval = clamp_u8((int)curval - (int)parsed);
+ } else {
+ curval = parsed;
+ }
+ } else {
+ sockbuf_write(sockbuf, "Unknown operation: '");
+ sockbuf_write(sockbuf, op);
+ sockbuf_write(sockbuf, "'\n");
+ return;
+ }
+
+ *ptr = curval;
+ snprintf(buf_, sizeof(buf_), "%s = %.3f\n", attr, curval / 255.0f);
+ sockbuf_write(sockbuf, buf_);
+}
+
+size_t serialize_to_json(fraction_t)(
+ char** out, size_t len, const char* attr, const char* dn, fraction_t value)
+{
+ float fval = value / 255.0f;
+ size_t newlen = snprintf(
+ *out,
+ len,
+ "\"%s\":{\"type\":\"fraction\",\"value\":\"%.4f\",\"disp\":\"%s\"}",
+ attr,
+ fval,
+ dn);
+ *out += newlen;
+ return len - newlen;
+}
+
+#define TAG "param_fraction"
+void http_handle(fraction_t)(httpd_req_t* req, fraction_t* val)
+{
+ char buf[128];
+ int buf_len = httpd_req_get_url_query_len(req) + 1;
+ esp_err_t err;
+
+ if (buf_len > 1) {
+ if (buf_len > sizeof(buf)) {
+ ESP_LOGI(TAG, "Invalid request. Query string too long.");
+ httpd_resp_set_status(req, HTTPD_400);
+ return;
+ }
+
+ if ((err = httpd_req_get_url_query_str(req, buf, buf_len)) == ESP_OK) {
+ char param[32];
+ fraction_t parsed;
+ if (httpd_query_key_value(buf, "add", param, sizeof(param)) == ESP_OK) {
+ if (parse_fraction(param, &parsed) != 0) {
+ httpd_resp_set_status(req, HTTPD_400);
+ return;
+ }
+ *val = clamp_u8((int)(*val) + (int)parsed);
+ } else if (
+ httpd_query_key_value(buf, "set", param, sizeof(param)) == ESP_OK) {
+ if (parse_fraction(param, &parsed) != 0) {
+ httpd_resp_set_status(req, HTTPD_400);
+ return;
+ }
+ *val = parsed;
+ } else if (
+ httpd_query_key_value(buf, "sub", param, sizeof(param)) == ESP_OK) {
+ if (parse_fraction(param, &parsed) != 0) {
+ httpd_resp_set_status(req, HTTPD_400);
+ return;
+ }
+ *val = clamp_u8((int)(*val) - (int)parsed);
+ } else {
+ ESP_LOGI(TAG, "No valid parameters.");
+ httpd_resp_set_status(req, HTTPD_400);
+ return;
+ }
+ } else {
+ ESP_LOGI(TAG, "Unable to get URL query string. [%d]", err);
+ httpd_resp_set_status(req, HTTPD_500);
+ return;
+ }
+ } else {
+ ESP_LOGI(TAG, "No query string provided?");
+ httpd_resp_set_status(req, HTTPD_400);
+ return;
+ }
+
+ httpd_resp_set_status(req, HTTPD_204);
+}
+
+void print(fraction_t)(sockbuf_t* sockbuf, const char* attr, fraction_t val)
+{
+ char buf[128];
+ snprintf(buf, sizeof(buf), "%s: %.3f :: fraction\n", attr, val / 255.0f);
+ sockbuf_write(sockbuf, buf);
+}
diff --git a/main/param/ratio.c b/main/param/ratio.c
new file mode 100644
index 0000000..bd7e3f4
--- /dev/null
+++ b/main/param/ratio.c
@@ -0,0 +1,139 @@
+#include "param.h"
+#include "http_server.h"
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static inline uint16_t clamp_u16(int v)
+{
+ if (v < 0) return 0;
+ if (v > 65535) return 65535;
+ return (uint16_t)v;
+}
+
+static int parse_ratio(const char* s, ratio_t* out)
+{
+ if (!s) return -1;
+ char* end = NULL;
+ float f = strtof(s, &end);
+ if (end == s) {
+ return -1;
+ }
+ *out = clamp_u16((int)lroundf(f * 255.0f));
+ return 0;
+}
+
+void cmd_handle(ratio_t)(const char* attr, sockbuf_t* sockbuf, ratio_t* ptr)
+{
+ char op_[8];
+ char buf_[32];
+
+ char* op = read_token(sockbuf, op_, sizeof(op_));
+ char* value = read_token(sockbuf, buf_, sizeof(buf_));
+
+ ratio_t curval = *ptr;
+ ratio_t parsed = 0;
+
+ if (!strcmp(op, "+=") || !strcmp(op, "-=") || !strcmp(op, "=")) {
+ if (parse_ratio(value, &parsed) != 0) {
+ sockbuf_write(sockbuf, "Invalid value\n");
+ return;
+ }
+ if (!strcmp(op, "+=")) {
+ curval = clamp_u16((int)curval + (int)parsed);
+ } else if (!strcmp(op, "-=")) {
+ curval = clamp_u16((int)curval - (int)parsed);
+ } else {
+ curval = parsed;
+ }
+ } else {
+ sockbuf_write(sockbuf, "Unknown operation: '");
+ sockbuf_write(sockbuf, op);
+ sockbuf_write(sockbuf, "'\n");
+ return;
+ }
+
+ *ptr = curval;
+ snprintf(buf_, sizeof(buf_), "%s = %.3f\n", attr, curval / 255.0f);
+ sockbuf_write(sockbuf, buf_);
+}
+
+size_t serialize_to_json(ratio_t)(
+ char** out, size_t len, const char* attr, const char* dn, ratio_t value)
+{
+ float fval = value / 255.0f;
+ size_t newlen = snprintf(
+ *out,
+ len,
+ "\"%s\":{\"type\":\"ratio\",\"value\":\"%.4f\",\"disp\":\"%s\"}",
+ attr,
+ fval,
+ dn);
+ *out += newlen;
+ return len - newlen;
+}
+
+#define TAG "param_ratio"
+void http_handle(ratio_t)(httpd_req_t* req, ratio_t* val)
+{
+ char buf[128];
+ int buf_len = httpd_req_get_url_query_len(req) + 1;
+ esp_err_t err;
+
+ if (buf_len > 1) {
+ if (buf_len > sizeof(buf)) {
+ ESP_LOGI(TAG, "Invalid request. Query string too long.");
+ httpd_resp_set_status(req, HTTPD_400);
+ return;
+ }
+
+ if ((err = httpd_req_get_url_query_str(req, buf, buf_len)) == ESP_OK) {
+ char param[32];
+ ratio_t parsed;
+ if (httpd_query_key_value(buf, "add", param, sizeof(param)) == ESP_OK) {
+ if (parse_ratio(param, &parsed) != 0) {
+ httpd_resp_set_status(req, HTTPD_400);
+ return;
+ }
+ *val = clamp_u16((int)(*val) + (int)parsed);
+ } else if (
+ httpd_query_key_value(buf, "set", param, sizeof(param)) == ESP_OK) {
+ if (parse_ratio(param, &parsed) != 0) {
+ httpd_resp_set_status(req, HTTPD_400);
+ return;
+ }
+ *val = parsed;
+ } else if (
+ httpd_query_key_value(buf, "sub", param, sizeof(param)) == ESP_OK) {
+ if (parse_ratio(param, &parsed) != 0) {
+ httpd_resp_set_status(req, HTTPD_400);
+ return;
+ }
+ *val = clamp_u16((int)(*val) - (int)parsed);
+ } else {
+ ESP_LOGI(TAG, "No valid parameters.");
+ httpd_resp_set_status(req, HTTPD_400);
+ return;
+ }
+ } else {
+ ESP_LOGI(TAG, "Unable to get URL query string. [%d]", err);
+ httpd_resp_set_status(req, HTTPD_500);
+ return;
+ }
+ } else {
+ ESP_LOGI(TAG, "No query string provided?");
+ httpd_resp_set_status(req, HTTPD_400);
+ return;
+ }
+
+ httpd_resp_set_status(req, HTTPD_204);
+}
+
+void print(ratio_t)(sockbuf_t* sockbuf, const char* attr, ratio_t val)
+{
+ char buf[128];
+ snprintf(buf, sizeof(buf), "%s: %.3f :: ratio\n", attr, val / 255.0f);
+ sockbuf_write(sockbuf, buf);
+}
diff --git a/main/ws2812b_writer.c b/main/ws2812b_writer.c
index 90c7405..63caf30 100644
--- a/main/ws2812b_writer.c
+++ b/main/ws2812b_writer.c
@@ -155,7 +155,8 @@ struct rgba interpolate(struct rgba c1, struct rgba c2, uint8_t amt)
void calculate_colors(const ws_state_t* state, ws2812b_buffer_t* buffer)
{
for (int i = 0; i < SIZE; ++i) {
- int x = i * 255 / state->x_scale;
+ int x_scale = state->x_scale ? state->x_scale : 1;
+ int x = i * 255 / x_scale;
if (!state->power) {
ws2812b_buffer_set_rgb(buffer, i, 0, 0, 0);
@@ -164,12 +165,13 @@ void calculate_colors(const ws_state_t* state, ws2812b_buffer_t* buffer)
struct rgba c;
int64_t real_time = state->time * FRAME_TIME_DELTA;
- if (state->speed % 1000 == 0) {
+ int32_t speed_scaled = ((int32_t)state->speed * 1000) / 255;
+ if (speed_scaled % 1000 == 0) {
// speed = 1000 is the "canonical" time speed. If speed is divisible by
// 1000, then no interpolation is required.
- c = get_rgba(x, (real_time * state->speed) / 1000, state);
+ c = get_rgba(x, (real_time * speed_scaled) / 1000, state);
} else {
- int64_t adj_time = real_time * state->speed;
+ int64_t adj_time = real_time * speed_scaled;
int64_t bot = adj_time / 1000;
int64_t top =
adj_time / 1000 + FRAME_TIME_DELTA * (adj_time >= 0 ? 1 : -1);