diff options
| author | unitexe <unitexe70@gmail.com> | 2026-04-08 23:53:17 -0500 |
|---|---|---|
| committer | unitexe <unitexe70@gmail.com> | 2026-04-08 23:54:28 -0500 |
| commit | de3f7ed45185f3a678ba0de04d98cc9ac92de0c8 (patch) | |
| tree | 3db0159c1370f240d42c1e8b89a67247538f43d1 | |
Initial commit
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Cargo.lock | 698 | ||||
| -rw-r--r-- | Cargo.toml | 6 | ||||
| -rw-r--r-- | LICENSE | 21 | ||||
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | tmp117-cli/Cargo.toml | 11 | ||||
| -rw-r--r-- | tmp117-cli/src/main.rs | 379 | ||||
| -rw-r--r-- | tmp117/Cargo.toml | 7 | ||||
| -rw-r--r-- | tmp117/src/client.rs | 199 | ||||
| -rw-r--r-- | tmp117/src/conf.rs | 547 | ||||
| -rw-r--r-- | tmp117/src/eeprom.rs | 7 | ||||
| -rw-r--r-- | tmp117/src/id.rs | 52 | ||||
| -rw-r--r-- | tmp117/src/lib.rs | 5 | ||||
| -rw-r--r-- | tmp117/src/reg.rs | 74 |
14 files changed, 2009 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..64780de --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,698 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.2.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clap" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "embedded-hal-nb" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605" +dependencies = [ + "embedded-hal", + "nb", +] + +[[package]] +name = "env_filter" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "gpio-cdev" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09831ec59b80be69e75d29cf36e16afbbe5fd1af9c1bf4689ad91c77db5aa6a6" +dependencies = [ + "bitflags 2.11.0", + "libc", + "nix 0.27.1", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "i2cdev" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b940f7497c4f95b863b21cd34c3737b53a67d80d94cf29055d7f7eeca6ffdb4" +dependencies = [ + "bitflags 2.11.0", + "byteorder", + "libc", + "nix 0.26.4", +] + +[[package]] +name = "io-kit-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" +dependencies = [ + "core-foundation-sys", + "mach2", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "jiff" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "libc" +version = "0.2.184" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" + +[[package]] +name = "linux-embedded-hal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8a605c95f708c78554738a12153b213f107d3bd5323f7ce32d6deb3faafb40" +dependencies = [ + "cast", + "embedded-hal", + "embedded-hal-nb", + "gpio-cdev", + "i2cdev", + "nb", + "nix 0.27.1", + "serialport", + "spidev", + "sysfs_gpio", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "nix" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" +dependencies = [ + "bitflags 1.3.2", + "cc", + "cfg-if", + "libc", + "memoffset 0.6.5", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.7.1", + "pin-utils", +] + +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "libc", +] + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serialport" +version = "4.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4d91116f97173694f1642263b2ff837f80d933aa837e2314969f6728f661df3" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "core-foundation", + "core-foundation-sys", + "io-kit-sys", + "mach2", + "nix 0.26.4", + "scopeguard", + "unescaper", + "windows-sys 0.52.0", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "spidev" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32dadd0a877f0652fa52dbc4d2ed9f4877bea5cd30725507b36e1970a5ef0519" +dependencies = [ + "bitflags 2.11.0", + "libc", + "nix 0.26.4", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sysfs_gpio" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8808c55bc926565c62ef7838bcaa8add51585236803e2bdfa1472e3a3ab5e17" +dependencies = [ + "nix 0.23.2", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tmp117" +version = "0.1.0" +dependencies = [ + "embedded-hal", +] + +[[package]] +name = "tmp117-cli" +version = "0.1.0" +dependencies = [ + "clap", + "env_logger", + "linux-embedded-hal", + "log", + "tmp117", +] + +[[package]] +name = "unescaper" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4064ed685c487dbc25bd3f0e9548f2e34bab9d18cefc700f9ec2dba74ba1138e" +dependencies = [ + "thiserror", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..73e389d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] +members = [ + "tmp117", + "tmp117-cli", +] +resolver = "2" @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Closed Circuit Consulting + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b7e2030 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# ti-tmp117-rs +Library and CLI for interacting with Texas Instruments TMP117 temperature sensor. diff --git a/tmp117-cli/Cargo.toml b/tmp117-cli/Cargo.toml new file mode 100644 index 0000000..79453f5 --- /dev/null +++ b/tmp117-cli/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "tmp117-cli" +version = "0.1.0" +edition = "2024" + +[dependencies] +clap = { version = "4.6.0", features = ["derive"] } +env_logger = "0.11.10" +linux-embedded-hal = "0.4.1" +log = "0.4.29" +tmp117 = { version = "0.1.0", path = "../tmp117" } diff --git a/tmp117-cli/src/main.rs b/tmp117-cli/src/main.rs new file mode 100644 index 0000000..0be782a --- /dev/null +++ b/tmp117-cli/src/main.rs @@ -0,0 +1,379 @@ +use clap::{Parser, Subcommand}; +use linux_embedded_hal::{Delay, I2cdev}; +use log::{info, trace}; +use tmp117::client::TMP117; +use tmp117::conf::{ + AlertPinSelect, Configuration, ConversionAveragingMode, ConversionMode, Mode, PinPolarity, +}; + +#[derive(Parser)] +#[command(version, about, long_about = None)] +struct Args { + /// I2C device path + #[arg(short('p'), long, default_value = "/dev/i2c-0")] + i2c_device_path: String, + + //// Temperature sensor address on I2C bus + #[arg(short('a'), long, default_value_t = 0x57)] + slave_address: u8, + + //// The command to run + #[command(subcommand)] + command: Option<Commands>, +} + +#[derive(Subcommand)] +enum TemperatureCommands { + /// Get the temperature from the sensor as raw value or in celsius + Get { + /// Get the temperature in celsius + #[arg(short, long, default_value_t = false)] + celsius: bool, + }, +} + +#[derive(Subcommand)] +enum ConfigureCommands { + /// Set the configuration register using raw value + Set { + /// Configuration value + #[arg(short, long)] + configuration: u16, + }, + + /// Set the configuration register using named fields + SetTyped { + /// If true then the ALERT pin reflects the status of the data ready flag otherwise it reflects the status of the alert flags + #[arg(short('d'), long, default_value_t = false)] + alert_pin_reflects_data_ready: bool, + + /// If true then the ALERT pin is active high otherwise it is active low + #[arg(short('p'), long, default_value_t = false)] + alert_pin_active_high: bool, + + /// If true then the sensor will operate in thermal mode otherwise it is in alert mode + #[arg(short, long, default_value_t = false)] + thermal_mode: bool, + + /// The number of conversion results that are collected and averaged (accumulative, not running) before updating temperature register + #[arg(short('c'), long, default_value_t = 8)] + averaged_conversions: u8, + }, + + /// Get the configuration from the sensor as raw value + Get {}, + + /// Get the configuration from the sensor as named fields + GetFields {}, +} + +#[derive(Subcommand)] +enum HighLimitCommands { + /// Set the high limit as raw value or in celsius + Set { + /// 16-bit high limit for comparison with the temperature result. One LSB equals 7.8125 m°C. The range of the register is ±256 °C. Negative numbers are represented in binary two's complement format. Following power-up or a general-call reset, the high-limit register is loaded with the stored value from the EEPROM. The factory default reset value is 6000h. + #[arg(short, long)] + limit: f32, + + /// The provided limit is temperature in celsius + #[arg(short, long, default_value_t = false)] + celsius: bool, + }, + + /// Get the high limit temperature as raw value or in celsius + Get { + /// Get the high limit in celsius + #[arg(short, long, default_value_t = false)] + celsius: bool, + }, +} + +#[derive(Subcommand)] +enum LowLimitCommands { + /// Set the low limit as raw value or in celsius + Set { + /// 16-bit low limit for comparison with the temperature result. One LSB equals 7.8125 m°C. The range of the register is ±256 °C. Negative numbers are represented in binary two's complement format. Following power-up or a general-call reset, the high-limit register is loaded with the stored value from the EEPROM. The factory default reset value is 8000h. + #[arg(short, long)] + limit: f32, + + /// The provided limit is temperature in celsius + #[arg(short, long, default_value_t = false)] + celsius: bool, + }, + + /// Get the low limit temperature as raw value or in celsius + Get { + /// Get the low limit in celsius + #[arg(short, long, default_value_t = false)] + celsius: bool, + }, +} + +#[derive(Subcommand)] +enum OffsetCommands { + /// Set the temperature offset as raw value or in celsius + Set { + /// Offset will be added to temperature result after linearization. It has a same resolution of 7.8125 m°C and same range of ±256 °C as the temperature result register. The data format is the same as the temperature register. If the added result is out of boundary, then the temperature result will show as the maximum or minimum value. + #[arg(short, long)] + offset: f32, + + /// The provided offset is temperature in celsius + #[arg(short, long, default_value_t = false)] + celsius: bool, + }, + + /// Get the temperature offset as raw value or in celsius + Get { + /// Get the temperature offset in celsius + #[arg(short, long, default_value_t = false)] + celsius: bool, + }, +} + +#[derive(Subcommand)] +enum Commands { + /// Interact with temperature register + Temperature { + #[command(subcommand)] + command: TemperatureCommands, + }, + + /// Interact with configuration register + Configure { + #[command(subcommand)] + command: ConfigureCommands, + }, + + /// Interact with high limit register + HighLimit { + #[command(subcommand)] + command: HighLimitCommands, + }, + + /// Interact with low limit register + LowLimit { + #[command(subcommand)] + command: LowLimitCommands, + }, + + /// Interact with temperature offset register + Offset { + #[command(subcommand)] + command: OffsetCommands, + }, + + /// Reset the sensor + Reset {}, +} + +fn main() { + env_logger::init(); + + let cli = Args::parse(); + + trace!("I2C device path: {}", &cli.i2c_device_path); + trace!("I2C slave address: {:#04x}", cli.slave_address); + + match &cli.command { + Some(Commands::Temperature { command }) => match command { + TemperatureCommands::Get { celsius } => { + info!("Getting temperature..."); + + let i2c = I2cdev::new(&cli.i2c_device_path).unwrap(); + let delay = Delay; + let mut tmp117 = TMP117::new(cli.slave_address, i2c, delay); + + if *celsius { + let temperature = tmp117.get_temperature_celsius().unwrap(); + println!("{:.4} °C", temperature); + } else { + let temperature = tmp117.get_temperature().unwrap(); + println!("{:#06x}", temperature); + } + } + }, + Some(Commands::Configure { command }) => match command { + ConfigureCommands::Set { configuration } => { + info!("Setting configuration..."); + trace!("Configuration: {:#06x}", configuration); + + let i2c = I2cdev::new(&cli.i2c_device_path).unwrap(); + let delay = Delay; + let mut tmp117 = TMP117::new(cli.slave_address, i2c, delay); + + tmp117.configure(*configuration).unwrap(); + } + ConfigureCommands::SetTyped { + alert_pin_reflects_data_ready, + alert_pin_active_high, + thermal_mode, + averaged_conversions, + } => { + info!("Setting configuration..."); + trace!( + "Alert pin reflects data ready? {}", + alert_pin_reflects_data_ready + ); + trace!("Alert pin active high? {}", alert_pin_active_high); + trace!("Thermal mode enabled? {}", thermal_mode); + trace!("Averaged conversions: {}", averaged_conversions); + + let configuration = Configuration { + alert_pin_select: match alert_pin_reflects_data_ready { + false => AlertPinSelect::Alert, + true => AlertPinSelect::DataReady, + }, + alert_pin_polarity: match alert_pin_active_high { + false => PinPolarity::ActiveLow, + true => PinPolarity::ActiveHigh, + }, + mode: match thermal_mode { + false => Mode::Alert, + true => Mode::Thermal, + }, + conversion_averaging_mode: match averaged_conversions { + 0 => ConversionAveragingMode::None, + 8 => ConversionAveragingMode::Avg8, + 32 => ConversionAveragingMode::Avg32, + 64 => ConversionAveragingMode::Avg64, + _ => todo!("Handle invalid averaging value"), + }, + conversion_mode: ConversionMode::Continuous, + }; + + let i2c = I2cdev::new(&cli.i2c_device_path).unwrap(); + let delay = Delay; + let mut tmp117 = TMP117::new(cli.slave_address, i2c, delay); + + tmp117.configure_typed(configuration).unwrap(); + } + ConfigureCommands::Get {} => { + info!("Getting configuration..."); + + let i2c = I2cdev::new(&cli.i2c_device_path).unwrap(); + let delay = Delay; + let mut tmp117 = TMP117::new(cli.slave_address, i2c, delay); + + let configuration = tmp117.get_configuration().unwrap(); + println!("{:#06x}", configuration) + } + ConfigureCommands::GetFields {} => { + info!("Getting configuration..."); + + let i2c = I2cdev::new(&cli.i2c_device_path).unwrap(); + let delay = Delay; + let mut tmp117 = TMP117::new(cli.slave_address, i2c, delay); + + let configuration = tmp117.get_configuration_typed().unwrap(); + println!("{}", configuration); + } + }, + Some(Commands::HighLimit { command }) => match command { + HighLimitCommands::Set { limit, celsius } => { + info!("Setting high limit..."); + + let i2c = I2cdev::new(&cli.i2c_device_path).unwrap(); + let delay = Delay; + let mut tmp117 = TMP117::new(cli.slave_address, i2c, delay); + + if *celsius { + trace!("Limit (°C): {:.4}", *limit); + tmp117.configure_high_limit_celsius(*limit).unwrap(); + } else { + trace!("Limit: {:#06x}", (*limit as i16)); + tmp117.configure_high_limit(*limit as i16).unwrap(); + } + } + HighLimitCommands::Get { celsius } => { + info!("Getting high limit..."); + + let i2c = I2cdev::new(&cli.i2c_device_path).unwrap(); + let delay = Delay; + let mut tmp117 = TMP117::new(cli.slave_address, i2c, delay); + + if *celsius { + let limit = tmp117.get_high_limit_celsius().unwrap(); + println!("{:.4} °C", limit); + } else { + let limit = tmp117.get_high_limit().unwrap(); + println!("{:#06x}", limit); + } + } + }, + Some(Commands::LowLimit { command }) => match command { + LowLimitCommands::Set { limit, celsius } => { + info!("Setting low limit..."); + + let i2c = I2cdev::new(&cli.i2c_device_path).unwrap(); + let delay = Delay; + let mut tmp117 = TMP117::new(cli.slave_address, i2c, delay); + + if *celsius { + trace!("Limit (°C): {:.4}", *limit); + tmp117.configure_low_limit_celsius(*limit).unwrap(); + } else { + trace!("Limit: {:#06x}", (*limit as i16)); + tmp117.configure_low_limit(*limit as i16).unwrap(); + } + } + LowLimitCommands::Get { celsius } => { + info!("Getting low limit..."); + + let i2c = I2cdev::new(&cli.i2c_device_path).unwrap(); + let delay = Delay; + let mut tmp117 = TMP117::new(cli.slave_address, i2c, delay); + + if *celsius { + let limit = tmp117.get_low_limit_celsius().unwrap(); + println!("{:.4} °C", limit); + } else { + let limit = tmp117.get_low_limit().unwrap(); + println!("{:#06x}", limit); + } + } + }, + Some(Commands::Offset { command }) => match command { + OffsetCommands::Set { offset, celsius } => { + info!("Setting temperature offset..."); + + let i2c = I2cdev::new(&cli.i2c_device_path).unwrap(); + let delay = Delay; + let mut tmp117 = TMP117::new(cli.slave_address, i2c, delay); + + if *celsius { + trace!("Offset (°C): {:.4}", *offset); + tmp117 + .configure_offset_temperature_celsius(*offset) + .unwrap(); + } else { + trace!("Offset: {:#06x}", (*offset as i16)); + tmp117.configure_offset_temperature(*offset as i16).unwrap(); + } + } + OffsetCommands::Get { celsius } => { + info!("Getting temperature offset..."); + + let i2c = I2cdev::new(&cli.i2c_device_path).unwrap(); + let delay = Delay; + let mut tmp117 = TMP117::new(cli.slave_address, i2c, delay); + + if *celsius { + let offset = tmp117.get_offset_temperature_celsius().unwrap(); + println!("{:.4} °C", offset); + } else { + let offset = tmp117.get_offset_temperature().unwrap(); + println!("{:#06x}", offset); + } + } + }, + Some(Commands::Reset {}) => { + info!("Resetting TI TMP117..."); + + let i2c = I2cdev::new(&cli.i2c_device_path).unwrap(); + let delay = Delay; + let mut tmp117 = TMP117::new(cli.slave_address, i2c, delay); + tmp117.software_reset().unwrap(); + } + None => {} + } +} diff --git a/tmp117/Cargo.toml b/tmp117/Cargo.toml new file mode 100644 index 0000000..aebc127 --- /dev/null +++ b/tmp117/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "tmp117" +version = "0.1.0" +edition = "2024" + +[dependencies] +embedded-hal = "1.0.0" diff --git a/tmp117/src/client.rs b/tmp117/src/client.rs new file mode 100644 index 0000000..6618d72 --- /dev/null +++ b/tmp117/src/client.rs @@ -0,0 +1,199 @@ +use crate::conf::Configuration; +use crate::eeprom::EEPROM; +use crate::id::Identity; +use crate::reg::Registers; +use embedded_hal::{delay::DelayNs, i2c::I2c}; + +pub struct TMP117<I2C, Delay> { + addr: u8, + i2c: I2C, + delay: Delay, +} + +impl<I2C: I2c, Delay: DelayNs> TMP117<I2C, Delay> { + const CELSIUS_PER_LSB: f32 = 7.8125e-3; + + pub fn new(addr: u8, i2c: I2C, delay: Delay) -> Self { + Self { addr, i2c, delay } + } + + pub fn configure(&mut self, configuration: u16) -> Result<(), I2C::Error> { + let bytes = configuration.to_be_bytes(); + self.i2c.write( + self.addr, + &[Registers::Configuration as u8, bytes[0], bytes[1]], + ) + } + + pub fn configure_typed(&mut self, configuration: Configuration) -> Result<(), I2C::Error> { + let configuration = u16::from(&configuration); + self.configure(configuration) + } + + pub fn get_configuration(&mut self) -> Result<u16, I2C::Error> { + let mut bytes = [0u8; 2]; + self.i2c + .write_read(self.addr, &[Registers::Configuration as u8], &mut bytes)?; + let configuration = u16::from_be_bytes(bytes); + Ok(configuration) + } + + pub fn get_configuration_typed(&mut self) -> Result<Configuration, I2C::Error> { + let configuration = self.get_configuration()?; + let configuration = Configuration::from(configuration); + Ok(configuration) + } + + pub fn configure_low_limit(&mut self, limit: i16) -> Result<(), I2C::Error> { + let bytes = limit.to_be_bytes(); + self.i2c + .write(self.addr, &[Registers::LowLimit as u8, bytes[0], bytes[1]]) + } + + pub fn configure_low_limit_celsius(&mut self, limit: f32) -> Result<(), I2C::Error> { + let limit = limit / Self::CELSIUS_PER_LSB; + let limit = limit.round() as i16; + self.configure_low_limit(limit) + } + + pub fn get_low_limit(&mut self) -> Result<i16, I2C::Error> { + let mut limit = [0u8; 2]; + self.i2c + .write_read(self.addr, &[Registers::LowLimit as u8], &mut limit)?; + let limit = i16::from_be_bytes(limit); + Ok(limit) + } + + pub fn get_low_limit_celsius(&mut self) -> Result<f32, I2C::Error> { + let limit = self.get_low_limit()?; + let limit = limit as f32 * Self::CELSIUS_PER_LSB; + Ok(limit) + } + + pub fn configure_high_limit(&mut self, limit: i16) -> Result<(), I2C::Error> { + let limit = limit.to_be_bytes(); + self.i2c + .write(self.addr, &[Registers::HighLimit as u8, limit[0], limit[1]]) + } + + pub fn configure_high_limit_celsius(&mut self, limit: f32) -> Result<(), I2C::Error> { + let limit = limit / Self::CELSIUS_PER_LSB; + let limit = limit.round() as i16; + self.configure_high_limit(limit) + } + + pub fn get_high_limit(&mut self) -> Result<i16, I2C::Error> { + let mut limit = [0u8; 2]; + self.i2c + .write_read(self.addr, &[Registers::HighLimit as u8], &mut limit)?; + let limit = i16::from_be_bytes(limit); + Ok(limit) + } + + pub fn get_high_limit_celsius(&mut self) -> Result<f32, I2C::Error> { + let high_limit = self.get_high_limit()?; + let high_limit = high_limit as f32 * Self::CELSIUS_PER_LSB; + Ok(high_limit) + } + + pub fn configure_offset_temperature(&mut self, offset: i16) -> Result<(), I2C::Error> { + let offset = offset.to_be_bytes(); + self.i2c.write( + self.addr, + &[Registers::TemperatureOffset as u8, offset[0], offset[1]], + ) + } + + pub fn configure_offset_temperature_celsius(&mut self, offset: f32) -> Result<(), I2C::Error> { + let offset = offset / Self::CELSIUS_PER_LSB; + let offset = offset.round() as i16; + self.configure_offset_temperature(offset) + } + + pub fn get_offset_temperature(&mut self) -> Result<i16, I2C::Error> { + let mut offset = [0u8; 2]; + self.i2c.write_read( + self.addr, + &[Registers::TemperatureOffset as u8], + &mut offset, + )?; + let offset = i16::from_be_bytes(offset); + Ok(offset) + } + + pub fn get_offset_temperature_celsius(&mut self) -> Result<f32, I2C::Error> { + let offset = self.get_offset_temperature()?; + let offset = offset as f32 * Self::CELSIUS_PER_LSB; + Ok(offset) + } + + pub fn get_eeprom_1(&mut self) -> Result<u16, I2C::Error> { + let mut eeprom = [0u8; 2]; + self.i2c + .write_read(self.addr, &[Registers::Eeprom1 as u8], &mut eeprom)?; + let eeprom = u16::from_be_bytes(eeprom); + Ok(eeprom) + } + + pub fn get_eeprom_2(&mut self) -> Result<u16, I2C::Error> { + let mut eeprom = [0u8; 2]; + self.i2c + .write_read(self.addr, &[Registers::Eeprom2 as u8], &mut eeprom)?; + let eeprom = u16::from_be_bytes(eeprom); + Ok(eeprom) + } + + pub fn get_eeprom_3(&mut self) -> Result<u16, I2C::Error> { + let mut eeprom = [0u8; 2]; + self.i2c + .write_read(self.addr, &[Registers::Eeprom3 as u8], &mut eeprom)?; + let eeprom = u16::from_be_bytes(eeprom); + Ok(eeprom) + } + + pub fn get_eeprom_all(&mut self) -> Result<EEPROM, I2C::Error> { + let eeprom1 = self.get_eeprom_1()?; + let eeprom2 = self.get_eeprom_2()?; + let eeprom3 = self.get_eeprom_3()?; + let eeprom = EEPROM { + eeprom1, + eeprom2, + eeprom3, + }; + Ok(eeprom) + } + + pub fn get_identity(&mut self) -> Result<Identity, I2C::Error> { + let mut identity = [0u8; 2]; + self.i2c + .write_read(self.addr, &[Registers::DeviceId as u8], &mut identity)?; + let identity = u16::from_be_bytes(identity); + let identity = Identity::from(identity); + Ok(identity) + } + + pub fn get_temperature(&mut self) -> Result<i16, I2C::Error> { + let mut temperature = [0u8; 2]; + self.i2c + .write_read(self.addr, &[Registers::Temperature as u8], &mut temperature)?; + let temperature = i16::from_be_bytes(temperature); + Ok(temperature) + } + + pub fn get_temperature_celsius(&mut self) -> Result<f32, I2C::Error> { + let temperature = self.get_temperature()?; + let temperature = temperature as f32 * Self::CELSIUS_PER_LSB; + Ok(temperature) + } + + pub fn software_reset(&mut self) -> Result<(), I2C::Error> { + let mask: u16 = 0x0002; + let mask = mask.to_be_bytes(); + self.i2c.write( + self.addr, + &[Registers::Configuration as u8, mask[0], mask[1]], + )?; + self.delay.delay_ms(2); + Ok(()) + } +} diff --git a/tmp117/src/conf.rs b/tmp117/src/conf.rs new file mode 100644 index 0000000..1ea275b --- /dev/null +++ b/tmp117/src/conf.rs @@ -0,0 +1,547 @@ +use core::fmt; + +#[derive(Debug, PartialEq)] +pub enum AlertPinSelect { + /// ALERT pin reflects the status of the alert flags + Alert, + + /// ALERT pin reflects the status of the data ready flag + DataReady, +} + +impl fmt::Display for AlertPinSelect { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Alert => write!(f, "Alert"), + Self::DataReady => write!(f, "Data Ready"), + } + } +} + +#[derive(Debug, PartialEq)] +pub enum PinPolarity { + /// ALERT pin is active low + ActiveLow, + + /// ALERT pin is active high + ActiveHigh, +} + +impl fmt::Display for PinPolarity { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::ActiveLow => write!(f, "Active Low"), + Self::ActiveHigh => write!(f, "Active High"), + } + } +} + +#[derive(Debug, PartialEq)] +pub enum Mode { + /// Alert mode + Alert, + + /// Thermal mode + Thermal, +} + +impl fmt::Display for Mode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Alert => write!(f, "Alert"), + Self::Thermal => write!(f, "Thermal"), + } + } +} + +#[derive(Debug, PartialEq)] +pub enum ConversionMode { + /// Continous conversion + Continuous, + + /// Shutdown + Shutdown, + + /// One-shot conversion + Oneshot, +} + +impl fmt::Display for ConversionMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Continuous => write!(f, "Continuous"), + Self::Shutdown => write!(f, "Shutdown"), + Self::Oneshot => write!(f, "One-shot"), + } + } +} + +/// Determines the number of conversion results that are collected and averaged before updating the temperature register. The average is an accumulated average, not a running average. +#[derive(Debug, PartialEq)] +pub enum ConversionAveragingMode { + /// No averaging + None, + + /// 8 averaged conversions + Avg8, + + /// 32 averaged conversions + Avg32, + + /// 64 averaged conversions + Avg64, +} + +impl ConversionAveragingMode { + fn minimum_conversion_cycle_time(&self) -> ConversionCycle { + match self { + ConversionAveragingMode::None => ConversionCycle::Microseconds15500, + ConversionAveragingMode::Avg8 => ConversionCycle::Milliseconds125, + ConversionAveragingMode::Avg32 => ConversionCycle::Milliseconds500, + ConversionAveragingMode::Avg64 => ConversionCycle::Seconds1, + } + } +} + +impl fmt::Display for ConversionAveragingMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::None => write!(f, "None"), + Self::Avg8 => write!(f, "8 conversions"), + Self::Avg32 => write!(f, "32 conversions"), + Self::Avg64 => write!(f, "64 conversions"), + } + } +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum ConversionCycle { + Microseconds15500, + Milliseconds125, + Milliseconds250, + Milliseconds500, + Seconds1, + Seconds4, + Seconds8, + Seconds16, +} + +impl fmt::Display for ConversionCycle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Microseconds15500 => write!(f, "15.5ms"), + Self::Milliseconds125 => write!(f, "125ms"), + Self::Milliseconds250 => write!(f, "250ms"), + Self::Milliseconds500 => write!(f, "500ms"), + Self::Seconds1 => write!(f, "1s"), + Self::Seconds4 => write!(f, "4s"), + Self::Seconds8 => write!(f, "8s"), + Self::Seconds16 => write!(f, "16s"), + } + } +} + +pub enum ConfigurationError { + CycleTimeAndAveragingModeAreIncompatible, +} + +pub struct Configuration { + /// ALERT pin select bit + pub alert_pin_select: AlertPinSelect, + + /// ALERT pin polarity bit + pub alert_pin_polarity: PinPolarity, + + /// Therm/alert mode select + pub mode: Mode, + + /// Conversion averaging modes + pub conversion_averaging_mode: ConversionAveragingMode, + + /// Conversion cycle bit + pub conversion_cycle: ConversionCycle, + + /// Conversion mode + pub conversion_mode: ConversionMode, +} + +impl Configuration { + pub fn validate(&self) -> Result<(), ConfigurationError> { + if self.conversion_cycle + < self + .conversion_averaging_mode + .minimum_conversion_cycle_time() + { + Err(ConfigurationError::CycleTimeAndAveragingModeAreIncompatible) + } else { + Ok(()) + } + } +} + +impl fmt::Display for Configuration { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "Alert Pin Select: {}", self.alert_pin_select)?; + writeln!(f, "Alert Pin Polarity: {}", self.alert_pin_polarity)?; + writeln!(f, "Mode: {}", self.mode)?; + writeln!( + f, + "Conversion Averaging: {}", + self.conversion_averaging_mode + )?; + writeln!(f, "Conversion Cycle: {}", self.conversion_cycle)?; + write!(f, "Conversion Mode: {}", self.conversion_mode) + } +} + +impl From<&Configuration> for u16 { + fn from(configuration: &Configuration) -> u16 { + let mut bits: u16 = 0; + + bits |= match configuration.alert_pin_select { + AlertPinSelect::Alert => 0 << 2, + AlertPinSelect::DataReady => 1 << 2, + }; + + bits |= match configuration.alert_pin_polarity { + PinPolarity::ActiveLow => 0 << 3, + PinPolarity::ActiveHigh => 1 << 3, + }; + + bits |= match configuration.mode { + Mode::Alert => 0 << 4, + Mode::Thermal => 1 << 4, + }; + + bits |= match configuration.conversion_averaging_mode { + ConversionAveragingMode::None => 0b00 << 5, + ConversionAveragingMode::Avg8 => 0b01 << 5, + ConversionAveragingMode::Avg32 => 0b10 << 5, + ConversionAveragingMode::Avg64 => 0b11 << 5, + }; + + bits |= match configuration.conversion_cycle { + ConversionCycle::Microseconds15500 => 0b000 << 7, + ConversionCycle::Milliseconds125 => 0b001 << 7, + ConversionCycle::Milliseconds250 => 0b010 << 7, + ConversionCycle::Milliseconds500 => 0b011 << 7, + ConversionCycle::Seconds1 => 0b100 << 7, + ConversionCycle::Seconds4 => 0b101 << 7, + ConversionCycle::Seconds8 => 0b110 << 7, + ConversionCycle::Seconds16 => 0b111 << 7, + }; + + bits |= match configuration.conversion_mode { + ConversionMode::Continuous => 0b00 << 10, + ConversionMode::Shutdown => 0b01 << 10, + ConversionMode::Oneshot => 0b11 << 10, + }; + + bits + } +} + +impl From<u16> for Configuration { + fn from(register: u16) -> Self { + let alert_pin_select = (register >> 2) & 0x1; + let alert_pin_polarity = (register >> 3) & 0x1; + let mode = (register >> 4) & 0x1; + let conversion_averaging_mode = (register >> 5) & 0x3; + let conversion_cycle = (register >> 7) & 0x7; + let conversion_mode = (register >> 10) & 0x3; + + let alert_pin_select = match alert_pin_select { + 0 => AlertPinSelect::Alert, + 1 => AlertPinSelect::DataReady, + _ => todo!("Handle unsupported alert pin select"), + }; + let alert_pin_polarity = match alert_pin_polarity { + 0 => PinPolarity::ActiveLow, + 1 => PinPolarity::ActiveHigh, + _ => todo!("Handle unsupported alert pin polarity"), + }; + let mode = match mode { + 0 => Mode::Alert, + 1 => Mode::Thermal, + _ => todo!("Handle unsupported mode"), + }; + let conversion_averaging_mode = match conversion_averaging_mode { + 0b00 => ConversionAveragingMode::None, + 0b01 => ConversionAveragingMode::Avg8, + 0b10 => ConversionAveragingMode::Avg32, + 0b11 => ConversionAveragingMode::Avg64, + _ => todo!("Handle unsupported conversion averaging mode"), + }; + let conversion_cycle = match conversion_cycle { + 0b000 => ConversionCycle::Microseconds15500, + 0b001 => ConversionCycle::Milliseconds125, + 0b010 => ConversionCycle::Milliseconds250, + 0b011 => ConversionCycle::Milliseconds500, + 0b100 => ConversionCycle::Seconds1, + 0b101 => ConversionCycle::Seconds4, + 0b110 => ConversionCycle::Seconds8, + 0b111 => ConversionCycle::Seconds16, + _ => todo!("Handle unsupported conversion cycle"), + }; + let conversion_mode = match conversion_mode { + 0b00 | 0b10 => ConversionMode::Continuous, + 0b01 => ConversionMode::Shutdown, + 0b11 => ConversionMode::Oneshot, + _ => todo!("Handle unsupported conversion mode"), + }; + + Self { + alert_pin_select, + alert_pin_polarity, + mode, + conversion_averaging_mode, + conversion_cycle, + conversion_mode, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // helpers to construct common configurations + fn default_configuration() -> Configuration { + Configuration { + alert_pin_select: AlertPinSelect::Alert, + alert_pin_polarity: PinPolarity::ActiveLow, + mode: Mode::Alert, + conversion_averaging_mode: ConversionAveragingMode::Avg8, + conversion_cycle: ConversionCycle::Seconds1, + conversion_mode: ConversionMode::Continuous, + } + } + + // --- From<&Configuration> for u16 --- + + #[test] + fn test_alert_pin_select_alert_bit() { + let mut config = default_configuration(); + config.alert_pin_select = AlertPinSelect::Alert; + assert_eq!(u16::from(&config) & (1 << 2), 0); + } + + #[test] + fn test_alert_pin_select_data_ready_bit() { + let mut config = default_configuration(); + config.alert_pin_select = AlertPinSelect::DataReady; + assert_ne!(u16::from(&config) & (1 << 2), 0); + } + + #[test] + fn test_pin_polarity_active_low_bit() { + let mut config = default_configuration(); + config.alert_pin_polarity = PinPolarity::ActiveLow; + assert_eq!(u16::from(&config) & (1 << 3), 0); + } + + #[test] + fn test_pin_polarity_active_high_bit() { + let mut config = default_configuration(); + config.alert_pin_polarity = PinPolarity::ActiveHigh; + assert_ne!(u16::from(&config) & (1 << 3), 0); + } + + #[test] + fn test_mode_alert_bit() { + let mut config = default_configuration(); + config.mode = Mode::Alert; + assert_eq!(u16::from(&config) & (1 << 4), 0); + } + + #[test] + fn test_mode_thermal_bit() { + let mut config = default_configuration(); + config.mode = Mode::Thermal; + assert_ne!(u16::from(&config) & (1 << 4), 0); + } + + #[test] + fn test_conversion_averaging_mode_bits() { + let mut config = default_configuration(); + + config.conversion_averaging_mode = ConversionAveragingMode::None; + assert_eq!((u16::from(&config) >> 5) & 0x3, 0b00); + + config.conversion_averaging_mode = ConversionAveragingMode::Avg8; + assert_eq!((u16::from(&config) >> 5) & 0x3, 0b01); + + config.conversion_averaging_mode = ConversionAveragingMode::Avg32; + assert_eq!((u16::from(&config) >> 5) & 0x3, 0b10); + + config.conversion_averaging_mode = ConversionAveragingMode::Avg64; + assert_eq!((u16::from(&config) >> 5) & 0x3, 0b11); + } + + #[test] + fn test_conversion_cycle_bits() { + let mut config = default_configuration(); + + config.conversion_cycle = ConversionCycle::Microseconds15500; + assert_eq!((u16::from(&config) >> 7) & 0x7, 0b000); + + config.conversion_cycle = ConversionCycle::Milliseconds125; + assert_eq!((u16::from(&config) >> 7) & 0x7, 0b001); + + config.conversion_cycle = ConversionCycle::Milliseconds250; + assert_eq!((u16::from(&config) >> 7) & 0x7, 0b010); + + config.conversion_cycle = ConversionCycle::Milliseconds500; + assert_eq!((u16::from(&config) >> 7) & 0x7, 0b011); + + config.conversion_cycle = ConversionCycle::Seconds1; + assert_eq!((u16::from(&config) >> 7) & 0x7, 0b100); + + config.conversion_cycle = ConversionCycle::Seconds4; + assert_eq!((u16::from(&config) >> 7) & 0x7, 0b101); + + config.conversion_cycle = ConversionCycle::Seconds8; + assert_eq!((u16::from(&config) >> 7) & 0x7, 0b110); + + config.conversion_cycle = ConversionCycle::Seconds16; + assert_eq!((u16::from(&config) >> 7) & 0x7, 0b111); + } + + #[test] + fn test_conversion_cycle_ordering() { + assert!(ConversionCycle::Microseconds15500 < ConversionCycle::Milliseconds125); + assert!(ConversionCycle::Milliseconds125 < ConversionCycle::Milliseconds250); + assert!(ConversionCycle::Milliseconds250 < ConversionCycle::Milliseconds500); + assert!(ConversionCycle::Milliseconds500 < ConversionCycle::Seconds1); + assert!(ConversionCycle::Seconds1 < ConversionCycle::Seconds4); + assert!(ConversionCycle::Seconds4 < ConversionCycle::Seconds8); + assert!(ConversionCycle::Seconds8 < ConversionCycle::Seconds16); + } + + #[test] + fn test_conversion_mode_bits() { + let mut config = default_configuration(); + + config.conversion_mode = ConversionMode::Continuous; + assert_eq!((u16::from(&config) >> 10) & 0x3, 0b00); + + config.conversion_mode = ConversionMode::Shutdown; + assert_eq!((u16::from(&config) >> 10) & 0x3, 0b01); + + config.conversion_mode = ConversionMode::Oneshot; + assert_eq!((u16::from(&config) >> 10) & 0x3, 0b11); + } + + // --- From<u16> for Configuration --- + + #[test] + fn test_configuration_from_factory_reset_value() { + // 0x0220 = 0b0000_0010_0010_0000 + // bits 6:5 = 0b01 -> Avg8 + // bits 11:10 = 0b00 -> Continuous + let config = Configuration::from(0x0220); + assert_eq!(config.alert_pin_select, AlertPinSelect::Alert); + assert_eq!(config.alert_pin_polarity, PinPolarity::ActiveLow); + assert_eq!(config.mode, Mode::Alert); + assert_eq!( + config.conversion_averaging_mode, + ConversionAveragingMode::Avg8 + ); + assert_eq!(config.conversion_mode, ConversionMode::Continuous); + } + + #[test] + fn test_configuration_from_thermal_mode() { + let register: u16 = 1 << 4; + let config = Configuration::from(register); + assert_eq!(config.mode, Mode::Thermal); + } + + #[test] + fn test_configuration_from_shutdown_mode() { + let register: u16 = 0b01 << 10; + let config = Configuration::from(register); + assert_eq!(config.conversion_mode, ConversionMode::Shutdown); + } + + // --- Round-trip --- + + #[test] + fn test_round_trip_factory_reset() { + let original: u16 = 0x0220; + let config = Configuration::from(original); + let restored = u16::from(&config); + assert_eq!(original, restored); + } + + #[test] + fn test_round_trip_all_fields_set() { + let config = Configuration { + alert_pin_select: AlertPinSelect::DataReady, + alert_pin_polarity: PinPolarity::ActiveHigh, + mode: Mode::Thermal, + conversion_averaging_mode: ConversionAveragingMode::Avg64, + conversion_cycle: ConversionCycle::Seconds1, + conversion_mode: ConversionMode::Oneshot, + }; + let bits = u16::from(&config); + let restored = Configuration::from(bits); + assert_eq!(restored.alert_pin_select, AlertPinSelect::DataReady); + assert_eq!(restored.alert_pin_polarity, PinPolarity::ActiveHigh); + assert_eq!(restored.mode, Mode::Thermal); + assert_eq!( + restored.conversion_averaging_mode, + ConversionAveragingMode::Avg64 + ); + assert_eq!(restored.conversion_cycle, ConversionCycle::Seconds1); + assert_eq!(restored.conversion_mode, ConversionMode::Oneshot); + } + + #[test] + fn test_validate_valid_combinations() { + let mut config = default_configuration(); + + // None allows any cycle time + config.conversion_averaging_mode = ConversionAveragingMode::None; + config.conversion_cycle = ConversionCycle::Microseconds15500; + assert!(config.validate().is_ok()); + + // Avg8 allows 125ms and above + config.conversion_averaging_mode = ConversionAveragingMode::Avg8; + config.conversion_cycle = ConversionCycle::Milliseconds125; + assert!(config.validate().is_ok()); + + // Avg32 allows 500ms and above + config.conversion_averaging_mode = ConversionAveragingMode::Avg32; + config.conversion_cycle = ConversionCycle::Milliseconds500; + assert!(config.validate().is_ok()); + + // Avg64 allows 1s and above + config.conversion_averaging_mode = ConversionAveragingMode::Avg64; + config.conversion_cycle = ConversionCycle::Seconds1; + assert!(config.validate().is_ok()); + } + + #[test] + fn test_validate_invalid_combinations() { + let mut config = default_configuration(); + + // Avg8 does not allow 15.5ms + config.conversion_averaging_mode = ConversionAveragingMode::Avg8; + config.conversion_cycle = ConversionCycle::Microseconds15500; + assert!(config.validate().is_err()); + + // Avg32 does not allow 125ms or 250ms + config.conversion_averaging_mode = ConversionAveragingMode::Avg32; + config.conversion_cycle = ConversionCycle::Milliseconds125; + assert!(config.validate().is_err()); + + config.conversion_cycle = ConversionCycle::Milliseconds250; + assert!(config.validate().is_err()); + + // Avg64 does not allow anything below 1s + config.conversion_averaging_mode = ConversionAveragingMode::Avg64; + config.conversion_cycle = ConversionCycle::Milliseconds500; + assert!(config.validate().is_err()); + } +} diff --git a/tmp117/src/eeprom.rs b/tmp117/src/eeprom.rs new file mode 100644 index 0000000..293c74f --- /dev/null +++ b/tmp117/src/eeprom.rs @@ -0,0 +1,7 @@ +pub struct EEPROM { + pub eeprom1: u16, + + pub eeprom2: u16, + + pub eeprom3: u16, +} diff --git a/tmp117/src/id.rs b/tmp117/src/id.rs new file mode 100644 index 0000000..e1fcc8b --- /dev/null +++ b/tmp117/src/id.rs @@ -0,0 +1,52 @@ +pub struct Identity { + pub device_id: u16, + + pub revision_number: u8, +} + +impl From<u16> for Identity { + fn from(register: u16) -> Self { + let device_id = register & 0x0FFF; + let revision_number = ((register >> 12) & 0xF) as u8; + Self { + device_id, + revision_number, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_identity_from_known_device_id() { + let identity = Identity::from(0x0117); + assert_eq!(identity.device_id, 0x117); + assert_eq!(identity.revision_number, 0); + } + + #[test] + fn test_identity_device_id_masked() { + // bits 11:0 are device ID + let identity = Identity::from(0x0FFF); + assert_eq!(identity.device_id, 0x0FFF); + assert_eq!(identity.revision_number, 0); + } + + #[test] + fn test_identity_revision_number_extracted() { + // bits 15:12 are revision + let identity = Identity::from(0x1000); + assert_eq!(identity.device_id, 0x000); + assert_eq!(identity.revision_number, 1); + } + + #[test] + fn test_identity_both_fields() { + // revision = 0xA, device_id = 0x117 + let identity = Identity::from(0xA117); + assert_eq!(identity.device_id, 0x117); + assert_eq!(identity.revision_number, 0xA); + } +} diff --git a/tmp117/src/lib.rs b/tmp117/src/lib.rs new file mode 100644 index 0000000..4c26e67 --- /dev/null +++ b/tmp117/src/lib.rs @@ -0,0 +1,5 @@ +pub mod client; +pub mod conf; +pub mod eeprom; +pub mod id; +pub mod reg; diff --git a/tmp117/src/reg.rs b/tmp117/src/reg.rs new file mode 100644 index 0000000..20ac429 --- /dev/null +++ b/tmp117/src/reg.rs @@ -0,0 +1,74 @@ +#[repr(u8)] +pub enum Registers { + Temperature = 0x00, + Configuration = 0x01, + HighLimit = 0x02, + LowLimit = 0x03, + Eeprom1 = 0x05, + Eeprom2 = 0x06, + TemperatureOffset = 0x07, + Eeprom3 = 0x08, + DeviceId = 0x0F, +} + +impl Registers { + pub const TEMPERATURE_RESET: u16 = 0x8000; + pub const CONFIGURATION_RESET: u16 = 0x0220; + pub const HIGH_LIMIT_RESET: u16 = 0x6000; + pub const LOW_LIMIT_RESET: u16 = 0x8000; + pub const TEMPERATURE_OFFSET_RESET: u16 = 0x0000; + pub const DEVICE_ID_RESET: u16 = 0x0117; + + pub fn factory_default_reset(&self) -> Option<u16> { + match self { + Registers::Temperature => Some(Registers::TEMPERATURE_RESET), + Registers::Configuration => Some(Registers::CONFIGURATION_RESET), + Registers::HighLimit => Some(Registers::HIGH_LIMIT_RESET), + Registers::LowLimit => Some(Registers::LOW_LIMIT_RESET), + Registers::TemperatureOffset => Some(Registers::TEMPERATURE_OFFSET_RESET), + Registers::DeviceId => Some(Registers::DEVICE_ID_RESET), + _ => None, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_register_addresses() { + assert_eq!(Registers::Temperature as u8, 0x00); + assert_eq!(Registers::Configuration as u8, 0x01); + assert_eq!(Registers::HighLimit as u8, 0x02); + assert_eq!(Registers::LowLimit as u8, 0x03); + assert_eq!(Registers::Eeprom1 as u8, 0x05); + assert_eq!(Registers::Eeprom2 as u8, 0x06); + assert_eq!(Registers::TemperatureOffset as u8, 0x07); + assert_eq!(Registers::Eeprom3 as u8, 0x08); + assert_eq!(Registers::DeviceId as u8, 0x0F); + } + + #[test] + fn test_factory_default_reset_known() { + assert_eq!(Registers::Temperature.factory_default_reset(), Some(0x8000)); + assert_eq!( + Registers::Configuration.factory_default_reset(), + Some(0x0220) + ); + assert_eq!(Registers::HighLimit.factory_default_reset(), Some(0x6000)); + assert_eq!(Registers::LowLimit.factory_default_reset(), Some(0x8000)); + assert_eq!( + Registers::TemperatureOffset.factory_default_reset(), + Some(0x0000) + ); + assert_eq!(Registers::DeviceId.factory_default_reset(), Some(0x0117)); + } + + #[test] + fn test_factory_default_reset_unknown() { + assert_eq!(Registers::Eeprom1.factory_default_reset(), None); + assert_eq!(Registers::Eeprom2.factory_default_reset(), None); + assert_eq!(Registers::Eeprom3.factory_default_reset(), None); + } +} |
