Compare commits

..

No commits in common. "main" and "alpha.8" have entirely different histories.

25 changed files with 155 additions and 1067 deletions

View file

@ -1,136 +0,0 @@
name: "BTClock CI"
on:
push:
tags:
- "*"
workflow_dispatch:
jobs:
build:
runs-on: docker
container:
image: ghcr.io/catthehacker/ubuntu:js-22.04
permissions:
contents: write
checks: write
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: actions/setup-node@v4
with:
node-version: lts/*
cache: yarn
cache-dependency-path: "**/yarn.lock"
- uses: actions/cache@v4
with:
path: |
~/.cache/pip
~/.platformio/.cache
~/data/node_modules
.pio
data/node_modules
key: ${{ runner.os }}-pio
- uses: actions/setup-python@v5
with:
python-version: "3.9"
cache: "pip"
- name: Get current date
id: dateAndTime
shell: bash
run: echo "dateAndTime=$(date +'%Y-%m-%d-%H:%M')" >> $GITHUB_OUTPUT
- name: Install PlatformIO Core
shell: bash
run: pip install --upgrade platformio
- name: Build BTClock firmware
shell: bash
run: pio run
- name: Build BTClock filesystem
shell: bash
run: pio run --target buildfs
- name: Copy bootloader to output folder
run: cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin .pio
- name: Upload artifacts
uses: https://code.forgejo.org/forgejo/upload-artifact@v4
with:
include-hidden-files: true
retention-days: 1
name: prepared-outputs
path: .pio/**/*.bin
merge:
runs-on: docker
container:
image: ghcr.io/catthehacker/ubuntu:js-22.04
permissions:
contents: write
checks: write
needs: build
continue-on-error: true
strategy:
matrix:
chip:
- name: lolin_s2_mini
version: esp32s2
- name: lolin_s3_mini
version: esp32s3
- name: orangeclock
version: esp32s3
epd_variant: [213epd, 29epd]
exclude:
- chip: { name: orangeclock, version: esp32s3 }
epd_variant: 213epd
steps:
- uses: https://code.forgejo.org/forgejo/download-artifact@v4
with:
name: prepared-outputs
path: .pio
- name: Install esptools.py
run: pip install --upgrade esptool
- name: Create merged firmware binary
run: mkdir -p ${{ matrix.chip.name }}_${{ matrix.epd_variant }} && esptool.py --chip ${{ matrix.chip.version }} merge_bin -o ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin --flash_mode dio 0x0000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/bootloader.bin 0x8000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/partitions.bin 0xe000 .pio/boot_app0.bin 0x10000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin 0x369000 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/littlefs.bin
- name: Create checksum for firmware
shell: bash
run: shasum -a 256 .pio/build/${{ matrix.chip.name }}_${{ matrix.epd_variant }}/firmware.bin | awk '{print $1}' > ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}_firmware.bin.sha256
- name: Create checksum for merged binary
run: shasum -a 256 ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.bin | awk '{print $1}' > ${{ matrix.chip.name }}_${{ matrix.epd_variant }}/${{ matrix.chip.name }}_${{ matrix.epd_variant }}.sha256
- name: Upload artifacts
uses: https://code.forgejo.org/forgejo/upload-artifact@v4
with:
name: build-${{ matrix.chip.name }}-${{ matrix.epd_variant }}
path: |
${{ matrix.chip.name }}_${{ matrix.epd_variant }}/*.bin
${{ matrix.chip.name }}_${{ matrix.epd_variant }}/*.sha256
release:
runs-on: docker
permissions:
contents: write
checks: write
needs: merge
steps:
- name: Download matrix outputs
uses: https://code.forgejo.org/forgejo/download-artifact@v4
with:
pattern: build-*
merge-multiple: false
path: temp
- name: Copy files
run: |
mkdir -p release
find temp -type f \( -name "*.bin" -o -name "*.sha256" \) -exec cp -f {} release/ \;
- name: Create release
uses: https://code.forgejo.org/actions/forgejo-release@v2.4.0
with:
url: "https://git.btclock.dev"
repo: "${{ github.repository }}"
direction: upload
tag: "${{ github.ref_name }}"
sha: "${{ github.sha }}"
release-dir: release
token: ${{ secrets.TOKEN }}
override: ${{ github.ref_type != 'tag' && github.ref_name != 'main' }}
prerelease: ${{ github.ref_type != 'tag' && github.ref_name != 'main' }}
release-notes-assistant: false

View file

@ -4,11 +4,11 @@ description: "Install and build"
runs: runs:
using: "composite" using: "composite"
steps: steps:
- uses: actions/setup-node@v4 # - uses: actions/setup-node@v4
with: # with:
node-version: lts/* # node-version: lts/*
cache: yarn # cache: yarn
cache-dependency-path: '**/yarn.lock' # cache-dependency-path: '**/yarn.lock'
- uses: actions/cache@v3 - uses: actions/cache@v3
with: with:
path: | path: |

View file

@ -23,7 +23,7 @@ jobs:
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
retention-days: 1 retention-days: 1
name: prepared-outputs name: build-outputs
path: .pio/**/*.bin path: .pio/**/*.bin
build: build:
@ -31,17 +31,12 @@ jobs:
continue-on-error: true continue-on-error: true
strategy: strategy:
matrix: matrix:
epd_variant: [213epd, 29epd]
chip: chip:
- name: lolin_s2_mini - name: lolin_s2_mini
version: esp32s2 version: esp32s2
- name: lolin_s3_mini - name: lolin_s3_mini
version: esp32s3 version: esp32s3
- name: orangeclock
version: esp32s3
epd_variant: [213epd, 29epd]
exclude:
- chip: orangeclock
epd_variant: 213epd
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: write contents: write
@ -49,7 +44,7 @@ jobs:
steps: steps:
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v4
with: with:
name: prepared-outputs name: build-outputs
path: .pio path: .pio
- name: Install esptools.py - name: Install esptools.py
run: pip install --upgrade esptool run: pip install --upgrade esptool
@ -90,7 +85,7 @@ jobs:
- name: Create release - name: Create release
uses: ncipollo/release-action@v1 uses: ncipollo/release-action@v1
with: with:
artifacts: "**/*.bin,**/*.sha256" artifacts: "*/*.bin,*/*.sha256"
allowUpdates: true allowUpdates: true
removeArtifacts: true removeArtifacts: true
makeLatest: true makeLatest: true

3
.gitmodules vendored
View file

@ -1,3 +0,0 @@
[submodule "data"]
path = data
url = https://git.btclock.dev/btclock/oc-webui.git

View file

