diff options
| author | diogo464 <[email protected]> | 2026-01-20 15:06:41 +0000 |
|---|---|---|
| committer | diogo464 <[email protected]> | 2026-01-20 15:06:41 +0000 |
| commit | 18cf7f77b0d1c93d9b24f1ba43183a81e3e5a81e (patch) | |
| tree | d4352b08f87dd51c4eddb2fd300cb2be13bd4e50 | |
| parent | 74e0f4ca0b0054e3317826f2af0932712d965671 (diff) | |
| parent | 8466e8720856786833099580931e7cc77d89d122 (diff) | |
Merge branch 'main' into embassy-gitembassy-git
| -rw-r--r-- | .claude/commands/release.md | 36 | ||||
| -rw-r--r-- | Cargo.lock | 2 | ||||
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rwxr-xr-x | release.sh | 59 | ||||
| -rw-r--r-- | src/command_policy.rs | 4 | ||||
| -rw-r--r-- | src/entity_sensor.rs | 4 | ||||
| -rw-r--r-- | src/entity_switch.rs | 4 | ||||
| -rw-r--r-- | src/lib.rs | 20 | ||||
| -rw-r--r-- | src/mqtt/rx.rs | 18 | ||||
| -rw-r--r-- | src/mqtt/tx.rs | 1 |
10 files changed, 115 insertions, 35 deletions
diff --git a/.claude/commands/release.md b/.claude/commands/release.md new file mode 100644 index 0000000..c0f072e --- /dev/null +++ b/.claude/commands/release.md | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | # Release Command | ||
| 2 | |||
| 3 | Create a new cargo release for embassy-ha. | ||
| 4 | |||
| 5 | ## Instructions | ||
| 6 | |||
| 7 | 1. **Ask for the new version number** - Prompt the user for the version (e.g., "0.3.0"). Do not include the "v" prefix in the version number itself. | ||
| 8 | |||
| 9 | 2. **Verify clean working directory** - Run `git status --porcelain` and fail if there are any uncommitted changes. The working directory must be clean before proceeding. | ||
| 10 | |||
| 11 | 3. **Run pre-release checks** - Run the following commands in order. If any command fails, **stop immediately**, report the error, and let the user fix the issues manually. Do NOT attempt to fix issues automatically: | ||
| 12 | - `cargo fmt --check` - Verify code is properly formatted | ||
| 13 | - `cargo clippy` - Run linter (must have no warnings) | ||
| 14 | - `cargo check --tests --examples` - Check compilation of tests and examples | ||
| 15 | - `cargo test` - Run the test suite | ||
| 16 | - `cargo publish --dry-run` - Verify the package can be published | ||
| 17 | |||
| 18 | 4. **Update version in Cargo.toml** - Only after all pre-release checks pass, update the `version` field in `Cargo.toml` to the new version. | ||
| 19 | |||
| 20 | 5. **Create release commit** - Create a commit with the message: `chore: release v{version}` | ||
| 21 | |||
| 22 | 6. **Create tag** - Create an annotated tag with the name `v{version}` and message `v{version}` | ||
| 23 | |||
| 24 | 7. **Push to remote** - Push both the commit and the tag to the remote: | ||
| 25 | - `git push` | ||
| 26 | - `git push --tags` | ||
| 27 | |||
| 28 | ## Important Notes | ||
| 29 | |||
| 30 | - If any pre-release check fails, **stop immediately** and report the failure. Do NOT: | ||
| 31 | - Attempt to fix the issues automatically (e.g., running `cargo fmt` to fix formatting) | ||
| 32 | - Update the version in Cargo.toml | ||
| 33 | - Create the commit or tag | ||
| 34 | - The user is responsible for fixing any issues and re-running the release command | ||
| 35 | - The tag format must be `v{version}` (e.g., `v0.3.0`) to match existing conventions | ||
| 36 | - Use `--dry-run` for cargo publish to verify without actually publishing | ||
| @@ -175,7 +175,7 @@ source = "git+https://github.com/embassy-rs/embassy#dd4b0ae19e97101dab86af061e69 | |||
| 175 | 175 | ||
| 176 | [[package]] | 176 | [[package]] |
| 177 | name = "embassy-ha" | 177 | name = "embassy-ha" |
| 178 | version = "0.2.0" | 178 | version = "0.3.0" |
| 179 | dependencies = [ | 179 | dependencies = [ |
| 180 | "critical-section", | 180 | "critical-section", |
| 181 | "defmt 1.0.1", | 181 | "defmt 1.0.1", |
| @@ -1,6 +1,6 @@ | |||
| 1 | [package] | 1 | [package] |
| 2 | name = "embassy-ha" | 2 | name = "embassy-ha" |
| 3 | version = "0.2.0" | 3 | version = "0.3.0" |
| 4 | edition = "2024" | 4 | edition = "2024" |
| 5 | authors = ["diogo464 <[email protected]>"] | 5 | authors = ["diogo464 <[email protected]>"] |
| 6 | description = "MQTT Home Assistant integration library for Embassy async runtime" | 6 | description = "MQTT Home Assistant integration library for Embassy async runtime" |
diff --git a/release.sh b/release.sh new file mode 100755 index 0000000..e79aa17 --- /dev/null +++ b/release.sh | |||
| @@ -0,0 +1,59 @@ | |||
| 1 | #!/bin/bash | ||
| 2 | set -euo pipefail | ||
| 3 | |||
| 4 | VERSION="${1:-}" | ||
| 5 | |||
| 6 | if [[ -z "$VERSION" ]]; then | ||
| 7 | echo "Usage: $0 <version>" | ||
| 8 | echo "Example: $0 0.3.0" | ||
| 9 | exit 1 | ||
| 10 | fi | ||
| 11 | |||
| 12 | if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | ||
| 13 | echo "Error: Version must be in format x.y.z" | ||
| 14 | exit 1 | ||
| 15 | fi | ||
| 16 | |||
| 17 | echo "Releasing version $VERSION" | ||
| 18 | |||
| 19 | # Verify clean working directory | ||
| 20 | if [[ -n "$(git status --porcelain)" ]]; then | ||
| 21 | echo "Error: Working directory has uncommitted changes" | ||
| 22 | git status --short | ||
| 23 | exit 1 | ||
| 24 | fi | ||
| 25 | |||
| 26 | echo "Running pre-release checks..." | ||
| 27 | |||
| 28 | echo " cargo fmt --check" | ||
| 29 | cargo fmt --check | ||
| 30 | |||
| 31 | echo " cargo clippy" | ||
| 32 | cargo clippy -- -D warnings | ||
| 33 | |||
| 34 | echo " cargo check --tests --examples" | ||
| 35 | cargo check --tests --examples | ||
| 36 | |||
| 37 | echo " cargo test" | ||
| 38 | cargo test | ||
| 39 | |||
| 40 | echo " cargo publish --dry-run" | ||
| 41 | cargo publish --dry-run | ||
| 42 | |||
| 43 | echo "All checks passed. Updating version..." | ||
| 44 | |||
| 45 | sed -i "s/^version = \".*\"/version = \"$VERSION\"/" Cargo.toml | ||
| 46 | |||
| 47 | echo "Creating commit..." | ||
| 48 | git add Cargo.toml | ||
| 49 | git commit -m "chore: release v$VERSION" | ||
| 50 | |||
| 51 | echo "Creating tag..." | ||
| 52 | git tag -a "v$VERSION" -m "v$VERSION" | ||
| 53 | |||
| 54 | echo "Pushing to remote..." | ||
| 55 | git push | ||
| 56 | git push --tags | ||
| 57 | |||
| 58 | echo "Release v$VERSION complete!" | ||
| 59 | echo "To publish to crates.io, run: cargo publish" | ||
diff --git a/src/command_policy.rs b/src/command_policy.rs index e5859bb..a1bb3bf 100644 --- a/src/command_policy.rs +++ b/src/command_policy.rs | |||
| @@ -63,8 +63,7 @@ | |||
| 63 | /// # } | 63 | /// # } |
| 64 | /// # async fn turn_on_motor() -> Result<(), ()> { Ok(()) } | 64 | /// # async fn turn_on_motor() -> Result<(), ()> { Ok(()) } |
| 65 | /// ``` | 65 | /// ``` |
| 66 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | 66 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] |
| 67 | #[derive(Default)] | ||
| 68 | pub enum CommandPolicy { | 67 | pub enum CommandPolicy { |
| 69 | /// Automatically publish the entity's state when a command is received. | 68 | /// Automatically publish the entity's state when a command is received. |
| 70 | #[default] | 69 | #[default] |
| @@ -73,4 +72,3 @@ pub enum CommandPolicy { | |||
| 73 | /// Do not automatically publish state. The application must manually update the state. | 72 | /// Do not automatically publish state. The application must manually update the state. |
| 74 | Manual, | 73 | Manual, |
| 75 | } | 74 | } |
| 76 | |||
diff --git a/src/entity_sensor.rs b/src/entity_sensor.rs index e221141..fd5e7f7 100644 --- a/src/entity_sensor.rs +++ b/src/entity_sensor.rs | |||
| @@ -18,8 +18,7 @@ impl StateClass { | |||
| 18 | } | 18 | } |
| 19 | } | 19 | } |
| 20 | 20 | ||
| 21 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | 21 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] |
| 22 | #[derive(Default)] | ||
| 23 | pub enum SensorClass { | 22 | pub enum SensorClass { |
| 24 | #[default] | 23 | #[default] |
| 25 | Generic, | 24 | Generic, |
| @@ -77,7 +76,6 @@ pub enum SensorClass { | |||
| 77 | Other(&'static str), | 76 | Other(&'static str), |
| 78 | } | 77 | } |
| 79 | 78 | ||
| 80 | |||
| 81 | impl SensorClass { | 79 | impl SensorClass { |
| 82 | pub fn as_str(&self) -> Option<&'static str> { | 80 | pub fn as_str(&self) -> Option<&'static str> { |
| 83 | match self { | 81 | match self { |
diff --git a/src/entity_switch.rs b/src/entity_switch.rs index 2b799a1..b685ce5 100644 --- a/src/entity_switch.rs +++ b/src/entity_switch.rs | |||
| @@ -14,15 +14,13 @@ pub enum SwitchClass { | |||
| 14 | /// Configuration for a switch entity. | 14 | /// Configuration for a switch entity. |
| 15 | /// | 15 | /// |
| 16 | /// See [`CommandPolicy`] for details on how commands are handled. | 16 | /// See [`CommandPolicy`] for details on how commands are handled. |
| 17 | #[derive(Debug)] | 17 | #[derive(Debug, Default)] |
| 18 | #[derive(Default)] | ||
| 19 | pub struct SwitchConfig { | 18 | pub struct SwitchConfig { |
| 20 | pub common: EntityCommonConfig, | 19 | pub common: EntityCommonConfig, |
| 21 | pub class: SwitchClass, | 20 | pub class: SwitchClass, |
| 22 | pub command_policy: CommandPolicy, | 21 | pub command_policy: CommandPolicy, |
| 23 | } | 22 | } |
| 24 | 23 | ||
| 25 | |||
| 26 | impl SwitchConfig { | 24 | impl SwitchConfig { |
| 27 | pub(crate) fn populate(&self, config: &mut EntityConfig) { | 25 | pub(crate) fn populate(&self, config: &mut EntityConfig) { |
| 28 | self.common.populate(config); | 26 | self.common.populate(config); |
| @@ -346,6 +346,7 @@ pub struct DeviceConfig { | |||
| 346 | pub model: &'static str, | 346 | pub model: &'static str, |
| 347 | } | 347 | } |
| 348 | 348 | ||
| 349 | #[derive(Default)] | ||
| 349 | pub struct DeviceBuffersOwned { | 350 | pub struct DeviceBuffersOwned { |
| 350 | pub publish: Vec<u8, 2048>, | 351 | pub publish: Vec<u8, 2048>, |
| 351 | pub subscribe: Vec<u8, 128>, | 352 | pub subscribe: Vec<u8, 128>, |
| @@ -357,21 +358,6 @@ pub struct DeviceBuffersOwned { | |||
| 357 | pub attributes_topic: String<128>, | 358 | pub attributes_topic: String<128>, |
| 358 | } | 359 | } |
| 359 | 360 | ||
| 360 | impl Default for DeviceBuffersOwned { | ||
| 361 | fn default() -> Self { | ||
| 362 | Self { | ||
| 363 | publish: Default::default(), | ||
| 364 | subscribe: Default::default(), | ||
| 365 | discovery: Default::default(), | ||
| 366 | availability_topic: Default::default(), | ||
| 367 | discovery_topic: Default::default(), | ||
| 368 | state_topic: Default::default(), | ||
| 369 | command_topic: Default::default(), | ||
| 370 | attributes_topic: Default::default(), | ||
| 371 | } | ||
| 372 | } | ||
| 373 | } | ||
| 374 | |||
| 375 | impl DeviceBuffersOwned { | 361 | impl DeviceBuffersOwned { |
| 376 | pub fn as_buffers_mut(&mut self) -> DeviceBuffers<'_> { | 362 | pub fn as_buffers_mut(&mut self) -> DeviceBuffers<'_> { |
| 377 | DeviceBuffers { | 363 | DeviceBuffers { |
| @@ -1093,10 +1079,8 @@ fn generate_entity_discovery( | |||
| 1093 | /// | 1079 | /// |
| 1094 | /// ```no_run | 1080 | /// ```no_run |
| 1095 | /// # use embassy_ha::{Device, Transport}; | 1081 | /// # use embassy_ha::{Device, Transport}; |
| 1096 | /// # async fn example(mut device: Device<'_>, create_transport: impl Fn() -> impl Transport) { | 1082 | /// # async fn example(mut device: Device<'_>, mut transport: impl Transport) { |
| 1097 | /// loop { | 1083 | /// loop { |
| 1098 | /// let mut transport = create_transport(); | ||
| 1099 | /// | ||
| 1100 | /// match embassy_ha::run(&mut device, &mut transport).await { | 1084 | /// match embassy_ha::run(&mut device, &mut transport).await { |
| 1101 | /// Ok(()) => { | 1085 | /// Ok(()) => { |
| 1102 | /// // Normal exit (this shouldn't happen in practice) | 1086 | /// // Normal exit (this shouldn't happen in practice) |
diff --git a/src/mqtt/rx.rs b/src/mqtt/rx.rs index 10b775a..0dfd858 100644 --- a/src/mqtt/rx.rs +++ b/src/mqtt/rx.rs | |||
| @@ -37,7 +37,9 @@ impl From<varint::Error> for Error { | |||
| 37 | fn from(value: varint::Error) -> Self { | 37 | fn from(value: varint::Error) -> Self { |
| 38 | match value { | 38 | match value { |
| 39 | varint::Error::NeedMoreData => Self::NeedMoreData, | 39 | varint::Error::NeedMoreData => Self::NeedMoreData, |
| 40 | varint::Error::InvalidVarInt => Self::InvalidPacket("invalid variable integer encoding"), | 40 | varint::Error::InvalidVarInt => { |
| 41 | Self::InvalidPacket("invalid variable integer encoding") | ||
| 42 | } | ||
| 41 | } | 43 | } |
| 42 | } | 44 | } |
| 43 | } | 45 | } |
| @@ -92,8 +94,10 @@ pub fn decode<'a>(buf: &'a [u8]) -> Result<(Packet<'a>, usize), Error> { | |||
| 92 | protocol::PACKET_TYPE_PUBLISH => { | 94 | protocol::PACKET_TYPE_PUBLISH => { |
| 93 | // Extract flags from the fixed header | 95 | // Extract flags from the fixed header |
| 94 | let retain = (packet_flags & protocol::PUBLISH_FLAG_RETAIN) != 0; | 96 | let retain = (packet_flags & protocol::PUBLISH_FLAG_RETAIN) != 0; |
| 95 | let qos_value = (packet_flags & protocol::PUBLISH_FLAG_QOS_MASK) >> protocol::PUBLISH_FLAG_QOS_SHIFT; | 97 | let qos_value = (packet_flags & protocol::PUBLISH_FLAG_QOS_MASK) |
| 96 | let qos = Qos::from_u8(qos_value).ok_or(Error::InvalidPacket("PUBLISH has invalid QoS value"))?; | 98 | >> protocol::PUBLISH_FLAG_QOS_SHIFT; |
| 99 | let qos = Qos::from_u8(qos_value) | ||
| 100 | .ok_or(Error::InvalidPacket("PUBLISH has invalid QoS value"))?; | ||
| 97 | let dup = (packet_flags & protocol::PUBLISH_FLAG_DUP) != 0; | 101 | let dup = (packet_flags & protocol::PUBLISH_FLAG_DUP) != 0; |
| 98 | 102 | ||
| 99 | // Track position after fixed header to calculate data length | 103 | // Track position after fixed header to calculate data length |
| @@ -113,7 +117,9 @@ pub fn decode<'a>(buf: &'a [u8]) -> Result<(Packet<'a>, usize), Error> { | |||
| 113 | let variable_header_len = reader.num_read() - variable_header_start; | 117 | let variable_header_len = reader.num_read() - variable_header_start; |
| 114 | let data_len = (packet_len as usize) | 118 | let data_len = (packet_len as usize) |
| 115 | .checked_sub(variable_header_len) | 119 | .checked_sub(variable_header_len) |
| 116 | .ok_or(Error::InvalidPacket("PUBLISH remaining length is too short for headers"))?; | 120 | .ok_or(Error::InvalidPacket( |
| 121 | "PUBLISH remaining length is too short for headers", | ||
| 122 | ))?; | ||
| 117 | 123 | ||
| 118 | Packet::Publish { | 124 | Packet::Publish { |
| 119 | topic, | 125 | topic, |
| @@ -140,7 +146,9 @@ pub fn decode<'a>(buf: &'a [u8]) -> Result<(Packet<'a>, usize), Error> { | |||
| 140 | } | 146 | } |
| 141 | if packet_len < 3 { | 147 | if packet_len < 3 { |
| 142 | // Minimum: 2 bytes packet ID + 1 byte return code | 148 | // Minimum: 2 bytes packet ID + 1 byte return code |
| 143 | return Err(Error::InvalidPacket("SUBACK remaining length must be at least 3")); | 149 | return Err(Error::InvalidPacket( |
| 150 | "SUBACK remaining length must be at least 3", | ||
| 151 | )); | ||
| 144 | } | 152 | } |
| 145 | let packet_id = PacketId::from(reader.read_u16()?); | 153 | let packet_id = PacketId::from(reader.read_u16()?); |
| 146 | let return_code = reader.read_u8()?; | 154 | let return_code = reader.read_u8()?; |
diff --git a/src/mqtt/tx.rs b/src/mqtt/tx.rs index 7a4d443..3b2ed66 100644 --- a/src/mqtt/tx.rs +++ b/src/mqtt/tx.rs | |||
| @@ -209,4 +209,3 @@ pub fn pingreq(buffer: &mut FieldBuffer) { | |||
| 209 | ))); | 209 | ))); |
| 210 | buffer.push(Field::VarInt(0)); | 210 | buffer.push(Field::VarInt(0)); |
| 211 | } | 211 | } |
| 212 | |||
