diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 001628bd..0d7873f6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,21 +5,40 @@ on: branches: ["master"] pull_request: branches: ["master"] - workflow_dispatch: # manual trigger jobs: - test: + build: runs-on: ubuntu-latest steps: - name: Checkout current repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Update Package Lists run: sudo apt-get update - name: Install Dependencies run: sudo apt-get install valgrind g++ make --fix-missing - - name: Build + - name: Build HeliosCLI run: make -j working-directory: HeliosCLI + - name: Archive HeliosCLI artifacts + run: zip -r "helioscli.zip" . + working-directory: HeliosCLI + - name: Upload HeliosCLI Artifact + uses: actions/upload-artifact@v4 + with: + name: helioscli-artifacts + path: HeliosCLI/helioscli.zip + + tests: + needs: build + runs-on: ubuntu-latest + steps: + - name: Checkout current repository + uses: actions/checkout@v4 + - name: Download HeliosCLI Artifact + uses: actions/download-artifact@v4 + with: + name: helioscli-artifacts + path: ./HeliosCLI - name: Set execute permissions for test script run: chmod +x ./runtests.sh working-directory: tests @@ -28,12 +47,13 @@ jobs: working-directory: tests embedded: - needs: test + needs: tests runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - name: Checkout current repository + uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: "3.x" - name: Install Dependencies @@ -42,12 +62,12 @@ jobs: - name: Build Binary run: make build working-directory: HeliosEmbedded - - name: Archive production artifacts - uses: actions/upload-artifact@v3 + - name: Archive HeliosEmbedded artifacts + run: zip -r "embedded-firmware.zip" . + working-directory: HeliosEmbedded + - name: Upload HeliosEmbedded Artifact + uses: actions/upload-artifact@v4 with: - name: embedded firmware - path: | - HeliosEmbedded/helios.bin - HeliosEmbedded/helios.elf - HeliosEmbedded/helios.map - HeliosEmbedded/helios.hex + name: embedded-firmware + path: HeliosEmbedded/embedded-firmware.zip + diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..c1f0818a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,211 @@ +name: Helios Release + +on: + workflow_dispatch: # manual trigger + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout current repository + uses: actions/checkout@v4 + - name: Update Package Lists + run: sudo apt-get update + - name: Install Dependencies + run: sudo apt-get install valgrind g++ make --fix-missing + - name: Build HeliosCLI + run: make -j + working-directory: HeliosCLI + - name: Archive HeliosCLI artifacts + run: zip -r "helioscli.zip" . + working-directory: HeliosCLI + - name: Upload HeliosCLI Artifact + uses: actions/upload-artifact@v4 + with: + name: helioscli-artifacts + path: HeliosCLI/helioscli.zip + + tests: + needs: build + runs-on: ubuntu-latest + steps: + - name: Checkout current repository + uses: actions/checkout@v4 + - name: Download HeliosCLI Artifact + uses: actions/download-artifact@v4 + with: + name: helioscli-artifacts + path: ./HeliosCLI + - name: Set execute permissions for test script + run: chmod +x ./runtests.sh + working-directory: tests + - name: Run general tests + run: ./runtests.sh + working-directory: tests + + embedded: + needs: tests + runs-on: ubuntu-latest + steps: + - name: Checkout current repository + uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.x" + - name: Install Dependencies + run: make install + working-directory: HeliosEmbedded + - name: Build Binary + run: make build + working-directory: HeliosEmbedded + - name: Archive HeliosEmbedded artifacts + run: zip -r "embedded-firmware.zip" . + working-directory: HeliosEmbedded + - name: Upload HeliosEmbedded Artifact + uses: actions/upload-artifact@v4 + with: + name: embedded-firmware + path: HeliosEmbedded/embedded-firmware.zip + + release: + needs: embedded + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout current repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all history for calculating version + + - name: Get the latest tag + id: get_tag + run: | + latest_tag=$(git describe --tags `git rev-list --tags --max-count=1` 2>/dev/null || echo "none") + echo "tag=$latest_tag" >> $GITHUB_ENV + + - name: Check for existing tags + id: check_tag + run: | + if [ "${{ env.tag }}" == "none" ]; then + echo "No tags found. Creating initial tag 1.0.0." + echo "new_tag=1.0.0" >> $GITHUB_ENV + fi + + - name: Calculate new version + id: calc_version + run: | + if [ "${{ env.new_tag }}" == "" ]; then + latest_tag=${{ env.tag }} + commits_since_tag=$(git rev-list $latest_tag..HEAD --count) + IFS='.' read -ra ADDR <<< "$latest_tag" + patch=$((ADDR[2] + commits_since_tag)) + new_version="${ADDR[0]}.${ADDR[1]}.$patch" + echo "new_version=$new_version" >> $GITHUB_ENV + echo "new_tag=$new_version" >> $GITHUB_ENV + else + echo "new_version=${{ env.new_tag }}" >> $GITHUB_ENV + fi + + - name: Check if new tag exists + run: | + if git rev-parse "refs/tags/${{ env.new_tag }}" >/dev/null 2>&1; then + echo "Tag ${{ env.new_tag }} already exists. Skipping tag creation." + echo "tag_exists=true" >> $GITHUB_ENV + else + echo "tag_exists=false" >> $GITHUB_ENV + fi + + - name: Create a new tag + if: env.tag_exists == 'false' + run: | + git config --global user.email "actions@github.com" + git config --global user.name "GitHub Actions" + git tag ${{ env.new_tag }} + git push origin ${{ env.new_tag }} + + - name: Download HeliosCLI Artifact + uses: actions/download-artifact@v4 + with: + name: helioscli-artifacts + path: ./artifact/helioscli + + - name: Download HeliosEmbedded Artifact + uses: actions/download-artifact@v4 + with: + name: embedded-firmware + path: ./artifact/embedded + + - name: Create directories for unzipping + run: mkdir -p ./artifact/unzipped/helioscli ./artifact/unzipped/embedded + + - name: Unzip HeliosCLI Artifact + run: unzip ./artifact/helioscli/helioscli.zip -d ./artifact/unzipped/helioscli + + - name: Unzip HeliosEmbedded Artifact + run: unzip ./artifact/embedded/embedded-firmware.zip -d ./artifact/unzipped/embedded + + - name: Create GitHub Release + id: create_release + uses: ncipollo/release-action@v1 + with: + tag: ${{ env.new_tag }} + name: Helios Vortex ${{ env.new_version }} + body: | + Release of Helios Vortex version ${{ env.new_version }} + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload Release Assets (helios.bin) + uses: actions/upload-release-asset@v1 + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./artifact/unzipped/embedded/helios.bin + asset_name: helios.bin + asset_content_type: application/octet-stream + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload Release Assets (helios.elf) + uses: actions/upload-release-asset@v1 + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./artifact/unzipped/embedded/helios.elf + asset_name: helios.elf + asset_content_type: application/octet-stream + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload Release Assets (helios.map) + uses: actions/upload-release-asset@v1 + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./artifact/unzipped/embedded/helios.map + asset_name: helios.map + asset_content_type: application/octet-stream + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload Release Assets (helios.hex) + uses: actions/upload-release-asset@v1 + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./artifact/unzipped/embedded/helios.hex + asset_name: helios.hex + asset_content_type: application/octet-stream + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload Release Assets (Helios CLI) + uses: actions/upload-release-asset@v1 + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./artifact/unzipped/helioscli/helios + asset_name: helios + asset_content_type: application/octet-stream + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + diff --git a/Helios/Helios.cpp b/Helios/Helios.cpp index 6401c7cd..c8b238aa 100644 --- a/Helios/Helios.cpp +++ b/Helios/Helios.cpp @@ -185,7 +185,7 @@ void Helios::save_cur_mode() Storage::write_pattern(cur_mode, pat); } -void Helios::load_global_flags() +void Helios::load_global_flags() { // read the global flags from index 0 config global_flags = (Flags)Storage::read_global_flags(); @@ -198,6 +198,10 @@ void Helios::load_global_flags() // If brightness is set in storage, use it if (saved_brightness > 0) { Led::setBrightness(saved_brightness); + } else { + // if the brightness was 0 then the storage was likely + // uninitialized or corrupt so write out the defaults + factory_reset(); } } @@ -715,26 +719,31 @@ void Helios::handle_state_set_defaults() if (Button::onLongClick()) { // if the user actually selected 'yes' if (menu_selection == 1) { - for (uint8_t i = 0; i < NUM_MODE_SLOTS; ++i) { - Patterns::make_default(i, pat); - Storage::write_pattern(i, pat); - } - // Reset global brightness to default - Led::setBrightness(DEFAULT_BRIGHTNESS); - Storage::write_brightness(DEFAULT_BRIGHTNESS); - // reset global flags - global_flags = FLAG_NONE; - cur_mode = 0; - // save global flags - save_global_flags(); - // re-load current mode - load_cur_mode(); + factory_reset(); } cur_state = STATE_MODES; } show_selection(RGB_WHITE_BRI_LOW); } +void Helios::factory_reset() +{ + for (uint8_t i = 0; i < NUM_MODE_SLOTS; ++i) { + Patterns::make_default(i, pat); + Storage::write_pattern(i, pat); + } + // Reset global brightness to default + Led::setBrightness(DEFAULT_BRIGHTNESS); + Storage::write_brightness(DEFAULT_BRIGHTNESS); + // reset global flags + global_flags = FLAG_NONE; + cur_mode = 0; + // save global flags + save_global_flags(); + // re-load current mode + load_cur_mode(); +} + void Helios::handle_state_set_global_brightness() { if (Button::onShortClick()) { @@ -783,9 +792,13 @@ inline uint32_t crc32(const uint8_t *data, uint8_t size) void Helios::handle_state_shift_mode() { - uint8_t new_mode = cur_mode ? (uint8_t)(cur_mode - 1) : (uint8_t)(NUM_MODE_SLOTS - 1); - Storage::swap_pattern(cur_mode, new_mode); + uint8_t new_mode = (cur_mode > 0) ? (uint8_t)(cur_mode - 1) : (uint8_t)(NUM_MODE_SLOTS - 1); + // copy the storage from the new position into our current position + Storage::copy_slot(new_mode, cur_mode); + // point at the new position cur_mode = new_mode; + // write out the current mode to the newly updated position + save_cur_mode(); cur_state = STATE_MODES; } @@ -796,7 +809,6 @@ void Helios::handle_state_randomize() Random ctx(seed); Colorset &cur_set = pat.colorset(); uint8_t num_cols = (ctx.next8() + 1) % NUM_COLOR_SLOTS; - cur_set.randomizeColors(ctx, num_cols); Patterns::make_pattern((PatternID)(ctx.next8() % PATTERN_COUNT), pat); pat.init(); diff --git a/Helios/Helios.h b/Helios/Helios.h index 93ba8485..7b1384c2 100644 --- a/Helios/Helios.h +++ b/Helios/Helios.h @@ -73,6 +73,7 @@ class Helios static void handle_state_randomize(); static void show_long_selection(RGBColor color); static void show_selection(RGBColor color); + static void factory_reset(); enum State : uint8_t { STATE_MODES, diff --git a/Helios/Pattern.h b/Helios/Pattern.h index a24fc11e..7d28eeb0 100644 --- a/Helios/Pattern.h +++ b/Helios/Pattern.h @@ -30,7 +30,7 @@ class Pattern // init the pattern to initial state void init(); - // pure virtual must override the play function + // play the pattern void play(); // set/get args @@ -47,9 +47,6 @@ class Pattern void clearColorset(); // comparison to other pattern - // NOTE: That may cause problems because the parameter is still a Pattern * - // which means comparison would need to cast the other upwards first - // NOTE2: Removing virtual because this probably shouldn't be overridden bool equals(const Pattern *other); // set a color in the colorset and re-initialize diff --git a/Helios/Storage.cpp b/Helios/Storage.cpp index 2a9ba333..af401f99 100644 --- a/Helios/Storage.cpp +++ b/Helios/Storage.cpp @@ -63,24 +63,17 @@ void Storage::write_pattern(uint8_t slot, const Pattern &pat) for (uint8_t i = 0; i < PATTERN_SIZE; ++i) { uint8_t val = ((uint8_t *)&pat)[i]; uint8_t target = pos + i; - // reads out the byte of the eeprom first to see if it's different - // before writing out the byte -- this is faster than always writing - if (val != read_byte(target)) { - write_byte(target, val); - } + write_byte(target, val); } write_crc(pos); } -void Storage::swap_pattern(uint8_t slot1, uint8_t slot2) +void Storage::copy_slot(uint8_t srcSlot, uint8_t dstSlot) { - uint8_t pos1 = slot1 * SLOT_SIZE; - uint8_t pos2 = slot2 * SLOT_SIZE; + uint8_t src = srcSlot * SLOT_SIZE; + uint8_t dst = dstSlot * SLOT_SIZE; for (uint8_t i = 0; i < SLOT_SIZE; ++i) { - uint8_t b1 = read_byte(pos1 + i); - uint8_t b2 = read_byte(pos2 + i); - write_byte(pos1 + i, b2); - write_byte(pos2 + i, b1); + write_byte(dst + i, read_byte(src + i)); } } @@ -127,21 +120,21 @@ void Storage::write_crc(uint8_t pos) write_byte(pos + PATTERN_SIZE, crc_pos(pos)); } -void Storage::write_byte(uint8_t address, volatile uint8_t data) +void Storage::write_byte(uint8_t address, uint8_t data) { #ifdef HELIOS_EMBEDDED - /* Wait for completion of previous write */ - while(EECR & (1<