From 1d3782cd9f037f9b51daec2fcec73480868b5f05 Mon Sep 17 00:00:00 2001 From: kelko Date: Sat, 5 Oct 2024 09:40:29 +0200 Subject: [PATCH] strengthen abstraction and isolation (#1) - `ApplicationService` - move the main working logic out of the main.rs loop into an "application service" - app service contains references to the static mutex-es for shared board values - main loop calls the app service inside the main loop - "Ports" - introduce traits for all things the "domain layer" - no hard-coded dependencies inside domain layer to the board - except for app service depending on `cortex_m` for mutex & critical section - structs adapting the ports to the board specifics in "infrastructure layer", one per trait - zero cost - compiled release binary was not increased but actually decreased in size - documentation - added various documenting comments --- Cargo.lock | 206 ++++++++---------- src/keret-adapter/Cargo.toml | 6 +- src/keret-controller-transmit/Cargo.toml | 4 +- src/keret-controller/Cargo.toml | 6 +- src/keret-controller/src/domain.rs | 76 ------- .../src/domain/application_service.rs | 110 ++++++++++ src/keret-controller/src/domain/mod.rs | 9 + .../src/domain/model/app_mode.rs | 69 ++++++ .../src/domain/model/duration.rs | 17 ++ .../src/domain/model/instant.rs | 44 ++++ src/keret-controller/src/domain/model/mod.rs | 68 ++++++ src/keret-controller/src/domain/port.rs | 29 +++ src/keret-controller/src/error.rs | 3 +- .../src/{ => infrastructure}/controls.rs | 30 +-- .../display/mod.rs} | 24 +- .../display/sprites.rs} | 2 +- .../src/infrastructure/mod.rs | 5 + .../src/{ => infrastructure}/serialize.rs | 18 +- .../src/{ => infrastructure}/time.rs | 29 ++- src/keret-controller/src/main.rs | 119 +++------- src/keret-service/Cargo.toml | 8 +- src/keret-service/example_requests.http | 16 ++ 22 files changed, 573 insertions(+), 325 deletions(-) delete mode 100644 src/keret-controller/src/domain.rs create mode 100644 src/keret-controller/src/domain/application_service.rs create mode 100644 src/keret-controller/src/domain/mod.rs create mode 100644 src/keret-controller/src/domain/model/app_mode.rs create mode 100644 src/keret-controller/src/domain/model/duration.rs create mode 100644 src/keret-controller/src/domain/model/instant.rs create mode 100644 src/keret-controller/src/domain/model/mod.rs create mode 100644 src/keret-controller/src/domain/port.rs rename src/keret-controller/src/{ => infrastructure}/controls.rs (84%) rename src/keret-controller/src/{display.rs => infrastructure/display/mod.rs} (57%) rename src/keret-controller/src/{render.rs => infrastructure/display/sprites.rs} (97%) create mode 100644 src/keret-controller/src/infrastructure/mod.rs rename src/keret-controller/src/{ => infrastructure}/serialize.rs (68%) rename src/keret-controller/src/{ => infrastructure}/time.rs (85%) create mode 100644 src/keret-service/example_requests.http diff --git a/Cargo.lock b/Cargo.lock index abc8ce2..e7807c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -92,13 +92,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.82" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -112,15 +112,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "axum" -version = "0.7.5" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" +checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae" dependencies = [ "async-trait", "axum-core", @@ -152,9 +152,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" dependencies = [ "async-trait", "bytes", @@ -165,7 +165,7 @@ dependencies = [ "mime", "pin-project-lite", "rustversion", - "sync_wrapper 0.1.2", + "sync_wrapper 1.0.1", "tower-layer", "tower-service", "tracing", @@ -245,9 +245,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "cast" @@ -257,9 +257,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.1.18" +version = "1.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" +checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" dependencies = [ "shlex", ] @@ -287,9 +287,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.17" +version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" +checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" dependencies = [ "clap_builder", "clap_derive", @@ -297,9 +297,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.17" +version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" +checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" dependencies = [ "anstream", "anstyle", @@ -309,14 +309,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.13" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -558,9 +558,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" [[package]] name = "heapless" @@ -640,9 +640,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -690,9 +690,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da62f120a8a37763efb0cf8fdf264b884c7b8b9ac8660b900c8661030c00e6ba" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" dependencies = [ "bytes", "futures-channel", @@ -703,16 +703,15 @@ dependencies = [ "pin-project-lite", "socket2", "tokio", - "tower", "tower-service", "tracing", ] [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -743,9 +742,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", "hashbrown", @@ -809,7 +808,7 @@ dependencies = [ "cortex-m-rt", "embedded-hal 1.0.0", "embedded-hal-nb", - "heapless 0.8.0", + "heapless 0.7.17", "keret-controller-transmit", "libm", "lsm303agr", @@ -866,9 +865,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libm" @@ -1154,9 +1153,12 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" +dependencies = [ + "portable-atomic", +] [[package]] name = "overload" @@ -1203,26 +1205,6 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "pin-project" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.77", -] - [[package]] name = "pin-project-lite" version = "0.2.14" @@ -1237,15 +1219,15 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "portable-atomic" -version = "1.7.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" [[package]] name = "postcard" @@ -1446,23 +1428,23 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] [[package]] name = "regex" -version = "1.10.6" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -1476,13 +1458,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", ] [[package]] @@ -1493,15 +1475,15 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.7" +version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" dependencies = [ "base64", "bytes", @@ -1596,9 +1578,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.36" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags 2.6.0", "errno", @@ -1623,19 +1605,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" +checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" [[package]] name = "rustls-webpki" @@ -1704,7 +1685,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1756,9 +1737,9 @@ dependencies = [ [[package]] name = "serialport" -version = "4.5.0" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241ebb629ed9bf598b2b392ba42aa429f9ef2a0099001246a36ac4c084ee183f" +checksum = "3ba776acc8c373b9175829206229366273225436845c04f9c20aab8099960e2e" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -1813,23 +1794,23 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "snafu" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b835cb902660db3415a672d862905e791e54d306c6e8189168c7f3d9ae1c79d" +checksum = "223891c85e2a29c3fe8fb900c1fae5e69c2e42415e3177752e8718475efa5019" dependencies = [ "snafu-derive", ] [[package]] name = "snafu-derive" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d1e02fca405f6280643174a50c942219f0bbf4dbf7d480f1dd864d6f211ae5" +checksum = "03c3c6b7927ffe7ecaa769ee0e3994da3b8cafc8f444578982c83ecb161af917" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1882,9 +1863,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -1908,22 +1889,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1983,7 +1964,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1999,14 +1980,14 @@ dependencies = [ [[package]] name = "tower" -version = "0.4.13" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" dependencies = [ "futures-core", "futures-util", - "pin-project", "pin-project-lite", + "sync_wrapper 0.1.2", "tokio", "tower-layer", "tower-service", @@ -2015,15 +1996,14 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.5.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" +checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97" dependencies = [ "bitflags 2.6.0", "bytes", "http", "http-body", - "http-body-util", "pin-project-lite", "tower-layer", "tower-service", @@ -2062,7 +2042,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -2148,15 +2128,15 @@ checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] @@ -2270,7 +2250,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "wasm-bindgen-shared", ] @@ -2304,7 +2284,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2327,9 +2307,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.5" +version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a" +checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" dependencies = [ "rustls-pki-types", ] @@ -2495,7 +2475,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] diff --git a/src/keret-adapter/Cargo.toml b/src/keret-adapter/Cargo.toml index 8e055b1..0197a74 100644 --- a/src/keret-adapter/Cargo.toml +++ b/src/keret-adapter/Cargo.toml @@ -4,9 +4,9 @@ version = "0.1.0" edition = "2021" [dependencies] -snafu = "0.8.4" -clap = { version = "4.5.17", features = ["derive"] } -serialport = "4.5.0" +snafu = "0.8" +clap = { version = "4.5", features = ["derive"] } +serialport = "4.5.1" keret-controller-transmit = { path = "../keret-controller-transmit" } keret-service-transmit = { path = "../keret-service-transmit" } reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } diff --git a/src/keret-controller-transmit/Cargo.toml b/src/keret-controller-transmit/Cargo.toml index a0ab1ef..97241f3 100644 --- a/src/keret-controller-transmit/Cargo.toml +++ b/src/keret-controller-transmit/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -base64 = { version = "0.22.1", default-features = false } -snafu = { version = "0.8.4", default-features = false } +base64 = { version = "0.22", default-features = false } +snafu = { version = "0.8", default-features = false } serde = { version = "1.0.210", default-features = false, features = ["derive"] } postcard = { version = "1.0.10" } heapless = "0.7.17" diff --git a/src/keret-controller/Cargo.toml b/src/keret-controller/Cargo.toml index 996cd88..0444f48 100644 --- a/src/keret-controller/Cargo.toml +++ b/src/keret-controller/Cargo.toml @@ -6,15 +6,15 @@ edition = "2021" [dependencies] microbit-v2 = { version = "0.15.1", features = ["embedded-hal-02"] } cortex-m = { version = "0.7.7", features = ["critical-section-single-core"] } -cortex-m-rt = "0.7.0" +cortex-m-rt = "0.7.3" rtt-target = { version = "0.5.0" } panic-rtt-target = { version = "0.1.3" } lsm303agr = "1.1.0" nb = "1.1.0" libm = "0.2.8" -heapless = "0.8.0" +heapless = "0.7.17" tiny-led-matrix = "1.0.2" embedded-hal = "1.0.0" embedded-hal-nb = "1.0.0" -snafu = { version = "0.8.4", default-features = false } +snafu = { version = "0.8", default-features = false } keret-controller-transmit = { path = "../keret-controller-transmit", default-features = false } \ No newline at end of file diff --git a/src/keret-controller/src/domain.rs b/src/keret-controller/src/domain.rs deleted file mode 100644 index d4a6b21..0000000 --- a/src/keret-controller/src/domain.rs +++ /dev/null @@ -1,76 +0,0 @@ -use crate::error::NoTimerSnafu; -use crate::{ - controls::InteractionRequest, - error::{Error, IncoherentTimestampsSnafu}, - serialize::SerialBus, -}; -use microbit::hal::uarte::Instance; - -type Instant = u64; - -/// current state of the application logic (the "domain") -#[derive(Debug, Copy, Clone, Default, PartialEq)] -pub(crate) enum AppMode { - #[default] - Idle, - Running(Instant), - Error, -} - -impl AppMode { - /// check what interaction the user requested to perform and calculate next state from that - pub(crate) fn handle_interaction_request( - &self, - request: InteractionRequest, - now: Option, - serial_bus: &mut SerialBus, - ) -> Result { - match request { - InteractionRequest::ToggleMode => { - let Some(timestamp) = now else { - return NoTimerSnafu.fail(); - }; - - self.toggle_mode(serial_bus, timestamp) - } - InteractionRequest::Reset => Ok(AppMode::Idle), - InteractionRequest::None => Ok(*self), - } - } - - /// user hit right button -> toggle between idle & running if possible - /// sending the report over the serial bus if necessary - #[inline(always)] - fn toggle_mode( - &self, - serial_bus: &mut SerialBus, - timestamp: u64, - ) -> Result { - match self { - AppMode::Idle => Ok(AppMode::Running(timestamp)), - AppMode::Running(start) => self.finish_report(serial_bus, *start, timestamp), - AppMode::Error => Ok(*self), - } - } - - /// user ended the timer, calculate duration and send it over the wire - fn finish_report( - &self, - serial_bus: &mut SerialBus, - start_timestamp: u64, - end_timestamp: u64, - ) -> Result { - if start_timestamp > end_timestamp { - return IncoherentTimestampsSnafu { - start: start_timestamp, - end: end_timestamp, - } - .fail(); - } - - let duration = end_timestamp - start_timestamp; - serial_bus.serialize_message(duration)?; - - Ok(AppMode::Idle) - } -} diff --git a/src/keret-controller/src/domain/application_service.rs b/src/keret-controller/src/domain/application_service.rs new file mode 100644 index 0000000..69f3482 --- /dev/null +++ b/src/keret-controller/src/domain/application_service.rs @@ -0,0 +1,110 @@ +use crate::{ + domain::{ + model::{AppMode, Instant, InteractionRequest, StateUpdateResult}, + port::{Display, OutsideMessaging, RunningTimeClock, UserInterface}, + }, + error::{report_error, Error, NoControlsSnafu}, +}; +use core::cell::RefCell; +use cortex_m::interrupt::{free, CriticalSection, Mutex}; + +/// application service to orchestrate the domain logic +pub(crate) struct ApplicationService<'a, TClock, TDisplay, TUserInterface, TSerialBus> +where + TClock: RunningTimeClock + 'a, + TDisplay: Display + 'a, + TUserInterface: UserInterface + 'a, + TSerialBus: OutsideMessaging + 'a, +{ + running_timer: &'a Mutex>>, + display: &'a Mutex>>, + controls: &'a Mutex>>, + serial_bus: TSerialBus, +} + +impl<'a, TClock, TDisplay, TUserInterface, TSerialBus> + ApplicationService<'a, TClock, TDisplay, TUserInterface, TSerialBus> +where + TClock: RunningTimeClock + 'a, + TDisplay: Display + 'a, + TUserInterface: UserInterface + 'a, + TSerialBus: OutsideMessaging + 'a, +{ + /// setup a new `ApplicationService` instance + #[inline] + pub(crate) fn new( + running_timer: &'a Mutex>>, + display: &'a Mutex>>, + controls: &'a Mutex>>, + serial_bus: TSerialBus, + ) -> Self { + Self { + running_timer, + display, + controls, + serial_bus, + } + } + + /// run the next cycle of the main logic loop, returning the new state + pub(crate) fn next_cycle(&mut self, mode: &AppMode) -> AppMode { + let next = self + .calculate_next_state(&mode) + .unwrap_or_else(handle_runtime_error); + self.show_mode(&next); + + next + } + + /// calculate the next state: + /// check what the user requested to do (by clicking on buttons) and + /// let domain layer calculate the next state based on this input + fn calculate_next_state(&mut self, mode: &AppMode) -> Result { + let (request, time) = free(|cs| (self.get_requested_interaction(cs), self.now(cs))); + let StateUpdateResult { mode, message } = + mode.handle_interaction_request(request?, time)?; + + if let Some(message) = message { + self.serial_bus.send_result(message)?; + } + + Ok(mode) + } + + /// convenience method to read the current "running time" from the static timer object + fn now(&self, cs: &CriticalSection) -> Option { + self.running_timer + .borrow(cs) + .borrow_mut() + .as_mut() + .map(|timer| timer.now()) + } + + /// convenience wrapper to read the user interaction from the static controls object + fn get_requested_interaction(&self, cs: &CriticalSection) -> Result { + if let Some(controls) = self.controls.borrow(cs).borrow_mut().as_mut() { + Ok(controls.requested_interaction()) + } else { + NoControlsSnafu.fail() + } + } + + /// convenience method to show the correct sprite for current mode on the display + fn show_mode(&self, mode: &AppMode) { + free(|cs| { + let mut display = self.display.borrow(cs).borrow_mut(); + let display = display + .as_mut() + .expect("Display must be set at this point. Need restart"); + + display.show_mode(mode); + }); + } +} + +/// report an error that happened while executing the main loop +/// and switch the AppMode appropriately to indicate it's in a failure state +fn handle_runtime_error(err: Error) -> AppMode { + report_error(err); + AppMode::Error +} diff --git a/src/keret-controller/src/domain/mod.rs b/src/keret-controller/src/domain/mod.rs new file mode 100644 index 0000000..ad5d80b --- /dev/null +++ b/src/keret-controller/src/domain/mod.rs @@ -0,0 +1,9 @@ +/// defines all domain layer models processing the logic +pub(crate) mod model; +/// defines all dependencies of the domain layer which are implemented outside +pub(crate) mod port; + +mod application_service; + +// only publish the ApplicationService itself from the application_service module, as it was defined here +pub(crate) use application_service::ApplicationService; diff --git a/src/keret-controller/src/domain/model/app_mode.rs b/src/keret-controller/src/domain/model/app_mode.rs new file mode 100644 index 0000000..7de8b92 --- /dev/null +++ b/src/keret-controller/src/domain/model/app_mode.rs @@ -0,0 +1,69 @@ +use crate::{ + domain::model::{Instant, InteractionRequest, StateUpdateResult}, + error::{Error, IncoherentTimestampsSnafu, NoTimerSnafu}, +}; + +/// current state of the application logic (the "domain") +#[derive(Debug, Copy, Clone, Default, PartialEq)] +pub(crate) enum AppMode { + /// app currently does nothing except idling + #[default] + Idle, + /// the app has marked when time tracking started, waiting to finish it + Running(Instant), + /// the app ran into a (recoverable) error in the main loop + Error, +} + +impl AppMode { + /// check what interaction the user requested to perform and calculate next state from that + pub(crate) fn handle_interaction_request( + &self, + request: InteractionRequest, + now: Option, + ) -> Result { + match request { + InteractionRequest::ToggleMode => { + let Some(timestamp) = now else { + return NoTimerSnafu.fail(); + }; + + self.toggle_mode(timestamp) + } + InteractionRequest::Reset => Ok(StateUpdateResult::new(AppMode::Idle)), + InteractionRequest::None => Ok(StateUpdateResult::new(*self)), + } + } + + /// user hit right button -> toggle between idle & running if possible + /// sending the report over the serial bus if necessary + #[inline(always)] + fn toggle_mode(&self, timestamp: Instant) -> Result { + match self { + AppMode::Idle => Ok(StateUpdateResult::new(AppMode::Running(timestamp))), + AppMode::Running(start) => self.finish_report(start, timestamp), + AppMode::Error => Ok(StateUpdateResult::new(*self)), + } + } + + /// user ended the timer, calculate duration and send it over the wire + fn finish_report( + &self, + start_timestamp: &Instant, + end_timestamp: Instant, + ) -> Result { + if start_timestamp > &end_timestamp { + return IncoherentTimestampsSnafu { + start: *start_timestamp, + end: end_timestamp, + } + .fail(); + } + + let duration = end_timestamp - *start_timestamp; + Ok(StateUpdateResult::with_message( + AppMode::Idle, + duration.into(), + )) + } +} diff --git a/src/keret-controller/src/domain/model/duration.rs b/src/keret-controller/src/domain/model/duration.rs new file mode 100644 index 0000000..e0319dc --- /dev/null +++ b/src/keret-controller/src/domain/model/duration.rs @@ -0,0 +1,17 @@ +/// measure of how long an action took +#[repr(transparent)] +pub(crate) struct Duration(u64); + +impl From for Duration { + #[inline(always)] + fn from(value: u64) -> Self { + Self(value) + } +} + +impl Into for Duration { + #[inline(always)] + fn into(self) -> u64 { + self.0 + } +} diff --git a/src/keret-controller/src/domain/model/instant.rs b/src/keret-controller/src/domain/model/instant.rs new file mode 100644 index 0000000..8768252 --- /dev/null +++ b/src/keret-controller/src/domain/model/instant.rs @@ -0,0 +1,44 @@ +use crate::domain::model::Duration; +use core::{ + fmt::{Display, Formatter}, + ops::Sub, +}; + +/// timestamp in controller-local time +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] +#[repr(transparent)] +pub(crate) struct Instant(u64); + +// display the instant +impl Display for Instant { + #[inline(always)] + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + Display::fmt(&self.0, f) + } +} + +// create the instant from a u64 timer value +impl From for Instant { + #[inline(always)] + fn from(value: u64) -> Self { + Self(value) + } +} + +// extract the u64 timestamp from the instant +impl Into for &Instant { + #[inline(always)] + fn into(self) -> u64 { + self.0 + } +} + +// calculate different between 2 instances, creating a `Duration` +impl Sub for Instant { + type Output = Duration; + + #[inline(always)] + fn sub(self, rhs: Self) -> Self::Output { + (self.0 - rhs.0).into() + } +} diff --git a/src/keret-controller/src/domain/model/mod.rs b/src/keret-controller/src/domain/model/mod.rs new file mode 100644 index 0000000..a695cfe --- /dev/null +++ b/src/keret-controller/src/domain/model/mod.rs @@ -0,0 +1,68 @@ +mod app_mode; +mod duration; +mod instant; + +// re-export everything relevant from the submodules as if it was directly coded here +// hides internal structure of the module +pub(crate) use app_mode::AppMode; +pub(crate) use duration::Duration; +pub(crate) use instant::Instant; + +/// enum to indicate the users desired interaction +/// which is calculated by which button was pressed +#[derive(Debug, Copy, Clone, Default)] +pub(crate) enum InteractionRequest { + #[default] + None, + ToggleMode, + Reset, +} + +/// result of calculating the next state +pub(crate) struct StateUpdateResult { + /// the mode the controller is next + pub(crate) mode: AppMode, + + /// a message to send (if necessary) + pub(crate) message: Option, +} + +impl StateUpdateResult { + /// create a new updated state, without a message + #[inline] + fn new(mode: AppMode) -> Self { + Self { + mode, + message: None, + } + } + + /// create a new updated state and also include a message to be sent + #[inline] + fn with_message(mode: AppMode, message: TrackResult) -> Self { + Self { + mode, + message: Some(message), + } + } +} + +/// the result of a time tracking action +#[repr(transparent)] +pub(crate) struct TrackResult(pub Duration); + +// create a time tracking result using the given duration +impl From for TrackResult { + #[inline] + fn from(value: Duration) -> Self { + Self(value) + } +} + +// extract the duration as u64 +impl Into for TrackResult { + #[inline] + fn into(self) -> u64 { + self.0.into() + } +} diff --git a/src/keret-controller/src/domain/port.rs b/src/keret-controller/src/domain/port.rs new file mode 100644 index 0000000..8d7b048 --- /dev/null +++ b/src/keret-controller/src/domain/port.rs @@ -0,0 +1,29 @@ +use crate::domain::model::AppMode; +use crate::{ + domain::model::{Instant, InteractionRequest, TrackResult}, + error::Error, +}; + +/// Keep track of the running time, producing an ever-increasing, never resetting timestamp +pub(crate) trait RunningTimeClock { + /// return the current timestamp + fn now(&mut self) -> Instant; +} + +/// Send domain-specific messages to the outside +pub(crate) trait OutsideMessaging { + /// inform the outside of the time tracking result + fn send_result(&mut self, result: TrackResult) -> Result<(), Error>; +} + +/// Show domain-specific content on the display +pub(crate) trait Display { + /// display a sprite associated with the given `AppMode` + fn show_mode(&mut self, mode: &AppMode); +} + +/// retrieve input from the user +pub(crate) trait UserInterface { + /// return the requested interface (as calculated from the inputs the user made) + fn requested_interaction(&mut self) -> InteractionRequest; +} diff --git a/src/keret-controller/src/error.rs b/src/keret-controller/src/error.rs index e0db56f..f51221e 100644 --- a/src/keret-controller/src/error.rs +++ b/src/keret-controller/src/error.rs @@ -1,3 +1,4 @@ +use crate::domain::model::Instant; use core::fmt::{Debug, Display, Formatter}; use rtt_target::rprintln; use snafu::{Error as _, Snafu}; @@ -45,7 +46,7 @@ pub(crate) enum Error { source: keret_controller_transmit::Error, }, #[snafu(display("Incoherent timestamps. Started at {start} & ended at {end}"))] - IncoherentTimestamps { start: u64, end: u64 }, + IncoherentTimestamps { start: Instant, end: Instant }, #[snafu(display("Failed to initialize the clock"))] ClockInitializationFailed, #[snafu(display("No timer initialized to read the time from"))] diff --git a/src/keret-controller/src/controls.rs b/src/keret-controller/src/infrastructure/controls.rs similarity index 84% rename from src/keret-controller/src/controls.rs rename to src/keret-controller/src/infrastructure/controls.rs index a8c45ef..b908001 100644 --- a/src/keret-controller/src/controls.rs +++ b/src/keret-controller/src/infrastructure/controls.rs @@ -1,15 +1,6 @@ +use crate::domain::{model::InteractionRequest, port::UserInterface}; use microbit::{board::Buttons, hal::gpiote::Gpiote, pac}; -/// enum to indicate the users desired interaction -/// which is calculated by which button was pressed -#[derive(Debug, Copy, Clone, Default)] -pub(crate) enum InteractionRequest { - #[default] - None, - ToggleMode, - Reset, -} - /// reading and interpreting the button presses to calculate requested interaction pub(crate) struct InputControls { gpiote: Gpiote, @@ -41,15 +32,6 @@ impl InputControls { } } - /// return the last requested interaction and set it next to `None` - #[inline(always)] - pub(crate) fn get_requested_interaction(&mut self) -> InteractionRequest { - let current = self.request; - self.request = InteractionRequest::None; - - current - } - /// check the button channels to see which button was pressed and /// calculate the next interaction request, reset the buttons afterward pub(crate) fn check_input(&mut self) { @@ -70,3 +52,13 @@ impl InputControls { self.request = request; } } + +impl UserInterface for InputControls { + /// return the last requested interaction and set it next to `None` + fn requested_interaction(&mut self) -> InteractionRequest { + let current = self.request; + self.request = InteractionRequest::None; + + current + } +} diff --git a/src/keret-controller/src/display.rs b/src/keret-controller/src/infrastructure/display/mod.rs similarity index 57% rename from src/keret-controller/src/display.rs rename to src/keret-controller/src/infrastructure/display/mod.rs index e41ecea..966af1f 100644 --- a/src/keret-controller/src/display.rs +++ b/src/keret-controller/src/infrastructure/display/mod.rs @@ -3,6 +3,11 @@ use microbit::{ }; use tiny_led_matrix::Render; +mod sprites; + +use crate::domain::model::AppMode; +pub(crate) use sprites::FATAL_SPRITE; + /// convenience abstraction of the BSP display module #[repr(transparent)] pub(crate) struct Display { @@ -17,11 +22,22 @@ impl Display { Self { inner: display } } - pub(crate) fn display_image(&mut self, image: &impl Render) { - self.inner.show(image); - } - + /// interrupt-triggered event handling inside the display pub(crate) fn handle_display_event(&mut self) { self.inner.handle_display_event(); } + + /// display any kind of sprite + #[inline] + pub(crate) fn show_sprite(&mut self, sprite: &impl Render) { + self.inner.show(sprite); + } +} + +impl crate::domain::port::Display for Display { + /// display a sprite associated with the given `AppMode` + #[inline] + fn show_mode(&mut self, app_mode: &AppMode) { + self.inner.show(app_mode); + } } diff --git a/src/keret-controller/src/render.rs b/src/keret-controller/src/infrastructure/display/sprites.rs similarity index 97% rename from src/keret-controller/src/render.rs rename to src/keret-controller/src/infrastructure/display/sprites.rs index 9a7278e..234a478 100644 --- a/src/keret-controller/src/render.rs +++ b/src/keret-controller/src/infrastructure/display/sprites.rs @@ -1,4 +1,4 @@ -use crate::domain::AppMode; +use crate::domain::model::AppMode; use tiny_led_matrix::Render; /// mapping of an domain AppMode to the sprite that shall be shown on the display. diff --git a/src/keret-controller/src/infrastructure/mod.rs b/src/keret-controller/src/infrastructure/mod.rs new file mode 100644 index 0000000..ab81d22 --- /dev/null +++ b/src/keret-controller/src/infrastructure/mod.rs @@ -0,0 +1,5 @@ +// the "modules" of this app (think "package"/"namespace") in other languages +pub(crate) mod controls; +pub(crate) mod display; +pub(crate) mod serialize; +pub(crate) mod time; diff --git a/src/keret-controller/src/serialize.rs b/src/keret-controller/src/infrastructure/serialize.rs similarity index 68% rename from src/keret-controller/src/serialize.rs rename to src/keret-controller/src/infrastructure/serialize.rs index 2349fc5..c0cc6bc 100644 --- a/src/keret-controller/src/serialize.rs +++ b/src/keret-controller/src/infrastructure/serialize.rs @@ -1,4 +1,7 @@ -use crate::error::{DeserializeMessageFailedSnafu, Error, WritingToSerialPortFailedSnafu}; +use crate::{ + domain::model::TrackResult, + error::{DeserializeMessageFailedSnafu, Error, WritingToSerialPortFailedSnafu}, +}; use keret_controller_transmit::ActionReport; use microbit::{ board::UartPins, @@ -25,9 +28,8 @@ impl SerialBus { Self { serial } } - /// send the duration as message via the serial bus - pub(crate) fn serialize_message(&mut self, duration: u64) -> Result<(), Error> { - let report = ActionReport::new(duration); + /// serialize the message and send if over the bus + fn send_report(&mut self, report: ActionReport) -> Result<(), Error> { let serialized_message = report.as_message().context(DeserializeMessageFailedSnafu)?; self.serial @@ -43,3 +45,11 @@ impl SerialBus { Ok(()) } } + +impl crate::domain::port::OutsideMessaging for SerialBus { + /// send the duration as message via the serial bus + fn send_result(&mut self, result: TrackResult) -> Result<(), Error> { + let report = ActionReport::new(result.into()); + self.send_report(report) + } +} diff --git a/src/keret-controller/src/time.rs b/src/keret-controller/src/infrastructure/time.rs similarity index 85% rename from src/keret-controller/src/time.rs rename to src/keret-controller/src/infrastructure/time.rs index 576c546..dbcdc61 100644 --- a/src/keret-controller/src/time.rs +++ b/src/keret-controller/src/infrastructure/time.rs @@ -1,4 +1,7 @@ -use crate::error::{ClockInitializationFailedSnafu, Error}; +use crate::{ + domain::{model::Instant, port::RunningTimeClock}, + error::{ClockInitializationFailedSnafu, Error}, +}; use microbit::{ hal::rtc::RtcInterrupt, hal::rtc::{Instance, RtcCompareReg}, @@ -8,7 +11,7 @@ use microbit::{ /// a timer to keep track of the overall running time of the microcontroller /// it uses an RTC, reading the current ticks + handles any overflow of the timer -/// this way the timer can not only handle ~8 minutes (RTC overflow) but several years +/// this way the timer can not only handle minutes to hours (RTC overflow) but several years /// majority of functionality is modeled after /// https://github.com/embassy-rs/embassy/blob/main/embassy-nrf/src/time_driver.rs pub(crate) struct RunningTimer { @@ -23,7 +26,7 @@ impl RunningTimer { pub(crate) fn new(clock: CLOCK, rtc_component: T) -> Result { Clocks::new(clock).start_lfclk(); - let Ok(mut rtc) = Rtc::new(rtc_component, 0) else { + let Ok(mut rtc) = Rtc::new(rtc_component, 511) else { return ClockInitializationFailedSnafu.fail(); }; @@ -50,15 +53,6 @@ impl RunningTimer { }) } - /// calculates the current running time - /// using the periods + current tick count - #[inline(always)] - pub(crate) fn now(&mut self) -> u64 { - let current_value = self.rtc_timer.get_counter(); - - construct_ticks(self.period, current_value) / 32768 - } - /// handle a interrupt from the RTC (overflow or half-mark) pub(crate) fn tick_timer(&mut self) { let rtc = &self.rtc_timer; @@ -73,6 +67,17 @@ impl RunningTimer { } } +impl RunningTimeClock for RunningTimer { + /// calculates the current running time + /// using the periods + current tick count + #[inline(always)] + fn now(&mut self) -> Instant { + let current_value = self.rtc_timer.get_counter(); + + (construct_ticks(self.period, current_value) / 64).into() + } +} + /// see `calc_now` at https://github.com/embassy-rs/embassy/blob/main/embassy-nrf/src/time_driver.rs #[inline(always)] fn construct_ticks(period: u32, counter: u32) -> u64 { diff --git a/src/keret-controller/src/main.rs b/src/keret-controller/src/main.rs index 2a9e74d..baf8bf4 100644 --- a/src/keret-controller/src/main.rs +++ b/src/keret-controller/src/main.rs @@ -5,25 +5,24 @@ // so the "main" method is not indicator for code entry, but below you will find #[entry] // also as there is no OS the Rust std lib can't be used, as it depends on libc/musl/something similar -// the "modules" of this app (think "package"/"namespace") in other languages -mod controls; -mod display; mod domain; mod error; -mod render; -mod serialize; -mod time; - +mod infrastructure; // importing elements (modules, structs, traits, ...) from other modules to be used in this file + +/// convenience type alias to make code shorter/more readable +/// meant for those static values which exist once and used from interrupts and inside domain layer +type Singleton = Mutex>>; + use crate::{ - controls::{InputControls, InteractionRequest}, - display::Display, - domain::AppMode, - error::report_error, - error::{Error, NoControlsSnafu}, - render::FATAL_SPRITE, - serialize::SerialBus, - time::RunningTimer, + domain::{model::AppMode, port::Display as _, ApplicationService}, + error::{report_error, Error}, + infrastructure::{ + controls::InputControls, + display::{Display, FATAL_SPRITE}, + serialize::SerialBus, + time::RunningTimer, + }, }; use core::cell::RefCell; use cortex_m::{ @@ -33,9 +32,10 @@ use cortex_m::{ use cortex_m_rt::entry; use microbit::{ board::Board, - hal::timer::{Instance as TimerInstance, Periodic}, - hal::uarte::Instance as UarteInstance, - hal::Timer, + hal::{ + timer::{Instance, Periodic}, + Timer, + }, pac::{interrupt, Interrupt, NVIC, RTC1, TIMER0, TIMER1, UARTE0}, }; use panic_rtt_target as _; @@ -48,13 +48,13 @@ use rtt_target::rtt_init_print; // and in a `RefCell` so we can call mutable methods on it (so-called "inner mutability") /// the primary timer used to calculate the running time -static RUNNING_TIMER: Mutex>>> = Mutex::new(RefCell::new(None)); +static RUNNING_TIMER: Singleton> = Mutex::new(RefCell::new(None)); /// the display to show something on the LED matrix -static DISPLAY: Mutex>>> = Mutex::new(RefCell::new(None)); +static DISPLAY: Singleton> = Mutex::new(RefCell::new(None)); /// wrapper for input controls handling -static CONTROLS: Mutex>> = Mutex::new(RefCell::new(None)); +static CONTROLS: Singleton = Mutex::new(RefCell::new(None)); /// entry point for the application. Could have any name, `main` used to follow convention from C /// Initializes the controller as well as go into the execution loop. This method should never return @@ -70,21 +70,24 @@ fn main() -> ! { }; let mut mode = AppMode::Idle; - let (mut serial_bus, mut main_loop_timer) = initialize_board(board); + let (mut app_service, mut main_loop_timer) = initialize_app_service(board); // main execution loop, should never end loop { - mode = next_cycle(&mode, &mut serial_bus).unwrap_or_else(handle_runtime_error); - show_mode(&mode); - + mode = app_service.next_cycle(&mode); main_loop_timer.delay_ms(500_u32); } } /// initialize the board, creating all helper objects and put those necessary in the Mutexes -fn initialize_board(board: Board) -> (SerialBus, Timer) { +fn initialize_app_service<'a>( + board: Board, +) -> ( + ApplicationService<'a, RunningTimer, Display, InputControls, SerialBus>, + Timer, +) { let mut display = Display::new(board.TIMER1, board.display_pins); - display.display_image(&AppMode::Idle); + display.show_mode(&AppMode::Idle); let controls = InputControls::new(board.GPIOTE, board.buttons); let serial_bus = SerialBus::new(board.UARTE0, board.uart); @@ -111,65 +114,15 @@ fn initialize_board(board: Board) -> (SerialBus, Timer *RUNNING_TIMER.borrow(cs).borrow_mut() = Some(running_timer); }); - (serial_bus, main_loop_timer) -} - -/// calculate the next state in the next processing cycle: -/// check what the user requested to do (by clicking on buttons) and -/// let domain layer calculate the next state based on this input -fn next_cycle( - mode: &AppMode, - serial_bus: &mut SerialBus, -) -> Result { - let request = get_requested_interaction()?; - mode.handle_interaction_request(request, now(), serial_bus) -} - -/// convenience method to read the current "running time" from the static timer object -#[inline(always)] -fn now() -> Option { - free(|cs| { - RUNNING_TIMER - .borrow(cs) - .borrow_mut() - .as_mut() - .map(|timer| timer.now()) - }) -} - -/// convenience wrapper to read the user interaction from the static controls object -fn get_requested_interaction() -> Result { - free(|cs| { - if let Some(controls) = CONTROLS.borrow(cs).borrow_mut().as_mut() { - Ok(controls.get_requested_interaction()) - } else { - NoControlsSnafu.fail() - } - }) -} - -/// convenience method to show the correct sprite for current mode on the display -fn show_mode(mode: &AppMode) { - free(|cs| { - let mut display = DISPLAY.borrow(cs).borrow_mut(); - let display = display - .as_mut() - .expect("Display must be set at this point. Need restart"); - - display.display_image(mode); - }); -} - -/// report an error that happened while executing the main loop -/// and switch the AppMode appropriately to indicate it's in a failure state -fn handle_runtime_error(err: Error) -> AppMode { - report_error(err); - AppMode::Error + ( + ApplicationService::new(&RUNNING_TIMER, &DISPLAY, &CONTROLS, serial_bus), + main_loop_timer, + ) } /// report an error that happened during initialization, don't even go into the main loop -fn handle_init_error(mut display: Display, err: Error) -> ! { - display.display_image(&FATAL_SPRITE); +fn handle_init_error(mut display: Display, err: Error) -> ! { + display.show_sprite(&FATAL_SPRITE); report_error(err); loop { diff --git a/src/keret-service/Cargo.toml b/src/keret-service/Cargo.toml index bf9eada..ee3f7bb 100644 --- a/src/keret-service/Cargo.toml +++ b/src/keret-service/Cargo.toml @@ -4,12 +4,12 @@ version = "0.1.0" edition = "2021" [dependencies] -axum = { version = "0.7.5" } +axum = { version = "0.7.7" } serde = { version = "1.0", features = ["derive"] } serde_yaml = { version = "0.9.33" } -tokio = { version = "1.0", features = ["full"] } -tower = { version = "0.4", features = ["util", "timeout"] } -tower-http = { version = "0.5.0", features = ["add-extension", "trace"] } +tokio = { version = "1.40", features = ["full"] } +tower = { version = "0.5", features = ["util", "timeout"] } +tower-http = { version = "0.6.0", features = ["add-extension", "trace"] } tracing = { version = "0.1" } tracing-subscriber = { version = "0.3", features = ["env-filter"] } snafu = { version = "0.8.4" } diff --git a/src/keret-service/example_requests.http b/src/keret-service/example_requests.http new file mode 100644 index 0000000..5c401c6 --- /dev/null +++ b/src/keret-service/example_requests.http @@ -0,0 +1,16 @@ +@HOST_ADDRESS = http://localhost:3000 + +### GET request to example server +GET {{HOST_ADDRESS}}/api/v1.0/report + +### POST a new report +POST {{HOST_ADDRESS}}/api/v1.0/report +Content-Type: application/json + +{ + "timestamp": "1970-01-01T00:00:01.000Z", + "duration": { + "secs": 5, + "nanos": 0 + } +}