Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ on:
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: read
steps:
- name: Checkout (GitHub)
uses: actions/checkout@v6
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/changelog.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,8 @@ jobs:
changelog:
runs-on: ubuntu-latest
if: ${{ !github.event.pull_request.draft }}
permissions:
contents: read
pull-requests: read
steps:
- uses: dangoslen/changelog-enforcer@v3
57 changes: 57 additions & 0 deletions .github/workflows/clang-format-label.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: clang-format (format label)

# pull_request_target: GITHUB_TOKEN can remove PR labels; pull_request often gets 403 on label APIs.
on:
pull_request_target:
types: [labeled]

concurrency:
group: clang-format-label-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
clang-format:
if: >-
github.event.label.name == 'format' &&
github.event.pull_request.state == 'open' &&
github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
permissions:
contents: write
issues: write
pull-requests: write
steps:
- name: Checkout PR branch
uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha }}
token: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install pre-commit
run: pip install pre-commit
- name: Run clang-format (pre-commit hook)
run: |
# pre-commit exits 1 when hooks rewrite files; run again to verify a stable tree.
pre-commit run clang-format --all-files || true
pre-commit run clang-format --all-files
- name: Commit and push if changed
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add -u
if git diff --staged --quiet; then
echo "No clang-format changes."
else
git commit -m "Apply clang-format"
git push origin "HEAD:refs/heads/${{ github.event.pull_request.head.ref }}"
fi
- name: Remove format label
if: always()
env:
GH_TOKEN: ${{ github.token }}
run: |
gh api --method DELETE \
"repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/labels/format" \
|| true
8 changes: 5 additions & 3 deletions .github/workflows/pre-commit-autoupdate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,18 @@ on:
jobs:
auto-update:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: "3.11"
- name: Install dependencies
run: sudo apt-get install -y clang-tidy clang-format cppcheck
- uses: browniebroke/pre-commit-autoupdate-action@main
run: sudo apt-get install -y cppcheck
- uses: browniebroke/pre-commit-autoupdate-action@v1.0.1
- uses: peter-evans/create-pull-request@v8
if: always()
with:
token: ${{ secrets.GITHUB_TOKEN }}
branch: update/pre-commit-hooks
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/pre-commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ on:
jobs:
pre_commit:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout (GitHub)
uses: actions/checkout@v6
- name: Install dependencies
run: sudo apt install -y clang-tidy clang-format cppcheck
run: sudo apt install -y cppcheck
- uses: pre-commit/action@v3.0.1
with:
extra_args: --show-diff-on-failure --all-files
30 changes: 23 additions & 7 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ jobs:
create_release:
name: Create Release
runs-on: ubuntu-latest
permissions:
contents: write
packages: read
steps:
- name: Get version from tag
id: tag_name
Expand All @@ -34,12 +37,24 @@ jobs:
./build_all.sh
- name: Zip factory build files
run: |
# Extract filenames from flash_project_args (keeping paths)
FILES_TO_ZIP=$(awk 'NR > 1 {print "./build_factory/"$2}' ./build_factory/flash_project_args | tr '\n' ' ')
# Add additional files
FILES_TO_ZIP="$FILES_TO_ZIP EbbFlowControl-Setup_wifi_qr.png EbbFlowControl-Setup_connection_url_qr.png ./build_factory/flash_project_args"
echo "Files to zip: $FILES_TO_ZIP"
zip FactoryBuildFiles.zip $FILES_TO_ZIP
set -euo pipefail
FLASH_ARGS="./build_factory/flash_project_args"
test -f "$FLASH_ARGS"
mapfile -t bins < <(awk 'NR > 1 {print "./build_factory/"$2}' "$FLASH_ARGS")
if [[ ${#bins[@]} -eq 0 ]]; then
echo "No binary paths parsed from $FLASH_ARGS"
exit 1
fi
extras=(
EbbFlowControl-Setup_wifi_qr.png
EbbFlowControl-Setup_connection_url_qr.png
"$FLASH_ARGS"
)
for f in "${extras[@]}"; do
test -f "$f" || { echo "Missing required file: $f"; exit 1; }
done
echo "Zipping ${#bins[@]} firmware files plus extras"
zip FactoryBuildFiles.zip "${bins[@]}" "${extras[@]}"
- name: Get Changelog Entry
id: changelog_reader
uses: mindsers/changelog-reader-action@v2
Expand All @@ -51,12 +66,13 @@ jobs:
id: create_release
uses: softprops/action-gh-release@v3
with:
tag_name: ${{ steps.changelog_reader.outputs.version }}
tag_name: ${{ github.ref_name }}
name: Release ${{ steps.changelog_reader.outputs.version }}
body: ${{ steps.changelog_reader.outputs.changes }}
prerelease: ${{ steps.changelog_reader.outputs.status == 'prereleased' }}
draft: ${{ steps.changelog_reader.outputs.status == 'unreleased' }}
token: ${{ secrets.GITHUB_TOKEN }}
fail_on_unmatched_files: true
files: |
./build_app/EbbFlowControl.bin
FactoryBuildFiles.zip
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Use the following labels:

## [UNRELEASED]

- [Patch] Applied different fixes and small hardening improvements across the firmware (connectivity, updates, configuration, and related tooling).
- [Patch] Update IDF version to 6.0.1.
- [Minor] Add level sensor measuring the level of the nutrition solution.
- [Patch] Rework data store and remove duplicated code.
Expand Down
96 changes: 63 additions & 33 deletions components/config_page/config_page.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#include <stdlib.h>
#include <string.h>

#include <stddef.h>

#include "configuration.h"

extern const char config_page_start[] asm("_binary_config_page_html_start");
Expand All @@ -17,34 +19,42 @@ extern const char config_page_end[] asm("_binary_config_page_html_end");
static const char *TAG = "config_page";
TaskHandle_t configuration_task_handle_ = NULL;

/** URL decode a string in place */
void urldecode2(char *dst, const char *src) {
while (*src) {
/** URL decode into dst; writes at most dst_cap bytes including trailing NUL. */
static void urldecode2(char *dst, size_t dst_cap, const char *src) {
if (dst_cap == 0) {
return;
}
size_t w = 0;
while (*src && w + 1 < dst_cap) {
char a, b;
if ((*src == '%') && ((a = src[1]) && (b = src[2])) &&
(isxdigit(a) && isxdigit(b))) {
if (a >= 'a')
a -= 'a' - 'A';
if (a >= 'A')
a -= ('A' - 10);
else
a -= '0';
if (b >= 'a')
b -= 'a' - 'A';
if (b >= 'A')
b -= ('A' - 10);
else
b -= '0';
*dst++ = 16 * a + b;
(isxdigit((unsigned char)a) && isxdigit((unsigned char)b))) {
if (a >= 'a') {
a = (char)(a - ('a' - 'A'));
}
if (a >= 'A') {
a = (char)(a - ('A' - 10));
} else {
a = (char)(a - '0');
}
if (b >= 'a') {
b = (char)(b - ('a' - 'A'));
}
if (b >= 'A') {
b = (char)(b - ('A' - 10));
} else {
b = (char)(b - '0');
}
dst[w++] = (char)(16 * a + b);
src += 3;
} else if (*src == '+') {
*dst++ = ' ';
dst[w++] = ' ';
src++;
} else {
*dst++ = *src++;
dst[w++] = *src++;
}
}
*dst++ = '\0';
dst[w] = '\0';
}

// Helper function to replace placeholder in HTML
Expand Down Expand Up @@ -121,20 +131,35 @@ static const httpd_uri_t root = {

static esp_err_t set_config_post_handler(httpd_req_t *req) {
char buf[1024];
int ret, remaining = req->content_len;
const int remaining = req->content_len;

if (remaining > sizeof(buf) - 1) {
if (remaining <= 0) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Empty body");
return ESP_FAIL;
}
if (remaining > (int)sizeof(buf) - 1) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Content too long");
return ESP_FAIL;
}

ret = httpd_req_recv(req, buf, remaining);
if (ret <= 0) {
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
"Failed to receive data");
return ESP_FAIL;
int total = 0;
while (total < remaining) {
const int ret = httpd_req_recv(req, buf + total, remaining - total);
if (ret < 0) {
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
continue;
}
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
"Failed to receive data");
return ESP_FAIL;
}
if (ret == 0) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Unexpected end of body");
return ESP_FAIL;
}
total += ret;
}
buf[ret] = '\0';
buf[total] = '\0';

ESP_LOGI(TAG, "Received POST data: %s", buf);

Expand All @@ -150,15 +175,20 @@ static esp_err_t set_config_post_handler(httpd_req_t *req) {
if (strcmp(key, "board_id") == 0) {
configuration.id = atoi(value);
} else if (strcmp(key, "ssid") == 0) {
urldecode2(configuration.network.ssid, value);
urldecode2(configuration.network.ssid,
sizeof(configuration.network.ssid), value);
} else if (strcmp(key, "wifi_password") == 0) {
urldecode2(configuration.network.password, value);
urldecode2(configuration.network.password,
sizeof(configuration.network.password), value);
} else if (strcmp(key, "mqtt") == 0) {
urldecode2(configuration.network.mqtt_broker, value);
urldecode2(configuration.network.mqtt_broker,
sizeof(configuration.network.mqtt_broker), value);
} else if (strcmp(key, "mqtt_username") == 0) {
urldecode2(configuration.network.mqtt_username, value);
urldecode2(configuration.network.mqtt_username,
sizeof(configuration.network.mqtt_username), value);
} else if (strcmp(key, "mqtt_password") == 0) {
urldecode2(configuration.network.mqtt_password, value);
urldecode2(configuration.network.mqtt_password,
sizeof(configuration.network.mqtt_password), value);
}
}
token = strtok(NULL, "&");
Expand Down
8 changes: 7 additions & 1 deletion components/configuration/configuration.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ void load_configuration() {
save_configuration();
return;
}

if (err != ESP_OK) {
ESP_LOGE(TAG, "nvs_open failed: %s", esp_err_to_name(err));
return;
}
ESP_ERROR_CHECK_WITHOUT_ABORT(
nvs_get_u8(my_handle, CONFIG_ID_NAME, &configuration.id));
ESP_ERROR_CHECK_WITHOUT_ABORT(
Expand Down Expand Up @@ -315,6 +318,9 @@ void handle_new_configuration_callbacks() {
}

esp_err_t add_notify_for_new_config(TaskHandle_t task) {
if (!task) {
return ESP_ERR_INVALID_ARG;
}
if (nr_task_to_notify >= CONFIG_MAX_NUMBER_TASK_TO_NOTIFY) {
return ESP_ERR_NO_MEM;
}
Expand Down
21 changes: 14 additions & 7 deletions components/driver_gp8211s/driver_gp8211s.c
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@

#include "driver_gp8211s.h"

#include "driver/i2c_master.h"
#include "esp_log.h"
#include <esp_err.h>
#include <stdio.h>

static const char *TAG = "gp8211s";

i2c_master_bus_handle_t i2c_bus_handle;
i2c_master_dev_handle_t gp8211s_dev_handle;

Expand Down Expand Up @@ -53,19 +56,23 @@ void gp8211s_init_device() {
// Configure device
// Set to 10V output range
const uint8_t config_buffer[2] = {CONFIG_REGISTER, OUTPUT_RANGE_10V_DATA};
i2c_master_transmit(gp8211s_dev_handle, config_buffer, sizeof(config_buffer),
10000);
esp_err_t err = i2c_master_transmit(gp8211s_dev_handle, config_buffer,
sizeof(config_buffer), 10000);
if (err != ESP_OK) {
ESP_LOGW(TAG, "I2C config transmit failed: %s", esp_err_to_name(err));
}
}

void gp8211s_set_output(uint16_t value) {
if (value > 0x7FFF) {
value = 0x7FFF; // Clamp to max 15-bit value
}
// Maybe shift value one bit left

uint8_t send_buffer[3] = {OUTPUT_VALUE_REGISTER, (value & 0xff),
(value >> 8)};

i2c_master_transmit(gp8211s_dev_handle, send_buffer, sizeof(send_buffer),
10000);
esp_err_t err = i2c_master_transmit(gp8211s_dev_handle, send_buffer,
sizeof(send_buffer), 10000);
if (err != ESP_OK) {
ESP_LOGW(TAG, "I2C output transmit failed: %s", esp_err_to_name(err));
}
}
Loading
Loading