Repo of no-std crates for my personal embedded projects

feat: ESPHome Protocol crate

Changed files
+3292 -5
.tangled
workflows
sachy-esphome
+1
.tangled/workflows/test.yml
···
- clang
- cargo
- rustfmt
+
- protobuf
steps:
- name: Format check
+728 -4
Cargo.lock
···
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 = "anyhow"
+
version = "1.0.100"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
+
+
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
+
name = "block-buffer"
+
version = "0.10.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+
dependencies = [
+
"generic-array",
+
]
+
+
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+
[[package]]
+
name = "bytes"
+
version = "1.11.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
+
dependencies = [
+
"portable-atomic",
+
]
[[package]]
name = "cast"
···
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
+
name = "cpufeatures"
+
version = "0.2.17"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+
dependencies = [
+
"libc",
+
]
+
+
[[package]]
name = "critical-section"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[package]]
+
name = "crypto-common"
+
version = "0.1.7"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
+
dependencies = [
+
"generic-array",
+
"typenum",
+
]
+
+
[[package]]
+
name = "darling"
+
version = "0.20.11"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
+
dependencies = [
+
"darling_core",
+
"darling_macro",
+
]
+
+
[[package]]
+
name = "darling_core"
+
version = "0.20.11"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
+
dependencies = [
+
"fnv",
+
"ident_case",
+
"proc-macro2",
+
"quote",
+
"strsim",
+
"syn",
+
]
+
+
[[package]]
+
name = "darling_macro"
+
version = "0.20.11"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
+
dependencies = [
+
"darling_core",
+
"quote",
+
"syn",
+
]
+
+
[[package]]
name = "defmt"
version = "0.3.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
dependencies = [
-
"thiserror",
+
"thiserror 2.0.17",
+
]
+
+
[[package]]
+
name = "derive_builder"
+
version = "0.20.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
+
dependencies = [
+
"derive_builder_macro",
+
]
+
+
[[package]]
+
name = "derive_builder_core"
+
version = "0.20.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
+
dependencies = [
+
"darling",
+
"proc-macro2",
+
"quote",
+
"syn",
+
]
+
+
[[package]]
+
name = "derive_builder_macro"
+
version = "0.20.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
+
dependencies = [
+
"derive_builder_core",
+
"syn",
+
]
+
+
[[package]]
+
name = "digest"
+
version = "0.10.7"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+
dependencies = [
+
"block-buffer",
+
"crypto-common",
]
[[package]]
···
dependencies = [
"litrs",
]
+
+
[[package]]
+
name = "either"
+
version = "1.15.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "embassy-net"
···
]
[[package]]
+
name = "equivalent"
+
version = "1.0.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+
[[package]]
+
name = "errno"
+
version = "0.3.14"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+
dependencies = [
+
"libc",
+
"windows-sys 0.52.0",
+
]
+
+
[[package]]
+
name = "fastrand"
+
version = "2.3.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+
+
[[package]]
name = "find-msvc-tools"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
[[package]]
+
name = "fixedbitset"
+
version = "0.5.7"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
+
+
[[package]]
+
name = "fnv"
+
version = "1.0.7"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+
[[package]]
name = "futures-core"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
[[package]]
+
name = "generic-array"
+
version = "0.14.7"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+
dependencies = [
+
"typenum",
+
"version_check",
+
]
+
+
[[package]]
+
name = "getrandom"
+
version = "0.3.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
+
dependencies = [
+
"cfg-if",
+
"libc",
+
"r-efi",
+
"wasip2",
+
]
+
+
[[package]]
name = "gpio-cdev"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
+
name = "handlebars"
+
version = "6.3.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "759e2d5aea3287cb1190c8ec394f42866cb5bf74fcbf213f354e3c856ea26098"
+
dependencies = [
+
"derive_builder",
+
"log",
+
"num-order",
+
"pest",
+
"pest_derive",
+
"serde",
+
"serde_json",
+
"thiserror 2.0.17",
+
]
+
+
[[package]]
name = "hash32"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
dependencies = [
"byteorder",
]
+
+
[[package]]
+
name = "hashbrown"
+
version = "0.16.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "heapless"
···
"defmt 1.0.1",
"hash32",
"stable_deref_trait",
+
]
+
+
[[package]]
+
name = "heck"
+
version = "0.5.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+
[[package]]
+
name = "home"
+
version = "0.5.12"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d"
+
dependencies = [
+
"windows-sys 0.61.2",
]
[[package]]
···
]
[[package]]
+
name = "ident_case"
+
version = "1.0.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+
[[package]]
+
name = "indexmap"
+
version = "2.12.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
+
dependencies = [
+
"equivalent",
+
"hashbrown",
+
]
+
+
[[package]]
name = "io-kit-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
+
name = "itertools"
+
version = "0.14.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
+
dependencies = [
+
"either",
+
]
+
+
[[package]]
+
name = "itoa"
+
version = "1.0.15"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+
+
[[package]]
name = "libc"
version = "0.2.178"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
[[package]]
+
name = "libm"
+
version = "0.2.15"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
+
+
[[package]]
name = "linux-embedded-hal"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
+
name = "linux-raw-sys"
+
version = "0.4.15"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
+
+
[[package]]
+
name = "linux-raw-sys"
+
version = "0.11.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
+
+
[[package]]
name = "litrs"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
+
+
[[package]]
+
name = "log"
+
version = "0.4.29"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "mach2"
···
checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d"
[[package]]
+
name = "memchr"
+
version = "2.7.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
+
+
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
dependencies = [
"autocfg",
]
+
+
[[package]]
+
name = "multimap"
+
version = "0.10.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
[[package]]
name = "nb"
···
]
[[package]]
+
name = "num-derive"
+
version = "0.4.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
+
dependencies = [
+
"proc-macro2",
+
"quote",
+
"syn",
+
]
+
+
[[package]]
+
name = "num-modular"
+
version = "0.6.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "17bb261bf36fa7d83f4c294f834e91256769097b3cb505d44831e0a179ac647f"
+
+
[[package]]
+
name = "num-order"
+
version = "1.2.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "537b596b97c40fcf8056d153049eb22f481c17ebce72a513ec9286e4986d1bb6"
+
dependencies = [
+
"num-modular",
+
]
+
+
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
+
name = "once_cell"
+
version = "1.21.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+
[[package]]
+
name = "pest"
+
version = "2.8.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22"
+
dependencies = [
+
"memchr",
+
"ucd-trie",
+
]
+
+
[[package]]
+
name = "pest_derive"
+
version = "2.8.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "51f72981ade67b1ca6adc26ec221be9f463f2b5839c7508998daa17c23d94d7f"
+
dependencies = [
+
"pest",
+
"pest_generator",
+
]
+
+
[[package]]
+
name = "pest_generator"
+
version = "2.8.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "dee9efd8cdb50d719a80088b76f81aec7c41ed6d522ee750178f83883d271625"
+
dependencies = [
+
"pest",
+
"pest_meta",
+
"proc-macro2",
+
"quote",
+
"syn",
+
]
+
+
[[package]]
+
name = "pest_meta"
+
version = "2.8.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "bf1d70880e76bdc13ba52eafa6239ce793d85c8e43896507e43dd8984ff05b82"
+
dependencies = [
+
"pest",
+
"sha2",
+
]
+
+
[[package]]
+
name = "petgraph"
+
version = "0.7.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772"
+
dependencies = [
+
"fixedbitset",
+
"indexmap",
+
]
+
+
[[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.11.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
+
+
[[package]]
+
name = "prettyplease"
+
version = "0.2.37"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
+
dependencies = [
+
"proc-macro2",
+
"syn",
+
]
+
+
[[package]]
name = "proc-macro-error-attr2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
+
name = "prost"
+
version = "0.14.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d"
+
dependencies = [
+
"bytes",
+
"prost-derive",
+
]
+
+
[[package]]
+
name = "prost-build"
+
version = "0.14.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "ac6c3320f9abac597dcbc668774ef006702672474aad53c6d596b62e487b40b1"
+
dependencies = [
+
"heck",
+
"itertools",
+
"log",
+
"multimap",
+
"once_cell",
+
"petgraph",
+
"prettyplease",
+
"prost",
+
"prost-types",
+
"regex",
+
"syn",
+
"tempfile",
+
]
+
+
[[package]]
+
name = "prost-derive"
+
version = "0.14.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425"
+
dependencies = [
+
"anyhow",
+
"itertools",
+
"proc-macro2",
+
"quote",
+
"syn",
+
]
+
+
[[package]]
+
name = "prost-types"
+
version = "0.14.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b9b4db3d6da204ed77bb26ba83b6122a73aeb2e87e25fbf7ad2e84c4ccbf8f72"
+
dependencies = [
+
"prost",
+
]
+
+
[[package]]
+
name = "protobuf"
+
version = "3.7.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "d65a1d4ddae7d8b5de68153b48f6aa3bba8cb002b243dbdbc55a5afbc98f99f4"
+
dependencies = [
+
"once_cell",
+
"protobuf-support",
+
"thiserror 1.0.69",
+
]
+
+
[[package]]
+
name = "protobuf-parse"
+
version = "3.7.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b4aeaa1f2460f1d348eeaeed86aea999ce98c1bded6f089ff8514c9d9dbdc973"
+
dependencies = [
+
"anyhow",
+
"indexmap",
+
"log",
+
"protobuf",
+
"protobuf-support",
+
"tempfile",
+
"thiserror 1.0.69",
+
"which",
+
]
+
+
[[package]]
+
name = "protobuf-support"
+
version = "3.7.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "3e36c2f31e0a47f9280fb347ef5e461ffcd2c52dd520d8e216b52f93b0b0d7d6"
+
dependencies = [
+
"thiserror 1.0.69",
+
]
+
+
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
+
name = "r-efi"
+
version = "5.3.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+
[[package]]
+
name = "regex"
+
version = "1.12.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
+
dependencies = [
+
"aho-corasick",
+
"memchr",
+
"regex-automata",
+
"regex-syntax",
+
]
+
+
[[package]]
+
name = "regex-automata"
+
version = "0.4.13"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
+
dependencies = [
+
"aho-corasick",
+
"memchr",
+
"regex-syntax",
+
]
+
+
[[package]]
+
name = "regex-syntax"
+
version = "0.8.8"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
+
+
[[package]]
+
name = "rustix"
+
version = "0.38.44"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
+
dependencies = [
+
"bitflags 2.10.0",
+
"errno",
+
"libc",
+
"linux-raw-sys 0.4.15",
+
"windows-sys 0.52.0",
+
]
+
+
[[package]]
+
name = "rustix"
+
version = "1.1.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
+
dependencies = [
+
"bitflags 2.10.0",
+
"errno",
+
"libc",
+
"linux-raw-sys 0.11.0",
+
"windows-sys 0.52.0",
+
]
+
+
[[package]]
+
name = "ryu"
+
version = "1.0.20"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+
+
[[package]]
name = "sachy-battery"
version = "0.1.0"
···
]
[[package]]
+
name = "sachy-esphome"
+
version = "0.1.0"
+
dependencies = [
+
"anyhow",
+
"bytes",
+
"defmt 1.0.1",
+
"handlebars",
+
"heck",
+
"libm",
+
"num-derive",
+
"num-traits",
+
"prost",
+
"prost-build",
+
"prost-types",
+
"protobuf",
+
"protobuf-parse",
+
"serde",
+
"thiserror 2.0.17",
+
]
+
+
[[package]]
name = "sachy-fmt"
version = "0.1.0"
dependencies = [
···
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
+
name = "serde"
+
version = "1.0.228"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+
dependencies = [
+
"serde_core",
+
"serde_derive",
+
]
+
+
[[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 = "serde_json"
+
version = "1.0.145"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
+
dependencies = [
+
"itoa",
+
"memchr",
+
"ryu",
+
"serde",
+
"serde_core",
+
]
+
+
[[package]]
name = "serialport"
version = "4.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
"quote",
"scopeguard",
"unescaper",
-
"windows-sys",
+
"windows-sys 0.52.0",
+
]
+
+
[[package]]
+
name = "sha2"
+
version = "0.10.9"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
+
dependencies = [
+
"cfg-if",
+
"cpufeatures",
+
"digest",
]
[[package]]
···
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[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.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
+
name = "tempfile"
+
version = "3.23.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
+
dependencies = [
+
"fastrand",
+
"getrandom",
+
"once_cell",
+
"rustix 1.1.2",
+
"windows-sys 0.52.0",
+
]
+
+
[[package]]
+
name = "thiserror"
+
version = "1.0.69"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+
dependencies = [
+
"thiserror-impl 1.0.69",
+
]
+
+
[[package]]
name = "thiserror"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
dependencies = [
-
"thiserror-impl",
+
"thiserror-impl 2.0.17",
+
]
+
+
[[package]]
+
name = "thiserror-impl"
+
version = "1.0.69"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+
dependencies = [
+
"proc-macro2",
+
"quote",
+
"syn",
]
[[package]]
···
]
[[package]]
+
name = "typenum"
+
version = "1.19.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
+
+
[[package]]
+
name = "ucd-trie"
+
version = "0.1.7"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
+
+
[[package]]
name = "unescaper"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c01d12e3a56a4432a8b436f293c25f4808bdf9e9f9f98f9260bba1f1bc5a1f26"
dependencies = [
-
"thiserror",
+
"thiserror 2.0.17",
]
[[package]]
···
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
+
name = "version_check"
+
version = "0.9.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]]
+
name = "wasip2"
+
version = "1.0.1+wasi-0.2.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
+
dependencies = [
+
"wit-bindgen",
+
]
+
+
[[package]]
+
name = "which"
+
version = "4.4.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
+
dependencies = [
+
"either",
+
"home",
+
"once_cell",
+
"rustix 0.38.44",
+
]
+
+
[[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]]
···
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+
[[package]]
+
name = "wit-bindgen"
+
version = "0.46.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
+9 -1
Cargo.toml
···
[workspace]
resolver = "3"
-
members = ["sachy-battery","sachy-bthome", "sachy-fmt", "sachy-fnv", "sachy-shtc3", "sachy-sntp"]
+
members = [
+
"sachy-battery",
+
"sachy-bthome",
+
"sachy-esphome",
+
"sachy-fmt",
+
"sachy-fnv",
+
"sachy-shtc3",
+
"sachy-sntp",
+
]
[workspace.package]
authors = ["Sachymetsu <sachymetsu@tutamail.com>"]
+32
sachy-esphome/Cargo.toml
···
+
[package]
+
name = "sachy-esphome"
+
authors.workspace = true
+
edition.workspace = true
+
repository.workspace = true
+
license.workspace = true
+
version.workspace = true
+
rust-version.workspace = true
+
+
[dependencies]
+
defmt = { workspace = true, features = ["alloc"], optional = true }
+
bytes = { version = "1", default-features = false, features = ["extra-platforms"] }
+
libm = { version = "0.2.15", optional = true }
+
num-derive = "0.4.2"
+
num-traits = { version = "0.2.19", default-features = false }
+
prost = { version = "0.14.1", default-features = false, features = ["derive"] }
+
prost-types = { version = "0.14.1", default-features = false }
+
thiserror = { version = "2.0.12", default-features = false }
+
+
[build-dependencies]
+
anyhow = "1.0.98"
+
handlebars = "6.3.2"
+
heck = "0.5.0"
+
prost-build = "0.14.1"
+
protobuf = "3.7.2"
+
protobuf-parse = "3.7.2"
+
serde = { version = "1.0.219", features = ["derive"] }
+
+
[features]
+
default = []
+
libm = ["dep:libm"]
+
defmt = ["dep:defmt"]
+99
sachy-esphome/build.rs
···
+
use heck::ToUpperCamelCase;
+
use std::{io::BufWriter, path::PathBuf};
+
+
static MESSAGE_TEMPLATE: &str = include_str!("./templates/message.handlebars");
+
+
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize)]
+
struct ParsedMessageKind {
+
name: String,
+
id: u64,
+
}
+
+
fn main() -> anyhow::Result<()> {
+
let proto_files = ["protos/api.proto", "protos/api_options.proto"];
+
let includes = ["protos"];
+
+
prost_build::Config::new()
+
.btree_map(["."])
+
.message_attribute(
+
".",
+
"#[cfg_attr(feature = \"defmt\", derive(defmt::Format))]",
+
)
+
.default_package_filename("api")
+
.compile_protos(&proto_files, &includes)?;
+
+
let valid_sources = 0..=2;
+
+
let mut message_kinds: Vec<ParsedMessageKind> = protobuf_parse::Parser::new()
+
.inputs(proto_files)
+
.includes(includes)
+
.parse_and_typecheck()?
+
.file_descriptors
+
.into_iter()
+
.flat_map(|fd| fd.message_type)
+
.filter_map(|message| {
+
if let Some(protobuf::UnknownValueRef::Varint(id)) = message
+
.options
+
.get_or_default()
+
.special_fields
+
.unknown_fields()
+
.get(1036)
+
&& let Some(protobuf::UnknownValueRef::Varint(source)) = message
+
.options
+
.get_or_default()
+
.special_fields
+
.unknown_fields()
+
.get(1037)
+
&& valid_sources.contains(&source)
+
{
+
Some(ParsedMessageKind {
+
name: sanitize_identifier(message.name().to_upper_camel_case()),
+
id,
+
})
+
} else {
+
None
+
}
+
})
+
.collect();
+
+
message_kinds.sort();
+
+
let out_dir = PathBuf::from(std::env::var("OUT_DIR")?).join("api.rs");
+
+
let api_constants = BufWriter::new(std::fs::OpenOptions::new().append(true).open(out_dir)?);
+
+
handlebars::Handlebars::new().render_template_to_write(
+
MESSAGE_TEMPLATE,
+
&message_kinds,
+
api_constants,
+
)?;
+
+
Ok(())
+
}
+
+
/// From prost-build, as it is in a private module.
+
fn sanitize_identifier(ident: String) -> String {
+
// Use a raw identifier if the identifier matches a Rust keyword:
+
// https://doc.rust-lang.org/reference/keywords.html.
+
match ident.as_str() {
+
// 2015 strict keywords.
+
| "as" | "break" | "const" | "continue" | "else" | "enum" | "false"
+
| "fn" | "for" | "if" | "impl" | "in" | "let" | "loop" | "match" | "mod" | "move" | "mut"
+
| "pub" | "ref" | "return" | "static" | "struct" | "trait" | "true"
+
| "type" | "unsafe" | "use" | "where" | "while"
+
// 2018 strict keywords.
+
| "dyn"
+
// 2015 reserved keywords.
+
| "abstract" | "become" | "box" | "do" | "final" | "macro" | "override" | "priv" | "typeof"
+
| "unsized" | "virtual" | "yield"
+
// 2018 reserved keywords.
+
| "async" | "await" | "try"
+
// 2024 reserved keywords.
+
| "gen" => format!("r#{ident}"),
+
// the following keywords are not supported as raw identifiers and are therefore suffixed with an underscore.
+
"_" | "super" | "self" | "Self" | "extern" | "crate" => format!("{ident}_"),
+
// the following keywords begin with a number and are therefore prefixed with an underscore.
+
s if s.starts_with(char::is_numeric) => format!("_{ident}"),
+
_ => ident,
+
}
+
}
+2166
sachy-esphome/protos/api.proto
···
+
syntax = "proto3";
+
+
import "api_options.proto";
+
+
service APIConnection {
+
rpc hello (HelloRequest) returns (HelloResponse) {
+
option (needs_setup_connection) = false;
+
option (needs_authentication) = false;
+
}
+
rpc connect (ConnectRequest) returns (ConnectResponse) {
+
option (needs_setup_connection) = false;
+
option (needs_authentication) = false;
+
}
+
rpc disconnect (DisconnectRequest) returns (DisconnectResponse) {
+
option (needs_setup_connection) = false;
+
option (needs_authentication) = false;
+
}
+
rpc ping (PingRequest) returns (PingResponse) {
+
option (needs_setup_connection) = false;
+
option (needs_authentication) = false;
+
}
+
rpc device_info (DeviceInfoRequest) returns (DeviceInfoResponse) {
+
option (needs_authentication) = false;
+
}
+
rpc list_entities (ListEntitiesRequest) returns (void) {}
+
rpc subscribe_states (SubscribeStatesRequest) returns (void) {}
+
rpc subscribe_logs (SubscribeLogsRequest) returns (void) {}
+
rpc subscribe_homeassistant_services (SubscribeHomeassistantServicesRequest) returns (void) {}
+
rpc subscribe_home_assistant_states (SubscribeHomeAssistantStatesRequest) returns (void) {}
+
rpc get_time (GetTimeRequest) returns (GetTimeResponse) {
+
option (needs_authentication) = false;
+
}
+
rpc execute_service (ExecuteServiceRequest) returns (void) {}
+
rpc noise_encryption_set_key (NoiseEncryptionSetKeyRequest) returns (NoiseEncryptionSetKeyResponse) {}
+
+
rpc button_command (ButtonCommandRequest) returns (void) {}
+
rpc camera_image (CameraImageRequest) returns (void) {}
+
rpc climate_command (ClimateCommandRequest) returns (void) {}
+
rpc cover_command (CoverCommandRequest) returns (void) {}
+
rpc date_command (DateCommandRequest) returns (void) {}
+
rpc datetime_command (DateTimeCommandRequest) returns (void) {}
+
rpc fan_command (FanCommandRequest) returns (void) {}
+
rpc light_command (LightCommandRequest) returns (void) {}
+
rpc lock_command (LockCommandRequest) returns (void) {}
+
rpc media_player_command (MediaPlayerCommandRequest) returns (void) {}
+
rpc number_command (NumberCommandRequest) returns (void) {}
+
rpc select_command (SelectCommandRequest) returns (void) {}
+
rpc siren_command (SirenCommandRequest) returns (void) {}
+
rpc switch_command (SwitchCommandRequest) returns (void) {}
+
rpc text_command (TextCommandRequest) returns (void) {}
+
rpc time_command (TimeCommandRequest) returns (void) {}
+
rpc update_command (UpdateCommandRequest) returns (void) {}
+
rpc valve_command (ValveCommandRequest) returns (void) {}
+
+
rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
+
rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {}
+
rpc bluetooth_gatt_get_services(BluetoothGATTGetServicesRequest) returns (void) {}
+
rpc bluetooth_gatt_read(BluetoothGATTReadRequest) returns (void) {}
+
rpc bluetooth_gatt_write(BluetoothGATTWriteRequest) returns (void) {}
+
rpc bluetooth_gatt_read_descriptor(BluetoothGATTReadDescriptorRequest) returns (void) {}
+
rpc bluetooth_gatt_write_descriptor(BluetoothGATTWriteDescriptorRequest) returns (void) {}
+
rpc bluetooth_gatt_notify(BluetoothGATTNotifyRequest) returns (void) {}
+
rpc subscribe_bluetooth_connections_free(SubscribeBluetoothConnectionsFreeRequest) returns (BluetoothConnectionsFreeResponse) {}
+
rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
+
rpc bluetooth_scanner_set_mode(BluetoothScannerSetModeRequest) returns (void) {}
+
+
rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) returns (void) {}
+
rpc voice_assistant_get_configuration(VoiceAssistantConfigurationRequest) returns (VoiceAssistantConfigurationResponse) {}
+
rpc voice_assistant_set_configuration(VoiceAssistantSetConfiguration) returns (void) {}
+
+
rpc alarm_control_panel_command (AlarmControlPanelCommandRequest) returns (void) {}
+
}
+
+
+
// ==================== BASE PACKETS ====================
+
+
// The Home Assistant protocol is structured as a simple
+
// TCP socket with short binary messages encoded in the protocol buffers format
+
// First, a message in this protocol has a specific format:
+
// * A zero byte.
+
// * VarInt denoting the size of the message object. (type is not part of this)
+
// * VarInt denoting the type of message.
+
// * The message object encoded as a ProtoBuf message
+
+
// The connection is established in 4 steps:
+
// * First, the client connects to the server and sends a "Hello Request" identifying itself
+
// * The server responds with a "Hello Response" and selects the protocol version
+
// * After receiving this message, the client attempts to authenticate itself using
+
// the password and a "Connect Request"
+
// * The server responds with a "Connect Response" and notifies of invalid password.
+
// If anything in this initial process fails, the connection must immediately closed
+
// by both sides and _no_ disconnection message is to be sent.
+
+
// Message sent at the beginning of each connection
+
// Can only be sent by the client and only at the beginning of the connection
+
message HelloRequest {
+
option (id) = 1;
+
option (source) = SOURCE_CLIENT;
+
option (no_delay) = true;
+
+
// Description of client (like User Agent)
+
// For example "Home Assistant"
+
// Not strictly necessary to send but nice for debugging
+
// purposes.
+
string client_info = 1;
+
uint32 api_version_major = 2;
+
uint32 api_version_minor = 3;
+
}
+
+
// Confirmation of successful connection request.
+
// Can only be sent by the server and only at the beginning of the connection
+
message HelloResponse {
+
option (id) = 2;
+
option (source) = SOURCE_SERVER;
+
option (no_delay) = true;
+
+
// The version of the API to use. The _client_ (for example Home Assistant) needs to check
+
// for compatibility and if necessary adopt to an older API.
+
// Major is for breaking changes in the base protocol - a mismatch will lead to immediate disconnect_client_
+
// Minor is for breaking changes in individual messages - a mismatch will lead to a warning message
+
uint32 api_version_major = 1;
+
uint32 api_version_minor = 2;
+
+
// A string identifying the server (ESP); like client info this may be empty
+
// and only exists for debugging/logging purposes.
+
// For example "ESPHome v1.10.0 on ESP8266"
+
string server_info = 3;
+
+
// The name of the server (App.get_name())
+
string name = 4;
+
}
+
+
// Message sent at the beginning of each connection to authenticate the client
+
// Can only be sent by the client and only at the beginning of the connection
+
message ConnectRequest {
+
option (id) = 3;
+
option (source) = SOURCE_CLIENT;
+
option (no_delay) = true;
+
+
// The password to log in with
+
string password = 1;
+
}
+
+
// Confirmation of successful connection. After this the connection is available for all traffic.
+
// Can only be sent by the server and only at the beginning of the connection
+
message ConnectResponse {
+
option (id) = 4;
+
option (source) = SOURCE_SERVER;
+
option (no_delay) = true;
+
+
bool invalid_password = 1;
+
}
+
+
// Request to close the connection.
+
// Can be sent by both the client and server
+
message DisconnectRequest {
+
option (id) = 5;
+
option (source) = SOURCE_BOTH;
+
option (no_delay) = true;
+
+
// Do not close the connection before the acknowledgement arrives
+
}
+
+
message DisconnectResponse {
+
option (id) = 6;
+
option (source) = SOURCE_BOTH;
+
option (no_delay) = true;
+
+
// Empty - Both parties are required to close the connection after this
+
// message has been received.
+
}
+
+
message PingRequest {
+
option (id) = 7;
+
option (source) = SOURCE_BOTH;
+
// Empty
+
}
+
+
message PingResponse {
+
option (id) = 8;
+
option (source) = SOURCE_BOTH;
+
// Empty
+
}
+
+
message DeviceInfoRequest {
+
option (id) = 9;
+
option (source) = SOURCE_CLIENT;
+
// Empty
+
}
+
+
message AreaInfo {
+
uint32 area_id = 1;
+
string name = 2;
+
}
+
+
message DeviceInfo {
+
uint32 device_id = 1;
+
string name = 2;
+
uint32 area_id = 3;
+
}
+
+
message DeviceInfoResponse {
+
option (id) = 10;
+
option (source) = SOURCE_SERVER;
+
+
bool uses_password = 1;
+
+
// The name of the node, given by "App.set_name()"
+
string name = 2;
+
+
// The mac address of the device. For example "AC:BC:32:89:0E:A9"
+
string mac_address = 3;
+
+
// A string describing the ESPHome version. For example "1.10.0"
+
string esphome_version = 4;
+
+
// A string describing the date of compilation, this is generated by the compiler
+
// and therefore may not be in the same format all the time.
+
// If the user isn't using ESPHome, this will also not be set.
+
string compilation_time = 5;
+
+
// The model of the board. For example NodeMCU
+
string model = 6;
+
+
bool has_deep_sleep = 7;
+
+
// The esphome project details if set
+
string project_name = 8;
+
string project_version = 9;
+
+
uint32 webserver_port = 10;
+
+
uint32 legacy_bluetooth_proxy_version = 11;
+
uint32 bluetooth_proxy_feature_flags = 15;
+
+
string manufacturer = 12;
+
+
string friendly_name = 13;
+
+
uint32 legacy_voice_assistant_version = 14;
+
uint32 voice_assistant_feature_flags = 17;
+
+
string suggested_area = 16;
+
+
// The Bluetooth mac address of the device. For example "AC:BC:32:89:0E:AA"
+
string bluetooth_mac_address = 18;
+
+
// Supports receiving and saving api encryption key
+
bool api_encryption_supported = 19;
+
+
repeated DeviceInfo devices = 20;
+
repeated AreaInfo areas = 21;
+
+
// Top-level area info to phase out suggested_area
+
AreaInfo area = 22;
+
}
+
+
message ListEntitiesRequest {
+
option (id) = 11;
+
option (source) = SOURCE_CLIENT;
+
// Empty
+
}
+
message ListEntitiesDoneResponse {
+
option (id) = 19;
+
option (source) = SOURCE_SERVER;
+
option (no_delay) = true;
+
// Empty
+
}
+
message SubscribeStatesRequest {
+
option (id) = 20;
+
option (source) = SOURCE_CLIENT;
+
// Empty
+
}
+
+
// ==================== COMMON =====================
+
+
enum EntityCategory {
+
ENTITY_CATEGORY_NONE = 0;
+
ENTITY_CATEGORY_CONFIG = 1;
+
ENTITY_CATEGORY_DIAGNOSTIC = 2;
+
}
+
+
// ==================== BINARY SENSOR ====================
+
message ListEntitiesBinarySensorResponse {
+
option (id) = 12;
+
option (base_class) = "InfoResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_BINARY_SENSOR";
+
+
string object_id = 1;
+
fixed32 key = 2;
+
string name = 3;
+
string unique_id = 4;
+
+
string device_class = 5;
+
bool is_status_binary_sensor = 6;
+
bool disabled_by_default = 7;
+
string icon = 8;
+
EntityCategory entity_category = 9;
+
uint32 device_id = 10;
+
}
+
message BinarySensorStateResponse {
+
option (id) = 21;
+
option (base_class) = "StateResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_BINARY_SENSOR";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
bool state = 2;
+
// If the binary sensor does not have a valid state yet.
+
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
+
bool missing_state = 3;
+
uint32 device_id = 4;
+
}
+
+
// ==================== COVER ====================
+
message ListEntitiesCoverResponse {
+
option (id) = 13;
+
option (base_class) = "InfoResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_COVER";
+
+
string object_id = 1;
+
fixed32 key = 2;
+
string name = 3;
+
string unique_id = 4;
+
+
bool assumed_state = 5;
+
bool supports_position = 6;
+
bool supports_tilt = 7;
+
string device_class = 8;
+
bool disabled_by_default = 9;
+
string icon = 10;
+
EntityCategory entity_category = 11;
+
bool supports_stop = 12;
+
uint32 device_id = 13;
+
}
+
+
enum LegacyCoverState {
+
LEGACY_COVER_STATE_OPEN = 0;
+
LEGACY_COVER_STATE_CLOSED = 1;
+
}
+
enum CoverOperation {
+
COVER_OPERATION_IDLE = 0;
+
COVER_OPERATION_IS_OPENING = 1;
+
COVER_OPERATION_IS_CLOSING = 2;
+
}
+
message CoverStateResponse {
+
option (id) = 22;
+
option (base_class) = "StateResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_COVER";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
// legacy: state has been removed in 1.13
+
// clients/servers must still send/accept it until the next protocol change
+
LegacyCoverState legacy_state = 2;
+
+
float position = 3;
+
float tilt = 4;
+
CoverOperation current_operation = 5;
+
uint32 device_id = 6;
+
}
+
+
enum LegacyCoverCommand {
+
LEGACY_COVER_COMMAND_OPEN = 0;
+
LEGACY_COVER_COMMAND_CLOSE = 1;
+
LEGACY_COVER_COMMAND_STOP = 2;
+
}
+
message CoverCommandRequest {
+
option (id) = 30;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_COVER";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
+
// legacy: command has been removed in 1.13
+
// clients/servers must still send/accept it until the next protocol change
+
bool has_legacy_command = 2;
+
LegacyCoverCommand legacy_command = 3;
+
+
bool has_position = 4;
+
float position = 5;
+
bool has_tilt = 6;
+
float tilt = 7;
+
bool stop = 8;
+
}
+
+
// ==================== FAN ====================
+
message ListEntitiesFanResponse {
+
option (id) = 14;
+
option (base_class) = "InfoResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_FAN";
+
+
string object_id = 1;
+
fixed32 key = 2;
+
string name = 3;
+
string unique_id = 4;
+
+
bool supports_oscillation = 5;
+
bool supports_speed = 6;
+
bool supports_direction = 7;
+
int32 supported_speed_count = 8;
+
bool disabled_by_default = 9;
+
string icon = 10;
+
EntityCategory entity_category = 11;
+
repeated string supported_preset_modes = 12;
+
uint32 device_id = 13;
+
}
+
enum FanSpeed {
+
FAN_SPEED_LOW = 0;
+
FAN_SPEED_MEDIUM = 1;
+
FAN_SPEED_HIGH = 2;
+
}
+
enum FanDirection {
+
FAN_DIRECTION_FORWARD = 0;
+
FAN_DIRECTION_REVERSE = 1;
+
}
+
message FanStateResponse {
+
option (id) = 23;
+
option (base_class) = "StateResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_FAN";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
bool state = 2;
+
bool oscillating = 3;
+
FanSpeed speed = 4 [deprecated = true];
+
FanDirection direction = 5;
+
int32 speed_level = 6;
+
string preset_mode = 7;
+
uint32 device_id = 8;
+
}
+
message FanCommandRequest {
+
option (id) = 31;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_FAN";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
bool has_state = 2;
+
bool state = 3;
+
bool has_speed = 4 [deprecated = true];
+
FanSpeed speed = 5 [deprecated = true];
+
bool has_oscillating = 6;
+
bool oscillating = 7;
+
bool has_direction = 8;
+
FanDirection direction = 9;
+
bool has_speed_level = 10;
+
int32 speed_level = 11;
+
bool has_preset_mode = 12;
+
string preset_mode = 13;
+
}
+
+
// ==================== LIGHT ====================
+
enum ColorMode {
+
COLOR_MODE_UNKNOWN = 0;
+
COLOR_MODE_ON_OFF = 1;
+
COLOR_MODE_LEGACY_BRIGHTNESS = 2;
+
COLOR_MODE_BRIGHTNESS = 3;
+
COLOR_MODE_WHITE = 7;
+
COLOR_MODE_COLOR_TEMPERATURE = 11;
+
COLOR_MODE_COLD_WARM_WHITE = 19;
+
COLOR_MODE_RGB = 35;
+
COLOR_MODE_RGB_WHITE = 39;
+
COLOR_MODE_RGB_COLOR_TEMPERATURE = 47;
+
COLOR_MODE_RGB_COLD_WARM_WHITE = 51;
+
}
+
message ListEntitiesLightResponse {
+
option (id) = 15;
+
option (base_class) = "InfoResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_LIGHT";
+
+
string object_id = 1;
+
fixed32 key = 2;
+
string name = 3;
+
string unique_id = 4;
+
+
repeated ColorMode supported_color_modes = 12;
+
// next four supports_* are for legacy clients, newer clients should use color modes
+
bool legacy_supports_brightness = 5 [deprecated=true];
+
bool legacy_supports_rgb = 6 [deprecated=true];
+
bool legacy_supports_white_value = 7 [deprecated=true];
+
bool legacy_supports_color_temperature = 8 [deprecated=true];
+
float min_mireds = 9;
+
float max_mireds = 10;
+
repeated string effects = 11;
+
bool disabled_by_default = 13;
+
string icon = 14;
+
EntityCategory entity_category = 15;
+
uint32 device_id = 16;
+
}
+
message LightStateResponse {
+
option (id) = 24;
+
option (base_class) = "StateResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_LIGHT";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
bool state = 2;
+
float brightness = 3;
+
ColorMode color_mode = 11;
+
float color_brightness = 10;
+
float red = 4;
+
float green = 5;
+
float blue = 6;
+
float white = 7;
+
float color_temperature = 8;
+
float cold_white = 12;
+
float warm_white = 13;
+
string effect = 9;
+
uint32 device_id = 14;
+
}
+
message LightCommandRequest {
+
option (id) = 32;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_LIGHT";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
bool has_state = 2;
+
bool state = 3;
+
bool has_brightness = 4;
+
float brightness = 5;
+
bool has_color_mode = 22;
+
ColorMode color_mode = 23;
+
bool has_color_brightness = 20;
+
float color_brightness = 21;
+
bool has_rgb = 6;
+
float red = 7;
+
float green = 8;
+
float blue = 9;
+
bool has_white = 10;
+
float white = 11;
+
bool has_color_temperature = 12;
+
float color_temperature = 13;
+
bool has_cold_white = 24;
+
float cold_white = 25;
+
bool has_warm_white = 26;
+
float warm_white = 27;
+
bool has_transition_length = 14;
+
uint32 transition_length = 15;
+
bool has_flash_length = 16;
+
uint32 flash_length = 17;
+
bool has_effect = 18;
+
string effect = 19;
+
}
+
+
// ==================== SENSOR ====================
+
enum SensorStateClass {
+
STATE_CLASS_NONE = 0;
+
STATE_CLASS_MEASUREMENT = 1;
+
STATE_CLASS_TOTAL_INCREASING = 2;
+
STATE_CLASS_TOTAL = 3;
+
}
+
+
enum SensorLastResetType {
+
LAST_RESET_NONE = 0;
+
LAST_RESET_NEVER = 1;
+
LAST_RESET_AUTO = 2;
+
}
+
+
message ListEntitiesSensorResponse {
+
option (id) = 16;
+
option (base_class) = "InfoResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_SENSOR";
+
+
string object_id = 1;
+
fixed32 key = 2;
+
string name = 3;
+
string unique_id = 4;
+
+
string icon = 5;
+
string unit_of_measurement = 6;
+
int32 accuracy_decimals = 7;
+
bool force_update = 8;
+
string device_class = 9;
+
SensorStateClass state_class = 10;
+
// Last reset type removed in 2021.9.0
+
SensorLastResetType legacy_last_reset_type = 11;
+
bool disabled_by_default = 12;
+
EntityCategory entity_category = 13;
+
uint32 device_id = 14;
+
}
+
message SensorStateResponse {
+
option (id) = 25;
+
option (base_class) = "StateResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_SENSOR";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
float state = 2;
+
// If the sensor does not have a valid state yet.
+
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
+
bool missing_state = 3;
+
uint32 device_id = 4;
+
}
+
+
// ==================== SWITCH ====================
+
message ListEntitiesSwitchResponse {
+
option (id) = 17;
+
option (base_class) = "InfoResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_SWITCH";
+
+
string object_id = 1;
+
fixed32 key = 2;
+
string name = 3;
+
string unique_id = 4;
+
+
string icon = 5;
+
bool assumed_state = 6;
+
bool disabled_by_default = 7;
+
EntityCategory entity_category = 8;
+
string device_class = 9;
+
uint32 device_id = 10;
+
}
+
message SwitchStateResponse {
+
option (id) = 26;
+
option (base_class) = "StateResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_SWITCH";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
bool state = 2;
+
uint32 device_id = 3;
+
}
+
message SwitchCommandRequest {
+
option (id) = 33;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_SWITCH";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
bool state = 2;
+
}
+
+
// ==================== TEXT SENSOR ====================
+
message ListEntitiesTextSensorResponse {
+
option (id) = 18;
+
option (base_class) = "InfoResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_TEXT_SENSOR";
+
+
string object_id = 1;
+
fixed32 key = 2;
+
string name = 3;
+
string unique_id = 4;
+
+
string icon = 5;
+
bool disabled_by_default = 6;
+
EntityCategory entity_category = 7;
+
string device_class = 8;
+
uint32 device_id = 9;
+
}
+
message TextSensorStateResponse {
+
option (id) = 27;
+
option (base_class) = "StateResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_TEXT_SENSOR";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
string state = 2;
+
// If the text sensor does not have a valid state yet.
+
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
+
bool missing_state = 3;
+
uint32 device_id = 4;
+
}
+
+
// ==================== SUBSCRIBE LOGS ====================
+
enum LogLevel {
+
LOG_LEVEL_NONE = 0;
+
LOG_LEVEL_ERROR = 1;
+
LOG_LEVEL_WARN = 2;
+
LOG_LEVEL_INFO = 3;
+
LOG_LEVEL_CONFIG = 4;
+
LOG_LEVEL_DEBUG = 5;
+
LOG_LEVEL_VERBOSE = 6;
+
LOG_LEVEL_VERY_VERBOSE = 7;
+
}
+
message SubscribeLogsRequest {
+
option (id) = 28;
+
option (source) = SOURCE_CLIENT;
+
LogLevel level = 1;
+
bool dump_config = 2;
+
}
+
message SubscribeLogsResponse {
+
option (id) = 29;
+
option (source) = SOURCE_SERVER;
+
option (log) = false;
+
option (no_delay) = false;
+
+
LogLevel level = 1;
+
bytes message = 3;
+
bool send_failed = 4;
+
}
+
+
// ==================== NOISE ENCRYPTION ====================
+
message NoiseEncryptionSetKeyRequest {
+
option (id) = 124;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_API_NOISE";
+
+
bytes key = 1;
+
}
+
+
message NoiseEncryptionSetKeyResponse {
+
option (id) = 125;
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_API_NOISE";
+
+
bool success = 1;
+
}
+
+
// ==================== HOMEASSISTANT.SERVICE ====================
+
message SubscribeHomeassistantServicesRequest {
+
option (id) = 34;
+
option (source) = SOURCE_CLIENT;
+
}
+
+
message HomeassistantServiceMap {
+
string key = 1;
+
string value = 2;
+
}
+
+
message HomeassistantServiceResponse {
+
option (id) = 35;
+
option (source) = SOURCE_SERVER;
+
option (no_delay) = true;
+
+
string service = 1;
+
repeated HomeassistantServiceMap data = 2;
+
repeated HomeassistantServiceMap data_template = 3;
+
repeated HomeassistantServiceMap variables = 4;
+
bool is_event = 5;
+
}
+
+
// ==================== IMPORT HOME ASSISTANT STATES ====================
+
// 1. Client sends SubscribeHomeAssistantStatesRequest
+
// 2. Server responds with zero or more SubscribeHomeAssistantStateResponse (async)
+
// 3. Client sends HomeAssistantStateResponse for state changes.
+
message SubscribeHomeAssistantStatesRequest {
+
option (id) = 38;
+
option (source) = SOURCE_CLIENT;
+
}
+
+
message SubscribeHomeAssistantStateResponse {
+
option (id) = 39;
+
option (source) = SOURCE_SERVER;
+
string entity_id = 1;
+
string attribute = 2;
+
bool once = 3;
+
}
+
+
message HomeAssistantStateResponse {
+
option (id) = 40;
+
option (source) = SOURCE_CLIENT;
+
option (no_delay) = true;
+
+
string entity_id = 1;
+
string state = 2;
+
string attribute = 3;
+
}
+
+
// ==================== IMPORT TIME ====================
+
message GetTimeRequest {
+
option (id) = 36;
+
option (source) = SOURCE_BOTH;
+
}
+
+
message GetTimeResponse {
+
option (id) = 37;
+
option (source) = SOURCE_BOTH;
+
option (no_delay) = true;
+
+
fixed32 epoch_seconds = 1;
+
}
+
+
// ==================== USER-DEFINES SERVICES ====================
+
enum ServiceArgType {
+
SERVICE_ARG_TYPE_BOOL = 0;
+
SERVICE_ARG_TYPE_INT = 1;
+
SERVICE_ARG_TYPE_FLOAT = 2;
+
SERVICE_ARG_TYPE_STRING = 3;
+
SERVICE_ARG_TYPE_BOOL_ARRAY = 4;
+
SERVICE_ARG_TYPE_INT_ARRAY = 5;
+
SERVICE_ARG_TYPE_FLOAT_ARRAY = 6;
+
SERVICE_ARG_TYPE_STRING_ARRAY = 7;
+
}
+
message ListEntitiesServicesArgument {
+
string name = 1;
+
ServiceArgType type = 2;
+
}
+
message ListEntitiesServicesResponse {
+
option (id) = 41;
+
option (source) = SOURCE_SERVER;
+
+
string name = 1;
+
fixed32 key = 2;
+
repeated ListEntitiesServicesArgument args = 3;
+
}
+
message ExecuteServiceArgument {
+
bool bool_ = 1;
+
int32 legacy_int = 2;
+
float float_ = 3;
+
string string_ = 4;
+
// ESPHome 1.14 (api v1.3) make int a signed value
+
sint32 int_ = 5;
+
repeated bool bool_array = 6 [packed=false];
+
repeated sint32 int_array = 7 [packed=false];
+
repeated float float_array = 8 [packed=false];
+
repeated string string_array = 9;
+
}
+
message ExecuteServiceRequest {
+
option (id) = 42;
+
option (source) = SOURCE_CLIENT;
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
repeated ExecuteServiceArgument args = 2;
+
}
+
+
// ==================== CAMERA ====================
+
message ListEntitiesCameraResponse {
+
option (id) = 43;
+
option (base_class) = "InfoResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_CAMERA";
+
+
string object_id = 1;
+
fixed32 key = 2;
+
string name = 3;
+
string unique_id = 4;
+
bool disabled_by_default = 5;
+
string icon = 6;
+
EntityCategory entity_category = 7;
+
uint32 device_id = 8;
+
}
+
+
message CameraImageResponse {
+
option (id) = 44;
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_CAMERA";
+
+
fixed32 key = 1;
+
bytes data = 2;
+
bool done = 3;
+
}
+
message CameraImageRequest {
+
option (id) = 45;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_CAMERA";
+
option (no_delay) = true;
+
+
bool single = 1;
+
bool stream = 2;
+
}
+
+
// ==================== CLIMATE ====================
+
enum ClimateMode {
+
CLIMATE_MODE_OFF = 0;
+
CLIMATE_MODE_HEAT_COOL = 1;
+
CLIMATE_MODE_COOL = 2;
+
CLIMATE_MODE_HEAT = 3;
+
CLIMATE_MODE_FAN_ONLY = 4;
+
CLIMATE_MODE_DRY = 5;
+
CLIMATE_MODE_AUTO = 6;
+
}
+
enum ClimateFanMode {
+
CLIMATE_FAN_ON = 0;
+
CLIMATE_FAN_OFF = 1;
+
CLIMATE_FAN_AUTO = 2;
+
CLIMATE_FAN_LOW = 3;
+
CLIMATE_FAN_MEDIUM = 4;
+
CLIMATE_FAN_HIGH = 5;
+
CLIMATE_FAN_MIDDLE = 6;
+
CLIMATE_FAN_FOCUS = 7;
+
CLIMATE_FAN_DIFFUSE = 8;
+
CLIMATE_FAN_QUIET = 9;
+
}
+
enum ClimateSwingMode {
+
CLIMATE_SWING_OFF = 0;
+
CLIMATE_SWING_BOTH = 1;
+
CLIMATE_SWING_VERTICAL = 2;
+
CLIMATE_SWING_HORIZONTAL = 3;
+
}
+
enum ClimateAction {
+
CLIMATE_ACTION_OFF = 0;
+
// values same as mode for readability
+
CLIMATE_ACTION_COOLING = 2;
+
CLIMATE_ACTION_HEATING = 3;
+
CLIMATE_ACTION_IDLE = 4;
+
CLIMATE_ACTION_DRYING = 5;
+
CLIMATE_ACTION_FAN = 6;
+
}
+
enum ClimatePreset {
+
CLIMATE_PRESET_NONE = 0;
+
CLIMATE_PRESET_HOME = 1;
+
CLIMATE_PRESET_AWAY = 2;
+
CLIMATE_PRESET_BOOST = 3;
+
CLIMATE_PRESET_COMFORT = 4;
+
CLIMATE_PRESET_ECO = 5;
+
CLIMATE_PRESET_SLEEP = 6;
+
CLIMATE_PRESET_ACTIVITY = 7;
+
}
+
message ListEntitiesClimateResponse {
+
option (id) = 46;
+
option (base_class) = "InfoResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_CLIMATE";
+
+
string object_id = 1;
+
fixed32 key = 2;
+
string name = 3;
+
string unique_id = 4;
+
+
bool supports_current_temperature = 5;
+
bool supports_two_point_target_temperature = 6;
+
repeated ClimateMode supported_modes = 7;
+
float visual_min_temperature = 8;
+
float visual_max_temperature = 9;
+
float visual_target_temperature_step = 10;
+
// for older peer versions - in new system this
+
// is if CLIMATE_PRESET_AWAY exists is supported_presets
+
bool legacy_supports_away = 11;
+
bool supports_action = 12;
+
repeated ClimateFanMode supported_fan_modes = 13;
+
repeated ClimateSwingMode supported_swing_modes = 14;
+
repeated string supported_custom_fan_modes = 15;
+
repeated ClimatePreset supported_presets = 16;
+
repeated string supported_custom_presets = 17;
+
bool disabled_by_default = 18;
+
string icon = 19;
+
EntityCategory entity_category = 20;
+
float visual_current_temperature_step = 21;
+
bool supports_current_humidity = 22;
+
bool supports_target_humidity = 23;
+
float visual_min_humidity = 24;
+
float visual_max_humidity = 25;
+
uint32 device_id = 26;
+
}
+
message ClimateStateResponse {
+
option (id) = 47;
+
option (base_class) = "StateResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_CLIMATE";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
ClimateMode mode = 2;
+
float current_temperature = 3;
+
float target_temperature = 4;
+
float target_temperature_low = 5;
+
float target_temperature_high = 6;
+
// For older peers, equal to preset == CLIMATE_PRESET_AWAY
+
bool unused_legacy_away = 7;
+
ClimateAction action = 8;
+
ClimateFanMode fan_mode = 9;
+
ClimateSwingMode swing_mode = 10;
+
string custom_fan_mode = 11;
+
ClimatePreset preset = 12;
+
string custom_preset = 13;
+
float current_humidity = 14;
+
float target_humidity = 15;
+
uint32 device_id = 16;
+
}
+
message ClimateCommandRequest {
+
option (id) = 48;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_CLIMATE";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
bool has_mode = 2;
+
ClimateMode mode = 3;
+
bool has_target_temperature = 4;
+
float target_temperature = 5;
+
bool has_target_temperature_low = 6;
+
float target_temperature_low = 7;
+
bool has_target_temperature_high = 8;
+
float target_temperature_high = 9;
+
// legacy, for older peers, newer ones should use CLIMATE_PRESET_AWAY in preset
+
bool unused_has_legacy_away = 10;
+
bool unused_legacy_away = 11;
+
bool has_fan_mode = 12;
+
ClimateFanMode fan_mode = 13;
+
bool has_swing_mode = 14;
+
ClimateSwingMode swing_mode = 15;
+
bool has_custom_fan_mode = 16;
+
string custom_fan_mode = 17;
+
bool has_preset = 18;
+
ClimatePreset preset = 19;
+
bool has_custom_preset = 20;
+
string custom_preset = 21;
+
bool has_target_humidity = 22;
+
float target_humidity = 23;
+
}
+
+
// ==================== NUMBER ====================
+
enum NumberMode {
+
NUMBER_MODE_AUTO = 0;
+
NUMBER_MODE_BOX = 1;
+
NUMBER_MODE_SLIDER = 2;
+
}
+
message ListEntitiesNumberResponse {
+
option (id) = 49;
+
option (base_class) = "InfoResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_NUMBER";
+
+
string object_id = 1;
+
fixed32 key = 2;
+
string name = 3;
+
string unique_id = 4;
+
+
string icon = 5;
+
float min_value = 6;
+
float max_value = 7;
+
float step = 8;
+
bool disabled_by_default = 9;
+
EntityCategory entity_category = 10;
+
string unit_of_measurement = 11;
+
NumberMode mode = 12;
+
string device_class = 13;
+
uint32 device_id = 14;
+
}
+
message NumberStateResponse {
+
option (id) = 50;
+
option (base_class) = "StateResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_NUMBER";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
float state = 2;
+
// If the number does not have a valid state yet.
+
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
+
bool missing_state = 3;
+
uint32 device_id = 4;
+
}
+
message NumberCommandRequest {
+
option (id) = 51;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_NUMBER";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
float state = 2;
+
}
+
+
// ==================== SELECT ====================
+
message ListEntitiesSelectResponse {
+
option (id) = 52;
+
option (base_class) = "InfoResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_SELECT";
+
+
string object_id = 1;
+
fixed32 key = 2;
+
string name = 3;
+
string unique_id = 4;
+
+
string icon = 5;
+
repeated string options = 6;
+
bool disabled_by_default = 7;
+
EntityCategory entity_category = 8;
+
uint32 device_id = 9;
+
}
+
message SelectStateResponse {
+
option (id) = 53;
+
option (base_class) = "StateResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_SELECT";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
string state = 2;
+
// If the select does not have a valid state yet.
+
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
+
bool missing_state = 3;
+
uint32 device_id = 4;
+
}
+
message SelectCommandRequest {
+
option (id) = 54;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_SELECT";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
string state = 2;
+
}
+
+
// ==================== SIREN ====================
+
message ListEntitiesSirenResponse {
+
option (id) = 55;
+
option (base_class) = "InfoResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_SIREN";
+
+
string object_id = 1;
+
fixed32 key = 2;
+
string name = 3;
+
string unique_id = 4;
+
+
string icon = 5;
+
bool disabled_by_default = 6;
+
repeated string tones = 7;
+
bool supports_duration = 8;
+
bool supports_volume = 9;
+
EntityCategory entity_category = 10;
+
uint32 device_id = 11;
+
}
+
message SirenStateResponse {
+
option (id) = 56;
+
option (base_class) = "StateResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_SIREN";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
bool state = 2;
+
uint32 device_id = 3;
+
}
+
message SirenCommandRequest {
+
option (id) = 57;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_SIREN";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
bool has_state = 2;
+
bool state = 3;
+
bool has_tone = 4;
+
string tone = 5;
+
bool has_duration = 6;
+
uint32 duration = 7;
+
bool has_volume = 8;
+
float volume = 9;
+
}
+
+
// ==================== LOCK ====================
+
enum LockState {
+
LOCK_STATE_NONE = 0;
+
LOCK_STATE_LOCKED = 1;
+
LOCK_STATE_UNLOCKED = 2;
+
LOCK_STATE_JAMMED = 3;
+
LOCK_STATE_LOCKING = 4;
+
LOCK_STATE_UNLOCKING = 5;
+
}
+
enum LockCommand {
+
LOCK_UNLOCK = 0;
+
LOCK_LOCK = 1;
+
LOCK_OPEN = 2;
+
}
+
message ListEntitiesLockResponse {
+
option (id) = 58;
+
option (base_class) = "InfoResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_LOCK";
+
+
string object_id = 1;
+
fixed32 key = 2;
+
string name = 3;
+
string unique_id = 4;
+
+
string icon = 5;
+
bool disabled_by_default = 6;
+
EntityCategory entity_category = 7;
+
bool assumed_state = 8;
+
+
bool supports_open = 9;
+
bool requires_code = 10;
+
+
// Not yet implemented:
+
string code_format = 11;
+
uint32 device_id = 12;
+
}
+
message LockStateResponse {
+
option (id) = 59;
+
option (base_class) = "StateResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_LOCK";
+
option (no_delay) = true;
+
fixed32 key = 1;
+
LockState state = 2;
+
uint32 device_id = 3;
+
}
+
message LockCommandRequest {
+
option (id) = 60;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_LOCK";
+
option (no_delay) = true;
+
fixed32 key = 1;
+
LockCommand command = 2;
+
+
// Not yet implemented:
+
bool has_code = 3;
+
string code = 4;
+
}
+
+
// ==================== BUTTON ====================
+
message ListEntitiesButtonResponse {
+
option (id) = 61;
+
option (base_class) = "InfoResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_BUTTON";
+
+
string object_id = 1;
+
fixed32 key = 2;
+
string name = 3;
+
string unique_id = 4;
+
+
string icon = 5;
+
bool disabled_by_default = 6;
+
EntityCategory entity_category = 7;
+
string device_class = 8;
+
uint32 device_id = 9;
+
}
+
message ButtonCommandRequest {
+
option (id) = 62;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_BUTTON";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
}
+
+
// ==================== MEDIA PLAYER ====================
+
enum MediaPlayerState {
+
MEDIA_PLAYER_STATE_NONE = 0;
+
MEDIA_PLAYER_STATE_IDLE = 1;
+
MEDIA_PLAYER_STATE_PLAYING = 2;
+
MEDIA_PLAYER_STATE_PAUSED = 3;
+
}
+
enum MediaPlayerCommand {
+
MEDIA_PLAYER_COMMAND_PLAY = 0;
+
MEDIA_PLAYER_COMMAND_PAUSE = 1;
+
MEDIA_PLAYER_COMMAND_STOP = 2;
+
MEDIA_PLAYER_COMMAND_MUTE = 3;
+
MEDIA_PLAYER_COMMAND_UNMUTE = 4;
+
}
+
enum MediaPlayerFormatPurpose {
+
MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT = 0;
+
MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT = 1;
+
}
+
message MediaPlayerSupportedFormat {
+
option (ifdef) = "USE_MEDIA_PLAYER";
+
+
string format = 1;
+
uint32 sample_rate = 2;
+
uint32 num_channels = 3;
+
MediaPlayerFormatPurpose purpose = 4;
+
uint32 sample_bytes = 5;
+
}
+
message ListEntitiesMediaPlayerResponse {
+
option (id) = 63;
+
option (base_class) = "InfoResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_MEDIA_PLAYER";
+
+
string object_id = 1;
+
fixed32 key = 2;
+
string name = 3;
+
string unique_id = 4;
+
+
string icon = 5;
+
bool disabled_by_default = 6;
+
EntityCategory entity_category = 7;
+
+
bool supports_pause = 8;
+
+
repeated MediaPlayerSupportedFormat supported_formats = 9;
+
+
uint32 device_id = 10;
+
}
+
message MediaPlayerStateResponse {
+
option (id) = 64;
+
option (base_class) = "StateResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_MEDIA_PLAYER";
+
option (no_delay) = true;
+
fixed32 key = 1;
+
MediaPlayerState state = 2;
+
float volume = 3;
+
bool muted = 4;
+
uint32 device_id = 5;
+
}
+
message MediaPlayerCommandRequest {
+
option (id) = 65;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_MEDIA_PLAYER";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
+
bool has_command = 2;
+
MediaPlayerCommand command = 3;
+
+
bool has_volume = 4;
+
float volume = 5;
+
+
bool has_media_url = 6;
+
string media_url = 7;
+
+
bool has_announcement = 8;
+
bool announcement = 9;
+
}
+
+
// ==================== BLUETOOTH ====================
+
message SubscribeBluetoothLEAdvertisementsRequest {
+
option (id) = 66;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_BLUETOOTH_PROXY";
+
+
uint32 flags = 1;
+
}
+
+
message BluetoothServiceData {
+
string uuid = 1;
+
repeated uint32 legacy_data = 2 [deprecated = true]; // Removed in api version 1.7
+
bytes data = 3; // Added in api version 1.7
+
}
+
message BluetoothLEAdvertisementResponse {
+
option (id) = 67;
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_BLUETOOTH_PROXY";
+
option (no_delay) = true;
+
+
uint64 address = 1;
+
bytes name = 2;
+
sint32 rssi = 3;
+
+
repeated string service_uuids = 4;
+
repeated BluetoothServiceData service_data = 5;
+
repeated BluetoothServiceData manufacturer_data = 6;
+
+
uint32 address_type = 7;
+
}
+
+
message BluetoothLERawAdvertisement {
+
uint64 address = 1;
+
sint32 rssi = 2;
+
uint32 address_type = 3;
+
+
bytes data = 4;
+
}
+
+
message BluetoothLERawAdvertisementsResponse {
+
option (id) = 93;
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_BLUETOOTH_PROXY";
+
option (no_delay) = true;
+
+
repeated BluetoothLERawAdvertisement advertisements = 1;
+
}
+
+
enum BluetoothDeviceRequestType {
+
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0;
+
BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1;
+
BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR = 2;
+
BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3;
+
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE = 4;
+
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5;
+
BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE = 6;
+
}
+
+
message BluetoothDeviceRequest {
+
option (id) = 68;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_BLUETOOTH_PROXY";
+
+
uint64 address = 1;
+
BluetoothDeviceRequestType request_type = 2;
+
bool has_address_type = 3;
+
uint32 address_type = 4;
+
}
+
+
message BluetoothDeviceConnectionResponse {
+
option (id) = 69;
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_BLUETOOTH_PROXY";
+
+
uint64 address = 1;
+
bool connected = 2;
+
uint32 mtu = 3;
+
int32 error = 4;
+
}
+
+
message BluetoothGATTGetServicesRequest {
+
option (id) = 70;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_BLUETOOTH_PROXY";
+
+
uint64 address = 1;
+
}
+
+
message BluetoothGATTDescriptor {
+
repeated uint64 uuid = 1;
+
uint32 handle = 2;
+
}
+
+
message BluetoothGATTCharacteristic {
+
repeated uint64 uuid = 1;
+
uint32 handle = 2;
+
uint32 properties = 3;
+
repeated BluetoothGATTDescriptor descriptors = 4;
+
}
+
+
message BluetoothGATTService {
+
repeated uint64 uuid = 1;
+
uint32 handle = 2;
+
repeated BluetoothGATTCharacteristic characteristics = 3;
+
}
+
+
message BluetoothGATTGetServicesResponse {
+
option (id) = 71;
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_BLUETOOTH_PROXY";
+
+
uint64 address = 1;
+
repeated BluetoothGATTService services = 2;
+
}
+
+
message BluetoothGATTGetServicesDoneResponse {
+
option (id) = 72;
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_BLUETOOTH_PROXY";
+
+
uint64 address = 1;
+
}
+
+
message BluetoothGATTReadRequest {
+
option (id) = 73;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_BLUETOOTH_PROXY";
+
+
uint64 address = 1;
+
uint32 handle = 2;
+
}
+
+
message BluetoothGATTReadResponse {
+
option (id) = 74;
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_BLUETOOTH_PROXY";
+
+
uint64 address = 1;
+
uint32 handle = 2;
+
+
bytes data = 3;
+
+
}
+
+
message BluetoothGATTWriteRequest {
+
option (id) = 75;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_BLUETOOTH_PROXY";
+
+
uint64 address = 1;
+
uint32 handle = 2;
+
bool response = 3;
+
+
bytes data = 4;
+
}
+
+
message BluetoothGATTReadDescriptorRequest {
+
option (id) = 76;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_BLUETOOTH_PROXY";
+
+
uint64 address = 1;
+
uint32 handle = 2;
+
}
+
+
message BluetoothGATTWriteDescriptorRequest {
+
option (id) = 77;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_BLUETOOTH_PROXY";
+
+
uint64 address = 1;
+
uint32 handle = 2;
+
+
bytes data = 3;
+
}
+
+
message BluetoothGATTNotifyRequest {
+
option (id) = 78;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_BLUETOOTH_PROXY";
+
+
uint64 address = 1;
+
uint32 handle = 2;
+
bool enable = 3;
+
}
+
+
message BluetoothGATTNotifyDataResponse {
+
option (id) = 79;
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_BLUETOOTH_PROXY";
+
+
uint64 address = 1;
+
uint32 handle = 2;
+
+
bytes data = 3;
+
}
+
+
message SubscribeBluetoothConnectionsFreeRequest {
+
option (id) = 80;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_BLUETOOTH_PROXY";
+
}
+
+
message BluetoothConnectionsFreeResponse {
+
option (id) = 81;
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_BLUETOOTH_PROXY";
+
+
uint32 free = 1;
+
uint32 limit = 2;
+
repeated uint64 allocated = 3;
+
}
+
+
message BluetoothGATTErrorResponse {
+
option (id) = 82;
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_BLUETOOTH_PROXY";
+
+
uint64 address = 1;
+
uint32 handle = 2;
+
int32 error = 3;
+
}
+
+
message BluetoothGATTWriteResponse {
+
option (id) = 83;
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_BLUETOOTH_PROXY";
+
+
uint64 address = 1;
+
uint32 handle = 2;
+
}
+
+
message BluetoothGATTNotifyResponse {
+
option (id) = 84;
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_BLUETOOTH_PROXY";
+
+
uint64 address = 1;
+
uint32 handle = 2;
+
}
+
+
message BluetoothDevicePairingResponse {
+
option (id) = 85;
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_BLUETOOTH_PROXY";
+
+
uint64 address = 1;
+
bool paired = 2;
+
int32 error = 3;
+
}
+
+
message BluetoothDeviceUnpairingResponse {
+
option (id) = 86;
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_BLUETOOTH_PROXY";
+
+
uint64 address = 1;
+
bool success = 2;
+
int32 error = 3;
+
}
+
+
message UnsubscribeBluetoothLEAdvertisementsRequest {
+
option (id) = 87;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_BLUETOOTH_PROXY";
+
}
+
+
message BluetoothDeviceClearCacheResponse {
+
option (id) = 88;
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_BLUETOOTH_PROXY";
+
+
uint64 address = 1;
+
bool success = 2;
+
int32 error = 3;
+
}
+
+
enum BluetoothScannerState {
+
BLUETOOTH_SCANNER_STATE_IDLE = 0;
+
BLUETOOTH_SCANNER_STATE_STARTING = 1;
+
BLUETOOTH_SCANNER_STATE_RUNNING = 2;
+
BLUETOOTH_SCANNER_STATE_FAILED = 3;
+
BLUETOOTH_SCANNER_STATE_STOPPING = 4;
+
BLUETOOTH_SCANNER_STATE_STOPPED = 5;
+
}
+
+
enum BluetoothScannerMode {
+
BLUETOOTH_SCANNER_MODE_PASSIVE = 0;
+
BLUETOOTH_SCANNER_MODE_ACTIVE = 1;
+
}
+
+
message BluetoothScannerStateResponse {
+
option(id) = 126;
+
option(source) = SOURCE_SERVER;
+
option(ifdef) = "USE_BLUETOOTH_PROXY";
+
+
BluetoothScannerState state = 1;
+
BluetoothScannerMode mode = 2;
+
}
+
+
message BluetoothScannerSetModeRequest {
+
option(id) = 127;
+
option(source) = SOURCE_CLIENT;
+
option(ifdef) = "USE_BLUETOOTH_PROXY";
+
+
BluetoothScannerMode mode = 1;
+
}
+
+
// ==================== VOICE ASSISTANT ====================
+
enum VoiceAssistantSubscribeFlag {
+
VOICE_ASSISTANT_SUBSCRIBE_NONE = 0;
+
VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO = 1;
+
}
+
+
message SubscribeVoiceAssistantRequest {
+
option (id) = 89;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_VOICE_ASSISTANT";
+
+
bool subscribe = 1;
+
uint32 flags = 2;
+
}
+
+
enum VoiceAssistantRequestFlag {
+
VOICE_ASSISTANT_REQUEST_NONE = 0;
+
VOICE_ASSISTANT_REQUEST_USE_VAD = 1;
+
VOICE_ASSISTANT_REQUEST_USE_WAKE_WORD = 2;
+
}
+
+
message VoiceAssistantAudioSettings {
+
uint32 noise_suppression_level = 1;
+
uint32 auto_gain = 2;
+
float volume_multiplier = 3;
+
}
+
+
message VoiceAssistantRequest {
+
option (id) = 90;
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_VOICE_ASSISTANT";
+
+
bool start = 1;
+
string conversation_id = 2;
+
uint32 flags = 3;
+
VoiceAssistantAudioSettings audio_settings = 4;
+
string wake_word_phrase = 5;
+
}
+
+
message VoiceAssistantResponse {
+
option (id) = 91;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_VOICE_ASSISTANT";
+
+
uint32 port = 1;
+
bool error = 2;
+
}
+
+
enum VoiceAssistantEvent {
+
VOICE_ASSISTANT_ERROR = 0;
+
VOICE_ASSISTANT_RUN_START = 1;
+
VOICE_ASSISTANT_RUN_END = 2;
+
VOICE_ASSISTANT_STT_START = 3;
+
VOICE_ASSISTANT_STT_END = 4;
+
VOICE_ASSISTANT_INTENT_START = 5;
+
VOICE_ASSISTANT_INTENT_END = 6;
+
VOICE_ASSISTANT_TTS_START = 7;
+
VOICE_ASSISTANT_TTS_END = 8;
+
VOICE_ASSISTANT_WAKE_WORD_START = 9;
+
VOICE_ASSISTANT_WAKE_WORD_END = 10;
+
VOICE_ASSISTANT_STT_VAD_START = 11;
+
VOICE_ASSISTANT_STT_VAD_END = 12;
+
VOICE_ASSISTANT_TTS_STREAM_START = 98;
+
VOICE_ASSISTANT_TTS_STREAM_END = 99;
+
VOICE_ASSISTANT_INTENT_PROGRESS = 100;
+
}
+
+
message VoiceAssistantEventData {
+
string name = 1;
+
string value = 2;
+
}
+
+
message VoiceAssistantEventResponse {
+
option (id) = 92;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_VOICE_ASSISTANT";
+
+
VoiceAssistantEvent event_type = 1;
+
repeated VoiceAssistantEventData data = 2;
+
}
+
+
message VoiceAssistantAudio {
+
option (id) = 106;
+
option (source) = SOURCE_BOTH;
+
option (ifdef) = "USE_VOICE_ASSISTANT";
+
+
bytes data = 1;
+
bool end = 2;
+
}
+
+
enum VoiceAssistantTimerEvent {
+
VOICE_ASSISTANT_TIMER_STARTED = 0;
+
VOICE_ASSISTANT_TIMER_UPDATED = 1;
+
VOICE_ASSISTANT_TIMER_CANCELLED = 2;
+
VOICE_ASSISTANT_TIMER_FINISHED = 3;
+
}
+
+
message VoiceAssistantTimerEventResponse {
+
option (id) = 115;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_VOICE_ASSISTANT";
+
+
VoiceAssistantTimerEvent event_type = 1;
+
string timer_id = 2;
+
string name = 3;
+
uint32 total_seconds = 4;
+
uint32 seconds_left = 5;
+
bool is_active = 6;
+
}
+
+
message VoiceAssistantAnnounceRequest {
+
option (id) = 119;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_VOICE_ASSISTANT";
+
+
string media_id = 1;
+
string text = 2;
+
string preannounce_media_id = 3;
+
bool start_conversation = 4;
+
}
+
+
message VoiceAssistantAnnounceFinished {
+
option (id) = 120;
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_VOICE_ASSISTANT";
+
+
bool success = 1;
+
}
+
+
message VoiceAssistantWakeWord {
+
string id = 1;
+
string wake_word = 2;
+
repeated string trained_languages = 3;
+
}
+
+
message VoiceAssistantConfigurationRequest {
+
option (id) = 121;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_VOICE_ASSISTANT";
+
}
+
+
message VoiceAssistantConfigurationResponse {
+
option (id) = 122;
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_VOICE_ASSISTANT";
+
+
repeated VoiceAssistantWakeWord available_wake_words = 1;
+
repeated string active_wake_words = 2;
+
uint32 max_active_wake_words = 3;
+
}
+
+
message VoiceAssistantSetConfiguration {
+
option (id) = 123;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_VOICE_ASSISTANT";
+
+
repeated string active_wake_words = 1;
+
}
+
+
// ==================== ALARM CONTROL PANEL ====================
+
enum AlarmControlPanelState {
+
ALARM_STATE_DISARMED = 0;
+
ALARM_STATE_ARMED_HOME = 1;
+
ALARM_STATE_ARMED_AWAY = 2;
+
ALARM_STATE_ARMED_NIGHT = 3;
+
ALARM_STATE_ARMED_VACATION = 4;
+
ALARM_STATE_ARMED_CUSTOM_BYPASS = 5;
+
ALARM_STATE_PENDING = 6;
+
ALARM_STATE_ARMING = 7;
+
ALARM_STATE_DISARMING = 8;
+
ALARM_STATE_TRIGGERED = 9;
+
}
+
+
enum AlarmControlPanelStateCommand {
+
ALARM_CONTROL_PANEL_DISARM = 0;
+
ALARM_CONTROL_PANEL_ARM_AWAY = 1;
+
ALARM_CONTROL_PANEL_ARM_HOME = 2;
+
ALARM_CONTROL_PANEL_ARM_NIGHT = 3;
+
ALARM_CONTROL_PANEL_ARM_VACATION = 4;
+
ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS = 5;
+
ALARM_CONTROL_PANEL_TRIGGER = 6;
+
}
+
+
message ListEntitiesAlarmControlPanelResponse {
+
option (id) = 94;
+
option (base_class) = "InfoResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_ALARM_CONTROL_PANEL";
+
+
string object_id = 1;
+
fixed32 key = 2;
+
string name = 3;
+
string unique_id = 4;
+
string icon = 5;
+
bool disabled_by_default = 6;
+
EntityCategory entity_category = 7;
+
uint32 supported_features = 8;
+
bool requires_code = 9;
+
bool requires_code_to_arm = 10;
+
uint32 device_id = 11;
+
}
+
+
message AlarmControlPanelStateResponse {
+
option (id) = 95;
+
option (base_class) = "StateResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_ALARM_CONTROL_PANEL";
+
option (no_delay) = true;
+
fixed32 key = 1;
+
AlarmControlPanelState state = 2;
+
uint32 device_id = 3;
+
}
+
+
message AlarmControlPanelCommandRequest {
+
option (id) = 96;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_ALARM_CONTROL_PANEL";
+
option (no_delay) = true;
+
fixed32 key = 1;
+
AlarmControlPanelStateCommand command = 2;
+
string code = 3;
+
}
+
+
// ===================== TEXT =====================
+
enum TextMode {
+
TEXT_MODE_TEXT = 0;
+
TEXT_MODE_PASSWORD = 1;
+
}
+
message ListEntitiesTextResponse {
+
option (id) = 97;
+
option (base_class) = "InfoResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_TEXT";
+
+
string object_id = 1;
+
fixed32 key = 2;
+
string name = 3;
+
string unique_id = 4;
+
string icon = 5;
+
bool disabled_by_default = 6;
+
EntityCategory entity_category = 7;
+
+
uint32 min_length = 8;
+
uint32 max_length = 9;
+
string pattern = 10;
+
TextMode mode = 11;
+
uint32 device_id = 12;
+
}
+
message TextStateResponse {
+
option (id) = 98;
+
option (base_class) = "StateResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_TEXT";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
string state = 2;
+
// If the Text does not have a valid state yet.
+
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
+
bool missing_state = 3;
+
uint32 device_id = 4;
+
}
+
message TextCommandRequest {
+
option (id) = 99;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_TEXT";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
string state = 2;
+
}
+
+
+
// ==================== DATETIME DATE ====================
+
message ListEntitiesDateResponse {
+
option (id) = 100;
+
option (base_class) = "InfoResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_DATETIME_DATE";
+
+
string object_id = 1;
+
fixed32 key = 2;
+
string name = 3;
+
string unique_id = 4;
+
+
string icon = 5;
+
bool disabled_by_default = 6;
+
EntityCategory entity_category = 7;
+
uint32 device_id = 8;
+
}
+
message DateStateResponse {
+
option (id) = 101;
+
option (base_class) = "StateResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_DATETIME_DATE";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
// If the date does not have a valid state yet.
+
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
+
bool missing_state = 2;
+
uint32 year = 3;
+
uint32 month = 4;
+
uint32 day = 5;
+
uint32 device_id = 6;
+
}
+
message DateCommandRequest {
+
option (id) = 102;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_DATETIME_DATE";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
uint32 year = 2;
+
uint32 month = 3;
+
uint32 day = 4;
+
}
+
+
// ==================== DATETIME TIME ====================
+
message ListEntitiesTimeResponse {
+
option (id) = 103;
+
option (base_class) = "InfoResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_DATETIME_TIME";
+
+
string object_id = 1;
+
fixed32 key = 2;
+
string name = 3;
+
string unique_id = 4;
+
+
string icon = 5;
+
bool disabled_by_default = 6;
+
EntityCategory entity_category = 7;
+
uint32 device_id = 8;
+
}
+
message TimeStateResponse {
+
option (id) = 104;
+
option (base_class) = "StateResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_DATETIME_TIME";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
// If the time does not have a valid state yet.
+
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
+
bool missing_state = 2;
+
uint32 hour = 3;
+
uint32 minute = 4;
+
uint32 second = 5;
+
uint32 device_id = 6;
+
}
+
message TimeCommandRequest {
+
option (id) = 105;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_DATETIME_TIME";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
uint32 hour = 2;
+
uint32 minute = 3;
+
uint32 second = 4;
+
}
+
+
// ==================== EVENT ====================
+
message ListEntitiesEventResponse {
+
option (id) = 107;
+
option (base_class) = "InfoResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_EVENT";
+
+
string object_id = 1;
+
fixed32 key = 2;
+
string name = 3;
+
string unique_id = 4;
+
+
string icon = 5;
+
bool disabled_by_default = 6;
+
EntityCategory entity_category = 7;
+
string device_class = 8;
+
+
repeated string event_types = 9;
+
uint32 device_id = 10;
+
}
+
message EventResponse {
+
option (id) = 108;
+
option (base_class) = "StateResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_EVENT";
+
+
fixed32 key = 1;
+
string event_type = 2;
+
uint32 device_id = 3;
+
}
+
+
// ==================== VALVE ====================
+
message ListEntitiesValveResponse {
+
option (id) = 109;
+
option (base_class) = "InfoResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_VALVE";
+
+
string object_id = 1;
+
fixed32 key = 2;
+
string name = 3;
+
string unique_id = 4;
+
+
string icon = 5;
+
bool disabled_by_default = 6;
+
EntityCategory entity_category = 7;
+
string device_class = 8;
+
+
bool assumed_state = 9;
+
bool supports_position = 10;
+
bool supports_stop = 11;
+
uint32 device_id = 12;
+
}
+
+
enum ValveOperation {
+
VALVE_OPERATION_IDLE = 0;
+
VALVE_OPERATION_IS_OPENING = 1;
+
VALVE_OPERATION_IS_CLOSING = 2;
+
}
+
message ValveStateResponse {
+
option (id) = 110;
+
option (base_class) = "StateResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_VALVE";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
float position = 2;
+
ValveOperation current_operation = 3;
+
uint32 device_id = 4;
+
}
+
+
message ValveCommandRequest {
+
option (id) = 111;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_VALVE";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
bool has_position = 2;
+
float position = 3;
+
bool stop = 4;
+
}
+
+
// ==================== DATETIME DATETIME ====================
+
message ListEntitiesDateTimeResponse {
+
option (id) = 112;
+
option (base_class) = "InfoResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_DATETIME_DATETIME";
+
+
string object_id = 1;
+
fixed32 key = 2;
+
string name = 3;
+
string unique_id = 4;
+
+
string icon = 5;
+
bool disabled_by_default = 6;
+
EntityCategory entity_category = 7;
+
uint32 device_id = 8;
+
}
+
message DateTimeStateResponse {
+
option (id) = 113;
+
option (base_class) = "StateResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_DATETIME_DATETIME";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
// If the datetime does not have a valid state yet.
+
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
+
bool missing_state = 2;
+
fixed32 epoch_seconds = 3;
+
uint32 device_id = 4;
+
}
+
message DateTimeCommandRequest {
+
option (id) = 114;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_DATETIME_DATETIME";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
fixed32 epoch_seconds = 2;
+
}
+
+
// ==================== UPDATE ====================
+
message ListEntitiesUpdateResponse {
+
option (id) = 116;
+
option (base_class) = "InfoResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_UPDATE";
+
+
string object_id = 1;
+
fixed32 key = 2;
+
string name = 3;
+
string unique_id = 4;
+
+
string icon = 5;
+
bool disabled_by_default = 6;
+
EntityCategory entity_category = 7;
+
string device_class = 8;
+
uint32 device_id = 9;
+
}
+
message UpdateStateResponse {
+
option (id) = 117;
+
option (base_class) = "StateResponseProtoMessage";
+
option (source) = SOURCE_SERVER;
+
option (ifdef) = "USE_UPDATE";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
bool missing_state = 2;
+
bool in_progress = 3;
+
bool has_progress = 4;
+
float progress = 5;
+
string current_version = 6;
+
string latest_version = 7;
+
string title = 8;
+
string release_summary = 9;
+
string release_url = 10;
+
uint32 device_id = 11;
+
}
+
enum UpdateCommand {
+
UPDATE_COMMAND_NONE = 0;
+
UPDATE_COMMAND_UPDATE = 1;
+
UPDATE_COMMAND_CHECK = 2;
+
}
+
message UpdateCommandRequest {
+
option (id) = 118;
+
option (source) = SOURCE_CLIENT;
+
option (ifdef) = "USE_UPDATE";
+
option (no_delay) = true;
+
+
fixed32 key = 1;
+
UpdateCommand command = 2;
+
}
+25
sachy-esphome/protos/api_options.proto
···
+
syntax = "proto2";
+
+
import "google/protobuf/descriptor.proto";
+
+
enum APISourceType {
+
SOURCE_BOTH = 0;
+
SOURCE_SERVER = 1;
+
SOURCE_CLIENT = 2;
+
}
+
+
message void {}
+
+
extend google.protobuf.MethodOptions {
+
optional bool needs_setup_connection = 1038 [default=true];
+
optional bool needs_authentication = 1039 [default=true];
+
}
+
+
extend google.protobuf.MessageOptions {
+
optional uint32 id = 1036 [default=0];
+
optional APISourceType source = 1037 [default=SOURCE_BOTH];
+
optional string ifdef = 1038;
+
optional bool log = 1039 [default=true];
+
optional bool no_delay = 1040 [default=false];
+
optional string base_class = 1041;
+
}
+166
sachy-esphome/src/lib.rs
···
+
#![no_std]
+
+
use core::ops::{Index, RangeTo};
+
+
use num_traits::FromPrimitive;
+
pub use prost;
+
use prost::{bytes::Buf, bytes::BufMut};
+
use thiserror::Error;
+
+
#[allow(deprecated)]
+
pub mod api {
+
use prost::Message as ProstMessage;
+
include!(concat!(env!("OUT_DIR"), "/api.rs"));
+
}
+
+
pub use api::MessageKind;
+
+
use crate::api::Message;
+
+
#[derive(Error, Debug)]
+
pub enum EspHomeError {
+
#[error("First byte of message should be zero")]
+
InvalidStartByte,
+
+
#[error("Buffer is too small")]
+
BufferTooSmall,
+
+
#[error("Unable to decode protobuf")]
+
Decode(prost::DecodeError),
+
+
#[error("Unable to encode protobuf")]
+
Encode(prost::EncodeError),
+
+
#[error("Unknown message kind: {0}")]
+
UnknownMessageKind(u64),
+
}
+
+
#[cfg(feature = "defmt")]
+
impl defmt::Format for EspHomeError {
+
fn format(&self, fmt: defmt::Formatter) {
+
match self {
+
EspHomeError::InvalidStartByte => defmt::write!(fmt, "InvalidStartByte"),
+
EspHomeError::BufferTooSmall => defmt::write!(fmt, "BufferTooSmal"),
+
EspHomeError::Decode(_) => defmt::write!(fmt, "DecodeError"),
+
EspHomeError::Encode(_) => defmt::write!(fmt, "EncodeError"),
+
EspHomeError::UnknownMessageKind(msg) => {
+
defmt::write!(fmt, "UnknownMessageKind({})", msg)
+
}
+
}
+
}
+
}
+
+
impl From<prost::DecodeError> for EspHomeError {
+
#[inline]
+
fn from(value: prost::DecodeError) -> Self {
+
Self::Decode(value)
+
}
+
}
+
+
impl From<prost::EncodeError> for EspHomeError {
+
#[inline]
+
fn from(value: prost::EncodeError) -> Self {
+
Self::Encode(value)
+
}
+
}
+
+
pub type EspHomeResult<T> = core::result::Result<T, EspHomeError>;
+
+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+
struct EspHomeHeader {
+
_type: api::MessageKind,
+
size: u64,
+
}
+
+
impl EspHomeHeader {
+
pub fn encode_header(&self, buffer: &mut impl BufMut) -> EspHomeResult<()> {
+
buffer.put_u8(0);
+
+
try_encode_esp_varint(self.size, buffer)?;
+
try_encode_esp_varint(self._type as u64, buffer)
+
}
+
+
pub fn decode(buffer: &mut &[u8]) -> EspHomeResult<Self> {
+
if buffer.is_empty() {
+
return Err(EspHomeError::BufferTooSmall);
+
}
+
+
if buffer.get_u8() != 0 {
+
return Err(EspHomeError::InvalidStartByte);
+
}
+
+
let size = prost::encoding::decode_varint(buffer)?;
+
let kind = prost::encoding::decode_varint(buffer)?;
+
+
Ok(EspHomeHeader {
+
_type: api::MessageKind::from_u64(kind)
+
.ok_or(EspHomeError::UnknownMessageKind(kind))?,
+
size,
+
})
+
}
+
}
+
+
pub fn decode_esp_request(buffer: &mut &[u8]) -> EspHomeResult<api::Message> {
+
let header = EspHomeHeader::decode(buffer)?;
+
+
if header.size as usize > buffer.len() {
+
return Err(EspHomeError::BufferTooSmall);
+
}
+
+
let body = api::Message::decode(header._type, &mut &buffer[..header.size as usize])?;
+
+
buffer.advance(header.size as usize);
+
+
Ok(body)
+
}
+
+
pub struct EspEncoder<'any, B> {
+
buffer: &'any mut B,
+
written: usize,
+
}
+
+
impl<'any, B> EspEncoder<'any, B>
+
where
+
B: BufMut + Index<RangeTo<usize>, Output = [u8]>,
+
{
+
pub fn new(buffer: &'any mut B) -> Self {
+
Self { buffer, written: 0 }
+
}
+
+
pub fn encode(mut self, body: Message) -> EspHomeResult<Self> {
+
let remaining = self.buffer.remaining_mut();
+
+
let header = EspHomeHeader {
+
_type: MessageKind::from(&body),
+
size: body.encoded_len() as u64,
+
};
+
+
header.encode_header(self.buffer)?;
+
+
if self.buffer.remaining_mut() < header.size as usize {
+
return Err(EspHomeError::BufferTooSmall);
+
}
+
+
body.encode(self.buffer)?;
+
+
self.written += remaining - self.buffer.remaining_mut();
+
+
Ok(self)
+
}
+
+
pub fn finish(self) -> &'any [u8] {
+
&self.buffer[..self.written]
+
}
+
}
+
+
fn try_encode_esp_varint(value: u64, buf: &mut impl BufMut) -> EspHomeResult<()> {
+
let len = prost::encoding::encoded_len_varint(value);
+
+
if buf.remaining_mut() < len {
+
return Err(EspHomeError::BufferTooSmall);
+
}
+
+
prost::encoding::encode_varint(value, buf);
+
+
Ok(())
+
}
+66
sachy-esphome/templates/message.handlebars
···
+
#[derive(num_derive::FromPrimitive, num_derive::ToPrimitive, Copy, Clone)]
+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+
#[repr(u64)]
+
pub enum MessageKind {
+
{{#each this}}
+
{{ name }} = {{ id }},
+
{{/each}}
+
}
+
+
#[derive(Debug, Clone)]
+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+
pub enum Message {
+
{{#each this}}
+
{{ name }}({{ name }}),
+
{{/each}}
+
}
+
+
impl Message {
+
pub fn decode(message_kind: MessageKind, buffer: &mut &[u8]) -> Result<Self, prost::DecodeError> {
+
match message_kind {
+
{{#each this}}
+
MessageKind::{{ name }} => Ok(Self::{{ name }}({{name}}::decode(buffer)?)),
+
{{/each}}
+
}
+
}
+
+
pub fn encoded_len(&self) -> usize {
+
match self {
+
{{#each this}}
+
Self::{{ name }}(message) => message.encoded_len(),
+
{{/each}}
+
}
+
}
+
+
pub fn encode(&self, buf: &mut impl prost::bytes::BufMut) -> Result<(), prost::EncodeError> {
+
match self {
+
{{#each this}}
+
Self::{{ name }}(message) => message.encode(buf),
+
{{/each}}
+
}
+
}
+
}
+
+
impl From<&Message> for MessageKind {
+
fn from(value: &Message) -> Self {
+
match value {
+
{{#each this}}
+
Message::{{ name }}(message) => Self::from(message),
+
{{/each}}
+
}
+
}
+
}
+
+
{{#each this}}
+
impl From<&{{ name }}> for MessageKind {
+
fn from(_value: &{{name}}) -> Self {
+
Self::{{name}}
+
}
+
}
+
+
impl From<{{ name }}> for Message {
+
fn from(value: {{ name }}) -> Self {
+
Message::{{ name }}(value)
+
}
+
}
+
{{/each}}