@ -1,13 +0,0 @@
# OrangeBTClock (working title)
[![BTClock CI](https://github.com/btclock/OrangeBTClock/actions/workflows/tagging.yml/badge.svg)](https://github.com/btclock/OrangeBTClock/actions/workflows/tagging.yml)
Firmware for cheap ESP32-S2/S3 hardware combined with a eInk display
See releases for prebuilt binaries, ready to flash (e.g. with the [esphome web flasher](https://web.esphome.io/))
## Development
- [PlatformIO](https://platformio.org/platformio-ide).
- [Node.js](https://nodejs.org/en) and [yarn](https://yarnpkg.com/).

View file

@ -1,62 +0,0 @@
{
"build": {
"arduino":{
"ldscript": "esp32s3_out.ld",
"partitions": "default_8MB.csv",
"memory_type": "qio_opi"
},
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_ORANGECLOCK",
"-DARDUINO_ESP32S3_DEV",
"-DIS_ORANGECLOCK",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1",
"-DARDUINO_USB_CDC_ON_BOOT=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"psram_type": "opi",
"espidf": {
"sdkconfig_path": "boards"
},
"hwids": [
[
"0x303A",
"0x1001"
]
],
"mcu": "esp32s3",
"variant": "esp32s3"
},
"connectivity": [
"bluetooth",
"wifi"
],
"debug": {
"default_tool": "esp-builtin",
"onboard_tools": [
"esp-builtin"
],
"openocd_target": "esp32s3.cfg"
},
"frameworks": [
"arduino",
"espidf"
],
"name": "OrangeClock",
"upload": {
"flash_size": "8MB",
"maximum_ram_size": 327680,
"maximum_size": 16777216,
"use_1200bps_touch": true,
"wait_for_upload_port": true,
"require_upload_port": true,
"speed": 460800
},
"url": "http://github.com/btclock",
"vendor": "BTClock"
}

1
data

@ -1 +0,0 @@
Subproject commit 8332fec4a1ec0045d91f063617bb441914e7b67a

0
data/build/index.html Normal file
View file

View file

@ -9,27 +9,24 @@
; https://docs.platformio.org/page/projectconf.html ; https://docs.platformio.org/page/projectconf.html
[platformio] [platformio]
data_dir = data/build_gz data_dir = data/build
default_envs = lolin_s2_mini_213epd, lolin_s2_mini_29epd, lolin_s3_mini_213epd, lolin_s3_mini_29epd, orangeclock_29epd default_envs = lolin_s2_mini_213epd, lolin_s2_mini_29epd, lolin_s3_mini_213epd, lolin_s3_mini_29epd
[btclock_base] [btclock_base]
platform = espressif32 platform = espressif32
framework = arduino framework = arduino
platform_packages = platformio/framework-arduinoespressif32
monitor_speed = 115200 monitor_speed = 115200
monitor_filters = esp32_exception_decoder, colorize monitor_filters = esp32_exception_decoder, colorize
board_build.filesystem = littlefs board_build.filesystem = littlefs
board_build.partitions = partition.csv board_build.partitions = partition.csv
extra_scripts = post:scripts/extra_script.py
build_flags = build_flags =
!python scripts/git_rev.py
-DLAST_BUILD_TIME=$UNIX_TIME
lib_deps = lib_deps =
zinggjm/GxEPD2@^1.6.1 zinggjm/GxEPD2@^1.5.6
https://github.com/tzapu/WiFiManager.git#v2.0.17 https://github.com/tzapu/WiFiManager.git#v2.0.17
bblanchon/ArduinoJson@^7.2.1 bblanchon/ArduinoJson@^7.0.3
mathieucarbou/ESP Async WebServer@^3.0.6 mathieucarbou/ESP Async WebServer
fastled/FastLED@^3.9.6 gilmaimon/ArduinoWebsockets@^0.5.3
[env:lolin_s2_mini] [env:lolin_s2_mini]
extends = btclock_base extends = btclock_base
board = lolin_s2_mini board = lolin_s2_mini
@ -61,14 +58,3 @@ extends = env:lolin_s3_mini
build_flags = build_flags =
${btclock_base.build_flags} ${btclock_base.build_flags}
-D VERSION_EPD_2_9 -D VERSION_EPD_2_9
[env:orangeclock_29epd]
extends = btclock_base
board = orangeclock
build_flags =
${btclock_base.build_flags}
-D VERSION_EPD_2_9
-D IS_ORANGECLOCK
-D BUTTON_PIN=45
-D NUM_LEDS=2

View file

@ -1,38 +0,0 @@
Import("env")
import os
import gzip
from shutil import copyfileobj, rmtree
from pathlib import Path
def gzip_file(input_file, output_file):
with open(input_file, 'rb') as f_in:
with gzip.open(output_file, 'wb') as f_out:
copyfileobj(f_in, f_out)
def process_directory(input_dir, output_dir):
if os.path.exists(output_dir):
rmtree(output_dir)
for root, dirs, files in os.walk(input_dir):
relative_path = os.path.relpath(root, input_dir)
output_root = os.path.join(output_dir, relative_path)
Path(output_root).mkdir(parents=True, exist_ok=True)
for file in files:
# if file.endswith(('.html', '.css', '.js')):
input_file_path = os.path.join(root, file)
output_file_path = os.path.join(output_root, file + '.gz')
gzip_file(input_file_path, output_file_path)
print(f'Compressed: {input_file_path} -> {output_file_path}')
# Build web interface before building FS
def before_buildfs(source, target, env):
env.Execute("cd data && yarn && yarn postinstall && yarn build")
input_directory = 'data/dist'
output_directory = 'data/build_gz'
process_directory(input_directory, output_directory)
os.environ["PUBLIC_BASE_URL"] = ""
env.AddPreAction("$BUILD_DIR/littlefs.bin", before_buildfs)

View file

@ -1,8 +0,0 @@
import subprocess
revision = (
subprocess.check_output(["git", "rev-parse", "HEAD"])
.strip()
.decode("utf-8")
)
print("'-DGIT_REV=\"%s\"'" % revision)

View file

@ -1,81 +0,0 @@
#pragma once
#include <pgmspace.h>
// 'oclogo', 250x37px
const unsigned char epd_bitmap_oclogo [] PROGMEM = {
0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x07, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x1f, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x7f, 0xc3, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xfc, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0xf0, 0x0c, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0xe3, 0x9c, 0x07, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x07, 0xc7, 0x9c, 0x03, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf0, 0x00, 0x00,
0x0f, 0x8f, 0x9c, 0x01, 0xe0, 0x00, 0x07, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf0, 0x00, 0x00,
0x0f, 0x07, 0x9c, 0x00, 0xf0, 0x00, 0x1f, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xc0, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf0, 0x00, 0x00,
0x1e, 0x63, 0x9c, 0x00, 0x78, 0x00, 0x7f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xf0, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf0, 0x00, 0x00,
0x1e, 0x71, 0x9c, 0x00, 0x78, 0x00, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf0, 0x00, 0x00,
0x3c, 0xf8, 0x9c, 0x00, 0x38, 0x01, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf0, 0x00, 0x00,
0x3c, 0xfc, 0x1c, 0x00, 0x3c, 0x01, 0xfc, 0x1f, 0xe1, 0xf3, 0xc3, 0xfc, 0x03, 0xe7, 0xe0, 0x03,
0xf3, 0xe0, 0x1f, 0xc0, 0x3f, 0xc1, 0xf0, 0xf8, 0x07, 0xf0, 0x00, 0x7f, 0x03, 0xf0, 0x7e, 0x00,
0x39, 0xfe, 0x1c, 0x00, 0x1c, 0x03, 0xf8, 0x07, 0xe1, 0xff, 0xdf, 0xff, 0x03, 0xff, 0xf0, 0x0f,
0xff, 0xe0, 0x7f, 0xf0, 0x3f, 0x80, 0x40, 0xf8, 0x1f, 0xfc, 0x01, 0xff, 0xc3, 0xf0, 0xfe, 0x00,
0x39, 0xff, 0x1c, 0x00, 0x1c, 0x03, 0xf0, 0x07, 0xf1, 0xff, 0xdf, 0xff, 0x83, 0xff, 0xf8, 0x1f,
0xff, 0xe0, 0xff, 0xf8, 0x3f, 0x00, 0x00, 0xf8, 0x3f, 0xff, 0x03, 0xff, 0xe3, 0xf1, 0xfc, 0x00,
0x39, 0xff, 0x9c, 0x00, 0x1c, 0x03, 0xf0, 0x03, 0xf1, 0xff, 0xcf, 0xff, 0x83, 0xff, 0xfc, 0x3f,
0xff, 0xe1, 0xff, 0xfc, 0x7e, 0x00, 0x00, 0xf8, 0x7f, 0xff, 0x07, 0xff, 0xf3, 0xf3, 0xf8, 0x00,
0x78, 0x00, 0x1c, 0x00, 0x1c, 0x07, 0xe0, 0x03, 0xf1, 0xff, 0xce, 0x1f, 0xc3, 0xff, 0xfc, 0x3f,
0x8f, 0xe1, 0xf8, 0x7e, 0x7e, 0x00, 0x00, 0xf8, 0x7f, 0x3f, 0x8f, 0xe3, 0xe3, 0xf7, 0xf0, 0x00,
0x78, 0x00, 0x1e, 0x00, 0x1c, 0x07, 0xe0, 0x03, 0xf1, 0xfc, 0x00, 0x0f, 0xc3, 0xf0, 0xfc, 0x7f,
0x07, 0xe3, 0xf0, 0x3e, 0x7e, 0x00, 0x00, 0xf8, 0xfc, 0x1f, 0x8f, 0xc1, 0xc3, 0xff, 0xe0, 0x00,
0x78, 0x00, 0x1f, 0x00, 0x1c, 0x07, 0xe0, 0x03, 0xf1, 0xf8, 0x00, 0x0f, 0xc3, 0xe0, 0x7c, 0x7e,
0x03, 0xe3, 0xf0, 0x3e, 0x7e, 0x00, 0x00, 0xf8, 0xfc, 0x0f, 0xcf, 0x80, 0x03, 0xff, 0xc0, 0x00,
0x39, 0xff, 0x0f, 0x80, 0x1c, 0x07, 0xe0, 0x03, 0xf1, 0xf8, 0x07, 0xff, 0xc3, 0xe0, 0x7e, 0x7e,
0x03, 0xe3, 0xff, 0xfe, 0x7e, 0x00, 0x00, 0xf8, 0xfc, 0x0f, 0xdf, 0x80, 0x03, 0xff, 0xc0, 0x00,
0x39, 0xff, 0x07, 0xc0, 0x1c, 0x03, 0xf0, 0x03, 0xf1, 0xf8, 0x1f, 0xff, 0xc3, 0xe0, 0x7e, 0x7e,
0x03, 0xe3, 0xff, 0xfe, 0x7f, 0x00, 0x00, 0xf8, 0xf8, 0x0f, 0xdf, 0x80, 0x03, 0xff, 0xe0, 0x00,
0x39, 0xfe, 0x23, 0xe0, 0x1c, 0x03, 0xf0, 0x07, 0xf1, 0xf8, 0x1f, 0xff, 0xc3, 0xe0, 0x7e, 0x7f,
0x07, 0xe3, 0xf0, 0x00, 0x3f, 0x00, 0x40, 0xf8, 0xfc, 0x0f, 0xcf, 0x80, 0x03, 0xff, 0xe0, 0x00,
0x3c, 0xfc, 0x61, 0xf0, 0x3c, 0x03, 0xf8, 0x0f, 0xe1, 0xf8, 0x3f, 0x0f, 0xc3, 0xe0, 0x7e, 0x3f,
0x8f, 0xe3, 0xf0, 0x00, 0x3f, 0x80, 0xe0, 0xf8, 0xfc, 0x1f, 0x8f, 0xc1, 0x83, 0xff, 0xf0, 0x00,
0x3c, 0xf8, 0xe0, 0xf8, 0x38, 0x01, 0xff, 0x3f, 0xe1, 0xf8, 0x3f, 0x0f, 0xc3, 0xe0, 0x7e, 0x3f,
0xff, 0xe1, 0xf8, 0x30, 0x1f, 0xe3, 0xf0, 0xf8, 0x7e, 0x3f, 0x8f, 0xe3, 0xe3, 0xf3, 0xf8, 0x00,
0x1e, 0x71, 0xe4, 0x7c, 0x78, 0x00, 0xff, 0xff, 0xc1, 0xf8, 0x3f, 0x1f, 0xc3, 0xe0, 0x7e, 0x1f,
0xff, 0xe1, 0xff, 0xf8, 0x1f, 0xff, 0xf8, 0xf8, 0x7f, 0xff, 0x87, 0xff, 0xf3, 0xf1, 0xfc, 0x00,
0x1e, 0x43, 0xe6, 0x3c, 0x78, 0x00, 0x7f, 0xff, 0x81, 0xf8, 0x1f, 0xff, 0xc3, 0xe0, 0x7e, 0x0f,
0xff, 0xe0, 0xff, 0xfc, 0x0f, 0xff, 0xf0, 0xf8, 0x3f, 0xff, 0x03, 0xff, 0xe3, 0xf0, 0xfe, 0x00,
0x0f, 0x07, 0xe7, 0x1c, 0xf0, 0x00, 0x3f, 0xff, 0x01, 0xf8, 0x1f, 0xff, 0xc3, 0xe0, 0x7e, 0x03,
0xf3, 0xe0, 0x7f, 0xf8, 0x03, 0xff, 0xe0, 0xf8, 0x1f, 0xfe, 0x01, 0xff, 0xc3, 0xf0, 0x7e, 0x00,
0x0f, 0x8f, 0xe7, 0x81, 0xe0, 0x00, 0x0f, 0xfc, 0x01, 0xf8, 0x07, 0xe7, 0xc3, 0xe0, 0x7e, 0x00,
0x03, 0xe0, 0x1f, 0xf0, 0x01, 0xff, 0x80, 0xf8, 0x07, 0xf8, 0x00, 0x7f, 0x83, 0xf0, 0x7f, 0x00,
0x07, 0xc7, 0xe7, 0xe3, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
0x07, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0xe3, 0xe7, 0x87, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f,
0x1f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0xf0, 0x00, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f,
0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xfc, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f,
0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x7f, 0xc3, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f,
0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x1f, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x07, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

View file

@ -3,12 +3,8 @@
Preferences preferences; Preferences preferences;
const char *ntpServer = "pool.ntp.org"; const char *ntpServer = "pool.ntp.org";
// const long gmtOffset_sec = 0; const long gmtOffset_sec = 0;
const int daylightOffset_sec = 3600; const int daylightOffset_sec = 3600;
TaskHandle_t OTAHandle = NULL;
SemaphoreHandle_t xButtonSemaphore = NULL;
const TickType_t debounceDelay = pdMS_TO_TICKS(500);
TickType_t lastButtonPressTime = 0;
#define STA_SSID "" #define STA_SSID ""
#define STA_PASS "" #define STA_PASS ""
@ -17,7 +13,7 @@ bool isUpdating = false;
void setupTime() void setupTime()
{ {
configTime(preferences.getInt(SETTING_TIME_OFFSET_MIN), daylightOffset_sec, ntpServer); configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
} }
void setupPreferences() void setupPreferences()
@ -41,60 +37,16 @@ void setupPreferences()
{ {
preferences.putString(SETTING_CURRENCY, CURRENCY_USD); preferences.putString(SETTING_CURRENCY, CURRENCY_USD);
} }
if (!preferences.isKey(SETTING_HOSTNAME_PREFIX))
{
preferences.putString(SETTING_HOSTNAME_PREFIX, "oc");
}
if (!preferences.isKey(SETTING_MEMPOOL_INSTANCE))
{
preferences.putString(SETTING_MEMPOOL_INSTANCE, "https://mempool.space");
}
if (!preferences.isKey(SETTING_TIME_FORMAT))
{
preferences.putString(SETTING_TIME_FORMAT, "%H:%M:%S");
}
if (!preferences.isKey(SETTING_DATE_FORMAT))
{
preferences.putString(SETTING_DATE_FORMAT, "%d-%m-%Y");
}
if (!preferences.isKey(SETTING_DECIMAL_SEPARATOR))
{
preferences.putChar(SETTING_DECIMAL_SEPARATOR, '.');
}
if (!preferences.isKey(SETTING_POWER_SAVE_MODE))
{
preferences.putBool(SETTING_POWER_SAVE_MODE, false);
}
if (!preferences.isKey(SETTING_TIME_OFFSET_MIN))
{
preferences.putInt(SETTING_TIME_OFFSET_MIN, 0);
}
} }
void setupWifi() void setupWifi()
{ {
uint8_t mac[6];
WiFi.macAddress(mac);
unsigned long seed = 0;
for (int i = 0; i < 6; i++)
{
seed += (unsigned long)mac[i] << ((i & 1) * 8);
}
randomSeed(seed);
// WiFi.begin(, "); // WiFi.begin(, ");
WiFi.setAutoConnect(true);
WiFi.setAutoReconnect(true); WiFi.setAutoReconnect(true);
WiFiManager wm; WiFiManager wm;
#ifndef ARDUINO_ORANGECLOCK
// Touch pin 14 to reset // Touch pin 14 to reset
if (touchRead(14) > 9000) if (touchRead(14) > 9000)
{ {
@ -111,12 +63,16 @@ void setupWifi()
wm.resetSettings(); wm.resetSettings();
} }
} }
#endif
byte mac[6];
WiFi.macAddress(mac);
String softAP_SSID = String softAP_SSID =
String("OrangeBTClock"); String("OrangeBTClock");
WiFi.setHostname(softAP_SSID.c_str()); WiFi.setHostname(softAP_SSID.c_str());
String softAP_password = getAPPassword(); String softAP_password =
base64::encode(String(mac[2], 16) + String(mac[4], 16) +
String(mac[5], 16) + String(mac[1], 16))
.substring(2, 10);
// wm.setConfigPortalTimeout(preferences.getUInt("wpTimeout", 600)); // wm.setConfigPortalTimeout(preferences.getUInt("wpTimeout", 600));
wm.setWiFiAutoReconnect(false); wm.setWiFiAutoReconnect(false);
@ -153,7 +109,6 @@ void setupWifi()
Serial.println("WiFi connected"); Serial.println("WiFi connected");
Serial.println("IP address: "); Serial.println("IP address: ");
Serial.println(WiFi.localIP()); Serial.println(WiFi.localIP());
epdShowIp();
// WiFi.setTxPower(WIFI_POWER_8_5dBm); // WiFi.setTxPower(WIFI_POWER_8_5dBm);
// enableWiFi(); // enableWiFi();
} }
@ -180,6 +135,7 @@ void wakeModemSleep()
void enableWiFi() void enableWiFi()
{ {
adc_power_on();
delay(200); delay(200);
WiFi.disconnect(false); // Reconnect the network WiFi.disconnect(false); // Reconnect the network
@ -204,6 +160,7 @@ void enableWiFi()
void disableWiFi() void disableWiFi()
{ {
adc_power_off();
WiFi.disconnect(true); // Disconnect from the network WiFi.disconnect(true); // Disconnect from the network
WiFi.mode(WIFI_OFF); // Switch WiFi off WiFi.mode(WIFI_OFF); // Switch WiFi off
Serial.println(""); Serial.println("");
@ -244,104 +201,4 @@ void setupOTA()
else if (error == OTA_END_ERROR) Serial.println("End Failed"); }); else if (error == OTA_END_ERROR) Serial.println("End Failed"); });
ArduinoOTA.begin(); ArduinoOTA.begin();
xTaskCreatePinnedToCore(
OTAUpdateTask, // Task function
"OTAUpdateTask", // Task name
4096, // Stack size
NULL, // Task parameters
1, // Priority (higher value means higher priority)
&OTAHandle, // Task handle
0 // Core to run the task (0 or 1)
);
}
void OTAUpdateTask(void *pvParameters)
{
for (;;)
{
ArduinoOTA.handle(); // Handle OTA updates
vTaskDelay(1000 / portTICK_PERIOD_MS); // Delay to avoid high CPU usage
}
}
void HandleButtonTask(void *pvParameters)
{
for (;;)
{
if (xSemaphoreTake(xButtonSemaphore, portMAX_DELAY) == pdTRUE)
{
TickType_t currentTime = xTaskGetTickCount();
if ((currentTime - lastButtonPressTime) >= debounceDelay)
{
lastButtonPressTime = currentTime;
Serial.println("Button Pressed");
#ifdef NUM_LEDS
leds[0] = CRGB::SkyBlue;
leds[1] = CRGB::Black;
FastLED.show();
vTaskDelay(100);
leds[0] = CRGB::Black;
leds[1] = CRGB::DarkOrange;
FastLED.show();
vTaskDelay(100);
leds[0] = CRGB::Black;
leds[1] = CRGB::Black;
FastLED.show();
#endif
}
}
}
}
char getCurrencyIcon()
{
char ret;
String currency = preferences.getString(SETTING_CURRENCY);
if (currency.equals(CURRENCY_USD))
{
ret = ICON_DOLLAR;
}
else if (currency.equals(CURRENCY_EUR))
{
ret = ICON_EURO;
}
else if (currency.equals(CURRENCY_GBP))
{
ret = ICON_POUND;
}
else if (currency.equals(CURRENCY_JPY))
{
ret = ICON_YEN;
}
return ret;
}
void IRAM_ATTR onButtonPress()
{
xSemaphoreGiveFromISR(xButtonSemaphore, NULL);
}
void setupButtonISR()
{
xButtonSemaphore = xSemaphoreCreateBinary();
xTaskCreatePinnedToCore(
HandleButtonTask, // Task function
"Button Task", // Task name
2048, // Stack size (bytes)
NULL, // Task parameters
1, // Priority (1 is default)
NULL, // Task handle
0); // Core to run the task (0 or 1)
} }

View file

@ -3,6 +3,7 @@
#include <Arduino.h> #include <Arduino.h>
#include <Preferences.h> #include <Preferences.h>
#include "shared.hpp" #include "shared.hpp"
#include "driver/adc.h"
#include <WiFiManager.h> #include <WiFiManager.h>
#include <base64.h> #include <base64.h>
#include "epd.hpp" #include "epd.hpp"
@ -14,12 +15,7 @@ void setupTime();
void setupPreferences(); void setupPreferences();
void setupWifi(); void setupWifi();
void setupOTA(); void setupOTA();
void OTAUpdateTask(void *pvParameters);
void wakeModemSleep(); void wakeModemSleep();
void setModemSleep(); void setModemSleep();
bool inPowerSaveMode(); bool inPowerSaveMode();
char getCurrencyIcon();
void IRAM_ATTR onButtonPress();
void setupButtonISR();

View file

@ -1,11 +1,10 @@
#include "data.hpp" #include "data.hpp"
//const String mempoolInstance = "https://mempool.space"; const String mempoolInstance = "https://mempool.space";
const String mempoolPriceApi = "/api/v1/prices"; const String mempoolPriceApiUrl = mempoolInstance + "/api/v1/prices";
const String mempoolBlockApi = "/api/blocks/tip/height"; const String mempoolBlockApiUrl = mempoolInstance + "/api/blocks/tip/height";
const String mempoolFeeApi = "/api/v1/fees/recommended"; const String mempoolFeeApiUrl = mempoolInstance + "/api/v1/fees/recommended";
const String mempoolMedianFeeApi = "/api/v1/fees/mempool-blocks";
uint lastPrice; uint lastPrice;
uint lastBlock; uint lastBlock;
@ -15,7 +14,7 @@ uint getPrice()
HTTPClient http; HTTPClient http;
// Send HTTP request to CoinGecko API // Send HTTP request to CoinGecko API
http.begin(preferences.getString(SETTING_MEMPOOL_INSTANCE) + mempoolPriceApi); http.begin(mempoolPriceApiUrl);
int httpCode = http.GET(); int httpCode = http.GET();
@ -45,7 +44,7 @@ uint getBlock()
HTTPClient http; HTTPClient http;
// Send HTTP request to CoinGecko API // Send HTTP request to CoinGecko API
http.begin(preferences.getString(SETTING_MEMPOOL_INSTANCE) + mempoolBlockApi); http.begin(mempoolBlockApiUrl);
int httpCode = http.GET(); int httpCode = http.GET();
@ -72,7 +71,7 @@ String getMempoolFees()
HTTPClient http; HTTPClient http;
// Send HTTP request to CoinGecko API // Send HTTP request to CoinGecko API
http.begin(preferences.getString(SETTING_MEMPOOL_INSTANCE) + mempoolFeeApi); http.begin(mempoolFeeApiUrl);
int httpCode = http.GET(); int httpCode = http.GET();
@ -97,113 +96,3 @@ String getMempoolFees()
return ""; return "";
} }
uint getMempoolFeesMedian()
{
HTTPClient http;
// Send HTTP request to CoinGecko API
http.begin(preferences.getString(SETTING_MEMPOOL_INSTANCE) + mempoolMedianFeeApi);
int httpCode = http.GET();
if (httpCode == 200)
{
char feeString[20];
String payload = http.getString();
JsonDocument doc;
deserializeJson(doc, payload);
snprintf(feeString, 20, "L: %d M: %d H: %d", doc["hourFee"].as<uint>(), doc["halfHourFee"].as<uint>(), doc["fastestFee"].as<uint>());
return round(doc[0]["medianFee"].as<double>());
// preferences.putUInt("lastPrice", eurPrice);
}
else
{
Serial.printf("HTTP GET request mempool median fees failed with error: %s\n", http.errorToString(httpCode).c_str());
}
http.end();
return 0;
}
double getSupplyAtBlock(std::uint32_t blockNr)
{
if (blockNr >= 33 * 210000)
{
return 20999999.9769;
}
const int initialBlockReward = 50; // Initial block reward
const int halvingInterval = 210000; // Number of blocks before halving
int halvingCount = blockNr / halvingInterval;
double totalBitcoinInCirculation = 0;
for (int i = 0; i < halvingCount; ++i)
{
totalBitcoinInCirculation += halvingInterval * initialBlockReward * std::pow(0.5, i);
}
totalBitcoinInCirculation += (blockNr % halvingInterval) * initialBlockReward * std::pow(0.5, halvingCount);
return totalBitcoinInCirculation;
}
String formatNumberWithSuffix(std::uint64_t num, int numCharacters)
{
static char result[20]; // Adjust size as needed
const long long quadrillion = 1000000000000000LL;
const long long trillion = 1000000000000LL;
const long long billion = 1000000000;
const long long million = 1000000;
const long long thousand = 1000;
double numDouble = (double)num;
int numDigits = (int)log10(num) + 1;
char suffix;
if (num >= quadrillion || numDigits > 15)
{
numDouble /= quadrillion;
suffix = 'Q';
}
else if (num >= trillion || numDigits > 12)
{
numDouble /= trillion;
suffix = 'T';
}
else if (num >= billion || numDigits > 9)
{
numDouble /= billion;
suffix = 'B';
}
else if (num >= million || numDigits > 6)
{
numDouble /= million;
suffix = 'M';
}
else if (num >= thousand || numDigits > 3)
{
numDouble /= thousand;
suffix = 'K';
}
else
{
sprintf(result, "%llu", (unsigned long long)num);
return result;
}
// Add suffix
int len = snprintf(result, sizeof(result), "%.0f%c", numDouble, suffix);
// If there's room, add decimal places
if (len < numCharacters)
{
snprintf(result, sizeof(result), "%.*f%c", numCharacters - len - 1, numDouble, suffix);
}
return result;
}

