├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── package-lock.json ├── package.json └── src ├── config.rs ├── gateway.rs ├── main.rs └── mqtt.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | *.swp 4 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "byteorder" 5 | version = "0.4.2" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | 8 | [[package]] 9 | name = "cc" 10 | version = "1.0.4" 11 | source = "registry+https://github.com/rust-lang/crates.io-index" 12 | 13 | [[package]] 14 | name = "cmake" 15 | version = "0.1.29" 16 | source = "registry+https://github.com/rust-lang/crates.io-index" 17 | dependencies = [ 18 | "cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 19 | ] 20 | 21 | [[package]] 22 | name = "dtoa" 23 | version = "0.4.2" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | 26 | [[package]] 27 | name = "gcc" 28 | version = "0.3.54" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | 31 | [[package]] 32 | name = "itoa" 33 | version = "0.3.4" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | 36 | [[package]] 37 | name = "libc" 38 | version = "0.2.36" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | 41 | [[package]] 42 | name = "mqtt-adapter" 43 | version = "0.0.1" 44 | dependencies = [ 45 | "mqtt3 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 46 | "nanomsg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 47 | "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", 48 | "serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", 49 | "serde_json 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", 50 | ] 51 | 52 | [[package]] 53 | name = "mqtt3" 54 | version = "0.1.4" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | dependencies = [ 57 | "byteorder 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 58 | ] 59 | 60 | [[package]] 61 | name = "nanomsg" 62 | version = "0.6.2" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | dependencies = [ 65 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 66 | "nanomsg-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 67 | ] 68 | 69 | [[package]] 70 | name = "nanomsg-sys" 71 | version = "0.6.2" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | dependencies = [ 74 | "cmake 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", 75 | "gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)", 76 | "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", 77 | "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 78 | ] 79 | 80 | [[package]] 81 | name = "num-traits" 82 | version = "0.1.42" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | 85 | [[package]] 86 | name = "pkg-config" 87 | version = "0.3.9" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | 90 | [[package]] 91 | name = "quote" 92 | version = "0.3.15" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | 95 | [[package]] 96 | name = "serde" 97 | version = "1.0.27" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | 100 | [[package]] 101 | name = "serde_derive" 102 | version = "1.0.27" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | dependencies = [ 105 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 106 | "serde_derive_internals 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", 107 | "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", 108 | ] 109 | 110 | [[package]] 111 | name = "serde_derive_internals" 112 | version = "0.19.0" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | dependencies = [ 115 | "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", 116 | "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", 117 | ] 118 | 119 | [[package]] 120 | name = "serde_json" 121 | version = "1.0.9" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | dependencies = [ 124 | "dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 125 | "itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 126 | "num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 127 | "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", 128 | ] 129 | 130 | [[package]] 131 | name = "syn" 132 | version = "0.11.11" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | dependencies = [ 135 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 136 | "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", 137 | "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 138 | ] 139 | 140 | [[package]] 141 | name = "synom" 142 | version = "0.11.3" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | dependencies = [ 145 | "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 146 | ] 147 | 148 | [[package]] 149 | name = "unicode-xid" 150 | version = "0.0.4" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | 153 | [metadata] 154 | "checksum byteorder 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "96c8b41881888cc08af32d47ac4edd52bc7fa27fef774be47a92443756451304" 155 | "checksum cc 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "deaf9ec656256bb25b404c51ef50097207b9cbb29c933d31f92cae5a8a0ffee0" 156 | "checksum cmake 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)" = "56d741ea7a69e577f6d06b36b7dff4738f680593dc27a701ffa8506b73ce28bb" 157 | "checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab" 158 | "checksum gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)" = "5e33ec290da0d127825013597dbdfc28bee4964690c7ce1166cbc2a7bd08b1bb" 159 | "checksum itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8324a32baf01e2ae060e9de58ed0bc2320c9a2833491ee36cd3b4c414de4db8c" 160 | "checksum libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "1e5d97d6708edaa407429faa671b942dc0f2727222fb6b6539bf1db936e4b121" 161 | "checksum mqtt3 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "628dfa5018191ef66edded34a203f9ca013424f2385e3580e8b8640b83730654" 162 | "checksum nanomsg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "76ccf9e51352fe07b21a15056877f9058fb7b471aeae03f9249e7f7134147713" 163 | "checksum nanomsg-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a1c99d8b346eb279bcb61913440b558073ea1f1cc8c51adcf0efe90f690d2c5a" 164 | "checksum num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "9936036cc70fe4a8b2d338ab665900323290efb03983c86cbe235ae800ad8017" 165 | "checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" 166 | "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" 167 | "checksum serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "db99f3919e20faa51bb2996057f5031d8685019b5a06139b1ce761da671b8526" 168 | "checksum serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "f4ba7591cfe93755e89eeecdbcc668885624829b020050e6aec99c2a03bd3fd0" 169 | "checksum serde_derive_internals 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6e03f1c9530c3fb0a0a5c9b826bdd9246a5921ae995d75f512ac917fc4dd55b5" 170 | "checksum serde_json 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c9db7266c7d63a4c4b7fe8719656ccdd51acf1bed6124b174f933b009fb10bcb" 171 | "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" 172 | "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" 173 | "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" 174 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mqtt-adapter" 3 | version = "0.0.1" 4 | authors = ["James Hobin "] 5 | 6 | [dependencies] 7 | serde = "1.0" 8 | serde_derive = "1.0" 9 | serde_json = "1.0" 10 | mqtt3 = "0.1" 11 | nanomsg = "0.6" 12 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mqtt-adapter", 3 | "version": "0.0.2", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mqtt-adapter", 3 | "display_name": "Simple DIY MQTT Adapter", 4 | "author": "Mozilla IoT", 5 | "version": "0.0.2", 6 | "description": "MQTT adapter add-on for Mozilla IoT Gateway", 7 | "keywords": [ 8 | "mozilla", 9 | "iot", 10 | "adapter", 11 | "mqtt" 12 | ], 13 | "homepage": "https://iot.mozilla.org/", 14 | "license": "MPL-2.0", 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/mozilla-iot/mqtt-adapter.git" 18 | }, 19 | "bugs": { 20 | "url": "https://github.com/mozilla-iot/mqtt-adapter/issues" 21 | }, 22 | "files": [ 23 | "package.json" 24 | ], 25 | "moziot": { 26 | "api": { 27 | "min": 1, 28 | "max": 2 29 | }, 30 | "plugin": true, 31 | "exec": "/Users/jhobin/moziot/mqtt-adapter/target/debug/mqtt-adapter" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | pub const MQTT_SERVER: &'static str = "io.adafruit.com:1883"; 2 | pub const MQTT_USERNAME: &'static str = "username"; 3 | pub const MQTT_PASSWORD: &'static str = "ada-io-key"; 4 | 5 | -------------------------------------------------------------------------------- /src/gateway.rs: -------------------------------------------------------------------------------- 1 | use std; 2 | use std::collections::HashMap; 3 | use std::io::{self, Read, Write}; 4 | use std::sync::mpsc::{channel, Sender, Receiver}; 5 | use std::thread; 6 | use std::time::Duration; 7 | 8 | use nanomsg::{Protocol, Socket}; 9 | use serde_json::{self, Value}; 10 | 11 | const BASE_URL: &'static str = "ipc:///tmp"; 12 | const ADAPTER_MANAGER_URL: &'static str = "ipc:///tmp/gateway.addonManager"; 13 | 14 | #[derive(Serialize)] 15 | #[serde(tag = "messageType", content = "data", rename_all = "camelCase")] 16 | pub enum PluginRegisterMessage { 17 | #[serde(rename_all = "camelCase")] 18 | RegisterPlugin { 19 | plugin_id: String, 20 | }, 21 | } 22 | 23 | #[derive(Deserialize)] 24 | #[serde(tag = "messageType", content = "data", rename_all = "camelCase")] 25 | pub enum GatewayRegisterMessage { 26 | #[serde(rename_all = "camelCase")] 27 | RegisterPluginReply { 28 | plugin_id: String, 29 | ipc_base_addr: String, 30 | }, 31 | } 32 | 33 | #[derive(Debug, Deserialize)] 34 | #[serde(tag = "messageType", content = "data", rename_all = "camelCase")] 35 | pub enum GatewayMessage { 36 | #[serde(rename_all = "camelCase")] 37 | UnloadPlugin { 38 | plugin_id: String, 39 | }, 40 | #[serde(rename_all = "camelCase")] 41 | UnloadAdapter { 42 | plugin_id: String, 43 | adapter_id: String, 44 | }, 45 | 46 | #[serde(rename_all = "camelCase")] 47 | SetProperty { 48 | plugin_id: String, 49 | adapter_id: String, 50 | device_id: String, 51 | property_name: String, 52 | property_value: Value, 53 | }, 54 | #[serde(rename_all = "camelCase")] 55 | RequestAction { 56 | plugin_id: String, 57 | adapter_id: String, 58 | device_id: String, 59 | action_id: f64, 60 | action_name: String, 61 | input: Value, 62 | }, 63 | #[serde(rename_all = "camelCase")] 64 | StartPairing { 65 | plugin_id: String, 66 | adapter_id: String, 67 | timeout: f64, 68 | }, 69 | #[serde(rename_all = "camelCase")] 70 | CancelPairing { 71 | plugin_id: String, 72 | adapter_id: String, 73 | }, 74 | #[serde(rename_all = "camelCase")] 75 | RemoveThing { 76 | plugin_id: String, 77 | adapter_id: String, 78 | device_id: String, 79 | }, 80 | #[serde(rename_all = "camelCase")] 81 | CancelRemoveThing { 82 | plugin_id: String, 83 | adapter_id: String, 84 | device_id: String, 85 | }, 86 | } 87 | 88 | #[derive(Debug, Serialize)] 89 | #[serde(tag = "messageType", content = "data", rename_all = "camelCase")] 90 | pub enum PluginMessage { 91 | #[serde(rename_all = "camelCase")] 92 | PluginUnloaded { 93 | plugin_id: String, 94 | }, 95 | #[serde(rename_all = "camelCase")] 96 | AdapterUnloaded { 97 | plugin_id: String, 98 | adapter_id: String, 99 | }, 100 | 101 | #[serde(rename_all = "camelCase")] 102 | AddAdapter { 103 | plugin_id: String, 104 | adapter_id: String, 105 | name: String, 106 | package_name: String, 107 | }, 108 | #[serde(rename_all = "camelCase")] 109 | HandleDeviceAdded { 110 | plugin_id: String, 111 | adapter_id: String, 112 | id: String, 113 | name: String, 114 | #[serde(rename = "type")] 115 | typ: String, 116 | properties: HashMap, 117 | actions: HashMap, 118 | }, 119 | #[serde(rename_all = "camelCase")] 120 | HandleDeviceRemoved { 121 | plugin_id: String, 122 | adapter_id: String, 123 | id: String, 124 | }, 125 | #[serde(rename_all = "camelCase")] 126 | PropertyChanged { 127 | plugin_id: String, 128 | adapter_id: String, 129 | device_id: String, 130 | property: Property, 131 | }, 132 | } 133 | 134 | #[derive(Clone, Debug, Deserialize, Serialize)] 135 | pub struct PropertyDescription { 136 | pub name: String, 137 | pub value: Value, 138 | #[serde(rename = "type")] 139 | pub typ: String, 140 | #[serde(skip_serializing_if = "Option::is_none")] 141 | pub unit: Option, 142 | #[serde(skip_serializing_if = "Option::is_none")] 143 | pub description: Option, 144 | #[serde(skip_serializing_if = "Option::is_none")] 145 | pub min: Option, 146 | #[serde(skip_serializing_if = "Option::is_none")] 147 | pub max: Option, 148 | pub visible: bool, 149 | } 150 | 151 | #[derive(Debug, Deserialize, Serialize)] 152 | pub struct Property { 153 | pub name: String, 154 | pub value: Value, 155 | } 156 | 157 | #[derive(Clone, Debug, Deserialize, Serialize)] 158 | pub struct ActionDescription { 159 | pub name: String, 160 | } 161 | 162 | pub struct GatewayBridge { 163 | id: String, 164 | msg_sender: Sender, 165 | msg_receiver: Receiver 166 | } 167 | 168 | impl GatewayBridge { 169 | pub fn new(id: &str) -> (GatewayBridge, Sender, Receiver) { 170 | let (gp_sender, gp_receiver) = channel(); 171 | let (pg_sender, pg_receiver) = channel(); 172 | ( 173 | GatewayBridge { 174 | id: id.to_string(), 175 | msg_sender: gp_sender, 176 | msg_receiver: pg_receiver, 177 | }, 178 | pg_sender, 179 | gp_receiver 180 | ) 181 | } 182 | 183 | pub fn run_forever(&mut self) -> Result<(), io::Error> { 184 | let ipc_base_addr = { 185 | let mut socket = Socket::new(Protocol::Req)?; 186 | let mut endpoint = socket.connect(ADAPTER_MANAGER_URL)?; 187 | let req = PluginRegisterMessage::RegisterPlugin { 188 | plugin_id: self.id.to_string() 189 | }; 190 | socket.write_all(serde_json::to_string(&req)?.as_bytes())?; 191 | let mut rep = String::new(); 192 | socket.read_to_string(&mut rep)?; 193 | endpoint.shutdown()?; 194 | let msg: GatewayRegisterMessage = serde_json::from_str(&rep)?; 195 | // open a Req channel to adapterManager 196 | // send {messageType: 'registerPlugin', data: { pluginId: id }} 197 | // receives 198 | // { 199 | // messageType: 'registerPluginReply', 200 | // data: { 201 | // pluginId: 'pluginId-string', 202 | // ipcBaseAddr: 'gateway.plugin.xxx', 203 | // }, 204 | //} 205 | // connect to ipcBaseAddr as pair 206 | // then handle everything 207 | 208 | match msg { 209 | GatewayRegisterMessage::RegisterPluginReply {plugin_id, ipc_base_addr} => { 210 | if plugin_id != self.id { 211 | panic!("mismatched plugin id on channel") 212 | } 213 | ipc_base_addr 214 | }, 215 | } 216 | }; 217 | 218 | let mut socket_pair = Socket::new(Protocol::Pair)?; 219 | let addr = format!("{}/{}", BASE_URL, &ipc_base_addr); 220 | socket_pair.set_receive_timeout(33)?; 221 | let mut endpoint_pair = socket_pair.connect(&addr)?; 222 | thread::sleep(Duration::from_millis(33)); 223 | 224 | 225 | loop { 226 | let mut buf = Vec::new(); 227 | match socket_pair.read_to_end(&mut buf) { 228 | Ok(_) => { 229 | match serde_json::from_slice(&buf) { 230 | Ok(msg) => self.msg_sender.send(msg).unwrap(), 231 | Err(e) => println!("parse fail {:?}", e), 232 | } 233 | }, 234 | Err(_) => { 235 | } 236 | } 237 | 238 | 239 | if let Ok(msg_to_send) = self.msg_receiver.try_recv() { 240 | socket_pair.write_all(serde_json::to_string(&msg_to_send)?.as_bytes()).unwrap(); 241 | match msg_to_send { 242 | PluginMessage::PluginUnloaded {..} => { 243 | println!("run_forever exiting"); 244 | endpoint_pair.shutdown()?; 245 | return Ok(()); 246 | } 247 | _ => {} 248 | } 249 | } 250 | 251 | thread::sleep(Duration::from_millis(33)); 252 | } 253 | } 254 | } 255 | 256 | fn to_io_error(err: E) -> io::Error 257 | where E: Into> { 258 | io::Error::new(io::ErrorKind::Other, err) 259 | } 260 | 261 | pub trait Device { 262 | fn set_property(&mut self, property: Property) -> Result; 263 | fn request_action(&mut self, name: String) -> Result<(), io::Error>; 264 | 265 | fn get_name(&self) -> String { 266 | "Unknown Device".to_string() 267 | } 268 | 269 | fn get_type(&self) -> String { 270 | "thing".to_string() 271 | } 272 | 273 | fn get_actions(&self) -> HashMap { 274 | HashMap::new() 275 | } 276 | 277 | fn get_properties(&self) -> HashMap { 278 | HashMap::new() 279 | } 280 | } 281 | 282 | pub trait Adapter { 283 | fn get_name(&self) -> String { 284 | "Unknown Adapter".to_string() 285 | } 286 | fn get_devices(&self) -> &HashMap>; 287 | 288 | fn start_pairing(&mut self) -> Result<(), io::Error>; 289 | 290 | fn cancel_pairing(&mut self) -> Result<(), io::Error>; 291 | 292 | fn set_property(&mut self, device_id: &str, property: Property) -> Result; 293 | fn request_action(&mut self, device_id: &str, name: String) -> Result<(), io::Error>; 294 | } 295 | 296 | pub struct Plugin> { 297 | package_name: String, 298 | plugin_id: String, 299 | adapters: HashMap>, 300 | sender: Sender, 301 | receiver: Receiver, 302 | _marker: std::marker::PhantomData, 303 | } 304 | 305 | impl> Plugin { 306 | pub fn new(package_name: &str, plugin_id: &str, sender: Sender, 307 | receiver: Receiver) -> Plugin { 308 | Plugin { 309 | package_name: package_name.to_string(), 310 | plugin_id: plugin_id.to_string(), 311 | sender: sender, 312 | receiver: receiver, 313 | adapters: HashMap::new(), 314 | _marker: std::marker::PhantomData, 315 | } 316 | } 317 | 318 | fn handle_msg(&mut self, msg: GatewayMessage) -> Result<(), io::Error> { 319 | match msg { 320 | GatewayMessage::SetProperty { 321 | plugin_id, 322 | adapter_id, 323 | device_id, 324 | property_name, 325 | property_value, 326 | } => { 327 | if plugin_id != self.plugin_id { 328 | return Ok(()) 329 | } 330 | 331 | let set_prop = match self.adapters.get_mut(&adapter_id) { 332 | Some(adapter) => { 333 | let property = Property { 334 | name: property_name, 335 | value: property_value 336 | }; 337 | adapter.set_property(&device_id, property) 338 | } 339 | None => Err(io::Error::new(io::ErrorKind::Other, "Adapter not found")) 340 | }; 341 | let prop = set_prop?; 342 | self.sender.send(PluginMessage::PropertyChanged { 343 | plugin_id, 344 | adapter_id, 345 | device_id, 346 | property: prop 347 | }).map_err(to_io_error) 348 | }, 349 | GatewayMessage::RequestAction { 350 | plugin_id, 351 | adapter_id, 352 | device_id, 353 | action_id: _action_id, 354 | action_name, 355 | input: _input, 356 | } => { 357 | if plugin_id != self.plugin_id { 358 | return Ok(()) 359 | } 360 | 361 | match self.adapters.get_mut(&adapter_id) { 362 | Some(adapter) => { 363 | return adapter.request_action(&device_id, action_name); 364 | } 365 | None => Err(io::Error::new(io::ErrorKind::Other, "Adapter not found")) 366 | } 367 | }, 368 | GatewayMessage::UnloadPlugin {..} => { 369 | Ok(()) 370 | }, 371 | GatewayMessage::UnloadAdapter {..} => { 372 | Ok(()) 373 | }, 374 | GatewayMessage::StartPairing { 375 | plugin_id, 376 | adapter_id, 377 | timeout: _, 378 | } => { 379 | if plugin_id != self.plugin_id { 380 | return Ok(()) 381 | } 382 | 383 | match self.adapters.get_mut(&adapter_id) { 384 | Some(adapter) => adapter.start_pairing(), 385 | None => Err(io::Error::new(io::ErrorKind::Other, "Adapter not found")), 386 | } 387 | }, 388 | GatewayMessage::CancelPairing { 389 | plugin_id, 390 | adapter_id, 391 | } => { 392 | if plugin_id != self.plugin_id { 393 | return Ok(()) 394 | } 395 | 396 | match self.adapters.get_mut(&adapter_id) { 397 | Some(adapter) => adapter.cancel_pairing(), 398 | None => Err(io::Error::new(io::ErrorKind::Other, "Adapter not found")), 399 | } 400 | }, 401 | GatewayMessage::RemoveThing { .. } => { 402 | Ok(()) 403 | }, 404 | GatewayMessage::CancelRemoveThing { .. } => { 405 | Ok(()) 406 | } 407 | } 408 | } 409 | 410 | pub fn add_adapter(&mut self, adapter_id: &str, adapter: Box) { 411 | self.adapters.insert(adapter_id.to_string(), adapter); 412 | } 413 | 414 | pub fn run_forever(&mut self) -> Result<(), io::Error> { 415 | for (adapter_id, adapter) in &self.adapters { 416 | self.sender.send(PluginMessage::AddAdapter { 417 | plugin_id: self.plugin_id.clone(), 418 | package_name: self.package_name.clone(), 419 | adapter_id: adapter_id.clone(), 420 | name: adapter.get_name() 421 | }).map_err(to_io_error)?; 422 | for (device_id, device) in adapter.get_devices() { 423 | self.sender.send(PluginMessage::HandleDeviceAdded { 424 | plugin_id: self.plugin_id.clone(), 425 | adapter_id: adapter_id.clone(), 426 | id: device_id.clone(), 427 | name: device.get_name(), 428 | typ: device.get_type(), 429 | actions: device.get_actions(), 430 | properties: device.get_properties(), 431 | }).map_err(to_io_error)?; 432 | } 433 | } 434 | 435 | loop { 436 | match self.receiver.try_recv() { 437 | Ok(msg) => { 438 | self.handle_msg(msg)?; 439 | }, 440 | _ => { 441 | thread::sleep(Duration::from_millis(33)); 442 | } 443 | } 444 | } 445 | } 446 | } 447 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate mqtt3; 2 | extern crate nanomsg; 3 | #[macro_use] 4 | extern crate serde_derive; 5 | extern crate serde_json; 6 | 7 | use std::collections::HashMap; 8 | use std::io; 9 | use std::thread; 10 | 11 | use serde_json::Value; 12 | 13 | mod config; 14 | mod mqtt; 15 | mod gateway; 16 | 17 | use gateway::{Device, Adapter, Plugin, GatewayBridge, Property, PropertyDescription, ActionDescription}; 18 | 19 | struct MQTTDevice { 20 | prop_descrs: HashMap, 21 | action_descrs: HashMap, 22 | props: HashMap, 23 | mqtt: mqtt::MQTT 24 | } 25 | 26 | impl MQTTDevice { 27 | fn new(mqtt: mqtt::MQTT) -> MQTTDevice { 28 | let mut props = HashMap::new(); 29 | let mut prop_descrs = HashMap::new(); 30 | props.insert("on".to_string(), Value::Bool(true)); 31 | prop_descrs.insert("on".to_string(), PropertyDescription { 32 | name: "on".to_string(), 33 | value: Value::Bool(true), 34 | typ: "boolean".to_string(), 35 | description: None, 36 | unit: None, 37 | min: None, 38 | max: None, 39 | visible: true, 40 | }); 41 | let mut action_descrs = HashMap::new(); 42 | action_descrs.insert("forward".to_string(), ActionDescription { 43 | name: "forward".to_string(), 44 | }); 45 | action_descrs.insert("backward".to_string(), ActionDescription { 46 | name: "backward".to_string(), 47 | }); 48 | 49 | MQTTDevice { 50 | props: props, 51 | prop_descrs: prop_descrs, 52 | action_descrs: action_descrs, 53 | mqtt: mqtt 54 | } 55 | } 56 | 57 | } 58 | 59 | impl Device for MQTTDevice { 60 | fn set_property(&mut self, property: Property) -> Result { 61 | self.mqtt.publish_value(&property.name, &property.value) 62 | .map_err(|_| return io::Error::new(io::ErrorKind::Other, "mqtt3 error"))?; 63 | self.props.insert(property.name.clone(), property.value.clone()); 64 | Ok(property) 65 | } 66 | 67 | fn request_action(&mut self, name: String) -> Result<(), io::Error> { 68 | self.mqtt.publish_action(&name) 69 | .map_err(|_| return io::Error::new(io::ErrorKind::Other, "mqtt3 error"))?; 70 | Ok(()) 71 | } 72 | 73 | fn get_properties(&self) -> HashMap { 74 | self.prop_descrs.clone() 75 | } 76 | 77 | fn get_actions(&self) -> HashMap { 78 | self.action_descrs.clone() 79 | } 80 | 81 | fn get_name(&self) -> String { 82 | "ESP8266 LED".to_string() 83 | } 84 | 85 | fn get_type(&self) -> String { 86 | "onOffSwitch".to_string() 87 | } 88 | } 89 | 90 | struct MQTTAdapter { 91 | devices: HashMap> 92 | } 93 | 94 | impl MQTTAdapter { 95 | fn new(id: &str, mqtt: mqtt::MQTT) -> MQTTAdapter { 96 | let mut devices = HashMap::new(); 97 | let device_id = format!("{}-0", id); 98 | devices.insert(device_id.to_string(), Box::new(MQTTDevice::new(mqtt))); 99 | MQTTAdapter { 100 | devices: devices 101 | } 102 | } 103 | } 104 | 105 | impl Adapter for MQTTAdapter { 106 | fn start_pairing(&mut self) -> Result<(), io::Error> { 107 | println!("start_pairing"); 108 | Ok(()) 109 | } 110 | 111 | fn cancel_pairing(&mut self) -> Result<(), io::Error> { 112 | println!("cancel_pairing"); 113 | Ok(()) 114 | } 115 | 116 | fn set_property(&mut self, device_id: &str, property: Property) -> Result { 117 | println!("set_property {} {:?}", device_id, property); 118 | match self.devices.get_mut(device_id) { 119 | Some(device) => device.set_property(property), 120 | None => return Err(io::Error::new(io::ErrorKind::Other, "Device not found")) 121 | } 122 | } 123 | 124 | fn request_action(&mut self, device_id: &str, name: String) -> Result<(), io::Error> { 125 | println!("request_action {} {}", device_id, name); 126 | match self.devices.get_mut(device_id) { 127 | Some(device) => device.request_action(name), 128 | None => return Err(io::Error::new(io::ErrorKind::Other, "Device not found")) 129 | } 130 | } 131 | 132 | fn get_name(&self) -> String { 133 | "MQTT Adapter".to_string() 134 | } 135 | 136 | fn get_devices(&self) -> &HashMap> { 137 | &self.devices 138 | } 139 | } 140 | 141 | fn main() { 142 | let mut mqtt = mqtt::MQTT::new(config::MQTT_SERVER, config::MQTT_USERNAME, 143 | config::MQTT_PASSWORD); 144 | mqtt.send_connect().unwrap(); 145 | mqtt.publish_value("on", &Value::Bool(true)).unwrap(); 146 | 147 | let (mut gateway_bridge, msg_sender, msg_receiver) = GatewayBridge::new("mqtt-adapter"); 148 | thread::spawn(move || { 149 | gateway_bridge.run_forever().unwrap(); 150 | }); 151 | let mut plugin = Plugin::new("mqtt", "mqtt-adapter", msg_sender, msg_receiver); 152 | plugin.add_adapter("mqtt-0", Box::new(MQTTAdapter::new("mqtt-0", mqtt))); 153 | plugin.run_forever().unwrap(); 154 | } 155 | -------------------------------------------------------------------------------- /src/mqtt.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use std::net::TcpStream; 3 | use std::io::{Write, BufReader, BufWriter}; 4 | 5 | use mqtt3::{self, MqttRead, MqttWrite}; 6 | use serde_json::Value; 7 | 8 | pub struct MQTT { 9 | writer: BufWriter, 10 | reader: BufReader, 11 | username: String, 12 | password: String, 13 | } 14 | 15 | impl MQTT { 16 | pub fn new(server: &str, username: &str, password: &str) -> MQTT { 17 | let stream = TcpStream::connect(server).unwrap(); 18 | let reader = BufReader::new(stream.try_clone().unwrap()); 19 | let writer = BufWriter::new(stream.try_clone().unwrap()); 20 | MQTT { 21 | reader, 22 | writer, 23 | username: username.to_string(), 24 | password: password.to_string() 25 | } 26 | } 27 | 28 | pub fn send_connect(&mut self) -> Result { 29 | let connect = mqtt3::Packet::Connect(Box::new(mqtt3::Connect { 30 | protocol: mqtt3::Protocol::MQTT(4), 31 | keep_alive: 65535, // TODO properly fix 32 | client_id: "rust-mq-example-pub".to_string(), 33 | clean_session: true, 34 | last_will: None, 35 | username: Some(self.username.clone()), 36 | password: Some(self.password.clone()), 37 | })); 38 | self.writer.write_packet(&connect)?; 39 | self.writer.flush()?; 40 | self.reader.read_packet() 41 | } 42 | 43 | pub fn publish_value(&mut self, prop: &str, value: &Value) -> Result { 44 | let publish = mqtt3::Packet::Publish(Box::new(mqtt3::Publish { 45 | dup: false, 46 | qos: mqtt3::QoS::AtLeastOnce, 47 | retain: false, 48 | topic_name: format!("{}/feeds/{}", self.username, prop).to_owned(), 49 | pid: Some(mqtt3::PacketIdentifier(10)), 50 | payload: Arc::new(value.to_string().into_bytes()) 51 | })); 52 | self.writer.write_packet(&publish)?; 53 | self.writer.flush()?; 54 | self.reader.read_packet() 55 | } 56 | 57 | pub fn publish_action(&mut self, name: &str) -> Result { 58 | let publish = mqtt3::Packet::Publish(Box::new(mqtt3::Publish { 59 | dup: false, 60 | qos: mqtt3::QoS::AtLeastOnce, 61 | retain: false, 62 | topic_name: format!("{}/feeds/actions", self.username).to_owned(), 63 | pid: Some(mqtt3::PacketIdentifier(10)), 64 | payload: Arc::new(name.to_string().into_bytes()) 65 | })); 66 | self.writer.write_packet(&publish)?; 67 | self.writer.flush()?; 68 | self.reader.read_packet() 69 | } 70 | } 71 | --------------------------------------------------------------------------------