feat: add display UI with splash screen, dashboard, and RSSI sparkline
Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17624 files changed
+4564928
-38
No files matched your search
+1
@@ -0,0 +1 @@
|
||||
{"files":{".cargo_vcs_info.json":"2450c2a36a182ca06264b9489b44c33936106a1aea37a9c34d0f4afb531d7f1c","CHANGELOG.md":"4a6b70c4ce8abe01e89253b52ea87e3e56a2d52ee9d05e5ecb335e6bd59d34ff","Cargo.toml":"0963c35f9e2b32da4f1fa5bf8fe4e08095e7fb4961462b65c0e4b576d4a7342b","Cargo.toml.orig":"a349d63b9722d1acbfaa644dde3ab4d38aff99c4a6c9a09f8c7dacaf16731805","README.md":"bf10dd23cd1d0ae05dabf46b175dbb167a2a587786ce56367e1db552b7f3824e","src/delay.rs":"8660d1d46bac2e190d162f1e48999f6bafd0c524ebd5d426cbc213d5a115c43a","src/driver_mock.rs":"4a9cc1302afdd4bdeb4df7816000960ca68a5e139853c8b06f5445556f3fca7b","src/driver_std.rs":"4b82f96dc54f7dcb4f1a4c78f7627ebb3f8203c01fae2ad4207effa20a091c0f","src/driver_wasm.rs":"728a6ff8e77afc36031b6b2918360ce47945ba34cc54db5671ebe5035b1fe421","src/duration.rs":"5d041cbbd169b46da0e35307a94d2654282727d39b85bdc3673c64b056ce814f","src/fmt.rs":"29efbba075d0bcf8acd4c3f3228bbe5ab3be735c48d3894c86a957d15bab0f12","src/instant.rs":"c22ea4ddaaff12762d17040dbd9c4507dd9f3068d1c7e8aa28018b10cc42bf7c","src/lib.rs":"aec4b84b6d9e4f115a20eb99f7ad800da6ac15b93a58853264370d5a9fc3f7a8","src/queue_generic.rs":"64ad90995d64e67213f71d9117684858f4502c5831ea9829d5d4d82de08bec02","src/timer.rs":"087a9c535a6ca746b60730c6901e2ba0e77966765a5ef5fefd99179e80936135"},"package":"158080d48f824fad101d7b2fae2d83ac39e3f7a6fa01811034f7ab8ffc6e7309"}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"git": {
|
||||
"sha1": "d091b3f0a4bae98ee4a03e2e941920b009dbbfbf"
|
||||
},
|
||||
"path_in_vcs": "embassy-time"
|
||||
}
|
||||
Vendored
+71
@@ -0,0 +1,71 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 0.3.2 - 2024-08-05
|
||||
|
||||
- Implement with_timeout()/with_deadline() method style call on Future
|
||||
- Add collapse_debuginfo to fmt.rs macros.
|
||||
|
||||
## 0.3.1 - 2024-01-11
|
||||
|
||||
- Add with\_deadline convenience function and example
|
||||
- Implement Clone for Delay
|
||||
- Make Ticker::next Send+Sync
|
||||
- Add timestamp features
|
||||
|
||||
## 0.3.0 - 2024-01-11
|
||||
|
||||
- Update `embedded-hal-async` to `1.0.0`
|
||||
- Update `embedded-hal v1` to `1.0.0`
|
||||
- Split the time driver to a separate `embassy-time-driver` crate.
|
||||
|
||||
## 0.2.0 - 2023-12-04
|
||||
|
||||
- Added tick rates in multiples of 10 kHz
|
||||
- Remove nightly and unstable-traits features in preparation for 1.75.
|
||||
- Update heapless to 0.8.
|
||||
|
||||
## 0.1.5 - 2023-10-16
|
||||
|
||||
- Added `links` key to Cargo.toml, to prevent multiple copies of this crate in the same binary.
|
||||
Needed because different copies might get different tick rates, causing
|
||||
wrong delays if the time driver is using one copy and user code is using another.
|
||||
This is especially common when mixing crates from crates.io and git.
|
||||
|
||||
## 0.1.4 - 2023-10-12
|
||||
|
||||
- Added more tick rates
|
||||
|
||||
## 0.1.3 - 2023-08-28
|
||||
|
||||
- Update `embedded-hal-async` to `1.0.0-rc.2`
|
||||
- Update `embedded-hal v1` to `1.0.0-rc.2`
|
||||
|
||||
## 0.1.2 - 2023-07-05
|
||||
|
||||
- Update `embedded-hal-async` to `0.2.0-alpha.2`.
|
||||
- Update `embedded-hal v1` to `1.0.0-alpha.11`. (Note: v0.2 support is kept unchanged).
|
||||
|
||||
## 0.1.1 - 2023-04-13
|
||||
|
||||
- Update `embedded-hal-async` to `0.2.0-alpha.1` (uses `async fn` in traits).
|
||||
- Update `embedded-hal v1` to `1.0.0-alpha.10`. (Note: v0.2 support is kept unchanged).
|
||||
- Remove dep on `embassy-sync`.
|
||||
- Fix reentrancy issues in the `std` time driver (#1177)
|
||||
- Add `Duration::from_hz()`.
|
||||
- impl `From` conversions to/from `core::time::Duration`.
|
||||
- Add `#[must_use]` to all futures.
|
||||
- Add inherent `async fn tick()` to `Ticker`, so you can use it directly without the `Stream` trait.
|
||||
- Add more tick rates.
|
||||
- impl `Default` for `Signal`
|
||||
- Remove unnecessary uses of `atomic-polyfill`
|
||||
|
||||
## 0.1.0 - 2022-08-26
|
||||
|
||||
- First release
|
||||
Vendored
+298
@@ -0,0 +1,298 @@
|
||||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "embassy-time"
|
||||
version = "0.3.2"
|
||||
build = false
|
||||
autobins = false
|
||||
autoexamples = false
|
||||
autotests = false
|
||||
autobenches = false
|
||||
description = "Instant and Duration for embedded no-std systems, with async timer support"
|
||||
documentation = "https://docs.embassy.dev/embassy-time"
|
||||
readme = "README.md"
|
||||
categories = [
|
||||
"embedded",
|
||||
"no-std",
|
||||
"concurrency",
|
||||
"asynchronous",
|
||||
]
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/embassy-rs/embassy"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = [
|
||||
"defmt",
|
||||
"std",
|
||||
]
|
||||
|
||||
[package.metadata.embassy_docs]
|
||||
features = [
|
||||
"defmt",
|
||||
"std",
|
||||
]
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-time-v$VERSION/embassy-time/src/"
|
||||
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-time/src/"
|
||||
target = "x86_64-unknown-linux-gnu"
|
||||
|
||||
[lib]
|
||||
name = "embassy_time"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies.cfg-if]
|
||||
version = "1.0.0"
|
||||
|
||||
[dependencies.critical-section]
|
||||
version = "1.1"
|
||||
|
||||
[dependencies.defmt]
|
||||
version = "0.3"
|
||||
optional = true
|
||||
|
||||
[dependencies.document-features]
|
||||
version = "0.2.7"
|
||||
|
||||
[dependencies.embassy-time-driver]
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies.embassy-time-queue-driver]
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies.embedded-hal-02]
|
||||
version = "0.2.6"
|
||||
package = "embedded-hal"
|
||||
|
||||
[dependencies.embedded-hal-1]
|
||||
version = "1.0"
|
||||
package = "embedded-hal"
|
||||
|
||||
[dependencies.embedded-hal-async]
|
||||
version = "1.0"
|
||||
|
||||
[dependencies.futures-util]
|
||||
version = "0.3.17"
|
||||
default-features = false
|
||||
|
||||
[dependencies.heapless]
|
||||
version = "0.8"
|
||||
|
||||
[dependencies.js-sys]
|
||||
version = "0.3"
|
||||
optional = true
|
||||
|
||||
[dependencies.log]
|
||||
version = "0.4.14"
|
||||
optional = true
|
||||
|
||||
[dependencies.wasm-bindgen]
|
||||
version = "0.2.81"
|
||||
optional = true
|
||||
|
||||
[dependencies.wasm-timer]
|
||||
version = "0.2.5"
|
||||
optional = true
|
||||
|
||||
[dev-dependencies.critical-section]
|
||||
version = "1.1"
|
||||
features = ["std"]
|
||||
|
||||
[dev-dependencies.embassy-executor]
|
||||
version = "0.6.0"
|
||||
|
||||
[dev-dependencies.serial_test]
|
||||
version = "0.9"
|
||||
|
||||
[features]
|
||||
defmt-timestamp-uptime = ["defmt"]
|
||||
defmt-timestamp-uptime-ms = ["defmt"]
|
||||
defmt-timestamp-uptime-s = ["defmt"]
|
||||
defmt-timestamp-uptime-tms = ["defmt"]
|
||||
defmt-timestamp-uptime-ts = ["defmt"]
|
||||
defmt-timestamp-uptime-tus = ["defmt"]
|
||||
defmt-timestamp-uptime-us = ["defmt"]
|
||||
generic-queue = []
|
||||
generic-queue-128 = ["generic-queue"]
|
||||
generic-queue-16 = ["generic-queue"]
|
||||
generic-queue-32 = ["generic-queue"]
|
||||
generic-queue-64 = ["generic-queue"]
|
||||
generic-queue-8 = ["generic-queue"]
|
||||
mock-driver = ["tick-hz-1_000_000"]
|
||||
std = [
|
||||
"tick-hz-1_000_000",
|
||||
"critical-section/std",
|
||||
]
|
||||
tick-hz-1 = ["embassy-time-driver/tick-hz-1"]
|
||||
tick-hz-10 = ["embassy-time-driver/tick-hz-10"]
|
||||
tick-hz-100 = ["embassy-time-driver/tick-hz-100"]
|
||||
tick-hz-100_000 = ["embassy-time-driver/tick-hz-100_000"]
|
||||
tick-hz-100_000_000 = ["embassy-time-driver/tick-hz-100_000_000"]
|
||||
tick-hz-10_000 = ["embassy-time-driver/tick-hz-10_000"]
|
||||
tick-hz-10_000_000 = ["embassy-time-driver/tick-hz-10_000_000"]
|
||||
tick-hz-10_240_000 = ["embassy-time-driver/tick-hz-10_240_000"]
|
||||
tick-hz-110_000_000 = ["embassy-time-driver/tick-hz-110_000_000"]
|
||||
tick-hz-120_000_000 = ["embassy-time-driver/tick-hz-120_000_000"]
|
||||
tick-hz-128 = ["embassy-time-driver/tick-hz-128"]
|
||||
tick-hz-128_000 = ["embassy-time-driver/tick-hz-128_000"]
|
||||
tick-hz-128_000_000 = ["embassy-time-driver/tick-hz-128_000_000"]
|
||||
tick-hz-12_000_000 = ["embassy-time-driver/tick-hz-12_000_000"]
|
||||
tick-hz-130_000_000 = ["embassy-time-driver/tick-hz-130_000_000"]
|
||||
tick-hz-131_072 = ["embassy-time-driver/tick-hz-131_072"]
|
||||
tick-hz-131_072_000 = ["embassy-time-driver/tick-hz-131_072_000"]
|
||||
tick-hz-140_000_000 = ["embassy-time-driver/tick-hz-140_000_000"]
|
||||
tick-hz-144_000_000 = ["embassy-time-driver/tick-hz-144_000_000"]
|
||||
tick-hz-150_000_000 = ["embassy-time-driver/tick-hz-150_000_000"]
|
||||
tick-hz-16 = ["embassy-time-driver/tick-hz-16"]
|
||||
tick-hz-160_000 = ["embassy-time-driver/tick-hz-160_000"]
|
||||
tick-hz-160_000_000 = ["embassy-time-driver/tick-hz-160_000_000"]
|
||||
tick-hz-163_840_000 = ["embassy-time-driver/tick-hz-163_840_000"]
|
||||
tick-hz-16_000 = ["embassy-time-driver/tick-hz-16_000"]
|
||||
tick-hz-16_000_000 = ["embassy-time-driver/tick-hz-16_000_000"]
|
||||
tick-hz-16_384 = ["embassy-time-driver/tick-hz-16_384"]
|
||||
tick-hz-16_384_000 = ["embassy-time-driver/tick-hz-16_384_000"]
|
||||
tick-hz-16_777_216 = ["embassy-time-driver/tick-hz-16_777_216"]
|
||||
tick-hz-170_000_000 = ["embassy-time-driver/tick-hz-170_000_000"]
|
||||
tick-hz-180_000_000 = ["embassy-time-driver/tick-hz-180_000_000"]
|
||||
tick-hz-18_000_000 = ["embassy-time-driver/tick-hz-18_000_000"]
|
||||
tick-hz-190_000_000 = ["embassy-time-driver/tick-hz-190_000_000"]
|
||||
tick-hz-192_000_000 = ["embassy-time-driver/tick-hz-192_000_000"]
|
||||
tick-hz-1_000 = ["embassy-time-driver/tick-hz-1_000"]
|
||||
tick-hz-1_000_000 = ["embassy-time-driver/tick-hz-1_000_000"]
|
||||
tick-hz-1_000_000_000 = ["embassy-time-driver/tick-hz-1_000_000_000"]
|
||||
tick-hz-1_024 = ["embassy-time-driver/tick-hz-1_024"]
|
||||
tick-hz-1_024_000 = ["embassy-time-driver/tick-hz-1_024_000"]
|
||||
tick-hz-1_048_576 = ["embassy-time-driver/tick-hz-1_048_576"]
|
||||
tick-hz-1_280_000 = ["embassy-time-driver/tick-hz-1_280_000"]
|
||||
tick-hz-1_310_720_000 = ["embassy-time-driver/tick-hz-1_310_720_000"]
|
||||
tick-hz-2 = ["embassy-time-driver/tick-hz-2"]
|
||||
tick-hz-200_000_000 = ["embassy-time-driver/tick-hz-200_000_000"]
|
||||
tick-hz-20_000 = ["embassy-time-driver/tick-hz-20_000"]
|
||||
tick-hz-20_000_000 = ["embassy-time-driver/tick-hz-20_000_000"]
|
||||
tick-hz-20_480_000 = ["embassy-time-driver/tick-hz-20_480_000"]
|
||||
tick-hz-210_000_000 = ["embassy-time-driver/tick-hz-210_000_000"]
|
||||
tick-hz-220_000_000 = ["embassy-time-driver/tick-hz-220_000_000"]
|
||||
tick-hz-230_000_000 = ["embassy-time-driver/tick-hz-230_000_000"]
|
||||
tick-hz-240_000_000 = ["embassy-time-driver/tick-hz-240_000_000"]
|
||||
tick-hz-24_000_000 = ["embassy-time-driver/tick-hz-24_000_000"]
|
||||
tick-hz-250_000_000 = ["embassy-time-driver/tick-hz-250_000_000"]
|
||||
tick-hz-256 = ["embassy-time-driver/tick-hz-256"]
|
||||
tick-hz-256_000 = ["embassy-time-driver/tick-hz-256_000"]
|
||||
tick-hz-256_000_000 = ["embassy-time-driver/tick-hz-256_000_000"]
|
||||
tick-hz-260_000_000 = ["embassy-time-driver/tick-hz-260_000_000"]
|
||||
tick-hz-262_144 = ["embassy-time-driver/tick-hz-262_144"]
|
||||
tick-hz-262_144_000 = ["embassy-time-driver/tick-hz-262_144_000"]
|
||||
tick-hz-270_000_000 = ["embassy-time-driver/tick-hz-270_000_000"]
|
||||
tick-hz-280_000_000 = ["embassy-time-driver/tick-hz-280_000_000"]
|
||||
tick-hz-288_000_000 = ["embassy-time-driver/tick-hz-288_000_000"]
|
||||
tick-hz-290_000_000 = ["embassy-time-driver/tick-hz-290_000_000"]
|
||||
tick-hz-2_000 = ["embassy-time-driver/tick-hz-2_000"]
|
||||
tick-hz-2_000_000 = ["embassy-time-driver/tick-hz-2_000_000"]
|
||||
tick-hz-2_048 = ["embassy-time-driver/tick-hz-2_048"]
|
||||
tick-hz-2_048_000 = ["embassy-time-driver/tick-hz-2_048_000"]
|
||||
tick-hz-2_097_152 = ["embassy-time-driver/tick-hz-2_097_152"]
|
||||
tick-hz-2_560_000 = ["embassy-time-driver/tick-hz-2_560_000"]
|
||||
tick-hz-2_621_440_000 = ["embassy-time-driver/tick-hz-2_621_440_000"]
|
||||
tick-hz-300_000_000 = ["embassy-time-driver/tick-hz-300_000_000"]
|
||||
tick-hz-30_000_000 = ["embassy-time-driver/tick-hz-30_000_000"]
|
||||
tick-hz-32 = ["embassy-time-driver/tick-hz-32"]
|
||||
tick-hz-320_000 = ["embassy-time-driver/tick-hz-320_000"]
|
||||
tick-hz-320_000_000 = ["embassy-time-driver/tick-hz-320_000_000"]
|
||||
tick-hz-327_680_000 = ["embassy-time-driver/tick-hz-327_680_000"]
|
||||
tick-hz-32_000 = ["embassy-time-driver/tick-hz-32_000"]
|
||||
tick-hz-32_000_000 = ["embassy-time-driver/tick-hz-32_000_000"]
|
||||
tick-hz-32_768 = ["embassy-time-driver/tick-hz-32_768"]
|
||||
tick-hz-32_768_000 = ["embassy-time-driver/tick-hz-32_768_000"]
|
||||
tick-hz-340_000_000 = ["embassy-time-driver/tick-hz-340_000_000"]
|
||||
tick-hz-360_000_000 = ["embassy-time-driver/tick-hz-360_000_000"]
|
||||
tick-hz-36_000_000 = ["embassy-time-driver/tick-hz-36_000_000"]
|
||||
tick-hz-380_000_000 = ["embassy-time-driver/tick-hz-380_000_000"]
|
||||
tick-hz-384_000_000 = ["embassy-time-driver/tick-hz-384_000_000"]
|
||||
tick-hz-3_000_000 = ["embassy-time-driver/tick-hz-3_000_000"]
|
||||
tick-hz-4 = ["embassy-time-driver/tick-hz-4"]
|
||||
tick-hz-400_000_000 = ["embassy-time-driver/tick-hz-400_000_000"]
|
||||
tick-hz-40_000 = ["embassy-time-driver/tick-hz-40_000"]
|
||||
tick-hz-40_000_000 = ["embassy-time-driver/tick-hz-40_000_000"]
|
||||
tick-hz-40_960_000 = ["embassy-time-driver/tick-hz-40_960_000"]
|
||||
tick-hz-420_000_000 = ["embassy-time-driver/tick-hz-420_000_000"]
|
||||
tick-hz-440_000_000 = ["embassy-time-driver/tick-hz-440_000_000"]
|
||||
tick-hz-460_000_000 = ["embassy-time-driver/tick-hz-460_000_000"]
|
||||
tick-hz-480_000_000 = ["embassy-time-driver/tick-hz-480_000_000"]
|
||||
tick-hz-48_000_000 = ["embassy-time-driver/tick-hz-48_000_000"]
|
||||
tick-hz-4_000 = ["embassy-time-driver/tick-hz-4_000"]
|
||||
tick-hz-4_000_000 = ["embassy-time-driver/tick-hz-4_000_000"]
|
||||
tick-hz-4_096 = ["embassy-time-driver/tick-hz-4_096"]
|
||||
tick-hz-4_096_000 = ["embassy-time-driver/tick-hz-4_096_000"]
|
||||
tick-hz-4_194_304 = ["embassy-time-driver/tick-hz-4_194_304"]
|
||||
tick-hz-500_000_000 = ["embassy-time-driver/tick-hz-500_000_000"]
|
||||
tick-hz-50_000_000 = ["embassy-time-driver/tick-hz-50_000_000"]
|
||||
tick-hz-512 = ["embassy-time-driver/tick-hz-512"]
|
||||
tick-hz-512_000 = ["embassy-time-driver/tick-hz-512_000"]
|
||||
tick-hz-512_000_000 = ["embassy-time-driver/tick-hz-512_000_000"]
|
||||
tick-hz-520_000_000 = ["embassy-time-driver/tick-hz-520_000_000"]
|
||||
tick-hz-524_288 = ["embassy-time-driver/tick-hz-524_288"]
|
||||
tick-hz-524_288_000 = ["embassy-time-driver/tick-hz-524_288_000"]
|
||||
tick-hz-540_000_000 = ["embassy-time-driver/tick-hz-540_000_000"]
|
||||
tick-hz-560_000_000 = ["embassy-time-driver/tick-hz-560_000_000"]
|
||||
tick-hz-576_000_000 = ["embassy-time-driver/tick-hz-576_000_000"]
|
||||
tick-hz-580_000_000 = ["embassy-time-driver/tick-hz-580_000_000"]
|
||||
tick-hz-5_120_000 = ["embassy-time-driver/tick-hz-5_120_000"]
|
||||
tick-hz-5_242_880_000 = ["embassy-time-driver/tick-hz-5_242_880_000"]
|
||||
tick-hz-600_000_000 = ["embassy-time-driver/tick-hz-600_000_000"]
|
||||
tick-hz-60_000_000 = ["embassy-time-driver/tick-hz-60_000_000"]
|
||||
tick-hz-620_000_000 = ["embassy-time-driver/tick-hz-620_000_000"]
|
||||
tick-hz-64 = ["embassy-time-driver/tick-hz-64"]
|
||||
tick-hz-640_000 = ["embassy-time-driver/tick-hz-640_000"]
|
||||
tick-hz-640_000_000 = ["embassy-time-driver/tick-hz-640_000_000"]
|
||||
tick-hz-64_000 = ["embassy-time-driver/tick-hz-64_000"]
|
||||
tick-hz-64_000_000 = ["embassy-time-driver/tick-hz-64_000_000"]
|
||||
tick-hz-655_360_000 = ["embassy-time-driver/tick-hz-655_360_000"]
|
||||
tick-hz-65_536 = ["embassy-time-driver/tick-hz-65_536"]
|
||||
tick-hz-65_536_000 = ["embassy-time-driver/tick-hz-65_536_000"]
|
||||
tick-hz-660_000_000 = ["embassy-time-driver/tick-hz-660_000_000"]
|
||||
tick-hz-680_000_000 = ["embassy-time-driver/tick-hz-680_000_000"]
|
||||
tick-hz-6_000_000 = ["embassy-time-driver/tick-hz-6_000_000"]
|
||||
tick-hz-700_000_000 = ["embassy-time-driver/tick-hz-700_000_000"]
|
||||
tick-hz-70_000_000 = ["embassy-time-driver/tick-hz-70_000_000"]
|
||||
tick-hz-720_000_000 = ["embassy-time-driver/tick-hz-720_000_000"]
|
||||
tick-hz-72_000_000 = ["embassy-time-driver/tick-hz-72_000_000"]
|
||||
tick-hz-740_000_000 = ["embassy-time-driver/tick-hz-740_000_000"]
|
||||
tick-hz-760_000_000 = ["embassy-time-driver/tick-hz-760_000_000"]
|
||||
tick-hz-768_000_000 = ["embassy-time-driver/tick-hz-768_000_000"]
|
||||
tick-hz-780_000_000 = ["embassy-time-driver/tick-hz-780_000_000"]
|
||||
tick-hz-8 = ["embassy-time-driver/tick-hz-8"]
|
||||
tick-hz-800_000_000 = ["embassy-time-driver/tick-hz-800_000_000"]
|
||||
tick-hz-80_000 = ["embassy-time-driver/tick-hz-80_000"]
|
||||
tick-hz-80_000_000 = ["embassy-time-driver/tick-hz-80_000_000"]
|
||||
tick-hz-81_920_000 = ["embassy-time-driver/tick-hz-81_920_000"]
|
||||
tick-hz-820_000_000 = ["embassy-time-driver/tick-hz-820_000_000"]
|
||||
tick-hz-840_000_000 = ["embassy-time-driver/tick-hz-840_000_000"]
|
||||
tick-hz-860_000_000 = ["embassy-time-driver/tick-hz-860_000_000"]
|
||||
tick-hz-880_000_000 = ["embassy-time-driver/tick-hz-880_000_000"]
|
||||
tick-hz-8_000 = ["embassy-time-driver/tick-hz-8_000"]
|
||||
tick-hz-8_000_000 = ["embassy-time-driver/tick-hz-8_000_000"]
|
||||
tick-hz-8_192 = ["embassy-time-driver/tick-hz-8_192"]
|
||||
tick-hz-8_192_000 = ["embassy-time-driver/tick-hz-8_192_000"]
|
||||
tick-hz-8_388_608 = ["embassy-time-driver/tick-hz-8_388_608"]
|
||||
tick-hz-900_000_000 = ["embassy-time-driver/tick-hz-900_000_000"]
|
||||
tick-hz-90_000_000 = ["embassy-time-driver/tick-hz-90_000_000"]
|
||||
tick-hz-920_000_000 = ["embassy-time-driver/tick-hz-920_000_000"]
|
||||
tick-hz-940_000_000 = ["embassy-time-driver/tick-hz-940_000_000"]
|
||||
tick-hz-960_000_000 = ["embassy-time-driver/tick-hz-960_000_000"]
|
||||
tick-hz-96_000_000 = ["embassy-time-driver/tick-hz-96_000_000"]
|
||||
tick-hz-980_000_000 = ["embassy-time-driver/tick-hz-980_000_000"]
|
||||
tick-hz-9_000_000 = ["embassy-time-driver/tick-hz-9_000_000"]
|
||||
wasm = [
|
||||
"dep:wasm-bindgen",
|
||||
"dep:js-sys",
|
||||
"dep:wasm-timer",
|
||||
"tick-hz-1_000_000",
|
||||
]
|
||||
Vendored
+47
@@ -0,0 +1,47 @@
|
||||
# embassy-time
|
||||
|
||||
Timekeeping, delays and timeouts.
|
||||
|
||||
Timekeeping is done with elapsed time since system boot. Time is represented in
|
||||
ticks, where the tick rate is defined either by the driver (in the case of a fixed-rate
|
||||
tick) or chosen by the user with a [tick rate](#tick-rate) feature. The chosen
|
||||
tick rate applies to everything in `embassy-time` and thus determines the maximum
|
||||
timing resolution of <code>(1 / tick_rate) seconds</code>.
|
||||
|
||||
Tick counts are 64 bits. The default tick rate of 1Mhz supports
|
||||
representing time spans of up to ~584558 years, which is big enough for all practical
|
||||
purposes and allows not having to worry about overflows.
|
||||
|
||||
## Global time driver
|
||||
|
||||
The `time` module is backed by a global "time driver" specified at build time.
|
||||
Only one driver can be active in a program.
|
||||
|
||||
All methods and structs transparently call into the active driver. This makes it
|
||||
possible for libraries to use `embassy_time` in a driver-agnostic way without
|
||||
requiring generic parameters.
|
||||
|
||||
For more details, check the [`embassy_time_driver`](https://crates.io/crates/embassy-time-driver) crate.
|
||||
|
||||
## Instants and Durations
|
||||
|
||||
[`Instant`] represents a given instant of time (relative to system boot), and [`Duration`]
|
||||
represents the duration of a span of time. They implement the math operations you'd expect,
|
||||
like addition and substraction.
|
||||
|
||||
## Delays and timeouts
|
||||
|
||||
[`Timer`] allows performing async delays. [`Ticker`] allows periodic delays without drifting over time.
|
||||
|
||||
An implementation of the `embedded-hal` delay traits is provided by [`Delay`], for compatibility
|
||||
with libraries from the ecosystem.
|
||||
|
||||
## Wall-clock time
|
||||
|
||||
The `time` module deals exclusively with a monotonically increasing tick count.
|
||||
Therefore it has no direct support for wall-clock time ("real life" datetimes
|
||||
like `2021-08-24 13:33:21`).
|
||||
|
||||
If persistence across reboots is not needed, support can be built on top of
|
||||
`embassy_time` by storing the offset between "seconds elapsed since boot"
|
||||
and "seconds since unix epoch".
|
||||
Vendored
+81
@@ -0,0 +1,81 @@
|
||||
use super::{Duration, Instant};
|
||||
use crate::Timer;
|
||||
|
||||
/// Blocks for at least `duration`.
|
||||
pub fn block_for(duration: Duration) {
|
||||
let expires_at = Instant::now() + duration;
|
||||
while Instant::now() < expires_at {}
|
||||
}
|
||||
|
||||
/// Type implementing async delays and blocking `embedded-hal` delays.
|
||||
///
|
||||
/// The delays are implemented in a "best-effort" way, meaning that the cpu will block for at least
|
||||
/// the amount provided, but accuracy can be affected by many factors, including interrupt usage.
|
||||
/// Make sure to use a suitable tick rate for your use case. The tick rate is defined by the currently
|
||||
/// active driver.
|
||||
#[derive(Clone)]
|
||||
pub struct Delay;
|
||||
|
||||
impl embedded_hal_1::delay::DelayNs for Delay {
|
||||
fn delay_ns(&mut self, ns: u32) {
|
||||
block_for(Duration::from_nanos(ns as u64))
|
||||
}
|
||||
|
||||
fn delay_us(&mut self, us: u32) {
|
||||
block_for(Duration::from_micros(us as u64))
|
||||
}
|
||||
|
||||
fn delay_ms(&mut self, ms: u32) {
|
||||
block_for(Duration::from_millis(ms as u64))
|
||||
}
|
||||
}
|
||||
|
||||
impl embedded_hal_async::delay::DelayNs for Delay {
|
||||
async fn delay_ns(&mut self, ns: u32) {
|
||||
Timer::after_nanos(ns as _).await
|
||||
}
|
||||
|
||||
async fn delay_us(&mut self, us: u32) {
|
||||
Timer::after_micros(us as _).await
|
||||
}
|
||||
|
||||
async fn delay_ms(&mut self, ms: u32) {
|
||||
Timer::after_millis(ms as _).await
|
||||
}
|
||||
}
|
||||
|
||||
impl embedded_hal_02::blocking::delay::DelayMs<u8> for Delay {
|
||||
fn delay_ms(&mut self, ms: u8) {
|
||||
block_for(Duration::from_millis(ms as u64))
|
||||
}
|
||||
}
|
||||
|
||||
impl embedded_hal_02::blocking::delay::DelayMs<u16> for Delay {
|
||||
fn delay_ms(&mut self, ms: u16) {
|
||||
block_for(Duration::from_millis(ms as u64))
|
||||
}
|
||||
}
|
||||
|
||||
impl embedded_hal_02::blocking::delay::DelayMs<u32> for Delay {
|
||||
fn delay_ms(&mut self, ms: u32) {
|
||||
block_for(Duration::from_millis(ms as u64))
|
||||
}
|
||||
}
|
||||
|
||||
impl embedded_hal_02::blocking::delay::DelayUs<u8> for Delay {
|
||||
fn delay_us(&mut self, us: u8) {
|
||||
block_for(Duration::from_micros(us as u64))
|
||||
}
|
||||
}
|
||||
|
||||
impl embedded_hal_02::blocking::delay::DelayUs<u16> for Delay {
|
||||
fn delay_us(&mut self, us: u16) {
|
||||
block_for(Duration::from_micros(us as u64))
|
||||
}
|
||||
}
|
||||
|
||||
impl embedded_hal_02::blocking::delay::DelayUs<u32> for Delay {
|
||||
fn delay_us(&mut self, us: u32) {
|
||||
block_for(Duration::from_micros(us as u64))
|
||||
}
|
||||
}
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
use core::cell::RefCell;
|
||||
|
||||
use critical_section::Mutex as CsMutex;
|
||||
use embassy_time_driver::{AlarmHandle, Driver};
|
||||
|
||||
use crate::{Duration, Instant};
|
||||
|
||||
/// A mock driver that can be manually advanced.
|
||||
/// This is useful for testing code that works with [`Instant`] and [`Duration`].
|
||||
///
|
||||
/// This driver can also be used to test runtime functionality, such as
|
||||
/// timers, delays, etc.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// fn has_a_second_passed(reference: Instant) -> bool {
|
||||
/// Instant::now().duration_since(reference) >= Duration::from_secs(1)
|
||||
/// }
|
||||
///
|
||||
/// fn test_second_passed() {
|
||||
/// let driver = embassy_time::MockDriver::get();
|
||||
/// let reference = Instant::now();
|
||||
/// assert_eq!(false, has_a_second_passed(reference));
|
||||
/// driver.advance(Duration::from_secs(1));
|
||||
/// assert_eq!(true, has_a_second_passed(reference));
|
||||
/// }
|
||||
/// ```
|
||||
pub struct MockDriver(CsMutex<RefCell<InnerMockDriver>>);
|
||||
|
||||
embassy_time_driver::time_driver_impl!(static DRIVER: MockDriver = MockDriver::new());
|
||||
|
||||
impl MockDriver {
|
||||
/// Creates a new mock driver.
|
||||
pub const fn new() -> Self {
|
||||
Self(CsMutex::new(RefCell::new(InnerMockDriver::new())))
|
||||
}
|
||||
|
||||
/// Gets a reference to the global mock driver.
|
||||
pub fn get() -> &'static MockDriver {
|
||||
&DRIVER
|
||||
}
|
||||
|
||||
/// Resets the internal state of the mock driver
|
||||
/// This will clear and deallocate all alarms, and reset the current time to 0.
|
||||
pub fn reset(&self) {
|
||||
critical_section::with(|cs| {
|
||||
self.0.borrow(cs).replace(InnerMockDriver::new());
|
||||
});
|
||||
}
|
||||
|
||||
/// Advances the time by the specified [`Duration`].
|
||||
/// Calling any alarm callbacks that are due.
|
||||
pub fn advance(&self, duration: Duration) {
|
||||
let notify = {
|
||||
critical_section::with(|cs| {
|
||||
let mut inner = self.0.borrow_ref_mut(cs);
|
||||
|
||||
inner.now += duration;
|
||||
|
||||
let now = inner.now.as_ticks();
|
||||
|
||||
inner
|
||||
.alarm
|
||||
.as_mut()
|
||||
.filter(|alarm| alarm.timestamp <= now)
|
||||
.map(|alarm| {
|
||||
alarm.timestamp = u64::MAX;
|
||||
|
||||
(alarm.callback, alarm.ctx)
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
if let Some((callback, ctx)) = notify {
|
||||
(callback)(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Driver for MockDriver {
|
||||
fn now(&self) -> u64 {
|
||||
critical_section::with(|cs| self.0.borrow_ref(cs).now).as_ticks()
|
||||
}
|
||||
|
||||
unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
|
||||
critical_section::with(|cs| {
|
||||
let mut inner = self.0.borrow_ref_mut(cs);
|
||||
|
||||
if inner.alarm.is_some() {
|
||||
None
|
||||
} else {
|
||||
inner.alarm.replace(AlarmState::new());
|
||||
|
||||
Some(AlarmHandle::new(0))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn set_alarm_callback(&self, _alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
|
||||
critical_section::with(|cs| {
|
||||
let mut inner = self.0.borrow_ref_mut(cs);
|
||||
|
||||
let Some(alarm) = inner.alarm.as_mut() else {
|
||||
panic!("Alarm not allocated");
|
||||
};
|
||||
|
||||
alarm.callback = callback;
|
||||
alarm.ctx = ctx;
|
||||
});
|
||||
}
|
||||
|
||||
fn set_alarm(&self, _alarm: AlarmHandle, timestamp: u64) -> bool {
|
||||
critical_section::with(|cs| {
|
||||
let mut inner = self.0.borrow_ref_mut(cs);
|
||||
|
||||
if timestamp <= inner.now.as_ticks() {
|
||||
false
|
||||
} else {
|
||||
let Some(alarm) = inner.alarm.as_mut() else {
|
||||
panic!("Alarm not allocated");
|
||||
};
|
||||
|
||||
alarm.timestamp = timestamp;
|
||||
true
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct InnerMockDriver {
|
||||
now: Instant,
|
||||
alarm: Option<AlarmState>,
|
||||
}
|
||||
|
||||
impl InnerMockDriver {
|
||||
const fn new() -> Self {
|
||||
Self {
|
||||
now: Instant::from_ticks(0),
|
||||
alarm: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AlarmState {
|
||||
timestamp: u64,
|
||||
callback: fn(*mut ()),
|
||||
ctx: *mut (),
|
||||
}
|
||||
|
||||
impl AlarmState {
|
||||
const fn new() -> Self {
|
||||
Self {
|
||||
timestamp: u64::MAX,
|
||||
callback: Self::noop,
|
||||
ctx: core::ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
fn noop(_ctx: *mut ()) {}
|
||||
}
|
||||
|
||||
unsafe impl Send for AlarmState {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serial_test::serial;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn setup() {
|
||||
DRIVER.reset();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_advance() {
|
||||
setup();
|
||||
|
||||
let driver = MockDriver::get();
|
||||
let reference = driver.now();
|
||||
driver.advance(Duration::from_secs(1));
|
||||
assert_eq!(Duration::from_secs(1).as_ticks(), driver.now() - reference);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_set_alarm_not_in_future() {
|
||||
setup();
|
||||
|
||||
let driver = MockDriver::get();
|
||||
let alarm = unsafe { AlarmHandle::new(0) };
|
||||
assert_eq!(false, driver.set_alarm(alarm, driver.now()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_alarm() {
|
||||
setup();
|
||||
|
||||
let driver = MockDriver::get();
|
||||
let alarm = unsafe { driver.allocate_alarm() }.expect("No alarms available");
|
||||
static mut CALLBACK_CALLED: bool = false;
|
||||
let ctx = &mut () as *mut ();
|
||||
driver.set_alarm_callback(alarm, |_| unsafe { CALLBACK_CALLED = true }, ctx);
|
||||
driver.set_alarm(alarm, driver.now() + 1);
|
||||
assert_eq!(false, unsafe { CALLBACK_CALLED });
|
||||
driver.advance(Duration::from_secs(1));
|
||||
assert_eq!(true, unsafe { CALLBACK_CALLED });
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_allocate_alarm() {
|
||||
setup();
|
||||
|
||||
let driver = MockDriver::get();
|
||||
assert!(unsafe { driver.allocate_alarm() }.is_some());
|
||||
assert!(unsafe { driver.allocate_alarm() }.is_none());
|
||||
}
|
||||
}
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
use core::sync::atomic::{AtomicU8, Ordering};
|
||||
use std::cell::{RefCell, UnsafeCell};
|
||||
use std::mem::MaybeUninit;
|
||||
use std::sync::{Condvar, Mutex, Once};
|
||||
use std::time::{Duration as StdDuration, Instant as StdInstant};
|
||||
use std::{mem, ptr, thread};
|
||||
|
||||
use critical_section::Mutex as CsMutex;
|
||||
use embassy_time_driver::{AlarmHandle, Driver};
|
||||
|
||||
const ALARM_COUNT: usize = 4;
|
||||
|
||||
struct AlarmState {
|
||||
timestamp: u64,
|
||||
|
||||
// This is really a Option<(fn(*mut ()), *mut ())>
|
||||
// but fn pointers aren't allowed in const yet
|
||||
callback: *const (),
|
||||
ctx: *mut (),
|
||||
}
|
||||
|
||||
unsafe impl Send for AlarmState {}
|
||||
|
||||
impl AlarmState {
|
||||
const fn new() -> Self {
|
||||
Self {
|
||||
timestamp: u64::MAX,
|
||||
callback: ptr::null(),
|
||||
ctx: ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TimeDriver {
|
||||
alarm_count: AtomicU8,
|
||||
|
||||
once: Once,
|
||||
// The STD Driver implementation requires the alarms' mutex to be reentrant, which the STD Mutex isn't
|
||||
// Fortunately, mutexes based on the `critical-section` crate are reentrant, because the critical sections
|
||||
// themselves are reentrant
|
||||
alarms: UninitCell<CsMutex<RefCell<[AlarmState; ALARM_COUNT]>>>,
|
||||
zero_instant: UninitCell<StdInstant>,
|
||||
signaler: UninitCell<Signaler>,
|
||||
}
|
||||
|
||||
const ALARM_NEW: AlarmState = AlarmState::new();
|
||||
embassy_time_driver::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver {
|
||||
alarm_count: AtomicU8::new(0),
|
||||
|
||||
once: Once::new(),
|
||||
alarms: UninitCell::uninit(),
|
||||
zero_instant: UninitCell::uninit(),
|
||||
signaler: UninitCell::uninit(),
|
||||
});
|
||||
|
||||
impl TimeDriver {
|
||||
fn init(&self) {
|
||||
self.once.call_once(|| unsafe {
|
||||
self.alarms.write(CsMutex::new(RefCell::new([ALARM_NEW; ALARM_COUNT])));
|
||||
self.zero_instant.write(StdInstant::now());
|
||||
self.signaler.write(Signaler::new());
|
||||
|
||||
thread::spawn(Self::alarm_thread);
|
||||
});
|
||||
}
|
||||
|
||||
fn alarm_thread() {
|
||||
let zero = unsafe { DRIVER.zero_instant.read() };
|
||||
loop {
|
||||
let now = DRIVER.now();
|
||||
|
||||
let next_alarm = critical_section::with(|cs| {
|
||||
let alarms = unsafe { DRIVER.alarms.as_ref() }.borrow(cs);
|
||||
loop {
|
||||
let pending = alarms
|
||||
.borrow_mut()
|
||||
.iter_mut()
|
||||
.find(|alarm| alarm.timestamp <= now)
|
||||
.map(|alarm| {
|
||||
alarm.timestamp = u64::MAX;
|
||||
|
||||
(alarm.callback, alarm.ctx)
|
||||
});
|
||||
|
||||
if let Some((callback, ctx)) = pending {
|
||||
// safety:
|
||||
// - we can ignore the possiblity of `f` being unset (null) because of the safety contract of `allocate_alarm`.
|
||||
// - other than that we only store valid function pointers into alarm.callback
|
||||
let f: fn(*mut ()) = unsafe { mem::transmute(callback) };
|
||||
f(ctx);
|
||||
} else {
|
||||
// No alarm due
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
alarms
|
||||
.borrow()
|
||||
.iter()
|
||||
.map(|alarm| alarm.timestamp)
|
||||
.min()
|
||||
.unwrap_or(u64::MAX)
|
||||
});
|
||||
|
||||
// Ensure we don't overflow
|
||||
let until = zero
|
||||
.checked_add(StdDuration::from_micros(next_alarm))
|
||||
.unwrap_or_else(|| StdInstant::now() + StdDuration::from_secs(1));
|
||||
|
||||
unsafe { DRIVER.signaler.as_ref() }.wait_until(until);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Driver for TimeDriver {
|
||||
fn now(&self) -> u64 {
|
||||
self.init();
|
||||
|
||||
let zero = unsafe { self.zero_instant.read() };
|
||||
StdInstant::now().duration_since(zero).as_micros() as u64
|
||||
}
|
||||
|
||||
unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
|
||||
let id = self.alarm_count.fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| {
|
||||
if x < ALARM_COUNT as u8 {
|
||||
Some(x + 1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
match id {
|
||||
Ok(id) => Some(AlarmHandle::new(id)),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
|
||||
self.init();
|
||||
critical_section::with(|cs| {
|
||||
let mut alarms = unsafe { self.alarms.as_ref() }.borrow_ref_mut(cs);
|
||||
let alarm = &mut alarms[alarm.id() as usize];
|
||||
alarm.callback = callback as *const ();
|
||||
alarm.ctx = ctx;
|
||||
});
|
||||
}
|
||||
|
||||
fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool {
|
||||
self.init();
|
||||
critical_section::with(|cs| {
|
||||
let mut alarms = unsafe { self.alarms.as_ref() }.borrow_ref_mut(cs);
|
||||
let alarm = &mut alarms[alarm.id() as usize];
|
||||
alarm.timestamp = timestamp;
|
||||
unsafe { self.signaler.as_ref() }.signal();
|
||||
});
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
struct Signaler {
|
||||
mutex: Mutex<bool>,
|
||||
condvar: Condvar,
|
||||
}
|
||||
|
||||
impl Signaler {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
mutex: Mutex::new(false),
|
||||
condvar: Condvar::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn wait_until(&self, until: StdInstant) {
|
||||
let mut signaled = self.mutex.lock().unwrap();
|
||||
while !*signaled {
|
||||
let now = StdInstant::now();
|
||||
|
||||
if now >= until {
|
||||
break;
|
||||
}
|
||||
|
||||
let dur = until - now;
|
||||
let (signaled2, timeout) = self.condvar.wait_timeout(signaled, dur).unwrap();
|
||||
signaled = signaled2;
|
||||
if timeout.timed_out() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
*signaled = false;
|
||||
}
|
||||
|
||||
fn signal(&self) {
|
||||
let mut signaled = self.mutex.lock().unwrap();
|
||||
*signaled = true;
|
||||
self.condvar.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct UninitCell<T>(MaybeUninit<UnsafeCell<T>>);
|
||||
unsafe impl<T> Send for UninitCell<T> {}
|
||||
unsafe impl<T> Sync for UninitCell<T> {}
|
||||
|
||||
impl<T> UninitCell<T> {
|
||||
pub const fn uninit() -> Self {
|
||||
Self(MaybeUninit::uninit())
|
||||
}
|
||||
|
||||
pub unsafe fn as_ptr(&self) -> *const T {
|
||||
(*self.0.as_ptr()).get()
|
||||
}
|
||||
|
||||
pub unsafe fn as_mut_ptr(&self) -> *mut T {
|
||||
(*self.0.as_ptr()).get()
|
||||
}
|
||||
|
||||
pub unsafe fn as_ref(&self) -> &T {
|
||||
&*self.as_ptr()
|
||||
}
|
||||
|
||||
pub unsafe fn write(&self, val: T) {
|
||||
ptr::write(self.as_mut_ptr(), val)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> UninitCell<T> {
|
||||
pub unsafe fn read(&self) -> T {
|
||||
ptr::read(self.as_mut_ptr())
|
||||
}
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
use core::sync::atomic::{AtomicU8, Ordering};
|
||||
use std::cell::UnsafeCell;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ptr;
|
||||
use std::sync::{Mutex, Once};
|
||||
|
||||
use embassy_time_driver::{AlarmHandle, Driver};
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_timer::Instant as StdInstant;
|
||||
|
||||
const ALARM_COUNT: usize = 4;
|
||||
|
||||
struct AlarmState {
|
||||
token: Option<f64>,
|
||||
closure: Option<Closure<dyn FnMut() + 'static>>,
|
||||
}
|
||||
|
||||
unsafe impl Send for AlarmState {}
|
||||
|
||||
impl AlarmState {
|
||||
const fn new() -> Self {
|
||||
Self {
|
||||
token: None,
|
||||
closure: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
fn setTimeout(closure: &Closure<dyn FnMut()>, millis: u32) -> f64;
|
||||
fn clearTimeout(token: f64);
|
||||
}
|
||||
|
||||
struct TimeDriver {
|
||||
alarm_count: AtomicU8,
|
||||
|
||||
once: Once,
|
||||
alarms: UninitCell<Mutex<[AlarmState; ALARM_COUNT]>>,
|
||||
zero_instant: UninitCell<StdInstant>,
|
||||
}
|
||||
|
||||
const ALARM_NEW: AlarmState = AlarmState::new();
|
||||
embassy_time_driver::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver {
|
||||
alarm_count: AtomicU8::new(0),
|
||||
once: Once::new(),
|
||||
alarms: UninitCell::uninit(),
|
||||
zero_instant: UninitCell::uninit(),
|
||||
});
|
||||
|
||||
impl TimeDriver {
|
||||
fn init(&self) {
|
||||
self.once.call_once(|| unsafe {
|
||||
self.alarms.write(Mutex::new([ALARM_NEW; ALARM_COUNT]));
|
||||
self.zero_instant.write(StdInstant::now());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Driver for TimeDriver {
|
||||
fn now(&self) -> u64 {
|
||||
self.init();
|
||||
|
||||
let zero = unsafe { self.zero_instant.read() };
|
||||
StdInstant::now().duration_since(zero).as_micros() as u64
|
||||
}
|
||||
|
||||
unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
|
||||
let id = self.alarm_count.fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| {
|
||||
if x < ALARM_COUNT as u8 {
|
||||
Some(x + 1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
match id {
|
||||
Ok(id) => Some(AlarmHandle::new(id)),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
|
||||
self.init();
|
||||
let mut alarms = unsafe { self.alarms.as_ref() }.lock().unwrap();
|
||||
let alarm = &mut alarms[alarm.id() as usize];
|
||||
alarm.closure.replace(Closure::new(move || {
|
||||
callback(ctx);
|
||||
}));
|
||||
}
|
||||
|
||||
fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool {
|
||||
self.init();
|
||||
let mut alarms = unsafe { self.alarms.as_ref() }.lock().unwrap();
|
||||
let alarm = &mut alarms[alarm.id() as usize];
|
||||
if let Some(token) = alarm.token {
|
||||
clearTimeout(token);
|
||||
}
|
||||
|
||||
let now = self.now();
|
||||
if timestamp <= now {
|
||||
false
|
||||
} else {
|
||||
let timeout = (timestamp - now) as u32;
|
||||
alarm.token = Some(setTimeout(alarm.closure.as_ref().unwrap(), timeout / 1000));
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct UninitCell<T>(MaybeUninit<UnsafeCell<T>>);
|
||||
unsafe impl<T> Send for UninitCell<T> {}
|
||||
unsafe impl<T> Sync for UninitCell<T> {}
|
||||
|
||||
impl<T> UninitCell<T> {
|
||||
pub const fn uninit() -> Self {
|
||||
Self(MaybeUninit::uninit())
|
||||
}
|
||||
unsafe fn as_ptr(&self) -> *const T {
|
||||
(*self.0.as_ptr()).get()
|
||||
}
|
||||
|
||||
pub unsafe fn as_mut_ptr(&self) -> *mut T {
|
||||
(*self.0.as_ptr()).get()
|
||||
}
|
||||
|
||||
pub unsafe fn as_ref(&self) -> &T {
|
||||
&*self.as_ptr()
|
||||
}
|
||||
|
||||
pub unsafe fn write(&self, val: T) {
|
||||
ptr::write(self.as_mut_ptr(), val)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> UninitCell<T> {
|
||||
pub unsafe fn read(&self) -> T {
|
||||
ptr::read(self.as_mut_ptr())
|
||||
}
|
||||
}
|
||||
Vendored
+219
@@ -0,0 +1,219 @@
|
||||
use core::fmt;
|
||||
use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
|
||||
|
||||
use super::{GCD_1K, GCD_1M, TICK_HZ};
|
||||
use crate::GCD_1G;
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
/// Represents the difference between two [Instant](struct.Instant.html)s
|
||||
pub struct Duration {
|
||||
pub(crate) ticks: u64,
|
||||
}
|
||||
|
||||
impl Duration {
|
||||
/// The smallest value that can be represented by the `Duration` type.
|
||||
pub const MIN: Duration = Duration { ticks: u64::MIN };
|
||||
/// The largest value that can be represented by the `Duration` type.
|
||||
pub const MAX: Duration = Duration { ticks: u64::MAX };
|
||||
|
||||
/// Tick count of the `Duration`.
|
||||
pub const fn as_ticks(&self) -> u64 {
|
||||
self.ticks
|
||||
}
|
||||
|
||||
/// Convert the `Duration` to seconds, rounding down.
|
||||
pub const fn as_secs(&self) -> u64 {
|
||||
self.ticks / TICK_HZ
|
||||
}
|
||||
|
||||
/// Convert the `Duration` to milliseconds, rounding down.
|
||||
pub const fn as_millis(&self) -> u64 {
|
||||
self.ticks * (1000 / GCD_1K) / (TICK_HZ / GCD_1K)
|
||||
}
|
||||
|
||||
/// Convert the `Duration` to microseconds, rounding down.
|
||||
pub const fn as_micros(&self) -> u64 {
|
||||
self.ticks * (1_000_000 / GCD_1M) / (TICK_HZ / GCD_1M)
|
||||
}
|
||||
|
||||
/// Creates a duration from the specified number of clock ticks
|
||||
pub const fn from_ticks(ticks: u64) -> Duration {
|
||||
Duration { ticks }
|
||||
}
|
||||
|
||||
/// Creates a duration from the specified number of seconds, rounding up.
|
||||
pub const fn from_secs(secs: u64) -> Duration {
|
||||
Duration { ticks: secs * TICK_HZ }
|
||||
}
|
||||
|
||||
/// Creates a duration from the specified number of milliseconds, rounding up.
|
||||
pub const fn from_millis(millis: u64) -> Duration {
|
||||
Duration {
|
||||
ticks: div_ceil(millis * (TICK_HZ / GCD_1K), 1000 / GCD_1K),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a duration from the specified number of microseconds, rounding up.
|
||||
/// NOTE: Delays this small may be inaccurate.
|
||||
pub const fn from_micros(micros: u64) -> Duration {
|
||||
Duration {
|
||||
ticks: div_ceil(micros * (TICK_HZ / GCD_1M), 1_000_000 / GCD_1M),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a duration from the specified number of nanoseconds, rounding up.
|
||||
/// NOTE: Delays this small may be inaccurate.
|
||||
pub const fn from_nanos(micros: u64) -> Duration {
|
||||
Duration {
|
||||
ticks: div_ceil(micros * (TICK_HZ / GCD_1G), 1_000_000_000 / GCD_1G),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a duration from the specified number of seconds, rounding down.
|
||||
pub const fn from_secs_floor(secs: u64) -> Duration {
|
||||
Duration { ticks: secs * TICK_HZ }
|
||||
}
|
||||
|
||||
/// Creates a duration from the specified number of milliseconds, rounding down.
|
||||
pub const fn from_millis_floor(millis: u64) -> Duration {
|
||||
Duration {
|
||||
ticks: millis * (TICK_HZ / GCD_1K) / (1000 / GCD_1K),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a duration from the specified number of microseconds, rounding down.
|
||||
/// NOTE: Delays this small may be inaccurate.
|
||||
pub const fn from_micros_floor(micros: u64) -> Duration {
|
||||
Duration {
|
||||
ticks: micros * (TICK_HZ / GCD_1M) / (1_000_000 / GCD_1M),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a duration corresponding to the specified Hz.
|
||||
/// NOTE: Giving this function a hz >= the TICK_HZ of your platform will clamp the Duration to 1
|
||||
/// tick. Doing so will not deadlock, but will certainly not produce the desired output.
|
||||
pub const fn from_hz(hz: u64) -> Duration {
|
||||
let ticks = {
|
||||
if hz >= TICK_HZ {
|
||||
1
|
||||
} else {
|
||||
(TICK_HZ + hz / 2) / hz
|
||||
}
|
||||
};
|
||||
Duration { ticks }
|
||||
}
|
||||
|
||||
/// Adds one Duration to another, returning a new Duration or None in the event of an overflow.
|
||||
pub fn checked_add(self, rhs: Duration) -> Option<Duration> {
|
||||
self.ticks.checked_add(rhs.ticks).map(|ticks| Duration { ticks })
|
||||
}
|
||||
|
||||
/// Subtracts one Duration to another, returning a new Duration or None in the event of an overflow.
|
||||
pub fn checked_sub(self, rhs: Duration) -> Option<Duration> {
|
||||
self.ticks.checked_sub(rhs.ticks).map(|ticks| Duration { ticks })
|
||||
}
|
||||
|
||||
/// Multiplies one Duration by a scalar u32, returning a new Duration or None in the event of an overflow.
|
||||
pub fn checked_mul(self, rhs: u32) -> Option<Duration> {
|
||||
self.ticks.checked_mul(rhs as _).map(|ticks| Duration { ticks })
|
||||
}
|
||||
|
||||
/// Divides one Duration a scalar u32, returning a new Duration or None in the event of an overflow.
|
||||
pub fn checked_div(self, rhs: u32) -> Option<Duration> {
|
||||
self.ticks.checked_div(rhs as _).map(|ticks| Duration { ticks })
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Duration {
|
||||
type Output = Duration;
|
||||
|
||||
fn add(self, rhs: Duration) -> Duration {
|
||||
self.checked_add(rhs).expect("overflow when adding durations")
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for Duration {
|
||||
fn add_assign(&mut self, rhs: Duration) {
|
||||
*self = *self + rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Duration {
|
||||
type Output = Duration;
|
||||
|
||||
fn sub(self, rhs: Duration) -> Duration {
|
||||
self.checked_sub(rhs).expect("overflow when subtracting durations")
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign for Duration {
|
||||
fn sub_assign(&mut self, rhs: Duration) {
|
||||
*self = *self - rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<u32> for Duration {
|
||||
type Output = Duration;
|
||||
|
||||
fn mul(self, rhs: u32) -> Duration {
|
||||
self.checked_mul(rhs)
|
||||
.expect("overflow when multiplying duration by scalar")
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Duration> for u32 {
|
||||
type Output = Duration;
|
||||
|
||||
fn mul(self, rhs: Duration) -> Duration {
|
||||
rhs * self
|
||||
}
|
||||
}
|
||||
|
||||
impl MulAssign<u32> for Duration {
|
||||
fn mul_assign(&mut self, rhs: u32) {
|
||||
*self = *self * rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<u32> for Duration {
|
||||
type Output = Duration;
|
||||
|
||||
fn div(self, rhs: u32) -> Duration {
|
||||
self.checked_div(rhs)
|
||||
.expect("divide by zero error when dividing duration by scalar")
|
||||
}
|
||||
}
|
||||
|
||||
impl DivAssign<u32> for Duration {
|
||||
fn div_assign(&mut self, rhs: u32) {
|
||||
*self = *self / rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for Duration {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{} ticks", self.ticks)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
const fn div_ceil(num: u64, den: u64) -> u64 {
|
||||
(num + den - 1) / den
|
||||
}
|
||||
|
||||
impl TryFrom<core::time::Duration> for Duration {
|
||||
type Error = <u64 as TryFrom<u128>>::Error;
|
||||
|
||||
/// Converts using [`Duration::from_micros`]. Fails if value can not be represented as u64.
|
||||
fn try_from(value: core::time::Duration) -> Result<Self, Self::Error> {
|
||||
Ok(Self::from_micros(value.as_micros().try_into()?))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Duration> for core::time::Duration {
|
||||
/// Converts using [`Duration::as_micros`].
|
||||
fn from(value: Duration) -> Self {
|
||||
core::time::Duration::from_micros(value.as_micros())
|
||||
}
|
||||
}
|
||||
Vendored
+270
@@ -0,0 +1,270 @@
|
||||
#![macro_use]
|
||||
#![allow(unused)]
|
||||
|
||||
use core::fmt::{Debug, Display, LowerHex};
|
||||
|
||||
#[cfg(all(feature = "defmt", feature = "log"))]
|
||||
compile_error!("You may not enable both `defmt` and `log` features.");
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! assert {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::assert!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::assert!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! assert_eq {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::assert_eq!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::assert_eq!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! assert_ne {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::assert_ne!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::assert_ne!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! debug_assert {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::debug_assert!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::debug_assert!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! debug_assert_eq {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::debug_assert_eq!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::debug_assert_eq!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! debug_assert_ne {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::debug_assert_ne!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::debug_assert_ne!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! todo {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::todo!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::todo!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! unreachable {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::unreachable!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::unreachable!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! panic {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::panic!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::panic!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! trace {
|
||||
($s:literal $(, $x:expr)* $(,)?) => {
|
||||
{
|
||||
#[cfg(feature = "log")]
|
||||
::log::trace!($s $(, $x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::trace!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! debug {
|
||||
($s:literal $(, $x:expr)* $(,)?) => {
|
||||
{
|
||||
#[cfg(feature = "log")]
|
||||
::log::debug!($s $(, $x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::debug!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! info {
|
||||
($s:literal $(, $x:expr)* $(,)?) => {
|
||||
{
|
||||
#[cfg(feature = "log")]
|
||||
::log::info!($s $(, $x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::info!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! warn {
|
||||
($s:literal $(, $x:expr)* $(,)?) => {
|
||||
{
|
||||
#[cfg(feature = "log")]
|
||||
::log::warn!($s $(, $x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::warn!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! error {
|
||||
($s:literal $(, $x:expr)* $(,)?) => {
|
||||
{
|
||||
#[cfg(feature = "log")]
|
||||
::log::error!($s $(, $x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::error!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! unwrap {
|
||||
($($x:tt)*) => {
|
||||
::defmt::unwrap!($($x)*)
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! unwrap {
|
||||
($arg:expr) => {
|
||||
match $crate::fmt::Try::into_result($arg) {
|
||||
::core::result::Result::Ok(t) => t,
|
||||
::core::result::Result::Err(e) => {
|
||||
::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
|
||||
}
|
||||
}
|
||||
};
|
||||
($arg:expr, $($msg:expr),+ $(,)? ) => {
|
||||
match $crate::fmt::Try::into_result($arg) {
|
||||
::core::result::Result::Ok(t) => t,
|
||||
::core::result::Result::Err(e) => {
|
||||
::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct NoneError;
|
||||
|
||||
pub trait Try {
|
||||
type Ok;
|
||||
type Error;
|
||||
fn into_result(self) -> Result<Self::Ok, Self::Error>;
|
||||
}
|
||||
|
||||
impl<T> Try for Option<T> {
|
||||
type Ok = T;
|
||||
type Error = NoneError;
|
||||
|
||||
#[inline]
|
||||
fn into_result(self) -> Result<T, NoneError> {
|
||||
self.ok_or(NoneError)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> Try for Result<T, E> {
|
||||
type Ok = T;
|
||||
type Error = E;
|
||||
|
||||
#[inline]
|
||||
fn into_result(self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Bytes<'a>(pub &'a [u8]);
|
||||
|
||||
impl<'a> Debug for Bytes<'a> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{:#02x?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Bytes<'a> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{:#02x?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> LowerHex for Bytes<'a> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{:#02x?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl<'a> defmt::Format for Bytes<'a> {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
defmt::write!(fmt, "{:02x}", self.0)
|
||||
}
|
||||
}
|
||||
Vendored
+161
@@ -0,0 +1,161 @@
|
||||
use core::fmt;
|
||||
use core::ops::{Add, AddAssign, Sub, SubAssign};
|
||||
|
||||
use super::{Duration, GCD_1K, GCD_1M, TICK_HZ};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
/// An Instant in time, based on the MCU's clock ticks since startup.
|
||||
pub struct Instant {
|
||||
ticks: u64,
|
||||
}
|
||||
|
||||
impl Instant {
|
||||
/// The smallest (earliest) value that can be represented by the `Instant` type.
|
||||
pub const MIN: Instant = Instant { ticks: u64::MIN };
|
||||
/// The largest (latest) value that can be represented by the `Instant` type.
|
||||
pub const MAX: Instant = Instant { ticks: u64::MAX };
|
||||
|
||||
/// Returns an Instant representing the current time.
|
||||
pub fn now() -> Instant {
|
||||
Instant {
|
||||
ticks: embassy_time_driver::now(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an Instant from a tick count since system boot.
|
||||
pub const fn from_ticks(ticks: u64) -> Self {
|
||||
Self { ticks }
|
||||
}
|
||||
|
||||
/// Create an Instant from a microsecond count since system boot.
|
||||
pub const fn from_micros(micros: u64) -> Self {
|
||||
Self {
|
||||
ticks: micros * (TICK_HZ / GCD_1M) / (1_000_000 / GCD_1M),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an Instant from a millisecond count since system boot.
|
||||
pub const fn from_millis(millis: u64) -> Self {
|
||||
Self {
|
||||
ticks: millis * (TICK_HZ / GCD_1K) / (1000 / GCD_1K),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an Instant from a second count since system boot.
|
||||
pub const fn from_secs(seconds: u64) -> Self {
|
||||
Self {
|
||||
ticks: seconds * TICK_HZ,
|
||||
}
|
||||
}
|
||||
|
||||
/// Tick count since system boot.
|
||||
pub const fn as_ticks(&self) -> u64 {
|
||||
self.ticks
|
||||
}
|
||||
|
||||
/// Seconds since system boot.
|
||||
pub const fn as_secs(&self) -> u64 {
|
||||
self.ticks / TICK_HZ
|
||||
}
|
||||
|
||||
/// Milliseconds since system boot.
|
||||
pub const fn as_millis(&self) -> u64 {
|
||||
self.ticks * (1000 / GCD_1K) / (TICK_HZ / GCD_1K)
|
||||
}
|
||||
|
||||
/// Microseconds since system boot.
|
||||
pub const fn as_micros(&self) -> u64 {
|
||||
self.ticks * (1_000_000 / GCD_1M) / (TICK_HZ / GCD_1M)
|
||||
}
|
||||
|
||||
/// Duration between this Instant and another Instant
|
||||
/// Panics on over/underflow.
|
||||
pub fn duration_since(&self, earlier: Instant) -> Duration {
|
||||
Duration {
|
||||
ticks: unwrap!(self.ticks.checked_sub(earlier.ticks)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Duration between this Instant and another Instant
|
||||
pub fn checked_duration_since(&self, earlier: Instant) -> Option<Duration> {
|
||||
if self.ticks < earlier.ticks {
|
||||
None
|
||||
} else {
|
||||
Some(Duration {
|
||||
ticks: self.ticks - earlier.ticks,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the duration since the "earlier" Instant.
|
||||
/// If the "earlier" instant is in the future, the duration is set to zero.
|
||||
pub fn saturating_duration_since(&self, earlier: Instant) -> Duration {
|
||||
Duration {
|
||||
ticks: if self.ticks < earlier.ticks {
|
||||
0
|
||||
} else {
|
||||
self.ticks - earlier.ticks
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Duration elapsed since this Instant.
|
||||
pub fn elapsed(&self) -> Duration {
|
||||
Instant::now() - *self
|
||||
}
|
||||
|
||||
/// Adds one Duration to self, returning a new `Instant` or None in the event of an overflow.
|
||||
pub fn checked_add(&self, duration: Duration) -> Option<Instant> {
|
||||
self.ticks.checked_add(duration.ticks).map(|ticks| Instant { ticks })
|
||||
}
|
||||
|
||||
/// Subtracts one Duration to self, returning a new `Instant` or None in the event of an overflow.
|
||||
pub fn checked_sub(&self, duration: Duration) -> Option<Instant> {
|
||||
self.ticks.checked_sub(duration.ticks).map(|ticks| Instant { ticks })
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Duration> for Instant {
|
||||
type Output = Instant;
|
||||
|
||||
fn add(self, other: Duration) -> Instant {
|
||||
self.checked_add(other)
|
||||
.expect("overflow when adding duration to instant")
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<Duration> for Instant {
|
||||
fn add_assign(&mut self, other: Duration) {
|
||||
*self = *self + other;
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Duration> for Instant {
|
||||
type Output = Instant;
|
||||
|
||||
fn sub(self, other: Duration) -> Instant {
|
||||
self.checked_sub(other)
|
||||
.expect("overflow when subtracting duration from instant")
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign<Duration> for Instant {
|
||||
fn sub_assign(&mut self, other: Duration) {
|
||||
*self = *self - other;
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Instant> for Instant {
|
||||
type Output = Duration;
|
||||
|
||||
fn sub(self, other: Instant) -> Duration {
|
||||
self.duration_since(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for Instant {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{} ticks", self.ticks)
|
||||
}
|
||||
}
|
||||
Vendored
+65
@@ -0,0 +1,65 @@
|
||||
#![cfg_attr(not(any(feature = "std", feature = "wasm", test)), no_std)]
|
||||
#![allow(async_fn_in_trait)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![allow(clippy::new_without_default)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
//! ## Feature flags
|
||||
#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
|
||||
|
||||
// This mod MUST go first, so that the others see its macros.
|
||||
pub(crate) mod fmt;
|
||||
|
||||
mod delay;
|
||||
mod duration;
|
||||
mod instant;
|
||||
mod timer;
|
||||
|
||||
#[cfg(feature = "mock-driver")]
|
||||
mod driver_mock;
|
||||
|
||||
#[cfg(feature = "mock-driver")]
|
||||
pub use driver_mock::MockDriver;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
mod driver_std;
|
||||
#[cfg(feature = "wasm")]
|
||||
mod driver_wasm;
|
||||
#[cfg(feature = "generic-queue")]
|
||||
mod queue_generic;
|
||||
|
||||
pub use delay::{block_for, Delay};
|
||||
pub use duration::Duration;
|
||||
pub use embassy_time_driver::TICK_HZ;
|
||||
pub use instant::Instant;
|
||||
pub use timer::{with_deadline, with_timeout, Ticker, TimeoutError, Timer, WithTimeout};
|
||||
|
||||
const fn gcd(a: u64, b: u64) -> u64 {
|
||||
if b == 0 {
|
||||
a
|
||||
} else {
|
||||
gcd(b, a % b)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const GCD_1K: u64 = gcd(TICK_HZ, 1_000);
|
||||
pub(crate) const GCD_1M: u64 = gcd(TICK_HZ, 1_000_000);
|
||||
pub(crate) const GCD_1G: u64 = gcd(TICK_HZ, 1_000_000_000);
|
||||
|
||||
#[cfg(feature = "defmt-timestamp-uptime-s")]
|
||||
defmt::timestamp! {"{=u64}", Instant::now().as_secs() }
|
||||
|
||||
#[cfg(feature = "defmt-timestamp-uptime-ms")]
|
||||
defmt::timestamp! {"{=u64:ms}", Instant::now().as_millis() }
|
||||
|
||||
#[cfg(any(feature = "defmt-timestamp-uptime", feature = "defmt-timestamp-uptime-us"))]
|
||||
defmt::timestamp! {"{=u64:us}", Instant::now().as_micros() }
|
||||
|
||||
#[cfg(feature = "defmt-timestamp-uptime-ts")]
|
||||
defmt::timestamp! {"{=u64:ts}", Instant::now().as_secs() }
|
||||
|
||||
#[cfg(feature = "defmt-timestamp-uptime-tms")]
|
||||
defmt::timestamp! {"{=u64:tms}", Instant::now().as_millis() }
|
||||
|
||||
#[cfg(feature = "defmt-timestamp-uptime-tus")]
|
||||
defmt::timestamp! {"{=u64:tus}", Instant::now().as_micros() }
|
||||
+348
@@ -0,0 +1,348 @@
|
||||
use core::cell::RefCell;
|
||||
use core::cmp::{min, Ordering};
|
||||
use core::task::Waker;
|
||||
|
||||
use critical_section::Mutex;
|
||||
use embassy_time_driver::{allocate_alarm, set_alarm, set_alarm_callback, AlarmHandle};
|
||||
use embassy_time_queue_driver::TimerQueue;
|
||||
use heapless::Vec;
|
||||
|
||||
use crate::Instant;
|
||||
|
||||
#[cfg(feature = "generic-queue-8")]
|
||||
const QUEUE_SIZE: usize = 8;
|
||||
#[cfg(feature = "generic-queue-16")]
|
||||
const QUEUE_SIZE: usize = 16;
|
||||
#[cfg(feature = "generic-queue-32")]
|
||||
const QUEUE_SIZE: usize = 32;
|
||||
#[cfg(feature = "generic-queue-64")]
|
||||
const QUEUE_SIZE: usize = 64;
|
||||
#[cfg(feature = "generic-queue-128")]
|
||||
const QUEUE_SIZE: usize = 128;
|
||||
#[cfg(not(any(
|
||||
feature = "generic-queue-8",
|
||||
feature = "generic-queue-16",
|
||||
feature = "generic-queue-32",
|
||||
feature = "generic-queue-64",
|
||||
feature = "generic-queue-128"
|
||||
)))]
|
||||
const QUEUE_SIZE: usize = 64;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Timer {
|
||||
at: Instant,
|
||||
waker: Waker,
|
||||
}
|
||||
|
||||
impl PartialEq for Timer {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.at == other.at
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Timer {}
|
||||
|
||||
impl PartialOrd for Timer {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
self.at.partial_cmp(&other.at)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Timer {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.at.cmp(&other.at)
|
||||
}
|
||||
}
|
||||
|
||||
struct InnerQueue {
|
||||
queue: Vec<Timer, QUEUE_SIZE>,
|
||||
alarm: AlarmHandle,
|
||||
}
|
||||
|
||||
impl InnerQueue {
|
||||
fn schedule_wake(&mut self, at: Instant, waker: &Waker) {
|
||||
self.queue
|
||||
.iter_mut()
|
||||
.find(|timer| timer.waker.will_wake(waker))
|
||||
.map(|timer| {
|
||||
timer.at = min(timer.at, at);
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
let mut timer = Timer {
|
||||
waker: waker.clone(),
|
||||
at,
|
||||
};
|
||||
|
||||
loop {
|
||||
match self.queue.push(timer) {
|
||||
Ok(()) => break,
|
||||
Err(e) => timer = e,
|
||||
}
|
||||
|
||||
self.queue.pop().unwrap().waker.wake();
|
||||
}
|
||||
});
|
||||
|
||||
// Don't wait for the alarm callback to trigger and directly
|
||||
// dispatch all timers that are already due
|
||||
//
|
||||
// Then update the alarm if necessary
|
||||
self.dispatch();
|
||||
}
|
||||
|
||||
fn dispatch(&mut self) {
|
||||
loop {
|
||||
let now = Instant::now();
|
||||
|
||||
let mut next_alarm = Instant::MAX;
|
||||
|
||||
let mut i = 0;
|
||||
while i < self.queue.len() {
|
||||
let timer = &self.queue[i];
|
||||
if timer.at <= now {
|
||||
let timer = self.queue.swap_remove(i);
|
||||
timer.waker.wake();
|
||||
} else {
|
||||
next_alarm = min(next_alarm, timer.at);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if self.update_alarm(next_alarm) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_alarm(&mut self, next_alarm: Instant) -> bool {
|
||||
if next_alarm == Instant::MAX {
|
||||
true
|
||||
} else {
|
||||
set_alarm(self.alarm, next_alarm.as_ticks())
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_alarm(&mut self) {
|
||||
self.dispatch();
|
||||
}
|
||||
}
|
||||
|
||||
struct Queue {
|
||||
inner: Mutex<RefCell<Option<InnerQueue>>>,
|
||||
}
|
||||
|
||||
impl Queue {
|
||||
const fn new() -> Self {
|
||||
Self {
|
||||
inner: Mutex::new(RefCell::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
fn schedule_wake(&'static self, at: Instant, waker: &Waker) {
|
||||
critical_section::with(|cs| {
|
||||
let mut inner = self.inner.borrow_ref_mut(cs);
|
||||
|
||||
if inner.is_none() {}
|
||||
|
||||
inner
|
||||
.get_or_insert_with(|| {
|
||||
let handle = unsafe { allocate_alarm() }.unwrap();
|
||||
set_alarm_callback(handle, Self::handle_alarm_callback, self as *const _ as _);
|
||||
InnerQueue {
|
||||
queue: Vec::new(),
|
||||
alarm: handle,
|
||||
}
|
||||
})
|
||||
.schedule_wake(at, waker)
|
||||
});
|
||||
}
|
||||
|
||||
fn handle_alarm(&self) {
|
||||
critical_section::with(|cs| self.inner.borrow_ref_mut(cs).as_mut().unwrap().handle_alarm())
|
||||
}
|
||||
|
||||
fn handle_alarm_callback(ctx: *mut ()) {
|
||||
unsafe { (ctx as *const Self).as_ref().unwrap() }.handle_alarm();
|
||||
}
|
||||
}
|
||||
|
||||
impl TimerQueue for Queue {
|
||||
fn schedule_wake(&'static self, at: u64, waker: &Waker) {
|
||||
Queue::schedule_wake(self, Instant::from_ticks(at), waker);
|
||||
}
|
||||
}
|
||||
|
||||
embassy_time_queue_driver::timer_queue_impl!(static QUEUE: Queue = Queue::new());
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "mock-driver")]
|
||||
mod tests {
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
use core::task::Waker;
|
||||
use std::sync::Arc;
|
||||
use std::task::Wake;
|
||||
|
||||
use serial_test::serial;
|
||||
|
||||
use crate::driver_mock::MockDriver;
|
||||
use crate::queue_generic::QUEUE;
|
||||
use crate::{Duration, Instant};
|
||||
|
||||
struct TestWaker {
|
||||
pub awoken: AtomicBool,
|
||||
}
|
||||
|
||||
impl Wake for TestWaker {
|
||||
fn wake(self: Arc<Self>) {
|
||||
self.awoken.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn wake_by_ref(self: &Arc<Self>) {
|
||||
self.awoken.store(true, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
fn test_waker() -> (Arc<TestWaker>, Waker) {
|
||||
let arc = Arc::new(TestWaker {
|
||||
awoken: AtomicBool::new(false),
|
||||
});
|
||||
let waker = Waker::from(arc.clone());
|
||||
|
||||
(arc, waker)
|
||||
}
|
||||
|
||||
fn setup() {
|
||||
MockDriver::get().reset();
|
||||
critical_section::with(|cs| *QUEUE.inner.borrow_ref_mut(cs) = None);
|
||||
}
|
||||
|
||||
fn queue_len() -> usize {
|
||||
critical_section::with(|cs| {
|
||||
QUEUE
|
||||
.inner
|
||||
.borrow_ref(cs)
|
||||
.as_ref()
|
||||
.map(|inner| inner.queue.iter().count())
|
||||
.unwrap_or(0)
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_schedule() {
|
||||
setup();
|
||||
|
||||
assert_eq!(queue_len(), 0);
|
||||
|
||||
let (flag, waker) = test_waker();
|
||||
|
||||
QUEUE.schedule_wake(Instant::from_secs(1), &waker);
|
||||
|
||||
assert!(!flag.awoken.load(Ordering::Relaxed));
|
||||
assert_eq!(queue_len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_schedule_same() {
|
||||
setup();
|
||||
|
||||
let (_flag, waker) = test_waker();
|
||||
|
||||
QUEUE.schedule_wake(Instant::from_secs(1), &waker);
|
||||
|
||||
assert_eq!(queue_len(), 1);
|
||||
|
||||
QUEUE.schedule_wake(Instant::from_secs(1), &waker);
|
||||
|
||||
assert_eq!(queue_len(), 1);
|
||||
|
||||
QUEUE.schedule_wake(Instant::from_secs(100), &waker);
|
||||
|
||||
assert_eq!(queue_len(), 1);
|
||||
|
||||
let (_flag2, waker2) = test_waker();
|
||||
|
||||
QUEUE.schedule_wake(Instant::from_secs(100), &waker2);
|
||||
|
||||
assert_eq!(queue_len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_trigger() {
|
||||
setup();
|
||||
|
||||
let (flag, waker) = test_waker();
|
||||
|
||||
QUEUE.schedule_wake(Instant::from_secs(100), &waker);
|
||||
|
||||
assert!(!flag.awoken.load(Ordering::Relaxed));
|
||||
|
||||
MockDriver::get().advance(Duration::from_secs(99));
|
||||
|
||||
assert!(!flag.awoken.load(Ordering::Relaxed));
|
||||
|
||||
assert_eq!(queue_len(), 1);
|
||||
|
||||
MockDriver::get().advance(Duration::from_secs(1));
|
||||
|
||||
assert!(flag.awoken.load(Ordering::Relaxed));
|
||||
|
||||
assert_eq!(queue_len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_immediate_trigger() {
|
||||
setup();
|
||||
|
||||
let (flag, waker) = test_waker();
|
||||
|
||||
QUEUE.schedule_wake(Instant::from_secs(100), &waker);
|
||||
|
||||
MockDriver::get().advance(Duration::from_secs(50));
|
||||
|
||||
let (flag2, waker2) = test_waker();
|
||||
|
||||
QUEUE.schedule_wake(Instant::from_secs(40), &waker2);
|
||||
|
||||
assert!(!flag.awoken.load(Ordering::Relaxed));
|
||||
assert!(flag2.awoken.load(Ordering::Relaxed));
|
||||
assert_eq!(queue_len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_queue_overflow() {
|
||||
setup();
|
||||
|
||||
for i in 1..super::QUEUE_SIZE {
|
||||
let (flag, waker) = test_waker();
|
||||
|
||||
QUEUE.schedule_wake(Instant::from_secs(310), &waker);
|
||||
|
||||
assert_eq!(queue_len(), i);
|
||||
assert!(!flag.awoken.load(Ordering::Relaxed));
|
||||
}
|
||||
|
||||
let (flag, waker) = test_waker();
|
||||
|
||||
QUEUE.schedule_wake(Instant::from_secs(300), &waker);
|
||||
|
||||
assert_eq!(queue_len(), super::QUEUE_SIZE);
|
||||
assert!(!flag.awoken.load(Ordering::Relaxed));
|
||||
|
||||
let (flag2, waker2) = test_waker();
|
||||
|
||||
QUEUE.schedule_wake(Instant::from_secs(305), &waker2);
|
||||
|
||||
assert_eq!(queue_len(), super::QUEUE_SIZE);
|
||||
assert!(flag.awoken.load(Ordering::Relaxed));
|
||||
|
||||
let (_flag3, waker3) = test_waker();
|
||||
QUEUE.schedule_wake(Instant::from_secs(320), &waker3);
|
||||
assert_eq!(queue_len(), super::QUEUE_SIZE);
|
||||
assert!(flag2.awoken.load(Ordering::Relaxed));
|
||||
}
|
||||
}
|
||||
Vendored
+269
@@ -0,0 +1,269 @@
|
||||
use core::future::{poll_fn, Future};
|
||||
use core::pin::{pin, Pin};
|
||||
use core::task::{Context, Poll};
|
||||
|
||||
use futures_util::future::{select, Either};
|
||||
use futures_util::stream::FusedStream;
|
||||
use futures_util::Stream;
|
||||
|
||||
use crate::{Duration, Instant};
|
||||
|
||||
/// Error returned by [`with_timeout`] and [`with_deadline`] on timeout.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct TimeoutError;
|
||||
|
||||
/// Runs a given future with a timeout.
|
||||
///
|
||||
/// If the future completes before the timeout, its output is returned. Otherwise, on timeout,
|
||||
/// work on the future is stopped (`poll` is no longer called), the future is dropped and `Err(TimeoutError)` is returned.
|
||||
pub async fn with_timeout<F: Future>(timeout: Duration, fut: F) -> Result<F::Output, TimeoutError> {
|
||||
let timeout_fut = Timer::after(timeout);
|
||||
match select(pin!(fut), timeout_fut).await {
|
||||
Either::Left((r, _)) => Ok(r),
|
||||
Either::Right(_) => Err(TimeoutError),
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs a given future with a deadline time.
|
||||
///
|
||||
/// If the future completes before the deadline, its output is returned. Otherwise, on timeout,
|
||||
/// work on the future is stopped (`poll` is no longer called), the future is dropped and `Err(TimeoutError)` is returned.
|
||||
pub async fn with_deadline<F: Future>(at: Instant, fut: F) -> Result<F::Output, TimeoutError> {
|
||||
let timeout_fut = Timer::at(at);
|
||||
match select(pin!(fut), timeout_fut).await {
|
||||
Either::Left((r, _)) => Ok(r),
|
||||
Either::Right(_) => Err(TimeoutError),
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides functions to run a given future with a timeout or a deadline.
|
||||
pub trait WithTimeout {
|
||||
/// Output type of the future.
|
||||
type Output;
|
||||
|
||||
/// Runs a given future with a timeout.
|
||||
///
|
||||
/// If the future completes before the timeout, its output is returned. Otherwise, on timeout,
|
||||
/// work on the future is stopped (`poll` is no longer called), the future is dropped and `Err(TimeoutError)` is returned.
|
||||
async fn with_timeout(self, timeout: Duration) -> Result<Self::Output, TimeoutError>;
|
||||
|
||||
/// Runs a given future with a deadline time.
|
||||
///
|
||||
/// If the future completes before the deadline, its output is returned. Otherwise, on timeout,
|
||||
/// work on the future is stopped (`poll` is no longer called), the future is dropped and `Err(TimeoutError)` is returned.
|
||||
async fn with_deadline(self, at: Instant) -> Result<Self::Output, TimeoutError>;
|
||||
}
|
||||
|
||||
impl<F: Future> WithTimeout for F {
|
||||
type Output = F::Output;
|
||||
|
||||
async fn with_timeout(self, timeout: Duration) -> Result<Self::Output, TimeoutError> {
|
||||
with_timeout(timeout, self).await
|
||||
}
|
||||
|
||||
async fn with_deadline(self, at: Instant) -> Result<Self::Output, TimeoutError> {
|
||||
with_deadline(at, self).await
|
||||
}
|
||||
}
|
||||
|
||||
/// A future that completes at a specified [Instant](struct.Instant.html).
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
pub struct Timer {
|
||||
expires_at: Instant,
|
||||
yielded_once: bool,
|
||||
}
|
||||
|
||||
impl Timer {
|
||||
/// Expire at specified [Instant](struct.Instant.html)
|
||||
pub fn at(expires_at: Instant) -> Self {
|
||||
Self {
|
||||
expires_at,
|
||||
yielded_once: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Expire after specified [Duration](struct.Duration.html).
|
||||
/// This can be used as a `sleep` abstraction.
|
||||
///
|
||||
/// Example:
|
||||
/// ``` no_run
|
||||
/// use embassy_time::{Duration, Timer};
|
||||
///
|
||||
/// #[embassy_executor::task]
|
||||
/// async fn demo_sleep_seconds() {
|
||||
/// // suspend this task for one second.
|
||||
/// Timer::after(Duration::from_secs(1)).await;
|
||||
/// }
|
||||
/// ```
|
||||
pub fn after(duration: Duration) -> Self {
|
||||
Self {
|
||||
expires_at: Instant::now() + duration,
|
||||
yielded_once: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Expire after the specified number of ticks.
|
||||
///
|
||||
/// This method is a convenience wrapper for calling `Timer::after(Duration::from_ticks())`.
|
||||
/// For more details, refer to [`Timer::after()`] and [`Duration::from_ticks()`].
|
||||
#[inline]
|
||||
pub fn after_ticks(ticks: u64) -> Self {
|
||||
Self::after(Duration::from_ticks(ticks))
|
||||
}
|
||||
|
||||
/// Expire after the specified number of nanoseconds.
|
||||
///
|
||||
/// This method is a convenience wrapper for calling `Timer::after(Duration::from_nanos())`.
|
||||
/// For more details, refer to [`Timer::after()`] and [`Duration::from_nanos()`].
|
||||
#[inline]
|
||||
pub fn after_nanos(nanos: u64) -> Self {
|
||||
Self::after(Duration::from_nanos(nanos))
|
||||
}
|
||||
|
||||
/// Expire after the specified number of microseconds.
|
||||
///
|
||||
/// This method is a convenience wrapper for calling `Timer::after(Duration::from_micros())`.
|
||||
/// For more details, refer to [`Timer::after()`] and [`Duration::from_micros()`].
|
||||
#[inline]
|
||||
pub fn after_micros(micros: u64) -> Self {
|
||||
Self::after(Duration::from_micros(micros))
|
||||
}
|
||||
|
||||
/// Expire after the specified number of milliseconds.
|
||||
///
|
||||
/// This method is a convenience wrapper for calling `Timer::after(Duration::from_millis())`.
|
||||
/// For more details, refer to [`Timer::after`] and [`Duration::from_millis()`].
|
||||
#[inline]
|
||||
pub fn after_millis(millis: u64) -> Self {
|
||||
Self::after(Duration::from_millis(millis))
|
||||
}
|
||||
|
||||
/// Expire after the specified number of seconds.
|
||||
///
|
||||
/// This method is a convenience wrapper for calling `Timer::after(Duration::from_secs())`.
|
||||
/// For more details, refer to [`Timer::after`] and [`Duration::from_secs()`].
|
||||
#[inline]
|
||||
pub fn after_secs(secs: u64) -> Self {
|
||||
Self::after(Duration::from_secs(secs))
|
||||
}
|
||||
}
|
||||
|
||||
impl Unpin for Timer {}
|
||||
|
||||
impl Future for Timer {
|
||||
type Output = ();
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
if self.yielded_once && self.expires_at <= Instant::now() {
|
||||
Poll::Ready(())
|
||||
} else {
|
||||
embassy_time_queue_driver::schedule_wake(self.expires_at.as_ticks(), cx.waker());
|
||||
self.yielded_once = true;
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Asynchronous stream that yields every Duration, indefinitely.
|
||||
///
|
||||
/// This stream will tick at uniform intervals, even if blocking work is performed between ticks.
|
||||
///
|
||||
/// For instance, consider the following code fragment.
|
||||
/// ``` no_run
|
||||
/// use embassy_time::{Duration, Timer};
|
||||
/// # fn foo() {}
|
||||
///
|
||||
/// #[embassy_executor::task]
|
||||
/// async fn ticker_example_0() {
|
||||
/// loop {
|
||||
/// foo();
|
||||
/// Timer::after(Duration::from_secs(1)).await;
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This fragment will not call `foo` every second.
|
||||
/// Instead, it will call it every second + the time it took to previously call `foo`.
|
||||
///
|
||||
/// Example using ticker, which will consistently call `foo` once a second.
|
||||
///
|
||||
/// ``` no_run
|
||||
/// use embassy_time::{Duration, Ticker};
|
||||
/// # fn foo(){}
|
||||
///
|
||||
/// #[embassy_executor::task]
|
||||
/// async fn ticker_example_1() {
|
||||
/// let mut ticker = Ticker::every(Duration::from_secs(1));
|
||||
/// loop {
|
||||
/// foo();
|
||||
/// ticker.next().await;
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub struct Ticker {
|
||||
expires_at: Instant,
|
||||
duration: Duration,
|
||||
}
|
||||
|
||||
impl Ticker {
|
||||
/// Creates a new ticker that ticks at the specified duration interval.
|
||||
pub fn every(duration: Duration) -> Self {
|
||||
let expires_at = Instant::now() + duration;
|
||||
Self { expires_at, duration }
|
||||
}
|
||||
|
||||
/// Resets the ticker back to its original state.
|
||||
/// This causes the ticker to go back to zero, even if the current tick isn't over yet.
|
||||
pub fn reset(&mut self) {
|
||||
self.expires_at = Instant::now() + self.duration;
|
||||
}
|
||||
|
||||
/// Reset the ticker at the deadline.
|
||||
/// If the deadline is in the past, the ticker will fire instantly.
|
||||
pub fn reset_at(&mut self, deadline: Instant) {
|
||||
self.expires_at = deadline + self.duration;
|
||||
}
|
||||
|
||||
/// Resets the ticker, after the specified duration has passed.
|
||||
/// If the specified duration is zero, the next tick will be after the duration of the ticker.
|
||||
pub fn reset_after(&mut self, after: Duration) {
|
||||
self.expires_at = Instant::now() + after + self.duration;
|
||||
}
|
||||
|
||||
/// Waits for the next tick.
|
||||
pub fn next(&mut self) -> impl Future<Output = ()> + Send + Sync + '_ {
|
||||
poll_fn(|cx| {
|
||||
if self.expires_at <= Instant::now() {
|
||||
let dur = self.duration;
|
||||
self.expires_at += dur;
|
||||
Poll::Ready(())
|
||||
} else {
|
||||
embassy_time_queue_driver::schedule_wake(self.expires_at.as_ticks(), cx.waker());
|
||||
Poll::Pending
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Unpin for Ticker {}
|
||||
|
||||
impl Stream for Ticker {
|
||||
type Item = ();
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
if self.expires_at <= Instant::now() {
|
||||
let dur = self.duration;
|
||||
self.expires_at += dur;
|
||||
Poll::Ready(Some(()))
|
||||
} else {
|
||||
embassy_time_queue_driver::schedule_wake(self.expires_at.as_ticks(), cx.waker());
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FusedStream for Ticker {
|
||||
fn is_terminated(&self) -> bool {
|
||||
// `Ticker` keeps yielding values until dropped, it never terminates.
|
||||
false
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user