View file

@ -10,6 +10,3 @@
uint getPrice(); uint getPrice();
uint getBlock(); uint getBlock();
String getMempoolFees(); String getMempoolFees();
uint getMempoolFeesMedian();
double getSupplyAtBlock(std::uint32_t blockNr);
String formatNumberWithSuffix(std::uint64_t num, int numCharacters);

View file

@ -9,10 +9,6 @@ String currentRow1 = "";
String currentRow2 = ""; String currentRow2 = "";
String currentRow3 = ""; String currentRow3 = "";
char currentIcon1;
char currentIcon2;
char currentIcon3;
void setupDisplay() void setupDisplay()
{ {
display.init(0, true); display.init(0, true);
@ -22,26 +18,17 @@ void setupDisplay()
display.setRotation(1); display.setRotation(1);
display.setFont(&Antonio_SemiBold20pt7b); display.setFont(&Antonio_SemiBold20pt7b);
display.setTextColor(GxEPD_WHITE); display.setTextColor(GxEPD_WHITE);
// int16_t tbx, tby; int16_t tbx, tby;
// uint16_t tbw, tbh; uint16_t tbw, tbh;
// display.getTextBounds("OrangeBTClock", 0, 0, &tbx, &tby, &tbw, &tbh); display.getTextBounds("OrangeBTClock", 0, 0, &tbx, &tby, &tbw, &tbh);
// // center the bounding box by transposition of the origin: // center the bounding box by transposition of the origin:
// uint16_t x = ((display.width() - tbw) / 2) - tbx; uint16_t x = ((display.width() - tbw) / 2) - tbx;
// uint16_t y = ((display.height() - tbh) / 2) - tby; uint16_t y = ((display.height() - tbh) / 2) - tby;
display.fillScreen(GxEPD_BLACK); display.fillScreen(GxEPD_BLACK);
// display.setCursor(x, y); display.setCursor(x, y);
// display.print("OrangeBTClock"); display.print("OrangeBTClock");
// display.drawImage(epd_bitmap_allArray[0], GxEPD_WHITE, 0,0 250,37);
int xPos = (display.width() - 250) / 2;
int yPos = (display.height() - 37) / 2;
display.drawBitmap(xPos,yPos, epd_bitmap_oclogo, 250, 37, GxEPD_WHITE);
display.display(false); display.display(false);
display.setCursor(0, 37);
// display.fillScreen(GxEPD_WHITE); // display.fillScreen(GxEPD_WHITE);
// display.drawLine(0, 10, display.width(), 10, GxEPD_BLACK); // display.drawLine(0, 10, display.width(), 10, GxEPD_BLACK);
// display.drawLine(0, row2, display.width(), row2, GxEPD_BLACK); // display.drawLine(0, row2, display.width(), row2, GxEPD_BLACK);
@ -56,11 +43,11 @@ void setupDisplay()
// display.display(true); // display.display(true);
// display.setRotation(1); display.setRotation(1);
// // display.fillRect(0, row1, display.width(), 54, GxEPD_BLACK); // display.fillRect(0, row1, display.width(), 54, GxEPD_BLACK);
// display.displayWindow(0, row1, display.width(), row2); display.displayWindow(0, row1, display.width(), row2);
// display.display(false); display.display(true);
// display.fillRect(0, row2, display.width(), 54, GxEPD_BLACK); // display.fillRect(0, row2, display.width(), 54, GxEPD_BLACK);
// display.displayWindow(0, row2, display.width(), 54); // display.displayWindow(0, row2, display.width(), 54);
@ -73,27 +60,8 @@ void setupDisplay()
// display.display(true); // display.display(true);
} }
void epdShowIp() { void updateRow2(String c)
display.setRotation(1);
display.setFont(&LibreFranklin_SemiBold10pt7b);
display.setTextColor(GxEPD_WHITE);
String ipStr = WiFi.localIP().toString();
int16_t tbx, tby;
uint16_t tbw, tbh;
display.getTextBounds(ipStr, 0, 0, &tbx, &tby, &tbw, &tbh);
// center the bounding box by transposition of the origin:
uint16_t x = ((display.width() - tbw) / 2) - tbx;
uint16_t y = ((display.height() - tbh) / 2) - tby + 37;
display.setCursor(x, y);
display.println(WiFi.localIP());
display.display(true);
}
void updateRow2(String c, char icon)
{ {
if (c.equals(currentRow2) && icon == currentIcon2)
return;
display.setRotation(1); display.setRotation(1);
display.setFont(&ROW2_FONT); display.setFont(&ROW2_FONT);
display.setTextColor(GxEPD_BLACK); display.setTextColor(GxEPD_BLACK);
@ -114,23 +82,17 @@ void updateRow2(String c, char icon)
display.setFont(&ROW2_ICONFONT); display.setFont(&ROW2_ICONFONT);
display.setCursor(x, y); display.setCursor(x, y);
display.print(icon); display.print(ICON_BLOCK);
display.setFont(&ROW2_FONT); display.setFont(&ROW2_FONT);
display.setCursor(x + ROW2_ICONWIDTH, y); display.setCursor(x + ROW2_ICONWIDTH, y);
display.print(c); display.print(c);
} while (display.nextPage()); } while (display.nextPage());
// display.display(true); // display.display(true);
currentRow2 = c;
currentIcon2 = icon;
} }
void updateRow3(String c, char icon) void updateRow3(String c)
{ {
if (c.equals(currentRow3) && icon == currentIcon3)
return;
display.setRotation(1); display.setRotation(1);
display.setFont(&LibreFranklin_SemiBold15pt7b); display.setFont(&LibreFranklin_SemiBold15pt7b);
display.setTextColor(GxEPD_WHITE); display.setTextColor(GxEPD_WHITE);
@ -152,16 +114,13 @@ void updateRow3(String c, char icon)
display.setFont(&orangeclock_icons15pt7b); display.setFont(&orangeclock_icons15pt7b);
display.setCursor(x, y); display.setCursor(x, y);
display.print(icon); display.print(ICON_SATS);
display.setFont(&LibreFranklin_SemiBold15pt7b); display.setFont(&LibreFranklin_SemiBold15pt7b);
display.setCursor(x + ROW3_ICONWIDTH, y); display.setCursor(x + ROW3_ICONWIDTH, y);
display.print(c); display.print(c);
} while (display.nextPage()); } while (display.nextPage());
currentRow3 = c;
currentIcon3 = icon;
} }
void showSetupText(String t) void showSetupText(String t)
@ -174,7 +133,7 @@ void showSetupText(String t)
// center the bounding box by transposition of the origin: // center the bounding box by transposition of the origin:
uint16_t x = ((display.width() - (tbw)) / 2) - tbx; uint16_t x = ((display.width() - (tbw)) / 2) - tbx;
uint16_t y = ((display.height() - tbh) / 2) - tby; uint16_t y = ((display.height() - tbh) / 2) - tby;
display.setFullWindow();
display.firstPage(); display.firstPage();
do do
{ {
@ -188,11 +147,8 @@ void showSetupText(String t)
} while (display.nextPage()); } while (display.nextPage());
} }
void updateRow1(String c, char icon) void updateRow1(String c)
{ {
if (c.equals(currentRow1) && icon == currentIcon1)
return;
// struct tm timeinfo; // struct tm timeinfo;
// if (!getLocalTime(&timeinfo)) // if (!getLocalTime(&timeinfo))
// { // {
@ -224,16 +180,13 @@ void updateRow1(String c, char icon)
display.setFont(&ROW1_ICONFONT); display.setFont(&ROW1_ICONFONT);
display.setCursor(x, y); display.setCursor(x, y);
display.print(icon); display.print(ICON_PIE);
display.setFont(&ROW1_FONT); display.setFont(&ROW1_FONT);
display.setCursor(x + ROW1_ICONWIDTH, y); display.setCursor(x + ROW1_ICONWIDTH, y);
display.print(c); display.print(c);
} while (display.nextPage()); } while (display.nextPage());
currentRow1 = c;
currentIcon1 = icon;
} }
void updateRows(String row1Content, String row2Content, String row3Content) void updateRows(String row1Content, String row2Content, String row3Content)

