diff options
| -rw-r--r-- | embassy-net-esp-hosted/CHANGELOG.md | 1 | ||||
| -rw-r--r-- | embassy-net-esp-hosted/src/control.rs | 40 | ||||
| -rw-r--r-- | embassy-net-esp-hosted/src/ioctl.rs | 27 | ||||
| -rw-r--r-- | embassy-net-esp-hosted/src/lib.rs | 5 | ||||
| -rw-r--r-- | embassy-net-esp-hosted/src/proto.rs | 12 |
5 files changed, 74 insertions, 11 deletions
diff --git a/embassy-net-esp-hosted/CHANGELOG.md b/embassy-net-esp-hosted/CHANGELOG.md index d8b912295..6991b39fd 100644 --- a/embassy-net-esp-hosted/CHANGELOG.md +++ b/embassy-net-esp-hosted/CHANGELOG.md | |||
| @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |||
| 11 | - Add an `Interface` trait to allow using other interface transports. | 11 | - Add an `Interface` trait to allow using other interface transports. |
| 12 | - Switch to `micropb` for protobuf. | 12 | - Switch to `micropb` for protobuf. |
| 13 | - Update protos to latest `esp-hosted-fg`. | 13 | - Update protos to latest `esp-hosted-fg`. |
| 14 | - Add support for OTA firmware updates. | ||
| 14 | 15 | ||
| 15 | ## 0.2.1 - 2025-08-26 | 16 | ## 0.2.1 - 2025-08-26 |
| 16 | 17 | ||
diff --git a/embassy-net-esp-hosted/src/control.rs b/embassy-net-esp-hosted/src/control.rs index 38ec648b4..eb79593f6 100644 --- a/embassy-net-esp-hosted/src/control.rs +++ b/embassy-net-esp-hosted/src/control.rs | |||
| @@ -146,6 +146,43 @@ impl<'a> Control<'a> { | |||
| 146 | Ok(()) | 146 | Ok(()) |
| 147 | } | 147 | } |
| 148 | 148 | ||
| 149 | /// Initiate a firmware update. | ||
| 150 | pub async fn ota_begin(&mut self) -> Result<(), Error> { | ||
| 151 | let req = proto::CtrlMsg_Req_OTABegin {}; | ||
| 152 | ioctl!(self, ReqOtaBegin, RespOtaBegin, req, resp); | ||
| 153 | Ok(()) | ||
| 154 | } | ||
| 155 | |||
| 156 | /// Write slice of firmware to a device. | ||
| 157 | /// | ||
| 158 | /// [`ota_begin`] must be called first. | ||
| 159 | /// | ||
| 160 | /// The slice is split into chunks that can be sent across | ||
| 161 | /// the ioctl protocol to the wifi adapter. | ||
| 162 | pub async fn ota_write(&mut self, data: &[u8]) -> Result<(), Error> { | ||
| 163 | for chunk in data.chunks(256) { | ||
| 164 | let req = proto::CtrlMsg_Req_OTAWrite { | ||
| 165 | ota_data: heapless::Vec::from_slice(chunk).unwrap(), | ||
| 166 | }; | ||
| 167 | ioctl!(self, ReqOtaWrite, RespOtaWrite, req, resp); | ||
| 168 | } | ||
| 169 | Ok(()) | ||
| 170 | } | ||
| 171 | |||
| 172 | /// End the OTA session. | ||
| 173 | /// | ||
| 174 | /// [`ota_begin`] must be called first. | ||
| 175 | /// | ||
| 176 | /// NOTE: Will reset the wifi adapter after 5 seconds. | ||
| 177 | pub async fn ota_end(&mut self) -> Result<(), Error> { | ||
| 178 | let req = proto::CtrlMsg_Req_OTAEnd {}; | ||
| 179 | ioctl!(self, ReqOtaEnd, RespOtaEnd, req, resp); | ||
| 180 | self.shared.ota_done(); | ||
| 181 | // Wait for re-init | ||
| 182 | self.init().await?; | ||
| 183 | Ok(()) | ||
| 184 | } | ||
| 185 | |||
| 149 | /// duration in seconds, clamped to [10, 3600] | 186 | /// duration in seconds, clamped to [10, 3600] |
| 150 | async fn set_heartbeat(&mut self, duration: u32) -> Result<(), Error> { | 187 | async fn set_heartbeat(&mut self, duration: u32) -> Result<(), Error> { |
| 151 | let req = proto::CtrlMsg_Req_ConfigHeartbeat { | 188 | let req = proto::CtrlMsg_Req_ConfigHeartbeat { |
| @@ -175,7 +212,8 @@ impl<'a> Control<'a> { | |||
| 175 | async fn ioctl(&mut self, msg: &mut CtrlMsg) -> Result<(), Error> { | 212 | async fn ioctl(&mut self, msg: &mut CtrlMsg) -> Result<(), Error> { |
| 176 | debug!("ioctl req: {:?}", &msg); | 213 | debug!("ioctl req: {:?}", &msg); |
| 177 | 214 | ||
| 178 | let mut buf = [0u8; 128]; | 215 | // Theoretical max overhead is 29 bytes. Biggest message is OTA write with 256 bytes. |
| 216 | let mut buf = [0u8; 256 + 29]; | ||
| 179 | let buf_len = buf.len(); | 217 | let buf_len = buf.len(); |
| 180 | 218 | ||
| 181 | let mut encoder = PbEncoder::new(&mut buf[..]); | 219 | let mut encoder = PbEncoder::new(&mut buf[..]); |
diff --git a/embassy-net-esp-hosted/src/ioctl.rs b/embassy-net-esp-hosted/src/ioctl.rs index a516f80c7..de0f867e8 100644 --- a/embassy-net-esp-hosted/src/ioctl.rs +++ b/embassy-net-esp-hosted/src/ioctl.rs | |||
| @@ -23,16 +23,23 @@ pub struct Shared(RefCell<SharedInner>); | |||
| 23 | 23 | ||
| 24 | struct SharedInner { | 24 | struct SharedInner { |
| 25 | ioctl: IoctlState, | 25 | ioctl: IoctlState, |
| 26 | is_init: bool, | 26 | state: ControlState, |
| 27 | control_waker: WakerRegistration, | 27 | control_waker: WakerRegistration, |
| 28 | runner_waker: WakerRegistration, | 28 | runner_waker: WakerRegistration, |
| 29 | } | 29 | } |
| 30 | 30 | ||
| 31 | #[derive(Clone, Copy)] | ||
| 32 | pub(crate) enum ControlState { | ||
| 33 | Init, | ||
| 34 | Reboot, | ||
| 35 | Ready, | ||
| 36 | } | ||
| 37 | |||
| 31 | impl Shared { | 38 | impl Shared { |
| 32 | pub fn new() -> Self { | 39 | pub fn new() -> Self { |
| 33 | Self(RefCell::new(SharedInner { | 40 | Self(RefCell::new(SharedInner { |
| 34 | ioctl: IoctlState::Done { resp_len: 0 }, | 41 | ioctl: IoctlState::Done { resp_len: 0 }, |
| 35 | is_init: false, | 42 | state: ControlState::Init, |
| 36 | control_waker: WakerRegistration::new(), | 43 | control_waker: WakerRegistration::new(), |
| 37 | runner_waker: WakerRegistration::new(), | 44 | runner_waker: WakerRegistration::new(), |
| 38 | })) | 45 | })) |
| @@ -99,18 +106,30 @@ impl Shared { | |||
| 99 | } | 106 | } |
| 100 | } | 107 | } |
| 101 | 108 | ||
| 109 | // ota | ||
| 110 | pub fn ota_done(&self) { | ||
| 111 | let mut this = self.0.borrow_mut(); | ||
| 112 | this.state = ControlState::Reboot; | ||
| 113 | } | ||
| 114 | |||
| 102 | // // // // // // // // // // // // // // // // // // // // | 115 | // // // // // // // // // // // // // // // // // // // // |
| 116 | // | ||
| 117 | // check if ota is in progress | ||
| 118 | pub(crate) fn state(&self) -> ControlState { | ||
| 119 | let this = self.0.borrow(); | ||
| 120 | this.state | ||
| 121 | } | ||
| 103 | 122 | ||
| 104 | pub fn init_done(&self) { | 123 | pub fn init_done(&self) { |
| 105 | let mut this = self.0.borrow_mut(); | 124 | let mut this = self.0.borrow_mut(); |
| 106 | this.is_init = true; | 125 | this.state = ControlState::Ready; |
| 107 | this.control_waker.wake(); | 126 | this.control_waker.wake(); |
| 108 | } | 127 | } |
| 109 | 128 | ||
| 110 | pub fn init_wait(&self) -> impl Future<Output = ()> + '_ { | 129 | pub fn init_wait(&self) -> impl Future<Output = ()> + '_ { |
| 111 | poll_fn(|cx| { | 130 | poll_fn(|cx| { |
| 112 | let mut this = self.0.borrow_mut(); | 131 | let mut this = self.0.borrow_mut(); |
| 113 | if this.is_init { | 132 | if let ControlState::Ready = this.state { |
| 114 | Poll::Ready(()) | 133 | Poll::Ready(()) |
| 115 | } else { | 134 | } else { |
| 116 | this.control_waker.register(cx.waker()); | 135 | this.control_waker.register(cx.waker()); |
diff --git a/embassy-net-esp-hosted/src/lib.rs b/embassy-net-esp-hosted/src/lib.rs index d882af8cf..2c7377281 100644 --- a/embassy-net-esp-hosted/src/lib.rs +++ b/embassy-net-esp-hosted/src/lib.rs | |||
| @@ -234,6 +234,11 @@ where | |||
| 234 | tx_buf[..PayloadHeader::SIZE].fill(0); | 234 | tx_buf[..PayloadHeader::SIZE].fill(0); |
| 235 | } | 235 | } |
| 236 | Either4::Fourth(()) => { | 236 | Either4::Fourth(()) => { |
| 237 | // Extend the deadline if initializing | ||
| 238 | if let ioctl::ControlState::Reboot = self.shared.state() { | ||
| 239 | self.heartbeat_deadline = Instant::now() + HEARTBEAT_MAX_GAP; | ||
| 240 | continue; | ||
| 241 | } | ||
| 237 | panic!("heartbeat from esp32 stopped") | 242 | panic!("heartbeat from esp32 stopped") |
| 238 | } | 243 | } |
| 239 | } | 244 | } |
diff --git a/embassy-net-esp-hosted/src/proto.rs b/embassy-net-esp-hosted/src/proto.rs index 74c67bd61..09bec8984 100644 --- a/embassy-net-esp-hosted/src/proto.rs +++ b/embassy-net-esp-hosted/src/proto.rs | |||
| @@ -16,7 +16,7 @@ Switch to a proper script when https://github.com/YuhanLiin/micropb/issues/30 is | |||
| 16 | // Special config for things that need to be larger | 16 | // Special config for things that need to be larger |
| 17 | g.configure( | 17 | g.configure( |
| 18 | ".CtrlMsg_Req_OTAWrite.ota_data", | 18 | ".CtrlMsg_Req_OTAWrite.ota_data", |
| 19 | micropb_gen::Config::new().max_bytes(1024), | 19 | micropb_gen::Config::new().max_bytes(256), |
| 20 | ); | 20 | ); |
| 21 | g.configure( | 21 | g.configure( |
| 22 | ".CtrlMsg_Event_ESPInit.init_data", | 22 | ".CtrlMsg_Event_ESPInit.init_data", |
| @@ -4296,28 +4296,28 @@ impl ::micropb::MessageEncode for CtrlMsg_Resp_OTABegin { | |||
| 4296 | #[derive(Debug, Default, PartialEq, Clone)] | 4296 | #[derive(Debug, Default, PartialEq, Clone)] |
| 4297 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | 4297 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] |
| 4298 | pub struct CtrlMsg_Req_OTAWrite { | 4298 | pub struct CtrlMsg_Req_OTAWrite { |
| 4299 | pub r#ota_data: ::micropb::heapless::Vec<u8, 1024>, | 4299 | pub r#ota_data: ::micropb::heapless::Vec<u8, 256>, |
| 4300 | } | 4300 | } |
| 4301 | impl CtrlMsg_Req_OTAWrite { | 4301 | impl CtrlMsg_Req_OTAWrite { |
| 4302 | ///Return a reference to `ota_data` | 4302 | ///Return a reference to `ota_data` |
| 4303 | #[inline] | 4303 | #[inline] |
| 4304 | pub fn r#ota_data(&self) -> &::micropb::heapless::Vec<u8, 1024> { | 4304 | pub fn r#ota_data(&self) -> &::micropb::heapless::Vec<u8, 256> { |
| 4305 | &self.r#ota_data | 4305 | &self.r#ota_data |
| 4306 | } | 4306 | } |
| 4307 | ///Return a mutable reference to `ota_data` | 4307 | ///Return a mutable reference to `ota_data` |
| 4308 | #[inline] | 4308 | #[inline] |
| 4309 | pub fn mut_ota_data(&mut self) -> &mut ::micropb::heapless::Vec<u8, 1024> { | 4309 | pub fn mut_ota_data(&mut self) -> &mut ::micropb::heapless::Vec<u8, 256> { |
| 4310 | &mut self.r#ota_data | 4310 | &mut self.r#ota_data |
| 4311 | } | 4311 | } |
| 4312 | ///Set the value of `ota_data` | 4312 | ///Set the value of `ota_data` |
| 4313 | #[inline] | 4313 | #[inline] |
| 4314 | pub fn set_ota_data(&mut self, value: ::micropb::heapless::Vec<u8, 1024>) -> &mut Self { | 4314 | pub fn set_ota_data(&mut self, value: ::micropb::heapless::Vec<u8, 256>) -> &mut Self { |
| 4315 | self.r#ota_data = value.into(); | 4315 | self.r#ota_data = value.into(); |
| 4316 | self | 4316 | self |
| 4317 | } | 4317 | } |
| 4318 | ///Builder method that sets the value of `ota_data`. Useful for initializing the message. | 4318 | ///Builder method that sets the value of `ota_data`. Useful for initializing the message. |
| 4319 | #[inline] | 4319 | #[inline] |
| 4320 | pub fn init_ota_data(mut self, value: ::micropb::heapless::Vec<u8, 1024>) -> Self { | 4320 | pub fn init_ota_data(mut self, value: ::micropb::heapless::Vec<u8, 256>) -> Self { |
| 4321 | self.r#ota_data = value.into(); | 4321 | self.r#ota_data = value.into(); |
| 4322 | self | 4322 | self |
| 4323 | } | 4323 | } |
