1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
//! Constructs for communication with the modem.
use std::fmt;
pub use crate::button::*;
use crate::command::Command;
use crate::device::Address;
pub use crate::link::*;
use crate::x10::Message as X10Message;
// TODO: Make this a real type.
/// Represents an ALL-Link device group.
pub type Group = u8;
/// Messages are notifications delivered by the modem to us.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Message {
/// A message (either standard or extended) was received.
///
/// This message type is merely an acknowledgement.
Received(Address, Option<Command>, u8, Option<[u8; 14]>),
/// An X10 message was received.
X10Received(X10Message),
/// An ALL-Link event completed.
LinkComplete(LinkResult),
/// A button on the device was pressed, held, or released.
ButtonEvent(ButtonEvent),
/// The user reset the modem (by pushing and holding SET while powering).
UserResetDetected,
/// A requested ALL-Link cleanup failed.
LinkCleanupFailed(Group, Address),
/// An ALL-Link record response.
LinkRecordResponse(u8, Group, Address, crate::link::LinkData),
/// The All-Link cleanup completed (successfully or not).
LinkCleanupStatus(bool),
DatabaseRecordFound([u8; 2], u8, u8, Address, crate::link::LinkData),
}
impl fmt::Display for Message {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Message::*;
match self {
Received(_addr, _flags, _cmds, data) => match data {
None => write!(f, "Received standard message."),
Some(msg) => write!(f, "Received extended message with data: {:x?}", msg),
},
X10Received(msg) => write!(f, "Received X10 result: {}", msg),
LinkComplete(_result) => write!(f, "ALL-Link completed (details omitted)."),
ButtonEvent(e) => write!(f, "{}", e),
UserResetDetected => write!(f, "User reset initiated."),
LinkCleanupFailed(group, address) => write!(
f,
"ALL-Link cleanup failed (group {:x}, id {:x?})",
group, address
),
LinkRecordResponse(_flags, _group, _id, _link) => {
write!(f, "ALL-Link record received (omitted).")
}
LinkCleanupStatus(finished) => {
if *finished {
write!(f, "ALL-Link cleanup completed.")
} else {
write!(f, "ALL-Link cleanup aborted due to traffic.")
}
}
DatabaseRecordFound(_address, _flags, _group, _id, _link) => {
write!(f, "Database record found (omitted).")
}
}
}
}
/// Encodes a modem configuration.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Config {
/// Whether linking should be initiated when the user presses and holds the SET button.
pub auto_link: bool,
/// Whether the modem is in monitor mode.
pub monitor: bool,
/// Whether the LED should be managed by the host (and not the modem).
pub manual_led: bool,
/// Whether the modem should timeout after 240 ms.
pub timeout: bool,
/// Whether the modem should reject commands (NAK) if it's busy processing.
pub busy_reject: bool,
}
impl Into<u8> for Config {
fn into(self) -> u8 {
((!self.auto_link as u8) << 7)
| ((self.monitor as u8) << 6)
| ((self.manual_led as u8) << 5)
| ((!self.timeout as u8) << 4)
| ((self.busy_reject as u8) << 3)
}
}
impl From<u8> for Config {
fn from(byte: u8) -> Self {
Self {
auto_link: byte & 0b1000_0000 == 0,
monitor: byte & 0b0100_0000 == 0b0100_0000,
manual_led: byte & 0b0010_0000 == 0b0010_0000,
timeout: byte & 0b0001_0000 == 0,
busy_reject: byte & 0b0000_1000 == 0b0000_1000,
}
}
}
impl Default for Config {
/// Returns the default configuration (`0`).
fn default() -> Self {
Config {
auto_link: true,
monitor: false,
manual_led: false,
timeout: true,
busy_reject: false,
}
}
}
/// Responses are delivered from the modem to us in response to issued commands.
///
/// They therefore differ in significance from messages because we request and expect them.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Response {
/// The device info was retrieved.
GotInfo(Address, [u8; 2], Option<u8>),
/// The requested link command was sent to the group.
SentLinkCommand(Group, u8, u8),
// TODO: Fix this
SentMessage(Message),
/// The requested X10 message was sent.
SentX10(X10Message),
StartedLink(u8, Group),
CanceledLink,
/// The host device category (and subcategory) were successfully set.
///
/// If applicable, this command also returns the firmware version.
SetCategory([u8; 2], Option<u8>),
/// The modem was successfully reset to factory settings, wiping the ALL-Link database.
Reset,
/// The ACK byte (`0x06`) will be followed by the requested (and returned) byte.
///
/// This is required for some direct commands.
SetAckByte(u8),
/// The first ALL-Link record was retrieved and will follow in an ALL-Link Record Response
/// message (`0x57`).
GotFirstLinkRecord,
/// The next ALL-Link record was retrieved and will follow in an ALL-Link Record Response
/// message (`0x57`).
GotNextLinkRecord,
/// The modem configuration was set as specified.
SetConfig(Config),
/// The ALL-Link record for the most recent known message sender was retrieved and will follow
/// in an ALL-Link Record Response Message (`0x57`).
GotSenderLinkRecord,
/// The LED was turned on.
LedOn,
/// The LED was turned off.
LedOff,
/// The specified ALL-Link record was inserted into the database.
UpdatedLinkRecord(u8, u8, Group, Address, [u8; 3]),
/// The NAK byte (`0x15`) will be followed by the requested (and returned) byte.
SetNakByte(u8),
/// The ACK byte (`0x06`) will be followed by the requested (and returned) bytes.
///
/// This is required for some direct commands.
SetAckBytes([u8; 2]),
/// The RF modem was put to sleep and will wake again when sent a byte.
Sleeping,
/// Returns the configuration flags for the modem.
///
/// ## Notes
/// There are two bytes of reserved data that come in this response as well, but this
/// implementation ignores them, returning only the first field (the configuration byte),
/// parsed into a convenient format.
GotConfig(Config),
// The following commands were added to the spec after the initial release.
/// ALL-Link cleanup was successfully canceled.
CanceledCleanup,
/// Eight bytes were read from the database successfully and will follow in a Database Record
/// Found Response Message (`0x59`).
ReadDatabaseBytes([u8; 2]),
/// The device will beep.
Beeping,
SetStatus(u8),
// The following commands are RF modem-only.
// It is left unclear whether they are in the initial version of the spec,
// but looking at their numbering, let's assume not.
SetLinkData([u8; 3]),
/// The number of application retries for new links was set as specified.
SetRetries(u8),
/// The RF frequency offset was set as requested.
SetFrequencyOffset(u8),
/// This message is woefully underdocumented, but its full name is "Set Acknowledge for
/// TempLinc command."
///
/// Godspeed.
SetTempLincAck(u8),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_config() {
let cfg = Config::default();
let byte: u8 = cfg.into();
assert_eq!(byte, 0);
let mut cfg = Config::default();
cfg.timeout = false;
let byte: u8 = cfg.into();
assert!(byte != 0);
}
}