commit 4e48060bfa905a4f7ad0a6b5c9c28a2db77768df Author: Paul Zinselmeyer Date: Tue Apr 30 20:59:29 2024 +0200 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c1ee528 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +Cargo.toml +target +.env +result diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..9ea2946 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,478 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + +[[package]] +name = "bare-metal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "bare-metal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603" + +[[package]] +name = "bitfield" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bxcan" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ac3d0c0a542d0ab5521211f873f62706a7136df415676f676d347e5a41dd80" +dependencies = [ + "bitflags", + "embedded-hal 0.2.7", + "nb 1.1.0", + "vcell", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "canome" +version = "0.1.0" +dependencies = [ + "bxcan", + "critical-section", + "defmt", + "embedded-hal 0.2.7", + "heapless", + "nb 1.1.0", + "rtic-monotonics", + "stm32f1xx-hal", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cortex-m" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" +dependencies = [ + "bare-metal 0.2.5", + "bitfield", + "embedded-hal 0.2.7", + "volatile-register", +] + +[[package]] +name = "cortex-m-rt" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2722f5b7d6ea8583cffa4d247044e280ccbb9fe501bed56552e2ba48b02d5f3d" +dependencies = [ + "cortex-m-rt-macros", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f6f3e36f203cfedbc78b357fb28730aa2c6dc1ab060ee5c2405e843988d3c7" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "critical-section" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" + +[[package]] +name = "defmt" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3939552907426de152b3c2c6f51ed53f98f448babd26f28694c95f5906194595" +dependencies = [ + "bitflags", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18bdc7a7b92ac413e19e95240e75d3a73a8d8e78aa24a594c22cbb4d44b4bbda" +dependencies = [ + "defmt-parser", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "defmt-parser" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4a5fefe330e8d7f31b16a318f9ce81000d8e35e69b93eae154d16d2278f70f" +dependencies = [ + "thiserror", +] + +[[package]] +name = "embedded-dma" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "994f7e5b5cb23521c22304927195f236813053eb9c065dd2226a32ba64695446" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "fugit" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17186ad64927d5ac8f02c1e77ccefa08ccd9eaa314d5a4772278aa204a22f7e7" +dependencies = [ + "gcd", +] + +[[package]] +name = "fugit-timer" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9607bfc4c388f9d629704f56ede4a007546cad417b3bcd6fc7c87dc7edce04a" +dependencies = [ + "fugit", + "nb 1.1.0", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "gcd" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rtic-common" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0786b50b81ef9d2a944a000f60405bb28bf30cd45da2d182f3fe636b2321f35c" +dependencies = [ + "critical-section", +] + +[[package]] +name = "rtic-monotonics" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "058c2397dbd5bb4c5650a0e368c3920953e458805ff5097a0511b8147b3619d7" +dependencies = [ + "atomic-polyfill", + "cfg-if", + "embedded-hal 1.0.0", + "fugit", + "rtic-time", +] + +[[package]] +name = "rtic-time" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b232e7aebc045cfea81cdd164bc2727a10aca9a4568d406d0a5661cdfd0f19" +dependencies = [ + "critical-section", + "futures-util", + "rtic-common", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "stm32f1" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dc80735831c28fe85384e1e28428fb6d201f67c696e369a239ed9c5eba369d" +dependencies = [ + "bare-metal 1.0.0", + "cortex-m", + "cortex-m-rt", + "vcell", +] + +[[package]] +name = "stm32f1xx-hal" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30845662b9ce46a2ec04da97666a2b32458bee5032bb0452d0caf1536a96a542" +dependencies = [ + "bitflags", + "bxcan", + "cortex-m", + "cortex-m-rt", + "embedded-dma", + "embedded-hal 0.2.7", + "fugit", + "fugit-timer", + "nb 1.1.0", + "stm32f1", + "void", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "volatile-register" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" +dependencies = [ + "vcell", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..93acceb --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[workspace] +resolver = "2" +members = [ + "canome" +] + + diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..6fb6a01 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,157 @@ +# GNU LESSER GENERAL PUBLIC LICENSE + +Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +This version of the GNU Lesser General Public License incorporates the +terms and conditions of version 3 of the GNU General Public License, +supplemented by the additional permissions listed below. + +## 0. Additional Definitions. + +As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the +GNU General Public License. + +"The Library" refers to a covered work governed by this License, other +than an Application or a Combined Work as defined below. + +An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + +A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + +The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + +The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + +## 1. Exception to Section 3 of the GNU GPL. + +You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + +## 2. Conveying Modified Versions. + +If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + +- a) under this License, provided that you make a good faith effort + to ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or +- b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + +## 3. Object Code Incorporating Material from Library Header Files. + +The object code form of an Application may incorporate material from a +header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + +- a) Give prominent notice with each copy of the object code that + the Library is used in it and that the Library and its use are + covered by this License. +- b) Accompany the object code with a copy of the GNU GPL and this + license document. + +## 4. Combined Works. + +You may convey a Combined Work under terms of your choice that, taken +together, effectively do not restrict modification of the portions of +the Library contained in the Combined Work and reverse engineering for +debugging such modifications, if you also do each of the following: + +- a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. +- b) Accompany the Combined Work with a copy of the GNU GPL and this + license document. +- c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. +- d) Do one of the following: + - 0) Convey the Minimal Corresponding Source under the terms of + this License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + - 1) Use a suitable shared library mechanism for linking with + the Library. A suitable mechanism is one that (a) uses at run + time a copy of the Library already present on the user's + computer system, and (b) will operate properly with a modified + version of the Library that is interface-compatible with the + Linked Version. +- e) Provide Installation Information, but only if you would + otherwise be required to provide such information under section 6 + of the GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the Application + with a modified version of the Linked Version. (If you use option + 4d0, the Installation Information must accompany the Minimal + Corresponding Source and Corresponding Application Code. If you + use option 4d1, you must provide the Installation Information in + the manner specified by section 6 of the GNU GPL for conveying + Corresponding Source.) + +## 5. Combined Libraries. + +You may place library facilities that are a work based on the Library +side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + +- a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities, conveyed under the terms of this License. +- b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + +## 6. Revised Versions of the GNU Lesser General Public License. + +The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +as you received it specifies that a certain numbered version of the +GNU Lesser General Public License "or any later version" applies to +it, you have the option of following the terms and conditions either +of that published version or of any later version published by the +Free Software Foundation. If the Library as you received it does not +specify a version number of the GNU Lesser General Public License, you +may choose any version of the GNU Lesser General Public License ever +published by the Free Software Foundation. + +If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/README.md b/README.md new file mode 100644 index 0000000..44c1db7 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ + +CANome is a library that assists you in writing your own CAN based, fully distributed, smart home system. + +A bus system provides many benefits over other system. Some of them are listed below: +- **Energy Efficiency:** Each device only needs to implement the fairly lightweight CAN stack. The bus cable can also transport the required power for the nodes, reducing the numbers of AC-DC-adapters. +- **Reliability:** A distributed system can handle the loss of nodes without impacting the complete system. Additionally, a wired channel is typically more reliable than a wireless one. +- **Security:** Embedded security is hard, using a bus doesn't make your application more secure, but you have a greatly reduced attack surface: An attacker needs physical access to your bus to even start to communicate with a node. + +Many other smart home projects use a meta-configuration-language to configure the firmware on each device. +A distributed system requires more logic on the nodes than a centralized one. +A meta-configuration-language will always have missing features that are a core component of a regular programming language, like rust. + +This project helps you in creating a distributed smart home system in rust by providing a library with the building blocks. +You express the entire node logic as regular embedded rust code using this library to avoid writing extensive repeating code. + +# Protocol +This project uses a very simple protocol that is based on CAN. +Each node can expose states. +A state is a Rust type that can be encoded/decoded from a CAN frame. +The format of a frame is implicitly transmitted by the CAN message identifier. +When a state changes, the node broadcasts the new state on the bus. + +Other nodes can "subscribe" to a CAN message identifier. +They always store a copy of the state that is automatically updated when a new message with that CAN message identifier arrives. +Additionally, a node can use a CAN remote frame to fetch the state from the source node. + +# Contributing +I'm happy about any contribution in any form. +Feel free to submit feature requests and bug reports using a GitHub Issue. +PR's are also appreciated. + +# License +The Sourcecode in this Library is licensed under [LGPLv3](https://www.gnu.org/licenses/lgpl-3.0.en.html). diff --git a/canome/Cargo.lock b/canome/Cargo.lock new file mode 100644 index 0000000..7edbc52 --- /dev/null +++ b/canome/Cargo.lock @@ -0,0 +1,540 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + +[[package]] +name = "bare-metal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "bare-metal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603" + +[[package]] +name = "bitfield" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bxcan" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ac3d0c0a542d0ab5521211f873f62706a7136df415676f676d347e5a41dd80" +dependencies = [ + "bitflags", + "embedded-hal 0.2.7", + "nb 1.1.0", + "vcell", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cortex-m" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" +dependencies = [ + "bare-metal 0.2.5", + "bitfield", + "embedded-hal 0.2.7", + "volatile-register", +] + +[[package]] +name = "cortex-m-rt" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee84e813d593101b1723e13ec38b6ab6abbdbaaa4546553f5395ed274079ddb1" +dependencies = [ + "cortex-m-rt-macros", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f6f3e36f203cfedbc78b357fb28730aa2c6dc1ab060ee5c2405e843988d3c7" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "critical-section" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" + +[[package]] +name = "defmt" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3939552907426de152b3c2c6f51ed53f98f448babd26f28694c95f5906194595" +dependencies = [ + "bitflags", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18bdc7a7b92ac413e19e95240e75d3a73a8d8e78aa24a594c22cbb4d44b4bbda" +dependencies = [ + "defmt-parser", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "defmt-parser" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4a5fefe330e8d7f31b16a318f9ce81000d8e35e69b93eae154d16d2278f70f" +dependencies = [ + "thiserror", +] + +[[package]] +name = "embedded-dma" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "994f7e5b5cb23521c22304927195f236813053eb9c065dd2226a32ba64695446" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "fugit" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17186ad64927d5ac8f02c1e77ccefa08ccd9eaa314d5a4772278aa204a22f7e7" +dependencies = [ + "gcd", +] + +[[package]] +name = "fugit-timer" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9607bfc4c388f9d629704f56ede4a007546cad417b3bcd6fc7c87dc7edce04a" +dependencies = [ + "fugit", + "nb 1.1.0", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "gcd" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "indexmap" +version = "2.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "pfzetto-smarthome" +version = "0.1.0" +dependencies = [ + "bxcan", + "critical-section", + "defmt", + "embedded-hal 0.2.7", + "heapless", + "hex", + "rtic", + "rtic-monotonics", + "stm32f1xx-hal", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rtic" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c443db16326376bdd64377da268f6616d5f804aba8ce799bac7d1f7f244e9d51" +dependencies = [ + "atomic-polyfill", + "bare-metal 1.0.0", + "critical-section", + "rtic-core", + "rtic-macros", +] + +[[package]] +name = "rtic-common" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0786b50b81ef9d2a944a000f60405bb28bf30cd45da2d182f3fe636b2321f35c" +dependencies = [ + "critical-section", +] + +[[package]] +name = "rtic-core" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9369355b04d06a3780ec0f51ea2d225624db777acbc60abd8ca4832da5c1a42" + +[[package]] +name = "rtic-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54053598ea24b1b74937724e366558412a1777eb2680b91ef646db540982789a" +dependencies = [ + "indexmap", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "rtic-monotonics" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "058c2397dbd5bb4c5650a0e368c3920953e458805ff5097a0511b8147b3619d7" +dependencies = [ + "atomic-polyfill", + "cfg-if", + "cortex-m", + "embedded-hal 1.0.0", + "fugit", + "rtic-time", +] + +[[package]] +name = "rtic-time" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b232e7aebc045cfea81cdd164bc2727a10aca9a4568d406d0a5661cdfd0f19" +dependencies = [ + "critical-section", + "futures-util", + "rtic-common", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "stm32f1" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dc80735831c28fe85384e1e28428fb6d201f67c696e369a239ed9c5eba369d" +dependencies = [ + "bare-metal 1.0.0", + "cortex-m", + "cortex-m-rt", + "vcell", +] + +[[package]] +name = "stm32f1xx-hal" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30845662b9ce46a2ec04da97666a2b32458bee5032bb0452d0caf1536a96a542" +dependencies = [ + "bitflags", + "bxcan", + "cortex-m", + "cortex-m-rt", + "embedded-dma", + "embedded-hal 0.2.7", + "fugit", + "fugit-timer", + "nb 1.1.0", + "stm32f1", + "void", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "volatile-register" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" +dependencies = [ + "vcell", +] diff --git a/canome/Cargo.toml b/canome/Cargo.toml new file mode 100644 index 0000000..fdd4488 --- /dev/null +++ b/canome/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "canome" +description = "a library that helps building a CAN-based smart home system" +version = "0.1.0" +authors = [ "Paul Zinselmeyer " ] +license = "LGPL-3.0-or-later" +edition = "2021" +repository = "https://github.com/pfzetto/canome" +keywords = [ "CAN", "embedded", "smarthome" ] + +[dependencies] +defmt = "0.3" + +heapless = { version = "0.8.0", default-features = false } +critical-section = "1.0" + +embedded-hal = { version = "0.2.7", optional = true } +stm32f1xx-hal = { version = "0.10.0", optional = true } +rtic-monotonics = { version = "1.0.0", optional = true } +bxcan = { version = "0.7.0", optional = true } +nb = "1.1.0" + +[features] +default = [ "can", "stm32" ] +can = [ "dep:bxcan" ] +stm32 = [ "dep:embedded-hal", "dep:stm32f1xx-hal", "dep:rtic-monotonics", "dep:bxcan" ] diff --git a/canome/src/bus.rs b/canome/src/bus.rs new file mode 100644 index 0000000..4a04049 --- /dev/null +++ b/canome/src/bus.rs @@ -0,0 +1,293 @@ +use core::{ + cell::RefCell, + fmt::Debug, + future::{poll_fn, Future}, + marker::PhantomData, + task::{Poll, Waker}, +}; + +use critical_section::Mutex; +use heapless::{LinearMap, Vec}; + +use crate::{BusBackend, Channel, Decode, Encode}; + +pub struct Bus { + rx_queues: critical_section::Mutex, CHANNELS>>>, + backend: B, +} + +#[derive(Debug)] +pub enum BusError { + Backend(B::Error), + ChannelCapExceeded, +} + +#[derive(Debug)] +struct BusInner { + state: B::Data, + wakers: Vec, + sender: bool, +} + +#[derive(Clone)] +pub struct BusChannelTransceiver<'a, B: BusBackend, C: Channel> { + bus: &'a dyn BusBehaviour, + _channel: PhantomData, +} +#[derive(Clone)] +pub struct BusChannelReceiver<'a, B: BusBackend, C: Channel> { + bus: &'a dyn BusBehaviour, + _channel: PhantomData, +} + +pub trait BusBehaviour { + fn get(&self, id: B::ID) -> Option; + fn register_waker(&self, id: B::ID, waker: &Waker); + fn publish(&self, id: B::ID, value: B::Data) -> Result<(), BusError>; + fn publish_local(&self, id: &B::ID, value: B::Data); + fn request(&self, id: B::ID) -> Result<(), BusError>; +} + +impl> BusBehaviour for &T { + fn get(&self, id: ::ID) -> Option<::Data> { + self.get(id) + } + + fn register_waker(&self, id: ::ID, waker: &Waker) { + self.register_waker(id, waker) + } + + fn publish( + &self, + id: ::ID, + value: ::Data, + ) -> Result<(), BusError> { + self.publish(id, value) + } + + fn publish_local(&self, id: &::ID, value: ::Data) { + self.publish_local(id, value) + } + + fn request(&self, id: ::ID) -> Result<(), BusError> { + self.request(id) + } +} + +pub trait BusRx, B: BusBackend> { + fn get(&self) -> C::State; + fn request(&self) -> Result<(), BusError>; + fn next(&self) -> impl Future; +} + +pub trait BusTx, B: BusBackend> { + fn publish(&self, value: C::State) -> Result<(), BusError>; +} + +impl BusBehaviour + for Bus +where + B::ID: Eq, +{ + fn get(&self, id: B::ID) -> Option { + critical_section::with(|cs| { + self.rx_queues + .borrow(cs) + .borrow_mut() + .get_mut(&id) + .map(|x| x.state.clone()) + }) + } + + fn register_waker(&self, id: B::ID, waker: &Waker) { + critical_section::with(|cs| { + if let Some(inner) = self.rx_queues.borrow(cs).borrow_mut().get_mut(&id) { + if inner.wakers.iter().any(|ew| waker.will_wake(ew)) { + return; + } + + if inner.wakers.is_full() { + inner.wake(); + } + + if inner.wakers.push(waker.clone()).is_err() { + panic!("tried to push a waker to a zero length waker list"); + } + } + }) + } + + fn publish(&self, id: B::ID, value: B::Data) -> Result<(), BusError> { + self.publish_local(&id, value.clone()); + + self.backend + .publish(id, Some(value)) + .map_err(BusError::Backend) + } + + fn publish_local(&self, id: &B::ID, value: B::Data) { + critical_section::with(|cs| { + if let Some(inner) = self.rx_queues.borrow(cs).borrow_mut().get_mut(id) { + inner.state = value.clone(); + inner.wake(); + } + }); + } + + fn request(&self, id: B::ID) -> Result<(), BusError> { + self.backend.publish(id, None).map_err(BusError::Backend) + } +} + +impl BusInner { + fn wake(&mut self) { + let mut wakers = Vec::new(); + core::mem::swap(&mut wakers, &mut self.wakers); + wakers.into_iter().for_each(|w| w.wake()); + } +} + +impl Bus +where + B::ID: Eq, +{ + pub const fn new(backend: B) -> Self { + Self { + rx_queues: Mutex::new(RefCell::new(LinearMap::new())), + backend, + } + } + + pub fn transceiver>(&self) -> Result, BusError> + where + C::State: Default + Encode, + { + critical_section::with(|cs| { + let mut rx_queues = self.rx_queues.borrow(cs).borrow_mut(); + if let Some(existing) = rx_queues.get_mut(&C::ID) { + existing.sender = true; + Ok(BusChannelTransceiver { + bus: self, + _channel: PhantomData, + }) + } else if rx_queues.len() < SUBS { + self.request(C::ID).ok(); + let _ = rx_queues.insert( + C::ID, + BusInner { + state: C::State::default().encode(), + wakers: Vec::new(), + sender: true, + }, + ); + Ok(BusChannelTransceiver { + bus: self, + _channel: PhantomData, + }) + } else { + Err(BusError::ChannelCapExceeded) + } + }) + } + + pub fn receiver>(&self) -> Result, BusError> + where + C::State: Default + Encode, + { + critical_section::with(|cs| { + let mut rx_queues = self.rx_queues.borrow(cs).borrow_mut(); + if rx_queues.contains_key(&C::ID) { + Ok(BusChannelReceiver { + bus: self, + _channel: PhantomData, + }) + } else if rx_queues.len() < SUBS { + self.request(C::ID).ok(); + let _ = rx_queues.insert( + C::ID, + BusInner { + state: C::State::default().encode(), + wakers: Vec::new(), + sender: false, + }, + ); + Ok(BusChannelReceiver { + bus: self, + _channel: PhantomData, + }) + } else { + Err(BusError::ChannelCapExceeded) + } + }) + } + + pub fn backend(&self) -> &B { + &self.backend + } +} + +impl<'a, B: BusBackend, C: Channel> BusTx for BusChannelTransceiver<'a, B, C> +where + C::State: Encode, +{ + fn publish(&self, value: C::State) -> Result<(), BusError> { + self.bus.publish(C::ID, value.encode()) + } +} + +impl<'a, B: BusBackend, C> BusRx for BusChannelTransceiver<'a, B, C> +where + C::State: Decode + PartialEq + Default, + C: Channel, +{ + fn get(&self) -> C::State { + self.bus + .get(C::ID) + .and_then(|x| C::State::decode(x).ok()) + .expect("valid group") + } + fn request(&self) -> Result<(), BusError> { + self.bus.request(C::ID) + } + async fn next(&self) -> C::State { + let old_state: C::State = self.get(); + poll_fn(|cx| { + self.bus.register_waker(C::ID, cx.waker()); + + let state: C::State = self.get(); + match state == old_state { + false => Poll::Ready(state), + true => Poll::Pending, + } + }) + .await + } +} +impl<'a, B: BusBackend, C> BusRx for BusChannelReceiver<'a, B, C> +where + C::State: Decode + PartialEq + Default, + C: Channel, +{ + fn get(&self) -> C::State { + self.bus + .get(C::ID) + .and_then(|x| C::State::decode(x).ok()) + .expect("valid group") + } + fn request(&self) -> Result<(), BusError> { + self.bus.request(C::ID) + } + async fn next(&self) -> C::State { + let old_state: C::State = self.get(); + poll_fn(|cx| { + self.bus.register_waker(C::ID, cx.waker()); + + let state: C::State = self.get(); + match state == old_state { + false => Poll::Ready(state), + true => Poll::Pending, + } + }) + .await + } +} diff --git a/canome/src/can.rs b/canome/src/can.rs new file mode 100644 index 0000000..80a6cb9 --- /dev/null +++ b/canome/src/can.rs @@ -0,0 +1,177 @@ +use core::{fmt::Debug, marker::ConstParamTy}; + +use crate::{bus::BusBehaviour, BusBackend, BusDataFormat}; +use bxcan::{ExtendedId, Id, Instance, Rx0, StandardId, Tx}; +use defmt::Format; +use heapless::mpmc::MpMcQueue; + +pub struct BxCanBus { + tx_queue: MpMcQueue<(::ID, Option<::Data>), TX_CAP>, + on_publish: &'static (dyn Fn(&Self) + Send + Sync), +} + +impl Debug for BxCanBus { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("BxCanBus").finish() + } +} + +#[derive(PartialEq, Eq, ConstParamTy, Debug, Clone, Copy)] +pub enum BxCanId { + Standard(u16), + Extended(u32), +} + +#[derive(Debug)] +pub enum BxCanBusError { + /// An object couldn't be added to the TX queue of [`BxCanBus`] because it is full. + TxQueueFull, +} + +impl BusBackend for BxCanBus { + type ID = BxCanId; + + type Data = CanDataFormat; + + type Error = BxCanBusError; + + fn publish(&self, id: Self::ID, data: Option) -> Result<(), Self::Error> { + self.tx_queue + .enqueue((id, data)) + .map_err(|_| Self::Error::TxQueueFull)?; + + (self.on_publish)(self); + + Ok(()) + } +} + +impl Default for BxCanBus { + fn default() -> Self { + Self { + tx_queue: MpMcQueue::new(), + on_publish: &Self::empty_hook, + } + } +} + +impl BxCanBus { + pub const fn new() -> Self { + Self { + tx_queue: MpMcQueue::new(), + on_publish: &Self::empty_hook, + } + } + + #[inline] + fn empty_hook(&self) {} + + pub const fn new_with_hook(on_publish: &'static (dyn Fn(&Self) + Send + Sync)) -> Self { + Self { + tx_queue: MpMcQueue::new(), + on_publish, + } + } + + pub fn handle_tx_interrupt( + &self, + can_tx: &mut Tx, + ) -> Result<(), BxCanBusError> { + let rx = &self.tx_queue; + + while let Some((channel_id, data)) = rx.dequeue() { + let frame = encode_frame(channel_id, data); + match can_tx.transmit(&frame) { + Ok(status) => match status.dequeued_frame().map(decode_frame) { + None => {} + Some((channel_id, data)) => rx + .enqueue((channel_id, data)) + .map_err(|_| BxCanBusError::TxQueueFull)?, + }, + Err(nb::Error::WouldBlock) => { + rx.enqueue((channel_id, data)) + .map_err(|_| BxCanBusError::TxQueueFull)?; + break; + } + Err(_) => unreachable!(), + } + } + Ok(()) + } + pub fn handle_rx_interrupt( + &self, + can_rx: &mut Rx0, + bus: impl BusBehaviour, + ) -> Result<(), BxCanBusError> { + loop { + match can_rx.receive() { + Ok(frame) => { + let (channel_id, data) = decode_frame(&frame); + if let Some(data) = data { + bus.publish_local(&channel_id, data); + } else if let Some(state) = bus.get(channel_id) { + self.tx_queue + .enqueue((channel_id, Some(state))) + .map_err(|_| BxCanBusError::TxQueueFull)?; + + (self.on_publish)(self); + } + } + Err(nb::Error::WouldBlock) => break, + Err(nb::Error::Other(_)) => {} + } + } + + Ok(()) + } +} + +#[derive(Clone, Copy, Format, Debug)] +pub struct CanDataFormat { + length: u8, + data: [u8; 8], +} + +impl CanDataFormat { + pub fn new(data: &[u8]) -> Self { + assert!(data.len() <= 8); + + let mut padded = [0_u8; 8]; + padded[0..data.len()].copy_from_slice(data); + + Self { + length: data.len() as u8, + data: padded, + } + } + + pub fn data(&self) -> &[u8] { + &self.data[..self.length as usize] + } +} + +impl BusDataFormat for CanDataFormat {} + +fn decode_frame(frame: &bxcan::Frame) -> (BxCanId, Option) { + let id = match frame.id() { + Id::Standard(id) => BxCanId::Standard(id.as_raw()), + Id::Extended(id) => BxCanId::Extended(id.as_raw()), + }; + if let Some(data) = frame.data() { + (id, Some(CanDataFormat::new(data))) + } else { + (id, None) + } +} + +fn encode_frame(id: BxCanId, data: Option) -> bxcan::Frame { + let id = match id { + BxCanId::Standard(id) => Id::Standard(StandardId::new(id).unwrap()), + BxCanId::Extended(id) => Id::Extended(ExtendedId::new(id).unwrap()), + }; + if let Some(data) = data { + bxcan::Frame::new_data(id, bxcan::Data::new(data.data()).expect("valid can data")) + } else { + bxcan::Frame::new_remote(id, 8) + } +} diff --git a/canome/src/contact.rs b/canome/src/contact.rs new file mode 100644 index 0000000..0a7d8e1 --- /dev/null +++ b/canome/src/contact.rs @@ -0,0 +1,52 @@ +use defmt::Format; + +#[derive(Default, Debug, Format, Clone, Copy, PartialEq, Eq)] +pub enum ContactState { + #[default] + Open, + Closed, +} + +impl ContactState { + pub fn is_open(&self) -> bool { + matches!(self, Self::Open) + } + pub fn is_closed(&self) -> bool { + matches!(self, Self::Closed) + } +} + +#[cfg(feature = "can")] +mod can { + use crate::{can::CanDataFormat, Decode, Encode}; + + use super::ContactState; + + impl Encode for ContactState { + fn encode(self) -> CanDataFormat { + let data = match self { + Self::Open => 0, + Self::Closed => 1, + }; + CanDataFormat::new(&[data]) + } + } + + impl Decode for ContactState { + fn decode(value: CanDataFormat) -> Result + where + Self: Sized, + { + let data = value.data(); + if data.len() != 1 { + return Err(value); + } + + Ok(match data[0] & 1 { + 0 => Self::Open, + 1 => Self::Closed, + _ => unreachable!(), + }) + } + } +} diff --git a/canome/src/cover.rs b/canome/src/cover.rs new file mode 100644 index 0000000..a3fc784 --- /dev/null +++ b/canome/src/cover.rs @@ -0,0 +1,51 @@ +use defmt::Format; + +#[derive(Default, Debug, Format, Clone, Copy, PartialEq, Eq)] +pub struct CoverState { + pub tilt: u16, + pub position: u16, +} + +impl CoverState { + pub fn is_open(&self) -> bool { + self.position > 0 + } + pub fn is_closed(&self) -> bool { + self.position == 0 + } +} + +#[cfg(feature = "can")] +mod can { + use crate::{can::CanDataFormat, Decode, Encode}; + + use super::CoverState; + + impl Encode for CoverState { + fn encode(self) -> CanDataFormat { + let hue = self.tilt.to_be_bytes(); + let saturation = self.position.to_be_bytes(); + CanDataFormat::new(&[hue[0], hue[1], saturation[0], saturation[1]]) + } + } + + impl Decode for CoverState { + fn decode(value: CanDataFormat) -> Result + where + Self: Sized, + { + let data = value.data(); + if data.len() != 4 { + return Err(value); + } + + let hue = u16::from_be_bytes([data[0], data[1]]); + let saturation = u16::from_be_bytes([data[2], data[3]]); + + Ok(Self { + tilt: hue, + position: saturation, + }) + } + } +} diff --git a/canome/src/lib.rs b/canome/src/lib.rs new file mode 100644 index 0000000..b7f5156 --- /dev/null +++ b/canome/src/lib.rs @@ -0,0 +1,36 @@ +#![no_std] +#![feature(adt_const_params)] +use core::fmt::Debug; + +pub mod bus; +#[cfg(feature = "can")] +pub mod can; +pub mod contact; +pub mod cover; +pub mod light; +pub mod state; +#[cfg(feature = "stm32")] +pub mod stm32; +pub mod time; + +pub trait Channel { + const ID: B::ID; + type State: Encode + Decode; +} + +pub trait Encode { + fn encode(self) -> F; +} +pub trait Decode { + fn decode(value: F) -> Result + where + Self: Sized; +} + +pub trait BusDataFormat: Debug + Clone {} +pub trait BusBackend { + type ID: Debug + Clone; + type Data: BusDataFormat; + type Error; + fn publish(&self, id: Self::ID, data: Option) -> Result<(), Self::Error>; +} diff --git a/canome/src/light.rs b/canome/src/light.rs new file mode 100644 index 0000000..94e286b --- /dev/null +++ b/canome/src/light.rs @@ -0,0 +1,53 @@ +use defmt::Format; + +#[derive(Default, Debug, Format, Clone, Copy, PartialEq, Eq)] +pub struct LightState { + pub hue: u16, + pub saturation: u16, + pub brightness: u16, +} + +#[cfg(feature = "can")] +mod can { + use crate::{can::CanDataFormat, Decode, Encode}; + + use super::LightState; + + impl Encode for LightState { + fn encode(self) -> CanDataFormat { + let hue = self.hue.to_be_bytes(); + let saturation = self.saturation.to_be_bytes(); + let brightness = self.brightness.to_be_bytes(); + CanDataFormat::new(&[ + hue[0], + hue[1], + saturation[0], + saturation[1], + brightness[0], + brightness[1], + ]) + } + } + + impl Decode for LightState { + fn decode(value: CanDataFormat) -> Result + where + Self: Sized, + { + let data = value.data(); + if data.len() != 6 { + return Err(value); + } + + let hue = u16::from_be_bytes([data[0], data[1]]); + let saturation = u16::from_be_bytes([data[2], data[3]]); + let brightness = u16::from_be_bytes([data[4], data[5]]); + + Ok(Self { + hue, + saturation, + brightness, + }) + } + } +} diff --git a/canome/src/state.rs b/canome/src/state.rs new file mode 100644 index 0000000..5716d80 --- /dev/null +++ b/canome/src/state.rs @@ -0,0 +1,41 @@ +#[derive(Copy, Clone, PartialEq, Eq, Default)] +pub enum ThreeWaySwitchState { + #[default] + Off, + A, + B, +} + +#[cfg(feature = "can")] +mod can { + use crate::{can::CanDataFormat, Decode, Encode}; + + use super::ThreeWaySwitchState; + + impl Encode for ThreeWaySwitchState { + fn encode(self) -> CanDataFormat { + let desc = match self { + Self::Off => 0, + Self::A => 1, + Self::B => 2, + }; + CanDataFormat::new(&[desc]) + } + } + impl Decode for ThreeWaySwitchState { + fn decode(value: CanDataFormat) -> Result + where + Self: Sized, + { + if value.data().len() != 1 { + return Err(value); + } + match value.data()[0] { + 0 => Ok(Self::Off), + 1 => Ok(Self::A), + 2 => Ok(Self::B), + _ => Err(value), + } + } + } +} diff --git a/canome/src/stm32.rs b/canome/src/stm32.rs new file mode 100644 index 0000000..0ecc0bf --- /dev/null +++ b/canome/src/stm32.rs @@ -0,0 +1,77 @@ +use core::ops::{Deref, DerefMut}; + +use crate::{bus::BusRx, state::ThreeWaySwitchState, BusBackend, Channel}; +use rtic_monotonics::{ + systick::{ + fugit::{Duration, Instant}, + Systick, + }, + Monotonic, +}; +use stm32f1xx_hal::gpio::{ExtiPin, Input, Pin, PullDown}; + +pub mod buttonmatrix; +pub mod motorfader; + +pub struct ThreeWaySwitch { + a: Pin>, + b: Pin>, +} + +impl ThreeWaySwitch { + pub const fn new(a: Pin>, b: Pin>) -> Self { + Self { a, b } + } + pub fn state(&self) -> ThreeWaySwitchState { + match (self.a.is_low(), self.b.is_low()) { + (true, false) => ThreeWaySwitchState::A, + (false, true) => ThreeWaySwitchState::B, + _ => ThreeWaySwitchState::Off, + } + } + pub fn a(&mut self) -> &mut Pin> { + &mut self.a + } + pub fn b(&mut self) -> &mut Pin> { + &mut self.b + } +} + +pub struct ChannelDerivative<'a, C: Channel, R: BusRx, B: BusBackend> { + channel: &'a R, + last: C::State, + updated: Instant, +} +impl<'a, C, R, B> ChannelDerivative<'a, C, R, B> +where + C: Channel, + C::State: Default + PartialEq + Clone, + R: BusRx, + B: BusBackend, +{ + pub fn new(channel: &'a R) -> Self { + Self { + channel, + last: C::State::default(), + updated: Systick::now(), + } + } + pub fn update(&mut self, new: C::State) -> Option<(C::State, Duration)> { + if new != self.last { + self.last = new.clone(); + let now = Systick::now(); + let last_state_for = now - self.updated; + self.updated = now; + Some((new, last_state_for)) + } else { + None + } + } + pub async fn next(&mut self) -> (C::State, Duration) { + loop { + if let Some(value) = self.update(self.channel.next().await) { + return value; + } + } + } +} diff --git a/canome/src/stm32/buttonmatrix.rs b/canome/src/stm32/buttonmatrix.rs new file mode 100644 index 0000000..b2826c6 --- /dev/null +++ b/canome/src/stm32/buttonmatrix.rs @@ -0,0 +1,78 @@ +use defmt::Format; +use rtic_monotonics::{systick::Systick, Monotonic}; +use stm32f1xx_hal::gpio::{ErasedPin, Input, Output, PullDown}; + +pub struct ButtonMatrix { + rows: [ErasedPin; ROWS], + cols: [ErasedPin>; COLS], + pub state: [[ButtonState; ROWS]; COLS], +} +impl ButtonMatrix { + pub fn new( + mut rows: [ErasedPin; ROWS], + cols: [ErasedPin>; COLS], + ) -> Self { + for row in rows.iter_mut() { + row.set_low(); + } + Self { + rows, + cols, + state: [[ButtonState::Released; ROWS]; COLS], + } + } + pub fn scan(&mut self) -> [[Option; ROWS]; COLS] { + const CLICK_TIME: u16 = 200; + let mut events = [[None; ROWS]; COLS]; + for row in self.rows.iter_mut() { + row.set_low(); + } + + let millis_since_epoch: u16 = Systick::now().duration_since_epoch().to_millis() as u16; + + for (ri, row) in self.rows.iter_mut().enumerate() { + row.set_high(); + for (ci, col) in self.cols.iter_mut().enumerate() { + let cell = &mut self.state[ci][ri]; + let pressed = col.is_high(); + match (&cell, pressed) { + (ButtonState::Released, true) => { + *cell = ButtonState::Ambigous(millis_since_epoch); + } + (ButtonState::Ambigous(pressed_at), true) + if millis_since_epoch.wrapping_sub(*pressed_at) > CLICK_TIME => + { + events[ci][ri] = Some(ButtonEvent::Pressed); + *cell = ButtonState::Pressed; + } + (ButtonState::Ambigous(pressed_at), false) + if millis_since_epoch.wrapping_sub(*pressed_at) <= CLICK_TIME => + { + events[ci][ri] = Some(ButtonEvent::Clicked); + *cell = ButtonState::Released; + } + (ButtonState::Pressed, false) => { + events[ci][ri] = Some(ButtonEvent::Released); + *cell = ButtonState::Released; + } + _ => {} + } + } + row.set_low(); + } + + events + } +} +#[derive(Copy, Clone, Format)] +pub enum ButtonEvent { + Clicked, + Pressed, + Released, +} +#[derive(Copy, Clone, Format, PartialEq, Eq)] +pub enum ButtonState { + Ambigous(u16), + Pressed, + Released, +} diff --git a/canome/src/stm32/motorfader.rs b/canome/src/stm32/motorfader.rs new file mode 100644 index 0000000..985dc24 --- /dev/null +++ b/canome/src/stm32/motorfader.rs @@ -0,0 +1,130 @@ +use core::{fmt::Debug, marker::PhantomData}; + +use embedded_hal::{ + adc::{Channel, OneShot}, + digital::v2::{InputPin, OutputPin}, + PwmPin, +}; +use rtic_monotonics::systick::{fugit::ExtU32, Systick}; +use stm32f1xx_hal::gpio::{Dynamic, Pin, HL}; + +pub struct Fader +where + PA: PwmPin, + PB: PwmPin, + PP: Channel, + Pin: HL, +{ + pub cap_avg: u16, + pub target: u16, + pub touched: bool, + pub position: u16, + pin_a: PA, + pin_b: PB, + pin_pot: PP, + adc_pot: PhantomData, + cap_pin: Pin, +} + +impl Fader +where + PA: PwmPin, + PB: PwmPin, + PP: Channel, + Pin: HL, +{ + pub fn new( + target: u16, + pin_a: PA, + pin_b: PB, + pin_pot: PP, + cap_pin: Pin, + ) -> Self { + Self { + cap_avg: 0, + target, + touched: false, + position: 0, + pin_a, + pin_b, + pin_pot, + adc_pot: PhantomData, + cap_pin, + } + } + pub async fn drive(&mut self, pot_adc: &mut O, crl: &mut as HL>::Cr) + where + O: OneShot, + >::Error: Debug, + { + //TODO remove unwrap + self.position = pot_adc.read(&mut self.pin_pot).unwrap() * 16; + + (self.touched, self.cap_avg) = sense_touch(&mut self.cap_pin, crl, self.cap_avg).await; + + let diff: i32 = (self.position as i32) - (self.target as i32); + + const START: u32 = 850; + + //TODO rework fader movement + let p = |max: u16| { + ((START + ((max as u32) - START) * diff.unsigned_abs() * 2 / (u16::MAX as u32)) as u16) + .clamp(0, max) + }; + + if self.touched { + self.pin_a.set_duty(0); + self.pin_b.set_duty(0); + } else if diff < 0 { + self.pin_a.set_duty(0); + self.pin_b.set_duty(p(self.pin_b.get_max_duty())); + } else { + self.pin_a.set_duty(p(self.pin_a.get_max_duty())); + self.pin_b.set_duty(0); + } + } +} + +//TODO rework sensing +pub async fn sense_touch( + pin: &mut Pin, + crl: &mut as HL>::Cr, + last_avg: u16, +) -> (bool, u16) +where + Pin: HL, +{ + const DEV: u16 = 5; + const SAMPLES: usize = 4; + let mut samples = [0_u16; SAMPLES]; + for sample in samples.iter_mut() { + pin.make_push_pull_output(crl); + pin.set_high(); + Systick::delay(5.micros()).await; + pin.set_low(); + Systick::delay(5.micros()).await; + + pin.make_floating_input(crl); + while pin.is_low().unwrap() && (*sample <= last_avg + DEV || last_avg == 0) { + *sample += 1; + } + } + + let total = samples.iter().sum::() / SAMPLES as u16; + + if last_avg == 0 { + (false, total) + } else if total > last_avg && diff(total, last_avg) > DEV { + (true, last_avg) + } else { + (false, total) + } +} + +fn diff(a: u16, b: u16) -> u16 { + if a > b { + a - b + } else { + b - a + } +} diff --git a/canome/src/time.rs b/canome/src/time.rs new file mode 100644 index 0000000..a60c26b --- /dev/null +++ b/canome/src/time.rs @@ -0,0 +1,40 @@ +use defmt::Format; + +#[derive(Default, Debug, Format, Clone, Copy, PartialEq, Eq)] +pub struct Timestamp { + millis_since_epoch: i64, +} + +#[cfg(feature = "can")] +mod can { + use crate::{can::CanDataFormat, Decode, Encode}; + + use super::Timestamp; + + impl Encode for Timestamp { + fn encode(self) -> CanDataFormat { + let timestamp = self.millis_since_epoch.to_be_bytes(); + CanDataFormat::new(×tamp) + } + } + + impl Decode for Timestamp { + fn decode(value: CanDataFormat) -> Result + where + Self: Sized, + { + let data = value.data(); + if data.len() != 8 { + return Err(value); + } + + let mut timestamp = [0; 8]; + timestamp.copy_from_slice(data); + let timestamp = i64::from_be_bytes(timestamp); + + Ok(Self { + millis_since_epoch: timestamp, + }) + } + } +}