aboutsummaryrefslogtreecommitdiff
path: root/main
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2025-12-07 16:59:51 -0700
committerJosh Rahm <joshuarahm@gmail.com>2025-12-07 16:59:51 -0700
commita4930f91ced80d26758fae30f3e01eee7abe4345 (patch)
tree7814e178c3a13b7f3bbc03dd5ad4d97af744baf8 /main
parent44c7f7a675d2cdb0281322f38be3227ef4fb1df2 (diff)
downloadesp32-ws2812b-a4930f91ced80d26758fae30f3e01eee7abe4345.tar.gz
esp32-ws2812b-a4930f91ced80d26758fae30f3e01eee7abe4345.tar.bz2
esp32-ws2812b-a4930f91ced80d26758fae30f3e01eee7abe4345.zip
Add a couple different parameter types.
fraction_t: semantically represents a value 0.0-1.0. Internally this is represented as a uint8 with 255 being 1 and 0 being 0. ratio_t: represents a fracitonal number that can be larger than 1.0.
Diffstat (limited to 'main')
-rw-r--r--main/param/fraction.c139
-rw-r--r--main/param/ratio.c139
-rw-r--r--main/ws2812b_writer.c10
3 files changed, 284 insertions, 4 deletions
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);