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
2 changes: 1 addition & 1 deletion .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
include = ["../crates/firmware/.cargo/esp-config.toml"]

[target.riscv32imac-unknown-none-elf]
runner = "espflash flash --monitor --chip esp32c6"
runner = "espflash flash --monitor --chip esp32c6 --partition-table partitions.csv"

[build]
target = "riscv32imac-unknown-none-elf"
Expand Down
8 changes: 6 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,12 +135,16 @@ jobs:

ELF="target/riscv32imac-unknown-none-elf/release/longfred"
cp "$ELF" "dist/longfred-${VARIANT}-esp32c6.elf"
espflash save-image --chip esp32c6 --merge "$ELF" \
espflash save-image --chip esp32c6 --partition-table partitions.csv --merge "$ELF" \
"dist/longfred-${VARIANT}-esp32c6.bin"
espflash save-image --chip esp32c6 --partition-table partitions.csv "$ELF" \
"dist/longfred-${VARIANT}-esp32c6.app.bin"

(
cd dist
sha256sum "longfred-${VARIANT}-esp32c6.elf" "longfred-${VARIANT}-esp32c6.bin" > SHA256SUMS
sha256sum "longfred-${VARIANT}-esp32c6.elf" \
"longfred-${VARIANT}-esp32c6.bin" \
"longfred-${VARIANT}-esp32c6.app.bin" > SHA256SUMS
)

- name: Upload firmware
Expand Down
55 changes: 55 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/firmware/.cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
include = ["esp-config.toml"]

[target.riscv32imac-unknown-none-elf]
runner = "espflash flash --monitor --chip esp32c6"
runner = "espflash flash --monitor --chip esp32c6 --partition-table partitions.csv"

[build]
target = "riscv32imac-unknown-none-elf"
Expand Down
3 changes: 2 additions & 1 deletion crates/firmware/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,12 @@ embassy-time = { version = "0.5.0", features = ["log"] }
ssd1306 = { version = "0.10", default-features = false, features = ["graphics"] }
embedded-graphics = "0.8"
embedded-hal = "1.0"
embassy-embedded-hal = "0.6"

log = "0.4"
heapless = "0.9"
nb = "1.1"
static_cell = "2.1"
embassy-embedded-hal = "0.6"
esp-hal-dhcp-server = { version = "0.4.0", default-features = false, features = ["log"] }
embedded-hal-async = "1.0"
critical-section = "1.2"
31 changes: 23 additions & 8 deletions crates/firmware/src/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
//! LongFred firmware entry point: HAL init, task spawn, and Soft-AP programming mode.

use embassy_executor::Spawner;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::mutex::Mutex;
use embassy_time::{Duration, Timer};
use esp_backtrace as _;
#[cfg(not(feature = "sim"))]
Expand All @@ -27,7 +29,7 @@ use longfred_firmware::{board, config, domain, input, storage, ui};

esp_bootloader_esp_idf::esp_app_desc!();

static FLASH: StaticCell<FlashStorage> = StaticCell::new();
static FLASH: StaticCell<Mutex<CriticalSectionRawMutex, FlashStorage<'static>>> = StaticCell::new();