View file

@ -2,12 +2,11 @@
#include "shared.hpp" #include "shared.hpp"
#include "fonts/fonts.hpp" #include "fonts/fonts.hpp"
#include "bitmap.hpp"
void setupDisplay(); void setupDisplay();
void showSetupText(String t); void showSetupText(String t);
void updateRow1(String c, char icon); void updateRow1(String c);
void updateRow2(String c, char icon); void updateRow2(String c);
void updateRow3(String c, char icon); void updateRow3(String c);
void updateRows(String row1Content, String row2Content, String row3Content); void updateRows(String row1Content, String row2Content, String row3Content);
void epdShowIp();

View file

@ -21,44 +21,23 @@ GxEPD2_BW<EPD_CLASS, EPD_CLASS::HEIGHT> display = EPD_CLASS(4, 2, 3, 1);
GxEPD2_BW<EPD_CLASS, EPD_CLASS::HEIGHT> display = EPD_CLASS(5, 3, 2, 1); GxEPD2_BW<EPD_CLASS, EPD_CLASS::HEIGHT> display = EPD_CLASS(5, 3, 2, 1);
#endif #endif
#ifdef ARDUINO_ORANGECLOCK typedef void (*MethodPtr)(String);
GxEPD2_BW<EPD_CLASS, EPD_CLASS::HEIGHT> display = EPD_CLASS(5, 3, 1, 2);
#endif
typedef void (*MethodPtr)(String, char); MethodPtr methods[] = { nullptr, updateRow1, updateRow2, updateRow3 };
MethodPtr methods[] = {nullptr, updateRow1, updateRow2, updateRow3}; WiFiClientSecure client;
WiFiClient client;
uint currentPrice = 0; uint currentPrice = 0;
String currentBlock = ""; String currentBlock = "";
String currentFees = ""; String currentFees = "";
#ifdef NUM_LEDS
CRGB leds[NUM_LEDS];
#endif
void setup() void setup()
{ {
// setCpuFrequencyMhz(40); // setCpuFrequencyMhz(40);
Serial.begin(115200); Serial.begin(115200);
#ifndef IS_ORANGECLOCK
pinMode(LED_BUILTIN, OUTPUT); pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH); digitalWrite(LED_BUILTIN, HIGH);
#else
pinMode(BUTTON_PIN, INPUT_PULLUP);
attachInterrupt(BUTTON_PIN, onButtonPress, FALLING);
FastLED.addLeds<WS2812B, 48, GRB>(leds, NUM_LEDS);
leds[0] = CRGB::GreenYellow;
leds[1] = CRGB::OrangeRed;
FastLED.show();
setupButtonISR();
#endif
setupPreferences(); setupPreferences();
setupDisplay(); setupDisplay();
@ -71,27 +50,15 @@ void setup()
setupWebserver(); setupWebserver();
setupOTA(); setupOTA();
} }
// client.setInsecure(); client.setInsecure();
#ifndef IS_ORANGECLOCK
digitalWrite(LED_BUILTIN, LOW); digitalWrite(LED_BUILTIN, LOW);
#else
leds[0] = CRGB::Black;
leds[1] = CRGB::Black;
FastLED.show();
delay(100);
#endif
display.setFullWindow();
display.clearScreen(GxEPD_WHITE);
display.display(true);
} }
void loop() void loop()
{ {
if (isUpdating) ArduinoOTA.handle();
{ if (isUpdating) {
delay(1000);
return; return;
} }
@ -103,95 +70,73 @@ void loop()
return; return;
} }
// client.setInsecure(); client.setInsecure();
// //
// IPAddress res; IPAddress res;
// uint result = WiFi.hostByName("mempool.space", res); uint result = WiFi.hostByName("mempool.space", res);
// if (result >= 0) if (result >= 0)
// {
// Serial.print("SUCCESS!");
// Serial.println(res.toString());
// }
// else
// {
// WiFi.reconnect();
// while (WiFi.status() != WL_CONNECTED)
// {
// Serial.print('.');
// delay(1000);
// }
// }
for (uint i = 1; i <= 3; i++)
{ {
Serial.print("SUCCESS!");
Serial.println(res.toString());
}
else
{
WiFi.reconnect();
while (WiFi.status() != WL_CONNECTED)
{
Serial.print('.');
delay(1000);
}
}
for (uint i = 1; i <= 3; i++) {
String rowContent = ""; String rowContent = "";
char icon;
char keyName[5]; char keyName[5];
snprintf(keyName, sizeof(keyName), "row%d", i); snprintf(keyName, sizeof(keyName), "row%d", i);
switch (preferences.getUInt(keyName)) Serial.print(keyName);
{ Serial.print(" ");
case LINE_BLOCKHEIGHT: Serial.println(preferences.getUInt(keyName));
icon = ICON_BLOCK; switch (preferences.getUInt(keyName)) {
rowContent = getBlock(); case LINE_BLOCKHEIGHT:
break; rowContent = getBlock();
case LINE_MEMPOOL_FEES: break;
icon = ICON_PIE; case LINE_MEMPOOL_FEES:
rowContent = getMempoolFees(); rowContent = getMempoolFees();
break; break;
case LINE_MEMPOOL_FEES_MEDIAN: case LINE_MEMPOOL_FEES_MEDIAN:
icon = ICON_PIE; rowContent = "NOT IMPL";
rowContent = getMempoolFeesMedian(); break;
break; case LINE_HALVING_COUNTDOWN:
case LINE_HALVING_COUNTDOWN: rowContent = "NOT IMPL";
{ break;
icon = ICON_HOURGLASS; case LINE_SATSPERUNIT: {
uint currentBlock = getBlock(); uint satsPerDollar = int(round(1 / float(getPrice()) * 10e7));
rowContent = 210000 - (currentBlock % 210000); rowContent = satsPerDollar;
break; break;
} }
case LINE_SATSPERUNIT: case LINE_FIATPRICE:
{ rowContent = getPrice();
icon = ICON_SATS; break;
uint satsPerDollar = int(round(1 / float(getPrice()) * 10e7)); case LINE_MARKETCAP:
rowContent = satsPerDollar; rowContent = "NOT IMPL";
break; break;
} case LINE_TIME:
case LINE_FIATPRICE: rowContent = "NOT IMPL";
icon = getCurrencyIcon(); break;
rowContent = getPrice(); case LINE_DATE:
break; rowContent = "NOT IMPL";
case LINE_MARKETCAP: break;
{ default:
icon = getCurrencyIcon(); rowContent = "DEFAULT";
int64_t marketCap = static_cast<std::int64_t>(getSupplyAtBlock(getBlock()) * double(getPrice()));
rowContent = String(formatNumberWithSuffix(marketCap, 8));
break;
}
case LINE_TIME:
{
icon = ICON_GLOBE;
char dateString[16];
strftime(dateString, sizeof(dateString), preferences.getString(SETTING_TIME_FORMAT).c_str(), &timeinfo);
rowContent = dateString;
break;
}
case LINE_DATE:
{
icon = ICON_GLOBE;
char dateString[16];
strftime(dateString, sizeof(dateString), preferences.getString(SETTING_DATE_FORMAT).c_str(), &timeinfo);
rowContent = dateString;
break;
}
default:
rowContent = "DEFAULT";
} }
methods[i](rowContent, icon); methods[i](rowContent);
} }
// String block = String(getBlock()); // String block = String(getBlock());
@ -286,20 +231,17 @@ void loop()
delay(2 * 1000); delay(2 * 1000);
if (inPowerSaveMode()) if (inPowerSaveMode()) {
{
display.hibernate(); display.hibernate();
setModemSleep(); setModemSleep();
esp_sleep_enable_timer_wakeup(50 * 1000000); esp_sleep_enable_timer_wakeup(50 * 1000000);
esp_light_sleep_start(); esp_light_sleep_start();
display.init(0, false); display.init(0, false);
wakeModemSleep(); wakeModemSleep();
} } else {
else
{
Serial.println(F("Sleeping")); Serial.println(F("Sleeping"));
sleep(50); sleep(50);
// delay(50 * 1000); // delay(50 * 1000);
Serial.println(F("Waking up")); Serial.println(F("Waking up"));
} }
} }

