1use knx_rs_core::address::{DestinationAddress, GroupAddress, IndividualAddress};
29use knx_rs_core::cemi::CemiFrame;
30use knx_rs_core::dpt::{self, Dpt, DptValue};
31use knx_rs_core::message::MessageCode;
32use knx_rs_core::types::Priority;
33
34use crate::error::KnxIpError;
35use crate::{KnxConnection, KnxFuture};
36
37pub trait GroupOps: KnxConnection {
42 fn group_write(&self, ga: GroupAddress, data: &[u8]) -> KnxFuture<'_, Result<(), KnxIpError>> {
48 let frame = match build_group_write(ga, data) {
49 Ok(frame) => frame,
50 Err(err) => return Box::pin(core::future::ready(Err(err))),
51 };
52 self.send(frame)
53 }
54
55 fn group_write_value(
61 &self,
62 ga: GroupAddress,
63 dpt: Dpt,
64 value: &DptValue,
65 ) -> KnxFuture<'_, Result<(), KnxIpError>> {
66 let encoded = match dpt::encode(dpt, value) {
67 Ok(encoded) => encoded,
68 Err(err) => {
69 return Box::pin(core::future::ready(Err(KnxIpError::Protocol(
70 err.to_string(),
71 ))));
72 }
73 };
74 let frame = match build_group_write(ga, &encoded) {
75 Ok(frame) => frame,
76 Err(err) => return Box::pin(core::future::ready(Err(err))),
77 };
78 self.send(frame)
79 }
80
81 fn group_read(&self, ga: GroupAddress) -> KnxFuture<'_, Result<(), KnxIpError>> {
89 let frame = match build_group_read(ga) {
90 Ok(frame) => frame,
91 Err(err) => return Box::pin(core::future::ready(Err(err))),
92 };
93 self.send(frame)
94 }
95
96 fn group_respond(
102 &self,
103 ga: GroupAddress,
104 data: &[u8],
105 ) -> KnxFuture<'_, Result<(), KnxIpError>> {
106 let frame = match build_group_response(ga, data) {
107 Ok(frame) => frame,
108 Err(err) => return Box::pin(core::future::ready(Err(err))),
109 };
110 self.send(frame)
111 }
112}
113
114impl<T: KnxConnection> GroupOps for T {}
116
117fn build_group_write(ga: GroupAddress, data: &[u8]) -> Result<CemiFrame, KnxIpError> {
120 let mut payload = Vec::with_capacity(2 + data.len());
121 payload.push(0x00); if data.len() == 1 && data[0] <= 0x3F {
123 payload.push(0x80 | (data[0] & 0x3F)); } else {
125 payload.push(0x80); payload.extend_from_slice(data);
127 }
128 CemiFrame::try_new_l_data(
129 MessageCode::LDataReq,
130 IndividualAddress::from_raw(0x0000), DestinationAddress::Group(ga),
132 Priority::Low,
133 &payload,
134 )
135 .map_err(|e| KnxIpError::Protocol(e.to_string()))
136}
137
138fn build_group_read(ga: GroupAddress) -> Result<CemiFrame, KnxIpError> {
139 CemiFrame::try_new_l_data(
140 MessageCode::LDataReq,
141 IndividualAddress::from_raw(0x0000),
142 DestinationAddress::Group(ga),
143 Priority::Low,
144 &[0x00, 0x00], )
146 .map_err(|e| KnxIpError::Protocol(e.to_string()))
147}
148
149fn build_group_response(ga: GroupAddress, data: &[u8]) -> Result<CemiFrame, KnxIpError> {
150 let mut payload = Vec::with_capacity(2 + data.len());
151 payload.push(0x00);
152 if data.len() == 1 && data[0] <= 0x3F {
153 payload.push(0x40 | (data[0] & 0x3F)); } else {
155 payload.push(0x40); payload.extend_from_slice(data);
157 }
158 CemiFrame::try_new_l_data(
159 MessageCode::LDataReq,
160 IndividualAddress::from_raw(0x0000),
161 DestinationAddress::Group(ga),
162 Priority::Low,
163 &payload,
164 )
165 .map_err(|e| KnxIpError::Protocol(e.to_string()))
166}
167
168#[cfg(test)]
169#[allow(clippy::unwrap_used)]
170mod tests {
171 use super::*;
172 use knx_rs_core::address::GroupAddress;
173 use knx_rs_core::dpt::{DPT_SWITCH, DPT_VALUE_TEMP};
174
175 #[test]
176 fn build_group_write_short() {
177 let frame = build_group_write(GroupAddress::from_raw(0x0801), &[0x01]).unwrap();
178 assert_eq!(frame.destination_address_raw(), 0x0801);
179 let payload = frame.payload();
180 assert_eq!(payload[0], 0x00); assert_eq!(payload[1], 0x81); }
183
184 #[test]
185 fn build_group_write_long() {
186 let data = [0x0C, 0x34]; let frame = build_group_write(GroupAddress::from_raw(0x0801), &data).unwrap();
188 let payload = frame.payload();
189 assert_eq!(payload[0], 0x00);
190 assert_eq!(payload[1], 0x80); assert_eq!(&payload[2..], &[0x0C, 0x34]);
192 }
193
194 #[test]
195 fn build_group_read_frame() {
196 let frame = build_group_read(GroupAddress::from_raw(0x0801)).unwrap();
197 let payload = frame.payload();
198 assert_eq!(payload, &[0x00, 0x00]);
199 }
200
201 #[test]
202 fn build_group_response_short() {
203 let frame = build_group_response(GroupAddress::from_raw(0x0801), &[0x01]).unwrap();
204 let payload = frame.payload();
205 assert_eq!(payload[1], 0x41); }
207
208 #[test]
209 fn dpt_encoding_in_write() {
210 let encoded = dpt::encode(DPT_SWITCH, &DptValue::Bool(true)).unwrap();
211 let frame = build_group_write(GroupAddress::from_raw(0x0802), &encoded).unwrap();
212 let payload = frame.payload();
213 assert_eq!(payload[1], 0x81); let encoded = dpt::encode(DPT_VALUE_TEMP, &DptValue::Float(21.5)).unwrap();
216 let frame = build_group_write(GroupAddress::from_raw(0x0801), &encoded).unwrap();
217 assert_eq!(frame.payload().len(), 4); }
219}