#[esp_rtos::main]
async fn main(spawner: Spawner) -> ! {
Expand All @@ -50,13 +52,24 @@ async fn main(spawner: Spawner) -> ! {
let rng = Rng::new();
let boot_entropy = rng.random();

let flash = FLASH.init(FlashStorage::new(peripherals.FLASH));
let boot = storage::ensure_boot(flash, boot_entropy);
let mut flash_dev = FlashStorage::new(peripherals.FLASH);
let boot = storage::ensure_boot(&mut flash_dev, boot_entropy);
info!("wifi hostname: {}", boot.wifi_hostname.as_str());
#[cfg(not(feature = "sim"))]
net::provisioning::ota::mark_running_slot_valid(&mut flash_dev);

let enter_programming = boot.programming_mode
|| (board::active_variant().auto_pair_when_unconfigured && !boot.has_wifi_credentials);

let mut prog_rec = boot.record.clone();
if enter_programming {
prog_rec.programming_mode = true;
if !boot.programming_mode {
storage::write_record(&mut flash_dev, &prog_rec);
}
}
let flash = FLASH.init(Mutex::new(flash_dev));

#[cfg(not(feature = "sim"))]
{
let seed = ((rng.random() as u64) << 32) | rng.random() as u64;
Expand All @@ -71,15 +84,12 @@ async fn main(spawner: Spawner) -> ! {
);
let mut prog_rec = boot.record.clone();
prog_rec.programming_mode = true;
if !boot.programming_mode {
storage::write_record(flash, &prog_rec);
}

let _ = net::provisioning::spawn_programming_net(
&spawner,
peripherals.WIFI,
seed,
prog_rec,
flash,
);
} else {
let controller = match WifiController::new(peripherals.WIFI, Default::default()) {
Expand Down Expand Up @@ -120,6 +130,7 @@ async fn main(spawner: Spawner) -> ! {
if let Ok(token) = net::session::task(stack) {
spawner.spawn(token);
}
net::provisioning::spawn_sta_http(&spawner, stack, boot.record.clone(), flash);
}
}

Expand Down Expand Up @@ -201,13 +212,17 @@ async fn main(spawner: Spawner) -> ! {
}
}

// MarkWTech: 3×4 keypad matrix (pins from markwtech constants).
// MarkWTech: 3×4 keypad matrix + extra tact cluster (pins from markwtech constants).
#[cfg(feature = "variant-markwtech")]
{
let keypad = input::keypad::build();
if let Ok(token) = input::keypad::task(keypad, raw_sender) {
spawner.spawn(token);
}
let extras = input::extra_buttons::build();
if let Ok(token) = input::extra_buttons::task(extras, raw_sender) {
spawner.spawn(token);
}
}

// Expanders: LongFred family + heiko-wifred.
Expand Down
2 changes: 2 additions & 0 deletions crates/firmware/src/board/raw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ pub enum ButtonId {
/// Keypad digit 0–9 (markwtech / heiko).
KeypadDigit(u8),
Menu,
/// Dedicated Back / Cancel (MarkWTech extra cluster).
Back,
Hash,
Star,
Extra(u8),
Expand Down
1 change: 1 addition & 0 deletions crates/firmware/src/board/variants/longfred_family.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ impl LongFredSurface {
| ButtonId::JoyRight
| ButtonId::JoyMenu
| ButtonId::Menu
| ButtonId::Back
| ButtonId::Direction
| ButtonId::EncoderButton
| ButtonId::KeypadDigit(_)
Expand Down
23 changes: 20 additions & 3 deletions crates/firmware/src/board/variants/markwtech.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! MarkWTech / WiTcontroller-style ControlSurface.
//!
//! Hardware: 3×4 keypad matrix, KY-040 encoder, OLED 128×64 (SSD1309), no MCP expanders.
//! Programming chord: Star (`*`, Menu) + Stop held for 8 s.
//! Hardware: 3×4 keypad matrix, extra tact cluster, KY-040 encoder, OLED 128×64
//! (SSD1309), no MCP expanders. Programming chord: Star (`*`) + Stop held for 8 s.

use embassy_time::Instant;

Expand All @@ -10,7 +10,7 @@ use crate::board::chord::{ChordDetector, PROGRAMMING_CHORD_MS};
use crate::board::descriptor::{LAYOUT_128X64, VariantDescriptor};
use crate::board::raw::{AnalogId, ButtonId, RawEvent, SwitchId};
use crate::config::board::Gpio;
use crate::input::InputEvent;
use crate::input::{InputEvent, NavDir};

/// Keypad matrix row GPIOs (driven, active-low scan).
pub const KEYPAD_ROW_PINS: [Gpio; 4] = [18, 19, 20, 21];
Expand Down Expand Up @@ -44,6 +44,20 @@ pub const KEYPAD_MAP: [[ButtonId; 3]; 4] = [
[ButtonId::Star, ButtonId::KeypadDigit(0), ButtonId::Hash],
];

/// Extra tact switches (active-low, internal pull-up): left, Stop, right, Back, Menu.
///
/// ESP32-C6-WROOM-1 does not expose GPIO 14, and GPIO 1 is the battery ADC, so the
/// cluster uses 4/5 (strapping, harmless with default eFuses) and 12 (USB_D-).
/// UART0 (16/17) stays free for the serial console.
pub const EXTRA_BUTTON_PINS: [Gpio; 5] = [11, 12, 4, 5, 15];
pub const EXTRA_BUTTON_MAP: [ButtonId; 5] = [
ButtonId::JoyLeft,
ButtonId::Stop,
ButtonId::JoyRight,
ButtonId::Back,
ButtonId::Menu,
];

pub const DESCRIPTOR: VariantDescriptor = VariantDescriptor {
id: "markwtech",
name: "MarkWTech",
Expand Down Expand Up @@ -91,6 +105,9 @@ impl MarkwtechSurface {
ButtonId::KeypadDigit(d) if pressed && d <= 9 => {
out(InputEvent::Digit((b'0' + d) as char));
}
ButtonId::JoyLeft if pressed => out(InputEvent::Nav(NavDir::Left)),
ButtonId::JoyRight if pressed => out(InputEvent::Nav(NavDir::Right)),
ButtonId::Back if pressed => out(InputEvent::Back),
ButtonId::Extra(n) => {
if pressed {
out(InputEvent::FnPress(n));
Expand Down
11 changes: 6 additions & 5 deletions crates/firmware/src/config/power.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,19 @@ pub const USE_BATTERY_TEST: bool = true;
/// ADC-to-voltage scaling factor (hardware calibration, stage 11).
pub const BATTERY_CONVERSION_FACTOR: f32 = 1.7;

/// Default display mode: icon + percent (when USE_BATTERY_TEST).
/// Default display mode: icon + percent (when `USE_BATTERY_TEST`).
pub const USE_BATTERY_PERCENT_WITH_ICON: bool = false;

/// Auto deep sleep when % < threshold; 0 = disabled.
pub const USE_BATTERY_SLEEP_AT_PERCENT: u8 = 0;
pub const USE_BATTERY_SLEEP_AT_PERCENT: u8 = 5;

pub const BATTERY_POLL_S: u64 = 10;
pub const ADC_READS: usize = 20;

/// Inactivity auto-off (no WiThrottle server connection), ms.
/// Set 0 to disable (recommended under Wokwi — deep sleep appears as reboot loop).
pub const AUTO_SLEEP_INACTIVITY_MS: u64 = 0;
/// Inactivity auto-off (no `WiThrottle` server connection), ms.
/// 0 = disabled. The `sim` build does not spawn `sleep::task`, so this is a
/// no-op under Wokwi regardless of the value.
pub const AUTO_SLEEP_INACTIVITY_MS: u64 = 240_000;

/// Sleep screen delay before deep sleep, ms.
pub const SLEEP_SCREEN_DELAY_MS: u64 = 2_000;
10 changes: 8 additions & 2 deletions crates/firmware/src/config/sizes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,11 @@ pub const MAX_ROUTE_LIST: usize = 60;
pub const MAX_SSID_LEN: usize = 32;
pub const MAX_PASSWORD_LEN: usize = 64;

/// embassy-net socket count (DHCP + DNS + mDNS UDP + session TCP/UDP).
pub const NET_SOCKETS: usize = 5;
/// embassy-net socket count (DHCP + DNS + mDNS UDP + session TCP/UDP + HTTP OTA).
pub const NET_SOCKETS: usize = 6;

/// Soft-AP programming stack: HTTP TCP + DHCP UDP + spare.
pub const PROG_NET_SOCKETS: usize = 4;

/// Inactive OTA app slot size from `partitions.csv` (`ota_0` / `ota_1`).
pub const OTA_SLOT_BYTES: u32 = 0x3C_0000;
Loading
Loading