View file

@ -1,3 +0,0 @@
#include "shared.hpp"
volatile bool buttonPressed = false;

View file

@ -1,16 +1,11 @@
#pragma once #pragma once
#include <Arduino.h> #include <Arduino.h>
#include <WiFiClient.h> #include <WiFiClientSecure.h>
#include <Preferences.h> #include <Preferences.h>
#include <GxEPD2.h> #include <GxEPD2.h>
#include <GxEPD2_BW.h> #include <GxEPD2_BW.h>
#include "utils.hpp"
#include "fonts/fonts.hpp" #include "fonts/fonts.hpp"
#include <FastLED.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <WiFi.h>
#ifdef VERSION_EPD_2_13 #ifdef VERSION_EPD_2_13
#define EPD_CLASS GxEPD2_213_B74 #define EPD_CLASS GxEPD2_213_B74
@ -31,48 +26,40 @@
#define EPD_CLASS GxEPD2_290_T94 #define EPD_CLASS GxEPD2_290_T94
#define ROW1_FONT LibreFranklin_SemiBold15pt7b #define ROW1_FONT LibreFranklin_SemiBold15pt7b
#define ROW1_ICONFONT orangeclock_icons14pt7b #define ROW1_ICONFONT orangeclock_icons14pt7b
#define ROW1_ICONWIDTH 29 #define ROW1_ICONWIDTH 27
#define ROW2_FONT LibreFranklin_Bold25pt7b #define ROW2_FONT LibreFranklin_Bold25pt7b
#define ROW2_ICONFONT orangeclock_icons25pt7b #define ROW2_ICONFONT orangeclock_icons25pt7b
#define ROW2_ICONWIDTH 52 #define ROW2_ICONWIDTH 52
#define ROW3_FONT LibreFranklin_SemiBold15pt7b #define ROW3_FONT LibreFranklin_SemiBold15pt7b
#define ROW3_ICONFONT orangeclock_icons14pt7b #define ROW3_ICONFONT orangeclock_icons14pt7b
#define ROW3_ICONWIDTH 29 #define ROW3_ICONWIDTH 27
#define SETUPFONT LibreFranklin_SemiBold12pt7b #define SETUPFONT LibreFranklin_SemiBold12pt7b
#endif #endif
#define ICON_BLOCK 'A' #define ICON_BLOCK "A"
#define ICON_EURO 'B' #define ICON_EURO "B"
#define ICON_POUND 'C' #define ICON_POUND "C"
#define ICON_YEN 'D' #define ICON_YEN "D"
#define ICON_DOLLAR 'E' #define ICON_DOLLAR "E"
#define ICON_PIE 'F' #define ICON_PIE "F"
#define ICON_GLOBE 'G' #define ICON_GLOBE "G"
#define ICON_HOURGLASS 'H' #define ICON_HOURGLASS "H"
#define ICON_LIGHTNING 'I' #define ICON_LIGHTNING "I"
#define ICON_REFRESH 'J' #define ICON_REFRESH "J"
#define ICON_NUCLEAR 'K' #define ICON_NUCLEAR "K"
#define ICON_SATS 'L' #define ICON_SATS "L"
#define ICON_SATUSD 'M' #define ICON_SATUSD "M"
#define ICON_SETTINGS 'N' #define ICON_SETTINGS "N"
#define ICON_WIFI 'O' #define ICON_WIFI "O"
#define ICON_CROSS 'P' #define ICON_CROSS "P"
#define ICON_CHECK 'Q' #define ICON_CHECK "Q"
#define ICON_WARNING 'R' #define ICON_WARNING "R"
#define SETTING_ROW1_CONTENT "row1" #define SETTING_ROW1_CONTENT "row1"
#define SETTING_ROW2_CONTENT "row2" #define SETTING_ROW2_CONTENT "row2"
#define SETTING_ROW3_CONTENT "row3" #define SETTING_ROW3_CONTENT "row3"
#define SETTING_CURRENCY "currency" #define SETTING_CURRENCY "currency"
#define SETTING_HOSTNAME_PREFIX "hostnamePrefix"
#define SETTING_MEMPOOL_INSTANCE "mempoolInstance"
#define SETTING_POWER_SAVE_MODE "powerSaveMode"
#define SETTING_TIME_OFFSET_MIN "timeOffsetMin"
#define SETTING_DECIMAL_SEPARATOR "decSeparator"
#define SETTING_TIME_FORMAT "timeFormat"
#define SETTING_DATE_FORMAT "dateFormat"
const int LINE_BLOCKHEIGHT = 0; const int LINE_BLOCKHEIGHT = 0;
const int LINE_MEMPOOL_FEES = 1; const int LINE_MEMPOOL_FEES = 1;
@ -92,20 +79,7 @@ const int LINE_DATE = 100;
#define CURRENCY_AUD "AUD" #define CURRENCY_AUD "AUD"
#define CURRENCY_JPY "JPY" #define CURRENCY_JPY "JPY"
extern WiFiClient client; extern WiFiClientSecure client;
extern GxEPD2_BW<EPD_CLASS, EPD_CLASS::HEIGHT> display; extern GxEPD2_BW<EPD_CLASS, EPD_CLASS::HEIGHT> display;
extern Preferences preferences; extern Preferences preferences;
extern bool isUpdating; extern bool isUpdating;
extern String currentRow1;
extern String currentRow2;
extern String currentRow3;
extern char currentIcon1;
extern char currentIcon2;
extern char currentIcon3;
#ifdef NUM_LEDS
extern CRGB leds[NUM_LEDS];
#endif
extern volatile bool buttonPressed;

View file

@ -1,32 +0,0 @@
#include "utils.hpp"
String getAPPassword()
{
byte mac[6];
WiFi.macAddress(mac);
const char charset[] = "abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ23456789";
char password[9]; // 8 characters + null terminator
snprintf(password, sizeof(password), "%c%c%c%c%c%c%c%c",
charset[mac[0] % (sizeof(charset) - 1)],
charset[mac[1] % (sizeof(charset) - 1)],
charset[mac[2] % (sizeof(charset) - 1)],
charset[mac[3] % (sizeof(charset) - 1)],
charset[mac[4] % (sizeof(charset) - 1)],
charset[mac[5] % (sizeof(charset) - 1)],
charset[(mac[0] + mac[1] + mac[2] + mac[3] + mac[4] + mac[5]) % (sizeof(charset) - 1)],
charset[(mac[0] * mac[1] * mac[2] * mac[3] * mac[4] * mac[5]) % (sizeof(charset) - 1)]);
return password;
}
String getMyHostname()
{
uint8_t mac[6];
// WiFi.macAddress(mac);
esp_efuse_mac_get_default(mac);
char hostname[15];
String hostnamePrefix = preferences.getString(SETTING_HOSTNAME_PREFIX);
snprintf(hostname, sizeof(hostname), "%s-%02x%02x%02x", hostnamePrefix,
mac[3], mac[4], mac[5]);
return hostname;
}

View file

@ -1,28 +0,0 @@
#pragma once
#include <WiFi.h>
#include <Arduino.h>
#include <ArduinoJson.hpp>
#include "shared.hpp"
#include <esp_mac.h>
namespace ArduinoJson {
template <>
struct Converter<char> {
static void toJson(char c, JsonVariant var) {
var.set(static_cast<signed char>(c));
}
static char fromJson(JsonVariantConst src) {
return static_cast<char>(src.as<signed char>());
}
static bool checkJson(JsonVariantConst src) {
return src.is<signed char>();
}
};
}
String getAPPassword();
String getMyHostname();

View file

@ -3,10 +3,8 @@
AsyncWebServer server(80); AsyncWebServer server(80);
const String uintSettings[] = {SETTING_ROW1_CONTENT, SETTING_ROW2_CONTENT, SETTING_ROW3_CONTENT}; const String uintSettings[] = {SETTING_ROW1_CONTENT, SETTING_ROW2_CONTENT, SETTING_ROW3_CONTENT};
const String intSettings[] = {SETTING_TIME_OFFSET_MIN}; const String stringSettings[] = {SETTING_CURRENCY};
const String stringSettings[] = {SETTING_CURRENCY, SETTING_MEMPOOL_INSTANCE, SETTING_TIME_FORMAT, SETTING_DATE_FORMAT}; const String boolSettings[] = {};
const String charSettings[] = {SETTING_DECIMAL_SEPARATOR};
const String boolSettings[] = {SETTING_POWER_SAVE_MODE};
void setupWebserver() void setupWebserver()
{ {
@ -15,17 +13,12 @@ void setupWebserver()
Serial.println(F("An Error has occurred while mounting LittleFS")); Serial.println(F("An Error has occurred while mounting LittleFS"));
} }
server.on("/api/status", HTTP_GET, onApiStatus);
server.on("/api/settings", HTTP_GET, onApiSettingsGet); server.on("/api/settings", HTTP_GET, onApiSettingsGet);
server.on("/api/restart", HTTP_GET, onApiRestart);
server.on("/api/full_refresh", HTTP_GET, onApiFullRefresh);
AsyncCallbackJsonWebHandler *settingsPatchHandler = AsyncCallbackJsonWebHandler *settingsPatchHandler =
new AsyncCallbackJsonWebHandler("/api/json/settings", onApiSettingsPatch); new AsyncCallbackJsonWebHandler("/api/json/settings", onApiSettingsPatch);
server.addHandler(settingsPatchHandler); server.addHandler(settingsPatchHandler);
server.serveStatic("/build", LittleFS, "/build"); server.serveStatic("/build", LittleFS, "/build");
server.serveStatic("/fonts", LittleFS, "/fonts");
server.on("/", HTTP_GET, onIndex); server.on("/", HTTP_GET, onIndex);
server.onNotFound(onNotFound); server.onNotFound(onNotFound);
@ -38,31 +31,6 @@ void setupWebserver()
server.begin(); server.begin();
} }
void onApiStatus(AsyncWebServerRequest *request)
{
JsonDocument root;
root["row1"] = currentRow1;
root["row2"] = currentRow2;
root["row3"] = currentRow3;
root["icon1"] = String(currentIcon1);
root["icon2"] = String(currentIcon2);
root["icon3"] = String(currentIcon3);
root["espUptime"] = esp_timer_get_time() / 1000000;
root["espFreeHeap"] = ESP.getFreeHeap();
root["espHeapSize"] = ESP.getHeapSize();
root["rssi"] = WiFi.RSSI();
AsyncResponseStream *response =
request->beginResponseStream("application/json");
serializeJson(root, *response);
request->send(response);
}
void onApiSettingsGet(AsyncWebServerRequest *request) void onApiSettingsGet(AsyncWebServerRequest *request)
{ {
JsonDocument root; JsonDocument root;
@ -71,37 +39,16 @@ void onApiSettingsGet(AsyncWebServerRequest *request)
root[setting] = preferences.getUInt(setting.c_str()); root[setting] = preferences.getUInt(setting.c_str());
} }
for (String setting : intSettings)
{
root[setting] = preferences.getInt(setting.c_str());
}
for (String setting : stringSettings) for (String setting : stringSettings)
{ {
root[setting] = preferences.getString(setting.c_str()); root[setting] = preferences.getString(setting.c_str());
} }
for (String setting : charSettings)
{
root[setting] = preferences.getChar(setting.c_str());
}
for (String setting : boolSettings) for (String setting : boolSettings)
{ {
root[setting] = preferences.getBool(setting.c_str()); root[setting] = preferences.getBool(setting.c_str());
} }
root["hostname"] = getMyHostname();
root["ip"] = WiFi.localIP();
root["txPower"] = WiFi.getTxPower();
#ifdef GIT_REV
root["gitRev"] = String(GIT_REV);
#endif
#ifdef LAST_BUILD_TIME
root["lastBuildTime"] = String(LAST_BUILD_TIME);
#endif
AsyncResponseStream *response = AsyncResponseStream *response =
request->beginResponseStream("application/json"); request->beginResponseStream("application/json");
serializeJson(root, *response); serializeJson(root, *response);
@ -123,16 +70,6 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json)
} }
} }
for (String setting : intSettings)
{
if (settings.containsKey(setting))
{
preferences.putInt(setting.c_str(), settings[setting].as<int>());
Serial.printf("Setting %s to %d\r\n", setting.c_str(),
settings[setting].as<uint>());
}
}
for (String setting : stringSettings) for (String setting : stringSettings)
{ {
if (settings.containsKey(setting)) if (settings.containsKey(setting))
@ -143,16 +80,6 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json)
} }
} }
for (String setting : charSettings)
{
if (settings.containsKey(setting))
{
preferences.putChar(setting.c_str(), settings[setting].as<char>());
Serial.printf("Setting %s to %s\r\n", setting.c_str(),
settings[setting].as<String>());
}
}
for (String setting : boolSettings) for (String setting : boolSettings)
{ {
if (settings.containsKey(setting)) if (settings.containsKey(setting))
@ -186,21 +113,3 @@ void onNotFound(AsyncWebServerRequest *request)
request->send(404); request->send(404);
} }
} }
void onApiRestart(AsyncWebServerRequest *request) {
request->send(200);
delay(500);
esp_restart();
}
/**
* @Api
* @Path("/api/full_refresh")
*/
void onApiFullRefresh(AsyncWebServerRequest *request) {
display.refresh();
request->send(200);
}

View file

@ -9,12 +9,8 @@
void setupWebserver(); void setupWebserver();
void onApiStatus(AsyncWebServerRequest *request);
void onApiSettingsGet(AsyncWebServerRequest *request); void onApiSettingsGet(AsyncWebServerRequest *request);
void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json); void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json);
void onApiFullRefresh(AsyncWebServerRequest *request);
void onApiRestart(AsyncWebServerRequest *request);
void onIndex(AsyncWebServerRequest *request); void onIndex(AsyncWebServerRequest *request);
void onNotFound(AsyncWebServerRequest *request); void onNotFound(AsyncWebServerRequest *request);