├── .cargo └── config.toml ├── .github └── workflows │ └── rust.yml ├── .gitignore ├── .gitmodules ├── Cargo.lock ├── Cargo.toml ├── README.md ├── arduino └── UNO │ └── min │ ├── min.c │ ├── min.h │ └── min.ino ├── build.rs ├── data_if.txt ├── data_template ├── Cargo.lock ├── Cargo.toml └── src │ └── lib.rs ├── gw.toml ├── src ├── data_manager.rs ├── interface.rs ├── main.rs ├── mqtt.rs └── types.rs └── tools ├── build_ar9331.sh ├── build_f133.sh ├── build_mt76x8.sh └── build_pi.sh /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.mips-unknown-linux-uclibc] 2 | linker = "mips-openwrt-linux-uclibc-gcc" 3 | ar = "mips-openwrt-linux-uclibc-ar" 4 | runner = "mips-openwrt-linux-uclibc-strip" 5 | 6 | [target.arm-unknown-linux-gnueabihf] 7 | linker = "arm-linux-gnueabihf-gcc" 8 | ar = "arm-linux-gnueabihf-ar" 9 | 10 | [target.mipsel-unknown-linux-musl] 11 | linker = "mipsel-openwrt-linux-musl-gcc" 12 | ar = "mipsel-openwrt-linux-musl-ar" 13 | 14 | [target.mips-unknown-linux-musl] 15 | linker = "mips-openwrt-linux-musl-gcc" 16 | ar = "mips-openwrt-linux-musl-ar" 17 | 18 | [target.riscv64gc-unknown-linux-gnu] 19 | linker = "riscv64-unknown-linux-gnu-gcc" 20 | ar = "riscv64-unknown-linux-gnu-ar" 21 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ dev ] 6 | pull_request: 7 | branches: [ dev ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | # 更新子模块 20 | with: 21 | submodules: recursive 22 | - name: Build 23 | run: cargo build --verbose 24 | - name: Run tests 25 | run: cargo test --verbose 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qianchenzhumeng/iot_gw/8bf947976d9a05d907f2b8fff6c8730de866199e/.gitmodules -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "CoreFoundation-sys" 7 | version = "0.1.4" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d0e9889e6db118d49d88d84728d0e964d973a5680befb5f85f55141beea5c20b" 10 | dependencies = [ 11 | "libc", 12 | "mach 0.1.2", 13 | ] 14 | 15 | [[package]] 16 | name = "IOKit-sys" 17 | version = "0.1.5" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "99696c398cbaf669d2368076bdb3d627fb0ce51a26899d7c61228c5c0af3bf4a" 20 | dependencies = [ 21 | "CoreFoundation-sys", 22 | "libc", 23 | "mach 0.1.2", 24 | ] 25 | 26 | [[package]] 27 | name = "adler" 28 | version = "1.0.2" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 31 | 32 | [[package]] 33 | name = "aho-corasick" 34 | version = "0.7.18" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 37 | dependencies = [ 38 | "memchr", 39 | ] 40 | 41 | [[package]] 42 | name = "ansi_term" 43 | version = "0.11.0" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 46 | dependencies = [ 47 | "winapi", 48 | ] 49 | 50 | [[package]] 51 | name = "antidote" 52 | version = "1.0.0" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5" 55 | 56 | [[package]] 57 | name = "arc-swap" 58 | version = "0.4.8" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "dabe5a181f83789739c194cbe5a897dde195078fac08568d09221fd6137a7ba8" 61 | 62 | [[package]] 63 | name = "atty" 64 | version = "0.2.14" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 67 | dependencies = [ 68 | "hermit-abi", 69 | "libc", 70 | "winapi", 71 | ] 72 | 73 | [[package]] 74 | name = "autocfg" 75 | version = "1.0.1" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 78 | 79 | [[package]] 80 | name = "bindgen" 81 | version = "0.52.0" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "f1c85344eb535a31b62f0af37be84441ba9e7f0f4111eb0530f43d15e513fe57" 84 | dependencies = [ 85 | "bitflags", 86 | "cexpr", 87 | "cfg-if 0.1.10", 88 | "clang-sys", 89 | "clap", 90 | "env_logger", 91 | "lazy_static", 92 | "lazycell", 93 | "log", 94 | "peeking_take_while", 95 | "proc-macro2", 96 | "quote", 97 | "regex", 98 | "rustc-hash", 99 | "shlex", 100 | "which", 101 | ] 102 | 103 | [[package]] 104 | name = "bitflags" 105 | version = "1.3.2" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 108 | 109 | [[package]] 110 | name = "cc" 111 | version = "1.0.70" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" 114 | dependencies = [ 115 | "jobserver", 116 | ] 117 | 118 | [[package]] 119 | name = "cexpr" 120 | version = "0.3.6" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "fce5b5fb86b0c57c20c834c1b412fd09c77c8a59b9473f86272709e78874cd1d" 123 | dependencies = [ 124 | "nom", 125 | ] 126 | 127 | [[package]] 128 | name = "cfg-if" 129 | version = "0.1.10" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 132 | 133 | [[package]] 134 | name = "cfg-if" 135 | version = "1.0.0" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 138 | 139 | [[package]] 140 | name = "chrono" 141 | version = "0.4.19" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 144 | dependencies = [ 145 | "libc", 146 | "num-integer", 147 | "num-traits", 148 | "time", 149 | "winapi", 150 | ] 151 | 152 | [[package]] 153 | name = "clang-sys" 154 | version = "0.28.1" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "81de550971c976f176130da4b2978d3b524eaa0fd9ac31f3ceb5ae1231fb4853" 157 | dependencies = [ 158 | "glob", 159 | "libc", 160 | "libloading", 161 | ] 162 | 163 | [[package]] 164 | name = "clap" 165 | version = "2.33.3" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 168 | dependencies = [ 169 | "ansi_term", 170 | "atty", 171 | "bitflags", 172 | "strsim", 173 | "textwrap", 174 | "unicode-width", 175 | "vec_map", 176 | ] 177 | 178 | [[package]] 179 | name = "cmake" 180 | version = "0.1.45" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "eb6210b637171dfba4cda12e579ac6dc73f5165ad56133e5d72ef3131f320855" 183 | dependencies = [ 184 | "cc", 185 | ] 186 | 187 | [[package]] 188 | name = "crc32fast" 189 | version = "1.2.1" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" 192 | dependencies = [ 193 | "cfg-if 1.0.0", 194 | ] 195 | 196 | [[package]] 197 | name = "data_template" 198 | version = "0.1.0" 199 | dependencies = [ 200 | "json", 201 | "regex", 202 | ] 203 | 204 | [[package]] 205 | name = "dtoa" 206 | version = "0.4.8" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" 209 | 210 | [[package]] 211 | name = "env_logger" 212 | version = "0.7.1" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" 215 | dependencies = [ 216 | "atty", 217 | "humantime", 218 | "log", 219 | "regex", 220 | "termcolor", 221 | ] 222 | 223 | [[package]] 224 | name = "fallible-iterator" 225 | version = "0.2.0" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" 228 | 229 | [[package]] 230 | name = "fallible-streaming-iterator" 231 | version = "0.1.9" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" 234 | 235 | [[package]] 236 | name = "flate2" 237 | version = "1.0.21" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "80edafed416a46fb378521624fab1cfa2eb514784fd8921adbe8a8d8321da811" 240 | dependencies = [ 241 | "cfg-if 1.0.0", 242 | "crc32fast", 243 | "libc", 244 | "miniz_oxide", 245 | ] 246 | 247 | [[package]] 248 | name = "fnv" 249 | version = "1.0.7" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 252 | 253 | [[package]] 254 | name = "form_urlencoded" 255 | version = "1.0.1" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" 258 | dependencies = [ 259 | "matches", 260 | "percent-encoding", 261 | ] 262 | 263 | [[package]] 264 | name = "futures" 265 | version = "0.3.25" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" 268 | dependencies = [ 269 | "futures-channel", 270 | "futures-core", 271 | "futures-executor", 272 | "futures-io", 273 | "futures-sink", 274 | "futures-task", 275 | "futures-util", 276 | ] 277 | 278 | [[package]] 279 | name = "futures-channel" 280 | version = "0.3.25" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" 283 | dependencies = [ 284 | "futures-core", 285 | "futures-sink", 286 | ] 287 | 288 | [[package]] 289 | name = "futures-core" 290 | version = "0.3.25" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" 293 | 294 | [[package]] 295 | name = "futures-executor" 296 | version = "0.3.25" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" 299 | dependencies = [ 300 | "futures-core", 301 | "futures-task", 302 | "futures-util", 303 | ] 304 | 305 | [[package]] 306 | name = "futures-io" 307 | version = "0.3.25" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" 310 | 311 | [[package]] 312 | name = "futures-macro" 313 | version = "0.3.25" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" 316 | dependencies = [ 317 | "proc-macro2", 318 | "quote", 319 | "syn", 320 | ] 321 | 322 | [[package]] 323 | name = "futures-sink" 324 | version = "0.3.25" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" 327 | 328 | [[package]] 329 | name = "futures-task" 330 | version = "0.3.25" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" 333 | 334 | [[package]] 335 | name = "futures-timer" 336 | version = "3.0.2" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" 339 | 340 | [[package]] 341 | name = "futures-util" 342 | version = "0.3.25" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" 345 | dependencies = [ 346 | "futures-channel", 347 | "futures-core", 348 | "futures-io", 349 | "futures-macro", 350 | "futures-sink", 351 | "futures-task", 352 | "memchr", 353 | "pin-project-lite", 354 | "pin-utils", 355 | "slab", 356 | ] 357 | 358 | [[package]] 359 | name = "getrandom" 360 | version = "0.2.3" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" 363 | dependencies = [ 364 | "cfg-if 1.0.0", 365 | "libc", 366 | "wasi", 367 | ] 368 | 369 | [[package]] 370 | name = "git2" 371 | version = "0.13.22" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "9c1cbbfc9a1996c6af82c2b4caf828d2c653af4fcdbb0e5674cc966eee5a4197" 374 | dependencies = [ 375 | "bitflags", 376 | "libc", 377 | "libgit2-sys", 378 | "log", 379 | "url", 380 | ] 381 | 382 | [[package]] 383 | name = "glob" 384 | version = "0.3.0" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" 387 | 388 | [[package]] 389 | name = "gpio-cdev" 390 | version = "0.5.1" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "409296415b8abc7b47e5b77096faae14595c53724972da227434fc8f4b05ec8b" 393 | dependencies = [ 394 | "bitflags", 395 | "libc", 396 | "nix 0.23.2", 397 | ] 398 | 399 | [[package]] 400 | name = "gw" 401 | version = "0.3.0" 402 | dependencies = [ 403 | "chrono", 404 | "clap", 405 | "data_template", 406 | "gpio-cdev", 407 | "json", 408 | "log", 409 | "log4rs", 410 | "min-rs", 411 | "openssl-src", 412 | "paho-mqtt", 413 | "paho-mqtt-sys", 414 | "rusqlite", 415 | "serde", 416 | "serde_derive", 417 | "serialport", 418 | "shadow-rs", 419 | "spidev", 420 | "time", 421 | "toml", 422 | "uuid", 423 | ] 424 | 425 | [[package]] 426 | name = "hashbrown" 427 | version = "0.11.2" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 430 | 431 | [[package]] 432 | name = "hermit-abi" 433 | version = "0.1.19" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 436 | dependencies = [ 437 | "libc", 438 | ] 439 | 440 | [[package]] 441 | name = "humantime" 442 | version = "1.3.0" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" 445 | dependencies = [ 446 | "quick-error", 447 | ] 448 | 449 | [[package]] 450 | name = "idna" 451 | version = "0.2.3" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" 454 | dependencies = [ 455 | "matches", 456 | "unicode-bidi", 457 | "unicode-normalization", 458 | ] 459 | 460 | [[package]] 461 | name = "indexmap" 462 | version = "1.7.0" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" 465 | dependencies = [ 466 | "autocfg", 467 | "hashbrown", 468 | ] 469 | 470 | [[package]] 471 | name = "itoa" 472 | version = "0.4.8" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" 475 | 476 | [[package]] 477 | name = "jobserver" 478 | version = "0.1.24" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" 481 | dependencies = [ 482 | "libc", 483 | ] 484 | 485 | [[package]] 486 | name = "json" 487 | version = "0.12.4" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" 490 | 491 | [[package]] 492 | name = "lazy_static" 493 | version = "1.4.0" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 496 | 497 | [[package]] 498 | name = "lazycell" 499 | version = "1.3.0" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 502 | 503 | [[package]] 504 | name = "libc" 505 | version = "0.2.135" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" 508 | 509 | [[package]] 510 | name = "libgit2-sys" 511 | version = "0.12.23+1.2.0" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "29730a445bae719db3107078b46808cc45a5b7a6bae3f31272923af969453356" 514 | dependencies = [ 515 | "cc", 516 | "libc", 517 | "libz-sys", 518 | "pkg-config", 519 | ] 520 | 521 | [[package]] 522 | name = "libloading" 523 | version = "0.5.2" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" 526 | dependencies = [ 527 | "cc", 528 | "winapi", 529 | ] 530 | 531 | [[package]] 532 | name = "libsqlite3-sys" 533 | version = "0.18.0" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "1e704a02bcaecd4a08b93a23f6be59d0bd79cd161e0963e9499165a0a35df7bd" 536 | dependencies = [ 537 | "cc", 538 | "pkg-config", 539 | "vcpkg", 540 | ] 541 | 542 | [[package]] 543 | name = "libz-sys" 544 | version = "1.1.3" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66" 547 | dependencies = [ 548 | "cc", 549 | "libc", 550 | "pkg-config", 551 | "vcpkg", 552 | ] 553 | 554 | [[package]] 555 | name = "linked-hash-map" 556 | version = "0.5.4" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" 559 | 560 | [[package]] 561 | name = "log" 562 | version = "0.4.14" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 565 | dependencies = [ 566 | "cfg-if 1.0.0", 567 | "serde", 568 | ] 569 | 570 | [[package]] 571 | name = "log-mdc" 572 | version = "0.1.0" 573 | source = "registry+https://github.com/rust-lang/crates.io-index" 574 | checksum = "a94d21414c1f4a51209ad204c1776a3d0765002c76c6abcb602a6f09f1e881c7" 575 | 576 | [[package]] 577 | name = "log4rs" 578 | version = "0.10.0" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "853db99624c59798ddcf027dbe486541dd5cb5008ac6a6aaf217cc6fa044ee71" 581 | dependencies = [ 582 | "antidote", 583 | "arc-swap", 584 | "chrono", 585 | "flate2", 586 | "fnv", 587 | "humantime", 588 | "libc", 589 | "log", 590 | "log-mdc", 591 | "serde", 592 | "serde-value", 593 | "serde_derive", 594 | "serde_json", 595 | "serde_yaml", 596 | "thread-id", 597 | "typemap", 598 | "winapi", 599 | ] 600 | 601 | [[package]] 602 | name = "lru-cache" 603 | version = "0.1.2" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" 606 | dependencies = [ 607 | "linked-hash-map", 608 | ] 609 | 610 | [[package]] 611 | name = "mach" 612 | version = "0.1.2" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "2fd13ee2dd61cc82833ba05ade5a30bb3d63f7ced605ef827063c63078302de9" 615 | dependencies = [ 616 | "libc", 617 | ] 618 | 619 | [[package]] 620 | name = "mach" 621 | version = "0.3.2" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" 624 | dependencies = [ 625 | "libc", 626 | ] 627 | 628 | [[package]] 629 | name = "matches" 630 | version = "0.1.9" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" 633 | 634 | [[package]] 635 | name = "memchr" 636 | version = "2.4.1" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 639 | 640 | [[package]] 641 | name = "memoffset" 642 | version = "0.6.5" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 645 | dependencies = [ 646 | "autocfg", 647 | ] 648 | 649 | [[package]] 650 | name = "min-rs" 651 | version = "0.1.0" 652 | source = "git+https://github.com/qianchenzhumeng/min-rs.git#ca485964494aba2a330c3bc3eb1370f21e511ff6" 653 | dependencies = [ 654 | "log", 655 | ] 656 | 657 | [[package]] 658 | name = "miniz_oxide" 659 | version = "0.4.4" 660 | source = "registry+https://github.com/rust-lang/crates.io-index" 661 | checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" 662 | dependencies = [ 663 | "adler", 664 | "autocfg", 665 | ] 666 | 667 | [[package]] 668 | name = "nix" 669 | version = "0.23.2" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" 672 | dependencies = [ 673 | "bitflags", 674 | "cc", 675 | "cfg-if 1.0.0", 676 | "libc", 677 | "memoffset", 678 | ] 679 | 680 | [[package]] 681 | name = "nix" 682 | version = "0.24.2" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" 685 | dependencies = [ 686 | "bitflags", 687 | "cfg-if 1.0.0", 688 | "libc", 689 | ] 690 | 691 | [[package]] 692 | name = "nom" 693 | version = "4.2.3" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" 696 | dependencies = [ 697 | "memchr", 698 | "version_check", 699 | ] 700 | 701 | [[package]] 702 | name = "num-integer" 703 | version = "0.1.44" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 706 | dependencies = [ 707 | "autocfg", 708 | "num-traits", 709 | ] 710 | 711 | [[package]] 712 | name = "num-traits" 713 | version = "0.2.14" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 716 | dependencies = [ 717 | "autocfg", 718 | ] 719 | 720 | [[package]] 721 | name = "openssl-src" 722 | version = "111.22.0+1.1.1q" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "8f31f0d509d1c1ae9cada2f9539ff8f37933831fd5098879e482aa687d659853" 725 | dependencies = [ 726 | "cc", 727 | ] 728 | 729 | [[package]] 730 | name = "openssl-sys" 731 | version = "0.9.77" 732 | source = "registry+https://github.com/rust-lang/crates.io-index" 733 | checksum = "b03b84c3b2d099b81f0953422b4d4ad58761589d0229b5506356afca05a3670a" 734 | dependencies = [ 735 | "autocfg", 736 | "cc", 737 | "libc", 738 | "openssl-src", 739 | "pkg-config", 740 | "vcpkg", 741 | ] 742 | 743 | [[package]] 744 | name = "ordered-float" 745 | version = "1.1.1" 746 | source = "registry+https://github.com/rust-lang/crates.io-index" 747 | checksum = "3305af35278dd29f46fcdd139e0b1fbfae2153f0e5928b39b035542dd31e37b7" 748 | dependencies = [ 749 | "num-traits", 750 | ] 751 | 752 | [[package]] 753 | name = "paho-mqtt" 754 | version = "0.9.2" 755 | source = "registry+https://github.com/rust-lang/crates.io-index" 756 | checksum = "c708cfa2932abe42d3adae19a885bb267181d6988b81c6c8159938bd11f76f5c" 757 | dependencies = [ 758 | "futures", 759 | "futures-timer", 760 | "libc", 761 | "log", 762 | "paho-mqtt-sys", 763 | "thiserror", 764 | ] 765 | 766 | [[package]] 767 | name = "paho-mqtt-sys" 768 | version = "0.5.0" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "1ad9ac6a77a7e7c70cd51262b94ab666c9e4c38fb0f4201dba8d7f8589aa8ce4" 771 | dependencies = [ 772 | "bindgen", 773 | "cmake", 774 | "openssl-sys", 775 | ] 776 | 777 | [[package]] 778 | name = "peeking_take_while" 779 | version = "0.1.2" 780 | source = "registry+https://github.com/rust-lang/crates.io-index" 781 | checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" 782 | 783 | [[package]] 784 | name = "percent-encoding" 785 | version = "2.1.0" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 788 | 789 | [[package]] 790 | name = "pin-project-lite" 791 | version = "0.2.9" 792 | source = "registry+https://github.com/rust-lang/crates.io-index" 793 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 794 | 795 | [[package]] 796 | name = "pin-utils" 797 | version = "0.1.0" 798 | source = "registry+https://github.com/rust-lang/crates.io-index" 799 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 800 | 801 | [[package]] 802 | name = "pkg-config" 803 | version = "0.3.19" 804 | source = "registry+https://github.com/rust-lang/crates.io-index" 805 | checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" 806 | 807 | [[package]] 808 | name = "proc-macro2" 809 | version = "1.0.29" 810 | source = "registry+https://github.com/rust-lang/crates.io-index" 811 | checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" 812 | dependencies = [ 813 | "unicode-xid", 814 | ] 815 | 816 | [[package]] 817 | name = "quick-error" 818 | version = "1.2.3" 819 | source = "registry+https://github.com/rust-lang/crates.io-index" 820 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 821 | 822 | [[package]] 823 | name = "quote" 824 | version = "1.0.9" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 827 | dependencies = [ 828 | "proc-macro2", 829 | ] 830 | 831 | [[package]] 832 | name = "redox_syscall" 833 | version = "0.1.57" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" 836 | 837 | [[package]] 838 | name = "regex" 839 | version = "1.6.0" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" 842 | dependencies = [ 843 | "aho-corasick", 844 | "memchr", 845 | "regex-syntax", 846 | ] 847 | 848 | [[package]] 849 | name = "regex-syntax" 850 | version = "0.6.27" 851 | source = "registry+https://github.com/rust-lang/crates.io-index" 852 | checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" 853 | 854 | [[package]] 855 | name = "rusqlite" 856 | version = "0.23.1" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "45d0fd62e1df63d254714e6cb40d0a0e82e7a1623e7a27f679d851af092ae58b" 859 | dependencies = [ 860 | "bitflags", 861 | "fallible-iterator", 862 | "fallible-streaming-iterator", 863 | "libsqlite3-sys", 864 | "lru-cache", 865 | "memchr", 866 | "smallvec", 867 | "time", 868 | ] 869 | 870 | [[package]] 871 | name = "rustc-hash" 872 | version = "1.1.0" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 875 | 876 | [[package]] 877 | name = "ryu" 878 | version = "1.0.5" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 881 | 882 | [[package]] 883 | name = "serde" 884 | version = "1.0.130" 885 | source = "registry+https://github.com/rust-lang/crates.io-index" 886 | checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" 887 | 888 | [[package]] 889 | name = "serde-value" 890 | version = "0.6.0" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | checksum = "5a65a7291a8a568adcae4c10a677ebcedbc6c9cec91c054dee2ce40b0e3290eb" 893 | dependencies = [ 894 | "ordered-float", 895 | "serde", 896 | ] 897 | 898 | [[package]] 899 | name = "serde_derive" 900 | version = "1.0.130" 901 | source = "registry+https://github.com/rust-lang/crates.io-index" 902 | checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" 903 | dependencies = [ 904 | "proc-macro2", 905 | "quote", 906 | "syn", 907 | ] 908 | 909 | [[package]] 910 | name = "serde_json" 911 | version = "1.0.67" 912 | source = "registry+https://github.com/rust-lang/crates.io-index" 913 | checksum = "a7f9e390c27c3c0ce8bc5d725f6e4d30a29d26659494aa4b17535f7522c5c950" 914 | dependencies = [ 915 | "itoa", 916 | "ryu", 917 | "serde", 918 | ] 919 | 920 | [[package]] 921 | name = "serde_yaml" 922 | version = "0.8.21" 923 | source = "registry+https://github.com/rust-lang/crates.io-index" 924 | checksum = "d8c608a35705a5d3cdc9fbe403147647ff34b921f8e833e49306df898f9b20af" 925 | dependencies = [ 926 | "dtoa", 927 | "indexmap", 928 | "serde", 929 | "yaml-rust", 930 | ] 931 | 932 | [[package]] 933 | name = "serialport" 934 | version = "4.2.0" 935 | source = "registry+https://github.com/rust-lang/crates.io-index" 936 | checksum = "aab92efb5cf60ad310548bc3f16fa6b0d950019cb7ed8ff41968c3d03721cf12" 937 | dependencies = [ 938 | "CoreFoundation-sys", 939 | "IOKit-sys", 940 | "bitflags", 941 | "cfg-if 1.0.0", 942 | "mach 0.3.2", 943 | "nix 0.24.2", 944 | "regex", 945 | "winapi", 946 | ] 947 | 948 | [[package]] 949 | name = "shadow-rs" 950 | version = "0.7.1" 951 | source = "registry+https://github.com/rust-lang/crates.io-index" 952 | checksum = "3ca10942770d8f21d47e4c4a270bdb9d20a9cb723aa6b672a14cba9f49920fad" 953 | dependencies = [ 954 | "chrono", 955 | "git2", 956 | ] 957 | 958 | [[package]] 959 | name = "shlex" 960 | version = "0.1.1" 961 | source = "registry+https://github.com/rust-lang/crates.io-index" 962 | checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" 963 | 964 | [[package]] 965 | name = "slab" 966 | version = "0.4.7" 967 | source = "registry+https://github.com/rust-lang/crates.io-index" 968 | checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" 969 | dependencies = [ 970 | "autocfg", 971 | ] 972 | 973 | [[package]] 974 | name = "smallvec" 975 | version = "1.6.1" 976 | source = "registry+https://github.com/rust-lang/crates.io-index" 977 | checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" 978 | 979 | [[package]] 980 | name = "spidev" 981 | version = "0.5.1" 982 | source = "registry+https://github.com/rust-lang/crates.io-index" 983 | checksum = "5c43e891adf1abc1e09b10f80c8d91959ee20ec28425c6dadac78844ba4c709f" 984 | dependencies = [ 985 | "bitflags", 986 | "libc", 987 | "nix 0.23.2", 988 | ] 989 | 990 | [[package]] 991 | name = "strsim" 992 | version = "0.8.0" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 995 | 996 | [[package]] 997 | name = "syn" 998 | version = "1.0.76" 999 | source = "registry+https://github.com/rust-lang/crates.io-index" 1000 | checksum = "c6f107db402c2c2055242dbf4d2af0e69197202e9faacbef9571bbe47f5a1b84" 1001 | dependencies = [ 1002 | "proc-macro2", 1003 | "quote", 1004 | "unicode-xid", 1005 | ] 1006 | 1007 | [[package]] 1008 | name = "termcolor" 1009 | version = "1.1.3" 1010 | source = "registry+https://github.com/rust-lang/crates.io-index" 1011 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 1012 | dependencies = [ 1013 | "winapi-util", 1014 | ] 1015 | 1016 | [[package]] 1017 | name = "textwrap" 1018 | version = "0.11.0" 1019 | source = "registry+https://github.com/rust-lang/crates.io-index" 1020 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 1021 | dependencies = [ 1022 | "unicode-width", 1023 | ] 1024 | 1025 | [[package]] 1026 | name = "thiserror" 1027 | version = "1.0.37" 1028 | source = "registry+https://github.com/rust-lang/crates.io-index" 1029 | checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" 1030 | dependencies = [ 1031 | "thiserror-impl", 1032 | ] 1033 | 1034 | [[package]] 1035 | name = "thiserror-impl" 1036 | version = "1.0.37" 1037 | source = "registry+https://github.com/rust-lang/crates.io-index" 1038 | checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" 1039 | dependencies = [ 1040 | "proc-macro2", 1041 | "quote", 1042 | "syn", 1043 | ] 1044 | 1045 | [[package]] 1046 | name = "thread-id" 1047 | version = "3.3.0" 1048 | source = "registry+https://github.com/rust-lang/crates.io-index" 1049 | checksum = "c7fbf4c9d56b320106cd64fd024dadfa0be7cb4706725fc44a7d7ce952d820c1" 1050 | dependencies = [ 1051 | "libc", 1052 | "redox_syscall", 1053 | "winapi", 1054 | ] 1055 | 1056 | [[package]] 1057 | name = "time" 1058 | version = "0.1.43" 1059 | source = "registry+https://github.com/rust-lang/crates.io-index" 1060 | checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" 1061 | dependencies = [ 1062 | "libc", 1063 | "winapi", 1064 | ] 1065 | 1066 | [[package]] 1067 | name = "tinyvec" 1068 | version = "1.4.0" 1069 | source = "registry+https://github.com/rust-lang/crates.io-index" 1070 | checksum = "5241dd6f21443a3606b432718b166d3cedc962fd4b8bea54a8bc7f514ebda986" 1071 | dependencies = [ 1072 | "tinyvec_macros", 1073 | ] 1074 | 1075 | [[package]] 1076 | name = "tinyvec_macros" 1077 | version = "0.1.0" 1078 | source = "registry+https://github.com/rust-lang/crates.io-index" 1079 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 1080 | 1081 | [[package]] 1082 | name = "toml" 1083 | version = "0.5.8" 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" 1085 | checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" 1086 | dependencies = [ 1087 | "serde", 1088 | ] 1089 | 1090 | [[package]] 1091 | name = "traitobject" 1092 | version = "0.1.0" 1093 | source = "registry+https://github.com/rust-lang/crates.io-index" 1094 | checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" 1095 | 1096 | [[package]] 1097 | name = "typemap" 1098 | version = "0.3.3" 1099 | source = "registry+https://github.com/rust-lang/crates.io-index" 1100 | checksum = "653be63c80a3296da5551e1bfd2cca35227e13cdd08c6668903ae2f4f77aa1f6" 1101 | dependencies = [ 1102 | "unsafe-any", 1103 | ] 1104 | 1105 | [[package]] 1106 | name = "unicode-bidi" 1107 | version = "0.3.6" 1108 | source = "registry+https://github.com/rust-lang/crates.io-index" 1109 | checksum = "246f4c42e67e7a4e3c6106ff716a5d067d4132a642840b242e357e468a2a0085" 1110 | 1111 | [[package]] 1112 | name = "unicode-normalization" 1113 | version = "0.1.19" 1114 | source = "registry+https://github.com/rust-lang/crates.io-index" 1115 | checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" 1116 | dependencies = [ 1117 | "tinyvec", 1118 | ] 1119 | 1120 | [[package]] 1121 | name = "unicode-width" 1122 | version = "0.1.8" 1123 | source = "registry+https://github.com/rust-lang/crates.io-index" 1124 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 1125 | 1126 | [[package]] 1127 | name = "unicode-xid" 1128 | version = "0.2.2" 1129 | source = "registry+https://github.com/rust-lang/crates.io-index" 1130 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 1131 | 1132 | [[package]] 1133 | name = "unsafe-any" 1134 | version = "0.4.2" 1135 | source = "registry+https://github.com/rust-lang/crates.io-index" 1136 | checksum = "f30360d7979f5e9c6e6cea48af192ea8fab4afb3cf72597154b8f08935bc9c7f" 1137 | dependencies = [ 1138 | "traitobject", 1139 | ] 1140 | 1141 | [[package]] 1142 | name = "url" 1143 | version = "2.2.2" 1144 | source = "registry+https://github.com/rust-lang/crates.io-index" 1145 | checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" 1146 | dependencies = [ 1147 | "form_urlencoded", 1148 | "idna", 1149 | "matches", 1150 | "percent-encoding", 1151 | ] 1152 | 1153 | [[package]] 1154 | name = "uuid" 1155 | version = "0.8.2" 1156 | source = "registry+https://github.com/rust-lang/crates.io-index" 1157 | checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" 1158 | dependencies = [ 1159 | "getrandom", 1160 | ] 1161 | 1162 | [[package]] 1163 | name = "vcpkg" 1164 | version = "0.2.15" 1165 | source = "registry+https://github.com/rust-lang/crates.io-index" 1166 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1167 | 1168 | [[package]] 1169 | name = "vec_map" 1170 | version = "0.8.2" 1171 | source = "registry+https://github.com/rust-lang/crates.io-index" 1172 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 1173 | 1174 | [[package]] 1175 | name = "version_check" 1176 | version = "0.1.5" 1177 | source = "registry+https://github.com/rust-lang/crates.io-index" 1178 | checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" 1179 | 1180 | [[package]] 1181 | name = "wasi" 1182 | version = "0.10.2+wasi-snapshot-preview1" 1183 | source = "registry+https://github.com/rust-lang/crates.io-index" 1184 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 1185 | 1186 | [[package]] 1187 | name = "which" 1188 | version = "3.1.1" 1189 | source = "registry+https://github.com/rust-lang/crates.io-index" 1190 | checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" 1191 | dependencies = [ 1192 | "libc", 1193 | ] 1194 | 1195 | [[package]] 1196 | name = "winapi" 1197 | version = "0.3.9" 1198 | source = "registry+https://github.com/rust-lang/crates.io-index" 1199 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1200 | dependencies = [ 1201 | "winapi-i686-pc-windows-gnu", 1202 | "winapi-x86_64-pc-windows-gnu", 1203 | ] 1204 | 1205 | [[package]] 1206 | name = "winapi-i686-pc-windows-gnu" 1207 | version = "0.4.0" 1208 | source = "registry+https://github.com/rust-lang/crates.io-index" 1209 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1210 | 1211 | [[package]] 1212 | name = "winapi-util" 1213 | version = "0.1.5" 1214 | source = "registry+https://github.com/rust-lang/crates.io-index" 1215 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1216 | dependencies = [ 1217 | "winapi", 1218 | ] 1219 | 1220 | [[package]] 1221 | name = "winapi-x86_64-pc-windows-gnu" 1222 | version = "0.4.0" 1223 | source = "registry+https://github.com/rust-lang/crates.io-index" 1224 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1225 | 1226 | [[package]] 1227 | name = "yaml-rust" 1228 | version = "0.4.5" 1229 | source = "registry+https://github.com/rust-lang/crates.io-index" 1230 | checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" 1231 | dependencies = [ 1232 | "linked-hash-map", 1233 | ] 1234 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gw" 3 | version = "0.3.0" 4 | authors = ["前尘逐梦"] 5 | edition = "2018" 6 | build = "build.rs" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | log = "0.4" 12 | clap = "2" 13 | log4rs = "0.10.0" 14 | time = "0.1" 15 | uuid = { version = "0.8", features = ["v4"] } 16 | json = "0.12.4" 17 | chrono = "0.4.13" 18 | toml = "0.5.6" 19 | serde_derive = "1.0" 20 | serde = "1.0" 21 | data_template = {path = "./data_template"} 22 | paho-mqtt = "0.9.1" 23 | shadow-rs = "0.7" 24 | serialport = {version = "4.0.0", default-features=false} 25 | min-rs = { git = "https://github.com/qianchenzhumeng/min-rs.git"} 26 | gpio-cdev = "0.5.1" 27 | spidev = "0.5.1" 28 | 29 | [dependencies.rusqlite] 30 | version = "0.23.1" 31 | features = ["bundled"] 32 | 33 | [dependencies.paho-mqtt-sys] 34 | features = ["bundled", "vendored-ssl"] 35 | version = "0.5" 36 | 37 | [dependencies.openssl-src] 38 | version = "111.10.1+1.1.1g" 39 | 40 | [build-dependencies] 41 | shadow-rs = "0.7" 42 | 43 | [features] 44 | default = ["build_bindgen"] 45 | build_bindgen = ["paho-mqtt-sys/build_bindgen"] 46 | ssl = [] 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 1. 功能描述 2 | 3 | 目前仅支持在 linux 上运行。 4 | 5 | **已支持以下功能**: 6 | 7 | - 数据上传:从文件读取或者从串口接收 JSON 格式的数据,通过 MQTT 发送给服务器 8 | - 远程控制:可以借助 MQTT 的订阅机制从服务器接收数据,通过串口发送出去 9 | - 网关离线时可以将数据暂时缓存到数据库内,网络连接恢复后再从数据库里面取出来上传 10 | - 支持数据模板功能,即可以根据数据模板重新格式化来自终端的数据、添加自定义属性、预定义属性(例如添加时间戳)等,从而生成新的JSON数据 11 | - 支持日志功能 12 | - 支持 MQTTS(TLS v1.2,v1.3) 13 | 14 | ### 2. 使用示例 15 | 16 | 在本地启动 MQTT broker,例如使用 mosquitto: 17 | 18 | ```bash 19 | # 默认监听 1883 端口 20 | mosquitto -v 21 | ``` 22 | 23 | 或者修改配置文件 `gw.toml`,指定可用的 MQTT broker: 24 | 25 | ```toml 26 | [server] 27 | address = "127.0.0.1:1883" 28 | ``` 29 | 30 | #### (1) 从文件读取数据(默认) 31 | 32 | 修改配置文件(默认是 gw.toml),指定文件,并且将数据接口类型设置为 `text_file`(默认为此配置): 33 | 34 | ```toml 35 | [data_if] 36 | if_name = "./data_if.txt" 37 | if_type = "text_file" 38 | ``` 39 | 40 | 运行网关程序: 41 | 42 | ```bash 43 | cargo run -- -c gw.toml 44 | ``` 45 | 46 | 在文件中写入数据(需要有换行 `'\n'`): 47 | 48 | ```bash 49 | echo "{\"id\":1,\"name\":\"SN-001\",\"temperature\": 27.45,\"humidity\": 25.36,\"voltage\": 3.88,\"status\": 0}" > data_if.txt 50 | ``` 51 | 52 | **说明**:网关程序每隔 1 秒读清一次文件,读清后需要手动写入数据。 53 | 54 | 顺利的话,可以在 mosquitto 的窗口内看到网关发送过去的消息。 55 | 56 | #### (2) 从串口读取数据 57 | 58 | 修改配置文件(默认是 gw.toml),指定串口,并且将数据接口类型设置为 `serial_port`: 59 | 60 | ```toml 61 | [data_if] 62 | if_name = "/dev/ttyS14" 63 | if_type = "serial_port" 64 | ``` 65 | 66 | 网关程序: 67 | 68 | ```bash 69 | cargo run -- -c gw.toml 70 | ``` 71 | 72 | 使用外部设备按 [MIN](https://github.com/min-protocol/min) 协议向串口发送数据( arduino/min 目录内有 Arduino UNO 的示例,烧录 min 中的程序): 73 | 74 | ``` 75 | {"id":1,"name":"SN-001","temperature": 27.45,"humidity": 25.36,"voltage": 3.88,"status": 0} 76 | ``` 77 | 78 | 如果是 Arduino UNO,只有一个串口,只能用于和网关通信,网关的配置文件中配置接该串口即可。 79 | 80 | 顺利的话,网关会收到 Arduino 发送的消息,并且会发送给 mosquitto(可以在 mosquitto 的窗口内看到)。 81 | 82 | #### (3) 从 SPI 读取数据 83 | 84 | 将 SX1276 Lora 模块连接到 SPI 口上,修改配置文件(默认是 gw.toml),指定 spi 设备,并且将数据接口类型设置为 `spi_sx1276`: 85 | 86 | ```toml 87 | [data_if] 88 | if_name = "/dev/spidev0.0" 89 | if_type = "spi_sx1276" 90 | ``` 91 | 92 | 网关程序: 93 | 94 | ```bash 95 | cargo run -- -c gw.toml 96 | ``` 97 | 98 | 需要使用另一个射频参数和 `SpiIf::setup_lora` 中定义的射频参数匹配的 Lora 设备发送数据(4字节头 + 前面提到的 json 字符串)。 99 | 100 | #### (4) 远程控制 101 | 102 | 网关已支持远程控制功能。该远程控制不是指可以远程控制网关,而是网关会将服务器发过来的控制命令发送给 MCU,MCU 去响应命令,例如点灯等。 103 | 104 | arduino 目录下有 Arduino UNO 和 Arduino DUE 的代码示例(min 子目录),可以通过服务器控制 Arduino 点亮或熄灭 LED。 105 | 106 | 这里还是以使用 mosquitto 作为 broker 为例: 107 | 108 | ```bash 109 | # 默认监听 1883 端口 110 | mosquitto -v 111 | ``` 112 | 113 | 按照 (2) 中的指引操作。 114 | 115 | 在另一个终端内使用 mosquitto_pub 按照 gw.toml 里面配置的 `sub_topic`(默认为“ctrl/#”) 发送数据: 116 | 117 | ```bash 118 | # 点亮 LED 119 | mosquitto_pub -d -h "localhost" -p 1883 -t "ctrl/1" -m "turn_on" 120 | # 熄灭 LED 121 | mosquitto_pub -d -h "localhost" -p 1883 -t "ctrl/1" -m "turn_off" 122 | ``` 123 | 124 | 从发布数据到 LED 点亮或熄灭大概会有 3s 左右延时。 125 | 126 | #### (5) 使用 TLS 127 | 128 | 在本地启动 MQTT broker,例如使用 mosquitto: 129 | 130 | ```bash 131 | mosquitto -c mosquitto.conf 132 | ``` 133 | 134 | 配置文件内容(按实际情况修改 ca 文件路径): 135 | 136 | ```shell 137 | # mosquitto.conf 138 | log_type error 139 | log_type warning 140 | log_type notice 141 | log_type information 142 | log_type debug 143 | 144 | allow_anonymous true 145 | 146 | # non-SSL listeners 147 | listener 1883 148 | 149 | # server authentication - no client authentication 150 | listener 18885 151 | 152 | # 指定 ca 文件路径 153 | cafile ca/ca.crt 154 | certfile ca/server.crt 155 | keyfile ca/server.key 156 | require_certificate false 157 | tls_version tlsv1.2 158 | ``` 159 | 160 | 修改 Cargo.toml 文件,开启 `ssl` 特性: 161 | 162 | ```toml 163 | [features] 164 | default = ["build_bindgen", "ssl"] 165 | build_bindgen = ["paho-mqtt-sys/build_bindgen"] 166 | ssl = [] 167 | ``` 168 | 169 | 修改配置文件(默认是 gw.toml),使用 ssl 协议,并指定 ca 文件: 170 | 171 | ```toml 172 | [server] 173 | #address = "127.0.0.1:1883" 174 | address = "ssl://127.0.0.1:18885" 175 | 176 | [tls] 177 | cafile = "ca/ca.crt" 178 | # pem 文件生成方式:cat client.crt client.key ca.crt > client.pem 179 | key_store = "ca/client.pem" 180 | ``` 181 | 182 | 编译运行网关程序: 183 | 184 | ```bash 185 | cargo run -- -c gw.toml 186 | ``` 187 | 188 | #### (6) 连接 ThingsBoard 189 | 190 | [待整理] 191 | 192 | ### 3. 数据模板引擎功能说明 193 | 194 | 数据模板引擎需要在配置文件中配置,减 gw.toml 内 `[msg]` 部分。 195 | 196 | 模板支持的功能: 197 | 198 | 1. 可以使用原消息中字符串类型的属性值作为属性名 199 | 2. 可以使用原消息中的属性值 200 | 3. 可以新增自定义的属性名/属性值 201 | 4. 可以使用有特定内容的模板,比如时间戳 202 | 203 | 对原始数据的要求: 204 | 205 | 1. 格式为 JSON 206 | 207 | 1. 不能有同名的属性 208 | 2. 打算用做模板属性名的字符串属性值需要符合 JSON 属性名命名规范 209 | 210 | 模板示例(以 `gw.toml` 中的为例): 211 | 212 | ```bash 213 | # 原始数据示例 214 | "{\"l\":\"SN-004\",\"t\": 27.45,\"h\": 25.36,\"v\": 3.88,\"e\": 0}" 215 | # 模板 216 | template = "{<{l}>: [{\"ts\": <#TS#>,\"values\": {\"temperature\": <{t}>, \"humidity\": <{h}>,\"voltage\": <{v}>,\"status\": <{e}>}}]}" 217 | ``` 218 | 219 | 以上设置的转换效果为: 220 | 221 | ```bash 222 | # 原始数据 223 | {"l":"SN-001","t": 27.45,"h": 25.36,"v": 3.88,"e": 0} 224 | # 输出数据 225 | {"SN-001": [{"ts": 1596965153255,"values": {"temperature": 27.45, "humidity": 25.36,"voltage": 3.88,"status": 0}}]} 226 | ``` 227 | 228 | 模板注解: 229 | 230 | 1. `<{l}>` :取原消息属性 `l` 对应的属性值。例如,需要使用消息 `"t": 27.45` 中 `t` 的属性值 `27.45` 作为输出数据中的属性值,需要在模板中填写 `<{t}>` 231 | 2. `<#NAME#>` :使用模板引擎可以提供的值。例如<#TS#>表示自 EPOCH 以来的秒数; 232 | 3. 符合 JSON 属性名命名规范的字符串类型的属性值可以作为模板中的属性名。需要将模板填成 "<{属性名}>" 的形式. 例如, 需要使用消息 `{"l": "SN-001"}`中 `l` 的属性值 `SN-001` 作为输出数据中的属性名, 需要在模板中填写 `<{l}>`。 233 | 234 | ### 4. 已支持的平台 235 | 236 | - x86_64-unknown-linux-gnu 237 | - mips-unknown-linux-uclibc 238 | - 需要为该目标平台编译 rust:[Cross Compile Rust For OpenWRT](https://qianchenzhumeng.github.io/posts/cross-compile-rust-for-openwrt/) 239 | 240 | - arm-unknown-linux-gnueabihf(树莓派) 241 | - mipsel-unknown-linux-musl 242 | - riscv64gc-unknown-linux-gnu 243 | 244 | 245 | 目录 tools 内有部分平台的编译脚本。 246 | 247 | ### 5. 交叉编译问题解答 248 | 249 | #### (1) 找不到 libanl 250 | 251 | > /mnt/f/wsl/OpenWRT/OpenWrt-SDK-ar71xx-for-linux-x86_64-gcc-4.8-linaro_uClibc-0.9.33.2/staging_dir/toolchain-mips_34kc_gcc-4.8-linaro_uClibc-0.9.33.2/bin/../lib/gcc/mips-openwrt-linux-uclibc/4.8.3/../../../../mips-openwrt-linux-uclibc/bin/ld: cannot find -lanl 252 | 253 | 按照 [src/CMakeLists.txt: fix build on uclibc or musl](https://github.com/eclipse/paho.mqtt.c/commit/517e8659ab566b15cc409490a432e8935b164de8) 修改 `.cargo/registry/src/crates.rustcc.com-a21e0f92747beca3/paho-mqtt-sys-0.3.0/paho.mqtt.c/src/CMakeLists.txt` 254 | 255 | 修改后仍然可能因找到了主机的 libanl 报错,如果还报错,按如下方式修改: 256 | 257 | ```cmake 258 | #SET(LIBS_SYSTEM c dl pthread anl rt) 259 | SET(LIBS_SYSTEM c dl pthread rt) 260 | ``` 261 | 262 | #### (2) 找不到 libclang 263 | 264 | > thread 'main' panicked at 'Unable to find libclang: "couldn\'t find any valid shared libraries matching: [\'libclang.so\', \'libclang-*.so\', \'libclang.so.*\'] 265 | 266 | ```bash 267 | sudo apt-get install clang libclang-dev 268 | ``` 269 | 270 | #### (3) 找不到 bindings 271 | > thread 'main' panicked at 'No generated bindings exist for the version/target: bindings/bindings_paho_mqtt_c_1.3.2-mips-unknown-linux-uclibc.rs', paho-mqtt-sys/build.rs:102:13 272 | 273 | ```bash 274 | cargo install bindgen 275 | sudo apt install libc6-dev-i386 276 | cd ~/.cargo/registry/src/crates.rustcc.com-a21e0f92747beca3/paho-mqtt-sys-0.5.0 277 | TARGET=mips-unknown-linux-uclibc bindgen wrapper.h -o bindings/bindings_paho_mqtt_c_1.3.2-mips-unknown-linux-uclibc.rs -- -Ipaho.mqtt.c/src --verbose 278 | ``` 279 | 280 | #### (4) 找不到 C 库头文件 281 | 282 | > debug:clang version: clang version 10.0.0-4ubuntu1 283 | > debug:bindgen include path: -I/mnt/f/wsl/project/iot_gw/target/mipsel-unknown-linux-musl/release/build/paho-mqtt-sys-0e7cd946c58a7093/out/include 284 | > 285 | > --- stderr 286 | > fatal: not a git repository (or any of the parent directories): .git 287 | > /usr/include/stdio.h:33:10: fatal error: 'stddef.h' file not found 288 | > /usr/include/stdio.h:33:10: fatal error: 'stddef.h' file not found, err: true 289 | > thread 'main' panicked at 'Unable to generate bindings: ()', /home/dell/.cargo/registry/src/crates.rustcc.com-a21e0f92747beca3/paho-mqtt-sys-0.3.0/build.rs:139:14 290 | > note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace 291 | 292 | 安装 clang 293 | 294 | ```bash 295 | sudo apt-get install clang 296 | ``` 297 | 298 | 指定交叉编译工具链,例如: 299 | 300 | ```bash 301 | export CC_mipsel_unknown_linux_musl=mipsel-openwrt-linux-gcc 302 | export CXX_mipsel_unknown_linux_musl=mipsel-openwrt-linux-g++ 303 | ``` 304 | 305 | #### (5) zlib 相关 306 | 307 | 找不到 `-lz`: 308 | 309 | 将对应平台上的 libz.so 复制到所对应的工具链的库目录下。例如: 310 | 311 | ```bin 312 | /mnt/f/wsl/tool/raspberrypi-tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/arm-linux-gnueabihf/lib$ ls -l libz* 313 | lrwxrwxrwx 1 dell dell 14 Oct 29 18:16 libz.so -> libz.so.1.2.11 314 | -rwxrwxrwx 1 dell dell 95880 Oct 29 18:15 libz.so.1.2.11 315 | ``` 316 | 317 | 找不到 `libz.h`: 318 | 319 | 根据运行环境上的 `libz.so` 的版本,下载对应的 `zlib` 源码,然后在编译时指定头文件路径,例如: 320 | 321 | ``` 322 | CFLAGS="-I/home/dell/wsl/source/libz-1.2.1100+2/libz 323 | ``` 324 | 325 | #### (6) 芒果派 MQR-F133(RISC-V64)生成 `paho-mqtt-sys` 绑定的问题 326 | 327 | > - error: unknown target triple 'riscv64gc-unknown-linux-gnu', please use -triple or -arch 328 | > thread 'main' panicked at 'libclang error; possible causes include: 329 | > - Invalid flag syntax 330 | > - Unrecognized flags 331 | > - Invalid flag arguments 332 | > - File I/O errors 333 | > If you encounter an error missing from this list, please file an issue or a PR!', /home/dell/.cargo/registry/src/mirrors.tuna.tsinghua.edu.cn-df7c3c540f42cdbd/bindgen-0.52.0/src/ir/context.rs:574:15 334 | 335 | 修改 `bindgen-0.52.0` 的源码,在打印上述信息前的位置将传给 `clang` 的标志打印出来: 336 | 337 | ``` 338 | println!("{:?}", clang_args); 339 | clang::TranslationUnit::parse( 340 | &index, 341 | "", 342 | &clang_args, 343 | &options.input_unsaved_files, 344 | parse_options, 345 | ).expect("libclang error; possible causes include: 346 | - Invalid flag syntax 347 | - Unrecognized flags 348 | - Invalid flag arguments 349 | - File I/O errors 350 | If you encounter an error missing from this list, please file an issue or a PR!") 351 | ``` 352 | 353 | 得到如下信息: 354 | 355 | > debug:Using bindgen for Paho C 356 | > debug:clang version: clang version 10.0.0-4ubuntu1 357 | > debug:bindgen include path: -I/mnt/f/wsl/project/iot_gw/target/riscv64gc-unknown-linux-gnu/release/build/paho-mqtt-sys-b3925b784bd5c394/out/include 358 | > ["--target=riscv64gc-unknown-linux-gnu", "-I/mnt/f/wsl/project/iot_gw/target/riscv64gc-unknown-linux-gnu/release/build/paho-mqtt-sys-b3925b784bd5c394/out/include", "-isystem", "/usr/local/include", "-isystem", "/usr/lib/llvm-10/lib/clang/10.0.0/include", "-isystem", "/usr/include/x86_64-linux-gnu", "-isystem", "/usr/include", "wrapper.h"] 359 | 360 | 可以看到,三元组 `riscv64gc-unknown-linux-gnu` 被传给了 `clang`,同时,从 `paho-mqtt-sys-0.5.0/build.rs` 打印的信息可以看到 `clang` 的版本比较老,可能还不支持 `riscv64gc-unknown-linux-gnu` 三元组。最新的 16.0.0 版本是支持的([](https://llvm.org/doxygen/Triple_8h_source.html))。 361 | 362 | 试一下 `clang-13`: 363 | 364 | ``` 365 | sudo apt-get install clang-13 366 | sudo apt-get install libclang-13-dev 367 | ls -l /usr/bin/clang* 368 | lrwxrwxrwx 1 root root 24 Mar 21 2020 /usr/bin/clang -> ../lib/llvm-10/bin/clang 369 | lrwxrwxrwx 1 root root 26 Mar 21 2020 /usr/bin/clang++ -> ../lib/llvm-10/bin/clang++ 370 | lrwxrwxrwx 1 root root 26 Apr 20 2020 /usr/bin/clang++-10 -> ../lib/llvm-10/bin/clang++ 371 | lrwxrwxrwx 1 root root 26 Jul 6 20:01 /usr/bin/clang++-13 -> ../lib/llvm-13/bin/clang++ 372 | lrwxrwxrwx 1 root root 24 Apr 20 2020 /usr/bin/clang-10 -> ../lib/llvm-10/bin/clang 373 | lrwxrwxrwx 1 root root 24 Jul 6 20:01 /usr/bin/clang-13 -> ../lib/llvm-13/bin/clang 374 | lrwxrwxrwx 1 root root 28 Apr 20 2020 /usr/bin/clang-cpp-10 -> ../lib/llvm-10/bin/clang-cpp 375 | lrwxrwxrwx 1 root root 28 Jul 6 20:01 /usr/bin/clang-cpp-13 -> ../lib/llvm-13/bin/clang-cpp 376 | ``` 377 | 378 | 将 `clang` 软链接指向 `clang-13`: 379 | 380 | ``` 381 | sudo rm /usr/bin/clang 382 | sudo ln -s /lib/llvm-13/bin/clang /usr/bin/clang 383 | ``` 384 | 385 | 仍然有问题: 386 | 387 | > debug:clang version: Ubuntu clang version 13.0.1-2ubuntu2~20.04.1 388 | > debug:bindgen include path: -I/mnt/f/wsl/project/iot_gw/target/riscv64gc-unknown-linux-gnu/release/build/paho-mqtt-sys-b3925b784bd5c394/out/include 389 | > ["--target=riscv64gc-unknown-linux-gnu", "-I/mnt/f/wsl/project/iot_gw/target/riscv64gc-unknown-linux-gnu/release/build/paho-mqtt-sys-b3925b784bd5c394/out/include", "-isystem", "/usr/lib/llvm-13/lib/clang/13.0.1/include", "-isystem", "/usr/local/include", "-isystem", "/usr/include/x86_64-linux-gnu", "-isystem", "/usr/include", "wrapper.h"] 390 | 391 | 从 llvm 源码可以看到,支持 riscv64(https://github.com/llvm/llvm-project/blob/release/13.x/llvm/include/llvm/ADT/Triple.h): 392 | 393 | ``` 394 | riscv32, // RISC-V (32-bit): riscv32 395 | riscv64, // RISC-V (64-bit): riscv64 396 | ``` 397 | 398 | 但是好像没有 `riscv64gc-unknown-linux-gnu` 的组合。 399 | 400 | 继续搜索,有人提到,RISC-V 在 `clang` 和 `rustc` 上的三元组不同:https://github.com/rust-lang/rust-bindgen/issues/2136,并且该问题已经得到了解决,但是查看代码时,发现相关提交是 `rust-bindgen` 0.52.0 之后合入的。 401 | 402 | 修改 `paho-mqtt-sys-0.5.0/Cargo.toml`,将 `bindgen` 的版本修改为 0.60,但还是不行,不知道什么原因,从报错信息中看,调用的仍然是 0.52.0 版。 403 | 404 | 阅读 `paho-mqtt-sys-0.5.0/build.rs` 源码可知,可以禁用 `build_bindgen` 属性,不要在编译过程中调用 `bindgen` 生成绑定。而是使用 `bindgen` 0.61.0 版手动生成绑定: 405 | 406 | ```bash 407 | # 删除老版本的 bindgen 408 | cargo uninstall bindgen 409 | 410 | # 0.61.0 版本,bindgen 的 crate 改名了 411 | cargo install bindgen-cli 412 | 413 | cd ~/.cargo/registry/src/mirrors.tuna.tsinghua.edu.cn-df7c3c540f42cdbd/paho-mqtt-sys-0.5.0 414 | 415 | # 但是运行的时候仍然是 bindgen 416 | RUST_BACKTRACE=full TARGET=riscv64gc-unknown-linux-gnu bindgen wrapper.h -o bindings/bindings_paho_mqtt_c_1.3.8-riscv64gc-unknown-linux-gnu.rs -- -Ipaho.mqtt.c/src --verbose 417 | ``` 418 | 419 | 有如下报错: 420 | 421 | > End of search list. 422 | > thread 'main' panicked at 'assertion failed: `(left == right)` 423 | > left: `4`, 424 | > right: `8`: Target platform requires `--no-size_t-is-usize`. The size of `ssize_t` (4) does not match the target pointer size (8)', /home/dell/.cargo/registry/src/mirrors.tuna.tsinghua.edu.cn-df7c3c540f42cdbd/bindgen-0.61.0/codegen/mod.rs:851:25 425 | 426 | 带上 `--no-size_t-is-usize` 选项: 427 | 428 | ``` 429 | RUST_BACKTRACE=full TARGET=riscv64gc-unknown-linux-gnu bindgen --no-size_t-is-usize wrapper.h -o bindings/bindings_paho_mqtt_c_1.3.8-riscv64gc-unknown-linux-gnu.rs -- -Ipaho.mqtt.c/src --verbose 430 | ``` 431 | 432 | 可以生成绑定 `paho-mqtt-sys-0.5.0/bindings/bindings_paho_mqtt_c_1.3.8-riscv64gc-unknown-linux-gnu.rs`: 433 | 434 | 然后在 `iot_gw/Cargo.toml` 中,禁用 `paho-mqtt-sys/build_bindgen` 特性: 435 | 436 | ```toml 437 | [features] 438 | default = [] 439 | build_bindgen = ["paho-mqtt-sys/build_bindgen"] 440 | ssl = [] 441 | ``` 442 | 443 | 进入 `tools` 目录,运行 `build_f133.sh` 即可。 444 | 445 | ### 6. 网关运行问题解答 446 | 447 | #### (1) 数据接口类型未知 448 | 449 | > thread 'main' panicked at 'Init data interface failed: DataIfUnknownType' 450 | 451 | Cargo.toml 中有关数据接口的特性和网关配置文件内的不一致。 452 | 453 | #### (2) 连接错误 454 | 455 | > Error connecting to the broker: NULL Parameter: NULL Parameter 456 | 457 | 使用 ssl 连接 broker,但是没有在 `Cargo.toml` 中启用 `ssl` 特性。 458 | 459 | -------------------------------------------------------------------------------- /arduino/UNO/min/min.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2017 JK Energy Ltd. 2 | // 3 | // Use authorized under the MIT license. 4 | 5 | #include "min.h" 6 | 7 | #define TRANSPORT_FIFO_SIZE_FRAMES_MASK ((uint8_t)((1U << TRANSPORT_FIFO_SIZE_FRAMES_BITS) - 1U)) 8 | #define TRANSPORT_FIFO_SIZE_FRAME_DATA_MASK ((uint16_t)((1U << TRANSPORT_FIFO_SIZE_FRAME_DATA_BITS) - 1U)) 9 | 10 | // Number of bytes needed for a frame with a given payload length, excluding stuff bytes 11 | // 3 header bytes, ID/control byte, length byte, seq byte, 4 byte CRC, EOF byte 12 | #define ON_WIRE_SIZE(p) ((p) + 11U) 13 | 14 | // Special protocol bytes 15 | enum { 16 | HEADER_BYTE = 0xaaU, 17 | STUFF_BYTE = 0x55U, 18 | EOF_BYTE = 0x55U, 19 | }; 20 | 21 | // Receiving state machine 22 | enum { 23 | SEARCHING_FOR_SOF, 24 | RECEIVING_ID_CONTROL, 25 | RECEIVING_SEQ, 26 | RECEIVING_LENGTH, 27 | RECEIVING_PAYLOAD, 28 | RECEIVING_CHECKSUM_3, 29 | RECEIVING_CHECKSUM_2, 30 | RECEIVING_CHECKSUM_1, 31 | RECEIVING_CHECKSUM_0, 32 | RECEIVING_EOF, 33 | }; 34 | 35 | #ifdef TRANSPORT_PROTOCOL 36 | 37 | #ifndef TRANSPORT_ACK_RETRANSMIT_TIMEOUT_MS 38 | #define TRANSPORT_ACK_RETRANSMIT_TIMEOUT_MS (25U) 39 | #endif 40 | #ifndef TRANSPORT_FRAME_RETRANSMIT_TIMEOUT_MS 41 | #define TRANSPORT_FRAME_RETRANSMIT_TIMEOUT_MS (50U) // Should be long enough for a whole window to be transmitted plus an ACK / NACK to get back 42 | #endif 43 | #ifndef TRANSPORT_MAX_WINDOW_SIZE 44 | #define TRANSPORT_MAX_WINDOW_SIZE (16U) 45 | #endif 46 | #ifndef TRANSPORT_IDLE_TIMEOUT_MS 47 | #define TRANSPORT_IDLE_TIMEOUT_MS (1000U) 48 | #endif 49 | 50 | enum { 51 | // Top bit must be set: these are for the transport protocol to use 52 | // 0x7f and 0x7e are reserved MIN identifiers. 53 | ACK = 0xffU, 54 | RESET = 0xfeU, 55 | }; 56 | 57 | // Where the payload data of the frame FIFO is stored 58 | uint8_t payloads_ring_buffer[TRANSPORT_FIFO_MAX_FRAME_DATA]; 59 | 60 | static uint32_t now; 61 | static void send_reset(struct min_context *self); 62 | #endif 63 | 64 | static void crc32_init_context(struct crc32_context *context) 65 | { 66 | context->crc = 0xffffffffU; 67 | } 68 | 69 | static void crc32_step(struct crc32_context *context, uint8_t byte) 70 | { 71 | context->crc ^= byte; 72 | for(uint32_t j = 0; j < 8; j++) { 73 | uint32_t mask = (uint32_t) -(context->crc & 1U); 74 | context->crc = (context->crc >> 1) ^ (0xedb88320U & mask); 75 | } 76 | } 77 | 78 | static uint32_t crc32_finalize(struct crc32_context *context) 79 | { 80 | return ~context->crc; 81 | } 82 | 83 | 84 | static void stuffed_tx_byte(struct min_context *self, uint8_t byte, bool crc) 85 | { 86 | // Transmit the byte 87 | min_tx_byte(self->port, byte); 88 | if(crc) { 89 | crc32_step(&self->tx_checksum, byte); 90 | } 91 | 92 | // See if an additional stuff byte is needed 93 | if(byte == HEADER_BYTE) { 94 | if(--self->tx_header_byte_countdown == 0) { 95 | min_tx_byte(self->port, STUFF_BYTE); // Stuff byte 96 | self->tx_header_byte_countdown = 2U; 97 | } 98 | } 99 | else { 100 | self->tx_header_byte_countdown = 2U; 101 | } 102 | } 103 | 104 | static void on_wire_bytes(struct min_context *self, uint8_t id_control, uint8_t seq, uint8_t const *payload_base, uint16_t payload_offset, uint16_t payload_mask, uint8_t payload_len) 105 | { 106 | uint8_t n, i; 107 | uint32_t checksum; 108 | 109 | self->tx_header_byte_countdown = 2U; 110 | crc32_init_context(&self->tx_checksum); 111 | 112 | min_tx_start(self->port); 113 | 114 | // Header is 3 bytes; because unstuffed will reset receiver immediately 115 | min_tx_byte(self->port, HEADER_BYTE); 116 | min_tx_byte(self->port, HEADER_BYTE); 117 | min_tx_byte(self->port, HEADER_BYTE); 118 | 119 | stuffed_tx_byte(self, id_control, true); 120 | if(id_control & 0x80U) { 121 | // Send the sequence number if it is a transport frame 122 | stuffed_tx_byte(self, seq, true); 123 | } 124 | 125 | stuffed_tx_byte(self, payload_len, true); 126 | 127 | for(i = 0, n = payload_len; n > 0; n--, i++) { 128 | stuffed_tx_byte(self, payload_base[payload_offset], true); 129 | payload_offset++; 130 | payload_offset &= payload_mask; 131 | } 132 | 133 | checksum = crc32_finalize(&self->tx_checksum); 134 | 135 | // Network order is big-endian. A decent C compiler will spot that this 136 | // is extracting bytes and will use efficient instructions. 137 | stuffed_tx_byte(self, (uint8_t)((checksum >> 24) & 0xffU), false); 138 | stuffed_tx_byte(self, (uint8_t)((checksum >> 16) & 0xffU), false); 139 | stuffed_tx_byte(self, (uint8_t)((checksum >> 8) & 0xffU), false); 140 | stuffed_tx_byte(self, (uint8_t)((checksum >> 0) & 0xffU), false); 141 | 142 | // Ensure end-of-frame doesn't contain 0xaa and confuse search for start-of-frame 143 | min_tx_byte(self->port, EOF_BYTE); 144 | 145 | min_tx_finished(self->port); 146 | } 147 | 148 | #ifdef TRANSPORT_PROTOCOL 149 | 150 | // Pops frame from front of queue, reclaims its ring buffer space 151 | static void transport_fifo_pop(struct min_context *self) 152 | { 153 | #ifdef ASSERTION_CHECKING 154 | assert(self->transport_fifo.n_frames != 0); 155 | #endif 156 | struct transport_frame *frame = &self->transport_fifo.frames[self->transport_fifo.head_idx]; 157 | min_debug_print("Popping frame id=%d seq=%d\n", frame->min_id, frame->seq); 158 | 159 | #ifdef ASSERTION_CHECKING 160 | assert(self->transport_fifo.n_ring_buffer_bytes >= frame->payload_len); 161 | #endif 162 | 163 | self->transport_fifo.n_frames--; 164 | self->transport_fifo.head_idx++; 165 | self->transport_fifo.head_idx &= TRANSPORT_FIFO_SIZE_FRAMES_MASK; 166 | self->transport_fifo.n_ring_buffer_bytes -= frame->payload_len; 167 | } 168 | 169 | // Claim a buffer slot from the FIFO. Returns 0 if there is no space. 170 | static struct transport_frame *transport_fifo_push(struct min_context *self, uint16_t data_size) 171 | { 172 | // A frame is only queued if there aren't too many frames in the FIFO and there is space in the 173 | // data ring buffer. 174 | struct transport_frame *ret = 0; 175 | if (self->transport_fifo.n_frames < TRANSPORT_FIFO_MAX_FRAMES) { 176 | // Is there space in the ring buffer for the frame payload? 177 | if(self->transport_fifo.n_ring_buffer_bytes <= TRANSPORT_FIFO_MAX_FRAME_DATA - data_size) { 178 | self->transport_fifo.n_frames++; 179 | if (self->transport_fifo.n_frames > self->transport_fifo.n_frames_max) { 180 | // High-water mark of FIFO (for diagnostic purposes) 181 | self->transport_fifo.n_frames_max = self->transport_fifo.n_frames; 182 | } 183 | // Create FIFO entry 184 | ret = &(self->transport_fifo.frames[self->transport_fifo.tail_idx]); 185 | ret->payload_offset = self->transport_fifo.ring_buffer_tail_offset; 186 | 187 | // Claim ring buffer space 188 | self->transport_fifo.n_ring_buffer_bytes += data_size; 189 | if(self->transport_fifo.n_ring_buffer_bytes > self->transport_fifo.n_ring_buffer_bytes_max) { 190 | // High-water mark of ring buffer usage (for diagnostic purposes) 191 | self->transport_fifo.n_ring_buffer_bytes_max = self->transport_fifo.n_ring_buffer_bytes; 192 | } 193 | self->transport_fifo.ring_buffer_tail_offset += data_size; 194 | self->transport_fifo.ring_buffer_tail_offset &= TRANSPORT_FIFO_SIZE_FRAME_DATA_MASK; 195 | 196 | // Claim FIFO space 197 | self->transport_fifo.tail_idx++; 198 | self->transport_fifo.tail_idx &= TRANSPORT_FIFO_SIZE_FRAMES_MASK; 199 | } 200 | else { 201 | min_debug_print("No FIFO payload space: data_size=%d, n_ring_buffer_bytes=%d\n", data_size, self->transport_fifo.n_ring_buffer_bytes); 202 | } 203 | } 204 | else { 205 | min_debug_print("No FIFO frame slots\n"); 206 | } 207 | return ret; 208 | } 209 | 210 | // Return the nth frame in the FIFO 211 | static struct transport_frame *transport_fifo_get(struct min_context *self, uint8_t n) 212 | { 213 | uint8_t idx = self->transport_fifo.head_idx; 214 | return &self->transport_fifo.frames[(idx + n) & TRANSPORT_FIFO_SIZE_FRAMES_MASK]; 215 | } 216 | 217 | // Sends the given frame to the serial line 218 | static void transport_fifo_send(struct min_context *self, struct transport_frame *frame) 219 | { 220 | min_debug_print("transport_fifo_send: min_id=%d, seq=%d, payload_len=%d\n", frame->min_id, frame->seq, frame->payload_len); 221 | on_wire_bytes(self, frame->min_id | (uint8_t)0x80U, frame->seq, payloads_ring_buffer, frame->payload_offset, TRANSPORT_FIFO_SIZE_FRAME_DATA_MASK, frame->payload_len); 222 | frame->last_sent_time_ms = now; 223 | } 224 | 225 | // We don't queue an ACK frame - we send it straight away (if there's space to do so) 226 | static void send_ack(struct min_context *self) 227 | { 228 | // In the embedded end we don't reassemble out-of-order frames and so never ask for retransmits. Payload is 229 | // always the same as the sequence number. 230 | min_debug_print("send ACK: seq=%d\n", self->transport_fifo.rn); 231 | if(ON_WIRE_SIZE(0) <= min_tx_space(self->port)) { 232 | on_wire_bytes(self, ACK, self->transport_fifo.rn, &self->transport_fifo.rn, 0, 0xffU, 1U); 233 | self->transport_fifo.last_sent_ack_time_ms = now; 234 | } 235 | } 236 | 237 | // We don't queue an RESET frame - we send it straight away (if there's space to do so) 238 | static void send_reset(struct min_context *self) 239 | { 240 | min_debug_print("send RESET\n"); 241 | if(ON_WIRE_SIZE(0) <= min_tx_space(self->port)) { 242 | on_wire_bytes(self, RESET, 0, 0, 0, 0, 0); 243 | } 244 | } 245 | 246 | static void transport_fifo_reset(struct min_context *self) 247 | { 248 | // Clear down the transmission FIFO queue 249 | self->transport_fifo.n_frames = 0; 250 | self->transport_fifo.head_idx = 0; 251 | self->transport_fifo.tail_idx = 0; 252 | self->transport_fifo.n_ring_buffer_bytes = 0; 253 | self->transport_fifo.ring_buffer_tail_offset = 0; 254 | self->transport_fifo.sn_max = 0; 255 | self->transport_fifo.sn_min = 0; 256 | self->transport_fifo.rn = 0; 257 | 258 | // Reset the timers 259 | self->transport_fifo.last_received_anything_ms = now; 260 | self->transport_fifo.last_sent_ack_time_ms = now; 261 | self->transport_fifo.last_received_frame_ms = 0; 262 | } 263 | 264 | void min_transport_reset(struct min_context *self, bool inform_other_side) 265 | { 266 | min_debug_print("Resetting %s other side\n", inform_other_side ? "and informing" : "without informing"); 267 | if (inform_other_side) { 268 | // Tell the other end we have gone away 269 | send_reset(self); 270 | } 271 | 272 | // Throw our frames away 273 | transport_fifo_reset(self); 274 | } 275 | 276 | // Queues a MIN ID / payload frame into the outgoing FIFO 277 | // API call. 278 | // Returns true if the frame was queued OK. 279 | bool min_queue_frame(struct min_context *self, uint8_t min_id, uint8_t const *payload, uint8_t payload_len) 280 | { 281 | struct transport_frame *frame = transport_fifo_push(self, payload_len); // Claim a FIFO slot, reserve space for payload 282 | 283 | // We are just queueing here: the poll() function puts the frame into the window and on to the wire 284 | if(frame != 0) { 285 | // Copy frame details into frame slot, copy payload into ring buffer 286 | frame->min_id = min_id & (uint8_t)0x3fU; 287 | frame->payload_len = payload_len; 288 | 289 | uint16_t payload_offset = frame->payload_offset; 290 | for(uint32_t i = 0; i < payload_len; i++) { 291 | payloads_ring_buffer[payload_offset] = payload[i]; 292 | payload_offset++; 293 | payload_offset &= TRANSPORT_FIFO_SIZE_FRAME_DATA_MASK; 294 | } 295 | min_debug_print("Queued ID=%d, len=%d\n", min_id, payload_len); 296 | return true; 297 | } 298 | else { 299 | self->transport_fifo.dropped_frames++; 300 | return false; 301 | } 302 | } 303 | 304 | bool min_queue_has_space_for_frame(struct min_context *self, uint8_t payload_len) { 305 | return self->transport_fifo.n_frames < TRANSPORT_FIFO_MAX_FRAMES && 306 | self->transport_fifo.n_ring_buffer_bytes <= TRANSPORT_FIFO_MAX_FRAME_DATA - payload_len; 307 | } 308 | 309 | // Finds the frame in the window that was sent least recently 310 | static struct transport_frame *find_retransmit_frame(struct min_context *self) 311 | { 312 | uint8_t window_size = self->transport_fifo.sn_max - self->transport_fifo.sn_min; 313 | 314 | #ifdef ASSERTION_CHECKING 315 | assert(window_size > 0); 316 | assert(window_size <= self->transport_fifo.n_frames); 317 | #endif 318 | 319 | // Start with the head of the queue and call this the oldest 320 | struct transport_frame *oldest_frame = &self->transport_fifo.frames[self->transport_fifo.head_idx]; 321 | uint32_t oldest_elapsed_time = now - oldest_frame->last_sent_time_ms; 322 | 323 | uint8_t idx = self->transport_fifo.head_idx; 324 | for(uint8_t i = 0; i < window_size; i++) { 325 | uint32_t elapsed = now - self->transport_fifo.frames[idx].last_sent_time_ms; 326 | if(elapsed > oldest_elapsed_time) { // Strictly older only; otherwise the earlier frame is deemed the older 327 | oldest_elapsed_time = elapsed; 328 | oldest_frame = &self->transport_fifo.frames[idx]; 329 | } 330 | idx++; 331 | idx &= TRANSPORT_FIFO_SIZE_FRAMES_MASK; 332 | } 333 | 334 | return oldest_frame; 335 | } 336 | #endif // TRANSPORT_PROTOCOL 337 | 338 | // This runs the receiving half of the transport protocol, acknowledging frames received, discarding 339 | // duplicates received, and handling RESET requests. 340 | static void valid_frame_received(struct min_context *self) 341 | { 342 | uint8_t id_control = self->rx_frame_id_control; 343 | uint8_t *payload = self->rx_frame_payload_buf; 344 | uint8_t payload_len = self->rx_control; 345 | 346 | #ifdef TRANSPORT_PROTOCOL 347 | uint8_t seq = self->rx_frame_seq; 348 | uint8_t num_acked; 349 | uint8_t num_nacked; 350 | uint8_t num_in_window; 351 | 352 | // When we receive anything we know the other end is still active and won't shut down 353 | self->transport_fifo.last_received_anything_ms = now; 354 | 355 | switch(id_control) { 356 | case ACK: 357 | // If we get an ACK then we remove all the acknowledged frames with seq < rn 358 | // The payload byte specifies the number of NACKed frames: how many we want retransmitted because 359 | // they have gone missing. 360 | // But we need to make sure we don't accidentally ACK too many because of a stale ACK from an old session 361 | num_acked = seq - self->transport_fifo.sn_min; 362 | num_nacked = payload[0] - seq; 363 | num_in_window = self->transport_fifo.sn_max - self->transport_fifo.sn_min; 364 | 365 | if(num_acked <= num_in_window) { 366 | self->transport_fifo.sn_min = seq; 367 | #ifdef ASSERTION_CHECKING 368 | assert(self->transport_fifo.n_frames >= num_in_window); 369 | assert(num_in_window <= TRANSPORT_MAX_WINDOW_SIZE); 370 | assert(num_nacked <= TRANSPORT_MAX_WINDOW_SIZE); 371 | #endif 372 | // Now pop off all the frames up to (but not including) rn 373 | // The ACK contains Rn; all frames before Rn are ACKed and can be removed from the window 374 | min_debug_print("Received ACK seq=%d, num_acked=%d, num_nacked=%d\n", seq, num_acked, num_nacked); 375 | for(uint8_t i = 0; i < num_acked; i++) { 376 | transport_fifo_pop(self); 377 | } 378 | uint8_t idx = self->transport_fifo.head_idx; 379 | // Now retransmit the number of frames that were requested 380 | for(uint8_t i = 0; i < num_nacked; i++) { 381 | struct transport_frame *retransmit_frame = &self->transport_fifo.frames[idx]; 382 | transport_fifo_send(self, retransmit_frame); 383 | idx++; 384 | idx &= TRANSPORT_FIFO_SIZE_FRAMES_MASK; 385 | } 386 | } 387 | else { 388 | min_debug_print("Received spurious ACK seq=%d\n", seq); 389 | self->transport_fifo.spurious_acks++; 390 | } 391 | break; 392 | case RESET: 393 | // If we get a RESET demand then we reset the transport protocol (empty the FIFO, reset the 394 | // sequence numbers, etc.) 395 | // We don't send anything, we just do it. The other end can send frames to see if this end is 396 | // alive (pings, etc.) or just wait to get application frames. 397 | min_debug_print("Received reset\n"); 398 | self->transport_fifo.resets_received++; 399 | transport_fifo_reset(self); 400 | break; 401 | default: 402 | if (id_control & 0x80U) { 403 | // Incoming application frames 404 | 405 | // Reset the activity time (an idle connection will be stalled) 406 | self->transport_fifo.last_received_frame_ms = now; 407 | 408 | if (seq == self->transport_fifo.rn) { 409 | // Accept this frame as matching the sequence number we were looking for 410 | 411 | // Now looking for the next one in the sequence 412 | self->transport_fifo.rn++; 413 | 414 | // Always send an ACK back for the frame we received 415 | // ACKs are short (should be about 9 microseconds to send on the wire) and 416 | // this will cut the latency down. 417 | // We also periodically send an ACK in case the ACK was lost, and in any case 418 | // frames are re-sent. 419 | send_ack(self); 420 | 421 | // Now ready to pass this up to the application handlers 422 | 423 | // Pass frame up to application handler to deal with 424 | min_debug_print("Incoming app transport frame seq=%d, id=%d, payload len=%d\n", seq, id_control & (uint8_t)0x3fU, payload_len); 425 | min_application_handler(id_control & (uint8_t)0x3fU, payload, payload_len, self->port); 426 | } else { 427 | // Discard this frame because we aren't looking for it: it's either a dupe because it was 428 | // retransmitted when our ACK didn't get through in time, or else it's further on in the 429 | // sequence and others got dropped. 430 | self->transport_fifo.sequence_mismatch_drop++; 431 | min_debug_print("Received mismatched frame seq=%d, looking for seq=%d\n", seq, self->transport_fifo.rn); 432 | } 433 | } 434 | else { 435 | // Not a transport frame 436 | min_debug_print("Incoming app frame id=%d, payload len=%d\n", id_control & (uint8_t)0x3fU, payload_len); 437 | min_application_handler(id_control & (uint8_t)0x3fU, payload, payload_len, self->port); 438 | } 439 | break; 440 | } 441 | #else // TRANSPORT_PROTOCOL 442 | min_application_handler(id_control & (uint8_t)0x3fU, payload, payload_len, self->port); 443 | #endif // TRANSPORT_PROTOCOL 444 | } 445 | 446 | static void rx_byte(struct min_context *self, uint8_t byte) 447 | { 448 | // Regardless of state, three header bytes means "start of frame" and 449 | // should reset the frame buffer and be ready to receive frame data 450 | // 451 | // Two in a row in over the frame means to expect a stuff byte. 452 | uint32_t crc; 453 | 454 | if(self->rx_header_bytes_seen == 2) { 455 | self->rx_header_bytes_seen = 0; 456 | if(byte == HEADER_BYTE) { 457 | self->rx_frame_state = RECEIVING_ID_CONTROL; 458 | return; 459 | } 460 | if(byte == STUFF_BYTE) { 461 | /* Discard this byte; carry on receiving on the next character */ 462 | return; 463 | } 464 | else { 465 | /* Something has gone wrong, give up on this frame and look for header again */ 466 | self->rx_frame_state = SEARCHING_FOR_SOF; 467 | return; 468 | } 469 | } 470 | 471 | if(byte == HEADER_BYTE) { 472 | self->rx_header_bytes_seen++; 473 | } 474 | else { 475 | self->rx_header_bytes_seen = 0; 476 | } 477 | 478 | switch(self->rx_frame_state) { 479 | case SEARCHING_FOR_SOF: 480 | break; 481 | case RECEIVING_ID_CONTROL: 482 | self->rx_frame_id_control = byte; 483 | self->rx_frame_payload_bytes = 0; 484 | crc32_init_context(&self->rx_checksum); 485 | crc32_step(&self->rx_checksum, byte); 486 | if(byte & 0x80U) { 487 | #ifdef TRANSPORT_PROTOCOL 488 | self->rx_frame_state = RECEIVING_SEQ; 489 | #else 490 | // If there is no transport support compiled in then all transport frames are ignored 491 | self->rx_frame_state = SEARCHING_FOR_SOF; 492 | #endif // TRANSPORT_PROTOCOL 493 | } 494 | else { 495 | self->rx_frame_seq = 0; 496 | self->rx_frame_state = RECEIVING_LENGTH; 497 | } 498 | break; 499 | case RECEIVING_SEQ: 500 | self->rx_frame_seq = byte; 501 | crc32_step(&self->rx_checksum, byte); 502 | self->rx_frame_state = RECEIVING_LENGTH; 503 | break; 504 | case RECEIVING_LENGTH: 505 | self->rx_frame_length = byte; 506 | self->rx_control = byte; 507 | crc32_step(&self->rx_checksum, byte); 508 | if(self->rx_frame_length > 0) { 509 | // Can reduce the RAM size by compiling limits to frame sizes 510 | if(self->rx_frame_length <= MAX_PAYLOAD) { 511 | self->rx_frame_state = RECEIVING_PAYLOAD; 512 | } 513 | else { 514 | // Frame dropped because it's longer than any frame we can buffer 515 | min_debug_print("Dropping frame because length %d > MAX_PAYLOAD %d", self->rx_frame_length, MAX_PAYLOAD); 516 | self->rx_frame_state = SEARCHING_FOR_SOF; 517 | } 518 | } 519 | else { 520 | self->rx_frame_state = RECEIVING_CHECKSUM_3; 521 | } 522 | break; 523 | case RECEIVING_PAYLOAD: 524 | self->rx_frame_payload_buf[self->rx_frame_payload_bytes++] = byte; 525 | crc32_step(&self->rx_checksum, byte); 526 | if(--self->rx_frame_length == 0) { 527 | self->rx_frame_state = RECEIVING_CHECKSUM_3; 528 | } 529 | break; 530 | case RECEIVING_CHECKSUM_3: 531 | self->rx_frame_checksum = ((uint32_t)byte) << 24; 532 | self->rx_frame_state = RECEIVING_CHECKSUM_2; 533 | break; 534 | case RECEIVING_CHECKSUM_2: 535 | self->rx_frame_checksum |= ((uint32_t)byte) << 16; 536 | self->rx_frame_state = RECEIVING_CHECKSUM_1; 537 | break; 538 | case RECEIVING_CHECKSUM_1: 539 | self->rx_frame_checksum |= ((uint32_t)byte) << 8; 540 | self->rx_frame_state = RECEIVING_CHECKSUM_0; 541 | break; 542 | case RECEIVING_CHECKSUM_0: 543 | self->rx_frame_checksum |= byte; 544 | crc = crc32_finalize(&self->rx_checksum); 545 | if(self->rx_frame_checksum != crc) { 546 | min_debug_print("Checksum failed, received 0x%08X, computed 0x%08X", self->rx_frame_checksum, crc); 547 | // Frame fails the checksum and so is dropped 548 | self->rx_frame_state = SEARCHING_FOR_SOF; 549 | } 550 | else { 551 | // Checksum passes, go on to check for the end-of-frame marker 552 | self->rx_frame_state = RECEIVING_EOF; 553 | } 554 | break; 555 | case RECEIVING_EOF: 556 | if(byte == 0x55u) { 557 | // Frame received OK, pass up data to handler 558 | valid_frame_received(self); 559 | } else { 560 | // else discard 561 | min_debug_print("Received invalid EOF 0x%02X", byte); 562 | } 563 | // Look for next frame */ 564 | self->rx_frame_state = SEARCHING_FOR_SOF; 565 | break; 566 | default: 567 | // Should never get here but in case we do then reset to a safe state 568 | min_debug_print("Received byte 0x%02X in invalid state %d", byte, self->rx_frame_state); 569 | self->rx_frame_state = SEARCHING_FOR_SOF; 570 | break; 571 | } 572 | } 573 | 574 | // API call: sends received bytes into a MIN context and runs the transport timeouts 575 | void min_poll(struct min_context *self, uint8_t const *buf, uint32_t buf_len) 576 | { 577 | for(uint32_t i = 0; i < buf_len; i++) { 578 | rx_byte(self, buf[i]); 579 | } 580 | 581 | #ifdef TRANSPORT_PROTOCOL 582 | uint8_t window_size; 583 | 584 | now = min_time_ms(); 585 | 586 | bool remote_connected = (now - self->transport_fifo.last_received_anything_ms < TRANSPORT_IDLE_TIMEOUT_MS); 587 | bool remote_active = (now - self->transport_fifo.last_received_frame_ms < TRANSPORT_IDLE_TIMEOUT_MS); 588 | 589 | // This sends one new frame or resends one old frame 590 | window_size = self->transport_fifo.sn_max - self->transport_fifo.sn_min; // Window size 591 | if((window_size < TRANSPORT_MAX_WINDOW_SIZE) && (self->transport_fifo.n_frames > window_size)) { 592 | // There are new frames we can send; but don't even bother if there's no buffer space for them 593 | struct transport_frame *frame = transport_fifo_get(self, window_size); 594 | if(ON_WIRE_SIZE(frame->payload_len) <= min_tx_space(self->port)) { 595 | frame->seq = self->transport_fifo.sn_max; 596 | transport_fifo_send(self, frame); 597 | 598 | // Move window on 599 | self->transport_fifo.sn_max++; 600 | } 601 | } 602 | else { 603 | // Sender cannot send new frames so resend old ones (if there's anyone there) 604 | if((window_size > 0) && remote_connected) { 605 | // There are unacknowledged frames. Can re-send an old frame. Pick the least recently sent one. 606 | struct transport_frame *oldest_frame = find_retransmit_frame(self); 607 | if(now - oldest_frame->last_sent_time_ms >= TRANSPORT_FRAME_RETRANSMIT_TIMEOUT_MS) { 608 | // Resending oldest frame if there's a chance there's enough space to send it 609 | if(ON_WIRE_SIZE(oldest_frame->payload_len) <= min_tx_space(self->port)) { 610 | transport_fifo_send(self, oldest_frame); 611 | } 612 | } 613 | } 614 | } 615 | 616 | #ifndef DISABLE_TRANSPORT_ACK_RETRANSMIT 617 | // Periodically transmit the ACK with the rn value, unless the line has gone idle 618 | if(now - self->transport_fifo.last_sent_ack_time_ms > TRANSPORT_ACK_RETRANSMIT_TIMEOUT_MS) { 619 | if(remote_active) { 620 | send_ack(self); 621 | } 622 | } 623 | #endif // DISABLE_TRANSPORT_ACK_RETRANSMIT 624 | #endif // TRANSPORT_PROTOCOL 625 | } 626 | 627 | #ifdef VALIDATE_MAX_PAYLOAD 628 | void min_init_context_validate(struct min_context *self, uint8_t port, void * p_rx_frame_checksum) 629 | #else 630 | void min_init_context(struct min_context *self, uint8_t port) 631 | #endif 632 | { 633 | #ifdef ASSERTION_CHECKING 634 | assert(self != 0); 635 | #ifdef VALIDATE_MAX_PAYLOAD 636 | // check the provided buffer is large enough. This could be false if MIN_PAYLOAD is defined differently when 637 | // compiling calling code and this code. 638 | assert((void *)(self->rx_frame_payload_buf + MAX_PAYLOAD) <= p_rx_frame_checksum); 639 | #endif 640 | #endif 641 | // Initialize context 642 | self->rx_header_bytes_seen = 0; 643 | self->rx_frame_state = SEARCHING_FOR_SOF; 644 | self->port = port; 645 | 646 | #ifdef TRANSPORT_PROTOCOL 647 | // Counters for diagnosis purposes 648 | self->transport_fifo.spurious_acks = 0; 649 | self->transport_fifo.sequence_mismatch_drop = 0; 650 | self->transport_fifo.dropped_frames = 0; 651 | self->transport_fifo.resets_received = 0; 652 | self->transport_fifo.n_ring_buffer_bytes_max = 0; 653 | self->transport_fifo.n_frames_max = 0; 654 | transport_fifo_reset(self); 655 | #endif // TRANSPORT_PROTOCOL 656 | min_debug_print("MIN init complete\n"); 657 | } 658 | 659 | // Sends an application MIN frame on the wire (do not put into the transport queue) 660 | void min_send_frame(struct min_context *self, uint8_t min_id, uint8_t const *payload, uint8_t payload_len) 661 | { 662 | if((ON_WIRE_SIZE(payload_len) <= min_tx_space(self->port))) { 663 | on_wire_bytes(self, min_id & (uint8_t) 0x3fU, 0, payload, 0, 0xffffU, payload_len); 664 | } 665 | } 666 | -------------------------------------------------------------------------------- /arduino/UNO/min/min.h: -------------------------------------------------------------------------------- 1 | // MIN Protocol v2.0. 2 | // 3 | // MIN is a lightweight reliable protocol for exchanging information from a microcontroller (MCU) to a host. 4 | // It is designed to run on an 8-bit MCU but also scale up to more powerful devices. A typical use case is to 5 | // send data from a UART on a small MCU over a UART-USB converter plugged into a PC host. A Python implementation 6 | // of host code is provided (or this code could be compiled for a PC). 7 | // 8 | // MIN supports frames of 0-255 bytes (with a lower limit selectable at compile time to reduce RAM). MIN frames 9 | // have identifier values between 0 and 63. 10 | // 11 | // An optional transport layer T-MIN can be compiled in. This provides sliding window reliable transmission of frames. 12 | // 13 | // Compile options: 14 | // 15 | // - Define NO_TRANSPORT_PROTOCOL to remove the code and other overheads of dealing with transport frames. Any 16 | // transport frames sent from the other side are dropped. 17 | // 18 | // - Define MAX_PAYLOAD if the size of the frames is to be limited. This is particularly useful with the transport 19 | // protocol where a deep FIFO is wanted but not for large frames. 20 | // 21 | // The API is as follows: 22 | // 23 | // - min_init_context() 24 | // A MIN context is a structure allocated by the programmer that stores details of the protocol. This permits 25 | // the code to be reentrant and multiple serial ports to be used. The port parameter is used in a callback to 26 | // allow the programmer's serial port drivers to place bytes in the right port. In a typical scenario there will 27 | // be just one context. 28 | // 29 | // - min_send_frame() 30 | // This sends a non-transport frame and will be dropped if the line is noisy. 31 | // 32 | // - min_queue_frame() 33 | // This queues a transport frame which will will be retransmitted until the other side receives it correctly. 34 | // 35 | // - min_poll() 36 | // This passes in received bytes to the context associated with the source. Note that if the transport protocol 37 | // is included then this must be called regularly to operate the transport state machine even if there are no 38 | // incoming bytes. 39 | // 40 | // There are several callbacks: these must be provided by the programmer and are called by the library: 41 | // 42 | // - min_tx_space() 43 | // The programmer's serial drivers must return the number of bytes of space available in the sending buffer. 44 | // This helps cut down on the number of lost frames (and hence improve throughput) if a doomed attempt to transmit a 45 | // frame can be avoided. 46 | // 47 | // - min_tx_byte() 48 | // The programmer's drivers must send a byte on the given port. The implementation of the serial port drivers 49 | // is in the domain of the programmer: they might be interrupt-based, polled, etc. 50 | // 51 | // - min_application_handler() 52 | // This is the callback that provides a MIN frame received on a given port to the application. The programmer 53 | // should then deal with the frame as part of the application. 54 | // 55 | // - min_time_ms() 56 | // This is called to obtain current time in milliseconds. This is used by the MIN transport protocol to drive 57 | // timeouts and retransmits. 58 | 59 | 60 | #ifndef MIN_H 61 | #define MIN_H 62 | 63 | #include 64 | #include 65 | 66 | #ifdef ASSERTION_CHECKING 67 | #include 68 | #endif 69 | 70 | #ifndef NO_TRANSPORT_PROTOCOL 71 | #define TRANSPORT_PROTOCOL 72 | #endif 73 | 74 | #ifndef MAX_PAYLOAD 75 | #define MAX_PAYLOAD (255U) 76 | #endif 77 | 78 | // Powers of two for FIFO management. Default is 16 frames in the FIFO, total of 1024 bytes for frame data 79 | #ifndef TRANSPORT_FIFO_SIZE_FRAMES_BITS 80 | #define TRANSPORT_FIFO_SIZE_FRAMES_BITS (4U) 81 | #endif 82 | #ifndef TRANSPORT_FIFO_SIZE_FRAME_DATA_BITS 83 | #define TRANSPORT_FIFO_SIZE_FRAME_DATA_BITS (10U) 84 | #endif 85 | 86 | #define TRANSPORT_FIFO_MAX_FRAMES (1U << TRANSPORT_FIFO_SIZE_FRAMES_BITS) 87 | #define TRANSPORT_FIFO_MAX_FRAME_DATA (1U << TRANSPORT_FIFO_SIZE_FRAME_DATA_BITS) 88 | 89 | #if (MAX_PAYLOAD > 255) 90 | #error "MIN frame payloads can be no bigger than 255 bytes" 91 | #endif 92 | 93 | // Indices into the frames FIFO are uint8_t and so can't have more than 256 frames in a FIFO 94 | #if (TRANSPORT_FIFO_MAX_FRAMES > 256) 95 | #error "Transport FIFO frames cannot exceed 256" 96 | #endif 97 | 98 | // Using a 16-bit offset into the frame data FIFO so it has to be addressable within 64Kbytes 99 | #if (TRANSPORT_FIFO_MAX_FRAME_DATA > 65536) 100 | #error "Transport FIFO data allocated cannot exceed 64Kbytes" 101 | #endif 102 | 103 | struct crc32_context { 104 | uint32_t crc; 105 | }; 106 | 107 | #ifdef TRANSPORT_PROTOCOL 108 | 109 | struct transport_frame { 110 | uint32_t last_sent_time_ms; // When frame was last sent (used for re-send timeouts) 111 | uint16_t payload_offset; // Where in the ring buffer the payload is 112 | uint8_t payload_len; // How big the payload is 113 | uint8_t min_id; // ID of frame 114 | uint8_t seq; // Sequence number of frame 115 | }; 116 | 117 | struct transport_fifo { 118 | struct transport_frame frames[TRANSPORT_FIFO_MAX_FRAMES]; 119 | uint32_t last_sent_ack_time_ms; 120 | uint32_t last_received_anything_ms; 121 | uint32_t last_received_frame_ms; 122 | uint32_t dropped_frames; // Diagnostic counters 123 | uint32_t spurious_acks; 124 | uint32_t sequence_mismatch_drop; 125 | uint32_t resets_received; 126 | uint16_t n_ring_buffer_bytes; // Number of bytes used in the payload ring buffer 127 | uint16_t n_ring_buffer_bytes_max; // Largest number of bytes ever used 128 | uint16_t ring_buffer_tail_offset; // Tail of the payload ring buffer 129 | uint8_t n_frames; // Number of frames in the FIFO 130 | uint8_t n_frames_max; // Larger number of frames in the FIFO 131 | uint8_t head_idx; // Where frames are taken from in the FIFO 132 | uint8_t tail_idx; // Where new frames are added 133 | uint8_t sn_min; // Sequence numbers for transport protocol 134 | uint8_t sn_max; 135 | uint8_t rn; 136 | }; 137 | #endif 138 | 139 | struct min_context { 140 | #ifdef TRANSPORT_PROTOCOL 141 | struct transport_fifo transport_fifo; // T-MIN queue of outgoing frames 142 | #endif 143 | uint8_t rx_frame_payload_buf[MAX_PAYLOAD]; // Payload received so far 144 | uint32_t rx_frame_checksum; // Checksum received over the wire 145 | struct crc32_context rx_checksum; // Calculated checksum for receiving frame 146 | struct crc32_context tx_checksum; // Calculated checksum for sending frame 147 | uint8_t rx_header_bytes_seen; // Countdown of header bytes to reset state 148 | uint8_t rx_frame_state; // State of receiver 149 | uint8_t rx_frame_payload_bytes; // Length of payload received so far 150 | uint8_t rx_frame_id_control; // ID and control bit of frame being received 151 | uint8_t rx_frame_seq; // Sequence number of frame being received 152 | uint8_t rx_frame_length; // Length of frame 153 | uint8_t rx_control; // Control byte 154 | uint8_t tx_header_byte_countdown; // Count out the header bytes 155 | uint8_t port; // Number of the port associated with the context 156 | }; 157 | 158 | #ifdef TRANSPORT_PROTOCOL 159 | // Queue a MIN frame in the transport queue 160 | bool min_queue_frame(struct min_context *self, uint8_t min_id, uint8_t const *payload, uint8_t payload_len); 161 | 162 | // Determine if MIN has space to queue a transport frame 163 | bool min_queue_has_space_for_frame(struct min_context *self, uint8_t payload_len); 164 | #endif 165 | 166 | // Send a non-transport frame MIN frame 167 | void min_send_frame(struct min_context *self, uint8_t min_id, uint8_t const *payload, uint8_t payload_len); 168 | 169 | // Must be regularly called, with the received bytes since the last call. 170 | // NB: if the transport protocol is being used then even if there are no bytes 171 | // this call must still be made in order to drive the state machine for retransmits. 172 | void min_poll(struct min_context *self, uint8_t const *buf, uint32_t buf_len); 173 | 174 | // Reset the state machine and (optionally) tell the other side that we have done so 175 | void min_transport_reset(struct min_context *self, bool inform_other_side); 176 | 177 | // CALLBACK. Handle incoming MIN frame 178 | void min_application_handler(uint8_t min_id, uint8_t const *min_payload, uint8_t len_payload, uint8_t port); 179 | 180 | #ifdef TRANSPORT_PROTOCOL 181 | // CALLBACK. Must return current time in milliseconds. 182 | // Typically a tick timer interrupt will increment a 32-bit variable every 1ms (e.g. SysTick on Cortex M ARM devices). 183 | uint32_t min_time_ms(void); 184 | #endif 185 | 186 | // CALLBACK. Must return current buffer space in the given port. Used to check that a frame can be 187 | // queued. 188 | uint16_t min_tx_space(uint8_t port); 189 | 190 | // CALLBACK. Send a byte on the given line. 191 | void min_tx_byte(uint8_t port, uint8_t byte); 192 | 193 | // CALLBACK. Indcates when frame transmission is finished; useful for buffering bytes into a single serial call. 194 | void min_tx_start(uint8_t port); 195 | void min_tx_finished(uint8_t port); 196 | 197 | // define to validate that MAX_PAYLOAD is defined the same value in calling code and min 198 | #ifdef VALIDATE_MAX_PAYLOAD 199 | void min_init_context_validate(struct min_context *self, uint8_t port, void * p_rx_frame_checksum); 200 | #define min_init_context(self, port) min_init_context_validate(self, port, &(self)->rx_frame_checksum) 201 | #else 202 | // Initialize a MIN context ready for receiving bytes from a serial link 203 | // (Can have multiple MIN contexts) 204 | void min_init_context(struct min_context *self, uint8_t port); 205 | #endif 206 | 207 | #ifdef MIN_DEBUG_PRINTING 208 | // Debug print 209 | void min_debug_print(const char *msg, ...); 210 | #else 211 | #define min_debug_print(...) 212 | #endif 213 | 214 | #endif //MIN_H 215 | -------------------------------------------------------------------------------- /arduino/UNO/min/min.ino: -------------------------------------------------------------------------------- 1 | #define NO_TRANSPORT_PROTOCOL 2 | 3 | #include "min.h" 4 | #include "min.c" 5 | 6 | struct min_context min_ctx; 7 | uint32_t last_sent = 0; 8 | 9 | int HEART_LED=A2; 10 | 11 | ////////////////////////////////// CALLBACKS /////////////////////////////////// 12 | 13 | void min_tx_start(uint8_t port){ 14 | 15 | } 16 | 17 | void min_tx_finished(uint8_t port) { 18 | } 19 | 20 | // Tell MIN how much space there is to write to the serial port. This is used 21 | // inside MIN to decide whether to bother sending a frame or not. 22 | uint16_t min_tx_space(uint8_t port) 23 | { 24 | return 255; 25 | } 26 | 27 | // Send a character on the designated port. 28 | void min_tx_byte(uint8_t port, uint8_t byte) 29 | { 30 | // Ignore 'port' because we have just one context. 31 | Serial.write(&byte, 1U); 32 | } 33 | 34 | void min_application_handler(uint8_t min_id, uint8_t const *min_payload, uint8_t len_payload, uint8_t port) 35 | { 36 | char msg[32] = {0}; 37 | char *turn_on = "turn_on"; 38 | char *turn_off = "turn_off"; 39 | 40 | memset(msg, 0, sizeof(msg)); 41 | snprintf(msg, len_payload < sizeof(msg) ? (len_payload+1) : sizeof(msg), "%s", min_payload); 42 | 43 | if(0 == strncmp(turn_on, msg, sizeof(msg))) { 44 | digitalWrite(HEART_LED, HIGH); 45 | } else if(0 == strncmp(turn_off, msg, sizeof(msg))) { 46 | digitalWrite(HEART_LED, LOW); 47 | } 48 | } 49 | 50 | void setup() { 51 | pinMode(HEART_LED, OUTPUT); 52 | Serial.begin(115200); 53 | while(!Serial) { 54 | ; // Wait for serial port 55 | } 56 | min_init_context(&min_ctx, 0); 57 | last_sent = millis(); 58 | digitalWrite(HEART_LED, HIGH); 59 | } 60 | 61 | uint8_t min_payload[128] = {0}; 62 | 63 | void loop() { 64 | char buf[32]; 65 | size_t buf_len, n; 66 | 67 | // Read some bytes from the USB serial port.. 68 | if(Serial.available() > 0) { 69 | buf_len = Serial.readBytes(buf, 32U); 70 | min_poll(&min_ctx, (uint8_t *)buf, (uint8_t)buf_len); 71 | } 72 | 73 | uint32_t now = millis(); 74 | if (now - last_sent > 1000U) { 75 | n = snprintf((char *)min_payload, sizeof(min_payload), "{\"l\":\"SN-004\",\"t\": 27.45,\"h\": 25.36,\"v\": 3.88,\"e\": 0}"); 76 | min_send_frame(&min_ctx, 0x33U, min_payload, n); 77 | last_sent = now; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | fn main() -> shadow_rs::SdResult<()> { 2 | shadow_rs::new() 3 | } -------------------------------------------------------------------------------- /data_if.txt: -------------------------------------------------------------------------------- 1 | {"l":"SN-001","t": 27.45,"h": 25.36,"v": 3.88,"e": 0} 2 | -------------------------------------------------------------------------------- /data_template/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "aho-corasick" 5 | version = "0.7.18" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 8 | dependencies = [ 9 | "memchr", 10 | ] 11 | 12 | [[package]] 13 | name = "data_template" 14 | version = "0.1.0" 15 | dependencies = [ 16 | "json", 17 | "regex", 18 | ] 19 | 20 | [[package]] 21 | name = "json" 22 | version = "0.12.4" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" 25 | 26 | [[package]] 27 | name = "memchr" 28 | version = "2.4.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" 31 | 32 | [[package]] 33 | name = "regex" 34 | version = "1.5.4" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 37 | dependencies = [ 38 | "aho-corasick", 39 | "memchr", 40 | "regex-syntax", 41 | ] 42 | 43 | [[package]] 44 | name = "regex-syntax" 45 | version = "0.6.25" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 48 | -------------------------------------------------------------------------------- /data_template/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "data_template" 3 | version = "0.1.0" 4 | authors = ["Dell"] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | regex = "1.3.9" 11 | json = "0.12.4" 12 | -------------------------------------------------------------------------------- /data_template/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate json; 2 | use regex::Regex; 3 | 4 | #[derive(Debug)] 5 | pub struct Template<'a> { 6 | template: &'a str, 7 | } 8 | 9 | #[derive(Debug)] 10 | pub enum Model { 11 | Value(String), 12 | } 13 | 14 | #[derive(Debug)] 15 | pub enum Value { 16 | String(String), 17 | Number(i64), 18 | } 19 | 20 | #[derive(Debug)] 21 | pub enum Error { 22 | RegexError(regex::Error), 23 | ParseError, 24 | CallError, 25 | } 26 | 27 | #[derive(Debug)] 28 | enum CallType { 29 | GetTimestamp, 30 | Unknown, 31 | } 32 | 33 | pub type Models = Vec; 34 | 35 | impl From for Error { 36 | fn from(error: regex::Error) -> Self { 37 | Error::RegexError(error) 38 | } 39 | } 40 | 41 | impl<'a> Template<'a> { 42 | pub fn new(template: &'a str) -> Self { 43 | Template { 44 | template: template, 45 | } 46 | } 47 | // <{ label }> 48 | pub fn get_value_models(&self) -> Result { 49 | let re = Regex::new(r"<\{\s*([^%>]+)\s*\}>")?; 50 | let models: Vec = re.captures_iter(self.template).map(|caps| Model::Value(caps[0].to_string())).collect(); 51 | Ok(models) 52 | } 53 | // <# TS #> 54 | pub fn get_call_models(&self) -> Result { 55 | let re = Regex::new(r"<#\s*([^%>]+)\s*#>")?; 56 | let models: Vec = re.captures_iter(self.template).map(|caps| Model::Value(caps[0].to_string())).collect(); 57 | Ok(models) 58 | } 59 | } 60 | 61 | impl Model { 62 | pub fn is_label(&self) -> bool { 63 | let re = match Regex::new(r"[^<\{\}\s%>]+") { 64 | Ok(re) => re, 65 | Err(_err) => return false, 66 | }; 67 | let model = match self { 68 | Model::Value(model) => model, 69 | }; 70 | match re.captures(&model) { 71 | Some(_) => true, 72 | None => false, 73 | } 74 | } 75 | 76 | pub fn is_call(&self) -> bool { 77 | let re = match Regex::new(r"<#\s*([^%>]+)\s*#>") { 78 | Ok(re) => re, 79 | Err(_err) => return false, 80 | }; 81 | let model = match self { 82 | Model::Value(model) => model, 83 | }; 84 | match re.captures(&model) { 85 | Some(_) => true, 86 | None => false, 87 | } 88 | } 89 | 90 | pub fn get_label(&self) -> Result { 91 | if let false = self.is_label() { 92 | return Err(crate::Error::ParseError); 93 | } 94 | let re = Regex::new(r"[^<\{\}\s%>]+")?; 95 | let model = match self { 96 | Model::Value(model) => model, 97 | }; 98 | let label = match re.captures(&model) { 99 | Some(cap) => { 100 | cap[0].to_string() 101 | }, 102 | None => "".to_string(), 103 | }; 104 | Ok(label) 105 | } 106 | 107 | fn get_call_type(&self) -> Result { 108 | if let false = self.is_call() { 109 | return Err(crate::Error::ParseError); 110 | } 111 | let re = Regex::new(r"[^<#\}\s#>]+")?; 112 | let model = match self { 113 | Model::Value(model) => model, 114 | }; 115 | let s = match re.captures(&model) { 116 | Some(cap) => { 117 | cap[0].to_string() 118 | }, 119 | None => "".to_string(), 120 | }; 121 | if s.eq("TS") { 122 | Ok(crate::CallType::GetTimestamp) 123 | } else { 124 | Ok(crate::CallType::Unknown) 125 | } 126 | } 127 | 128 | fn get_timestamp_msec(&self) -> Result { 129 | let n = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) { 130 | Ok(n) => n, 131 | Err(_) => std::time::Duration::from_secs(0), 132 | }; 133 | Ok(n.as_millis() as i64) 134 | } 135 | 136 | pub fn get_call_result(&self) -> Result { 137 | if let false = self.is_call() { 138 | return Err(crate::Error::ParseError); 139 | } 140 | Regex::new(r"<#\s*([^%>]+)\s*#>")?; 141 | match self.get_call_type() { 142 | Ok(call_type) => { 143 | match call_type { 144 | crate::CallType::GetTimestamp => { 145 | if let Ok(timestamp_msec) = self.get_timestamp_msec() { 146 | return Ok(crate::Value::Number(timestamp_msec)); 147 | } else { 148 | return Err(crate::Error::CallError); 149 | } 150 | } 151 | _ => Err(crate::Error::CallError), 152 | } 153 | }, 154 | Err(err) => Err(err), 155 | } 156 | } 157 | } 158 | 159 | impl PartialEq for Model { 160 | fn eq(&self, other: &Self) -> bool { 161 | match self { 162 | crate::Model::Value(self_value) => { 163 | match other { 164 | crate::Model::Value(other_value) => { 165 | if self_value != other_value { 166 | return false; 167 | } else { 168 | return true; 169 | } 170 | }, 171 | // _ => false, 172 | } 173 | }, 174 | // _ => false, 175 | } 176 | } 177 | } 178 | 179 | #[cfg(test)] 180 | mod tests { 181 | fn models_eq(this: crate::Models, other: crate::Models) -> bool { 182 | if this.len() == other.len() { 183 | for i in 0..this.len() { 184 | if this[i] != other[i] { 185 | println!("this: {:#?}, other: {:#?}", this, other); 186 | return false; 187 | } 188 | } 189 | true 190 | } else { 191 | println!("this: {:#?}, other: {:#?}", this, other); 192 | false 193 | } 194 | } 195 | 196 | #[test] 197 | fn template_get_value_models() { 198 | let template = crate::Template::new("{<{name}>: [{\"ts\": <#TS#>,\"values\": <{value}>}]}"); 199 | match template.get_value_models() { 200 | Ok(models) => { 201 | let v: Vec = vec![ 202 | crate::Model::Value("<{name}>".to_string()), 203 | crate::Model::Value("<{value}>".to_string()), 204 | ]; 205 | assert_eq!(models_eq(models, v), true); 206 | }, 207 | Err(_) => panic!("Template::get_value_models test failed"), 208 | } 209 | } 210 | 211 | #[test] 212 | fn template_get_call_models() { 213 | let template = crate::Template::new("{<{name}>: [{\"ts\": <#TS#>,\"values\": <{value}>}]}"); 214 | match template.get_call_models() { 215 | Ok(models) => { 216 | let v: Vec = vec![ 217 | crate::Model::Value("<#TS#>".to_string()), 218 | ]; 219 | assert_eq!(models_eq(models, v), true); 220 | }, 221 | Err(_) => panic!("Template::get_call_models test failed"), 222 | } 223 | } 224 | 225 | #[test] 226 | fn model_is_label() { 227 | assert_eq!(crate::Model::Value("<{name}>".to_string()).is_label(), true); 228 | } 229 | 230 | #[test] 231 | fn model_get_label() { 232 | match crate::Model::Value("<{name}>".to_string()).get_label() { 233 | Ok(label) => { 234 | assert_eq!(label, "name"); 235 | } 236 | _ => panic!("Model::get_label test failed"), 237 | } 238 | } 239 | 240 | #[test] 241 | fn model_is_call() { 242 | assert_eq!(crate::Model::Value("<#TS#>".to_string()).is_call(), true); 243 | } 244 | 245 | #[test] 246 | fn model_get_call_type() { 247 | match crate::Model::Value("<#TS#>".to_string()).get_call_type(){ 248 | Ok(call_type) => match call_type { 249 | crate::CallType::GetTimestamp => {}, 250 | _ => panic!("<#TS#> should be CallType::GetTimestamp"), 251 | }, 252 | Err(_) => panic!("<#TS#> should be CallType::GetTimestamp"), 253 | } 254 | } 255 | 256 | #[test] 257 | fn model_get_timestamp_msec() { 258 | match crate::Model::Value("<#TS#>".to_string()).get_timestamp_msec(){ 259 | Ok(_) => {}, 260 | Err(_) => panic!("Model::get_timestamp_msec test failed"), 261 | } 262 | } 263 | 264 | #[test] 265 | fn model_get_call_result() { 266 | match crate::Model::Value("<#TS#>".to_string()).get_call_result(){ 267 | Ok(_) => {}, 268 | Err(_) => panic!("Model::get_call_result test failed"), 269 | } 270 | } 271 | } -------------------------------------------------------------------------------- /gw.toml: -------------------------------------------------------------------------------- 1 | [log] 2 | file_path = "log/gw.log" 3 | file_path_pattern = "log/gw{}.log.gz" 4 | # error, warn, info, debug, trace 5 | level = "info" 6 | # The size limit in bytes. 7 | size = 1048576 #1*1024*1024 8 | # The maximum number of archived logs to maintain 9 | count = 5 10 | 11 | [server] 12 | address = "127.0.0.1:1883" 13 | #address = "ssl://127.0.0.1:18885" 14 | 15 | [tls] 16 | cafile = "ca/ca.crt" 17 | # pem 文件生成方式:cat client.crt client.key ca.crt > client.pem 18 | key_store = "ca/client.pem" 19 | 20 | [client] 21 | id = "pepper_gw" 22 | keep_alive = 60 23 | username = "pepper_gw" 24 | 25 | [topic] 26 | sub_topic = "ctrl/#" 27 | pub_topic = "v1/gateway/telemetry" 28 | pub_log_topic = "v1/devices/me/telemetry" 29 | qos = 0 30 | 31 | [msg] 32 | example = "{\"l\":\"SN-001\",\"t\": 27.45,\"h\": 25.36,\"v\": 3.88,\"e\": 0}" 33 | template = "{<{l}>: [{\"ts\": <#TS#>,\"values\": {\"temperature\": <{t}>, \"humidity\": <{h}>,\"voltage\": <{v}>,\"status\": <{e}>}}]}" 34 | 35 | [database] 36 | path = "./" 37 | name = "iot.db" 38 | 39 | [data_if] 40 | #if_name = "/dev/ttyS14" 41 | #if_type = "serial_port" 42 | #if_name = "/dev/spidev0.0" 43 | #if_type = "spi_sx1276" 44 | if_name = "./data_if.txt" 45 | if_type = "text_file" 46 | -------------------------------------------------------------------------------- /src/data_manager.rs: -------------------------------------------------------------------------------- 1 | pub mod data_management{ 2 | #[derive(Debug)] 3 | pub struct DeviceData { 4 | pub msg: String, 5 | } 6 | 7 | pub mod data_base{ 8 | pub fn open_data_base(path: &str, name: &str) -> Result { 9 | let full_path = String::from(path) + name; 10 | match rusqlite::Connection::open(&full_path) { 11 | Ok(conn) => Ok(conn), 12 | Err(_err) => Err(()), 13 | } 14 | } 15 | pub fn close_data_base(db: rusqlite::Connection) -> Result<(), ()> { 16 | match db.close() { 17 | Ok(_ok) => Ok(()), 18 | Err(_err) => Err(()), 19 | } 20 | } 21 | 22 | pub fn create_device_data_table(db: &rusqlite::Connection) -> Result<(), ()> { 23 | let r = db.execute( 24 | "CREATE TABLE DEVICE_DATA( 25 | ID INTEGER PRIMARY KEY, 26 | MSG CHAR(256) 27 | )", 28 | rusqlite::params![], 29 | ); 30 | match r { 31 | Ok(_ok) => Ok(()), 32 | Err(_err) => Err(()), 33 | } 34 | } 35 | 36 | pub fn device_data_table_exsits(db: &rusqlite::Connection) -> bool { 37 | match db.prepare("SELECT * FROM sqlite_master WHERE name='DEVICE_DATA' and type='table'") { 38 | Ok(mut stmt) => { 39 | match stmt.exists(rusqlite::NO_PARAMS) { 40 | Ok(r) => { 41 | r 42 | } 43 | Err(_) => false, 44 | } 45 | }, 46 | Err(_err) => false, 47 | } 48 | } 49 | 50 | pub fn insert_data_to_device_data_table(db: &rusqlite::Connection, data: &super::DeviceData) -> Result { 51 | let r = db.execute( 52 | "INSERT INTO DEVICE_DATA(MSG) VALUES(?1)", 53 | rusqlite::params![data.msg], 54 | ); 55 | match r { 56 | Ok(inserted) => Ok(inserted), 57 | Err(_err) => Err(()), 58 | } 59 | } 60 | 61 | pub fn querry_device_data(db: &rusqlite::Connection) -> Result { 62 | match db.prepare("SELECT * FROM DEVICE_DATA") { 63 | Ok(stmt) => Ok(stmt), 64 | Err(_err) => Err(()), 65 | } 66 | } 67 | 68 | pub fn delete_device_data(db: &rusqlite::Connection, id: u32) -> Result { 69 | let r = db.execute( 70 | "DELETE FROM DEVICE_DATA WHERE ID =(?1)", 71 | rusqlite::params![id], 72 | ); 73 | match r { 74 | Ok(deleted) => Ok(deleted), 75 | Err(_err) => Err(()), 76 | } 77 | } 78 | } 79 | 80 | impl DeviceData { 81 | pub fn new(msg: &str) -> DeviceData { 82 | DeviceData { 83 | msg: String::from(msg), 84 | } 85 | } 86 | } 87 | } 88 | 89 | #[cfg(test)] 90 | mod tests { 91 | use crate::data_manager::data_management::data_base; 92 | #[test] 93 | fn it_works() { 94 | let db = data_base::open_data_base("./", "test.db"); 95 | match db { 96 | Ok(db) => { 97 | match data_base::create_device_data_table(&db) { 98 | _ => {}, 99 | } 100 | assert_eq!(data_base::device_data_table_exsits(&db), true); 101 | }, 102 | Err(err) => { 103 | panic!("Problem opening the database: {:?}", err) 104 | }, 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/interface.rs: -------------------------------------------------------------------------------- 1 | extern crate min_rs as min; 2 | extern crate log; 3 | 4 | use std::io::prelude::*; 5 | use std::io; 6 | use std::cell::RefCell; 7 | use std::sync::{Arc, Mutex}; 8 | use log::{debug, trace}; 9 | use serialport::SerialPort; 10 | use spidev::{Spidev, SpidevTransfer}; 11 | use std::thread; 12 | 13 | pub const REG_OPMODE: u8 = 0x01; 14 | pub const REG_FIFO: u8 = 0x00; 15 | pub const REG_PA_CONFIG: u8 = 0x09; 16 | pub const REG_FIFO_ADDR_PTR: u8 = 0x0D; 17 | pub const REG_FIFO_TX_BASE_AD: u8 = 0x0E; 18 | pub const REG_FIFO_RX_BASE_AD: u8 = 0x0F; 19 | pub const REG_RX_NB_BYTES: u8 = 0x13; 20 | pub const REG_FIFO_RX_CURRENT_ADDR: u8 = 0x10; 21 | pub const REG_IRQ_FLAGS: u8 = 0x12; 22 | pub const REG_PKT_RSSI_VALUE: u8 = 0x1A; 23 | pub const REG_RSSI_VALUE: u8 = 0x1B; 24 | pub const REG_DIO_MAPPING_1: u8 = 0x40; 25 | pub const REG_DIO_MAPPING_2: u8 = 0x41; 26 | pub const REG_MODEM_CONFIG: u8 = 0x1D; 27 | pub const REG_MODEM_CONFIG2: u8 = 0x1E; 28 | pub const REG_MODEM_CONFIG3: u8 = 0x26; 29 | pub const REG_SYMB_TIMEOUT_LSB: u8 = 0x1F; 30 | pub const REG_PKT_SNR_VALUE: u8 = 0x19; 31 | pub const REG_PREAMBLE_MSB: u8 = 0x20; 32 | pub const REG_PREAMBLE_LSB: u8 = 0x21; 33 | pub const REG_PAYLOAD_LENGTH: u8 = 0x22; 34 | pub const REG_IRQ_FLAGS_MASK: u8 = 0x11; 35 | pub const REG_MAX_PAYLOAD_LENGTH: u8 = 0x23; 36 | pub const REG_HOP_PERIOD: u8 = 0x24; 37 | pub const REG_SYNC_WORD: u8 = 0x39; 38 | pub const REG_DIO_MAPPING1: u8 = 0x40; 39 | pub const REG_VERSION: u8 = 0x42; 40 | pub const REG_PA_DAC: u8 = 0x4d; 41 | 42 | // LOW NOISE AMPLIFIER 43 | pub const REG_LNA: u8 = 0x0C; 44 | pub const LNA_MAX_GAIN: u8 = 0x23; 45 | pub const LNA_OFF_GAIN: u8 = 0x00; 46 | pub const LNA_LOW_GAIN: u8 = 0x20; 47 | // FRF 48 | pub const REG_FRF_MSB: u8 = 0x06; 49 | pub const REG_FRF_MID: u8 = 0x07; 50 | pub const REG_FRF_LSB: u8 = 0x08; 51 | // PA_DAC 52 | pub const PA_DAC_DISABLE: u8 = 0x04; 53 | pub const PA_DAC_ENABLE: u8 = 0x07; 54 | // PA_CONFIG 55 | pub const PA_SELECT: u8 = 0x80; 56 | // OP_MODE 57 | pub const SX72_LONG_RANGE_MODE: u8 = 0x80; 58 | pub const SX72_MODE_SLEEP: u8 = 0x00; 59 | pub const SX72_MODE_STANDBY: u8 = 0x01; 60 | pub const SX72_MODE_TX: u8 = 0x03; 61 | pub const SX72_MODE_RX_CONTINUOS: u8 = 0x05; 62 | // REG_IRQ_FLAGS 63 | pub const RX_TIMEOUT: u8 = 0x80; 64 | pub const RX_DONE: u8 = 0x40; 65 | pub const PAYLOAD_CRC_ERROR: u8 = 0x20; 66 | pub const VALID_HEADER: u8 = 0x10; 67 | pub const TX_DONE: u8 = 0x08; 68 | pub const CAD_DONE: u8 = 0x04; 69 | pub const FHSS_CHANGE_CHANNEL: u8 = 0x02; 70 | pub const CAD_DETECTED: u8 = 0x01; 71 | 72 | pub const FREQ: u32 = 434000000; // 434 Mhz 73 | 74 | enum ModemConfigChoice { 75 | Bw125Cr45Sf128, //< Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Default medium range 76 | Bw500Cr45Sf128, //< Bw = 500 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Fast+short range 77 | Bw31_25Cr48Sf512, //< Bw = 31.25 kHz, Cr = 4/8, Sf = 512chips/symbol, CRC on. Slow+long range 78 | Bw125Cr48Sf4096, //< Bw = 125 kHz, Cr = 4/8, Sf = 4096chips/symbol, CRC on. Slow+long range 79 | } 80 | 81 | #[derive(Debug,Copy,Clone)] 82 | pub struct FileIf; 83 | 84 | impl FileIf { 85 | pub fn read(self, filename: &str) -> Result { 86 | match std::fs::read_to_string(filename) { 87 | Ok(msg) => { 88 | match std::fs::write(filename, "") { 89 | _ => {}, 90 | }; 91 | Ok(msg) 92 | }, 93 | Err(_err) => Err(()), 94 | } 95 | } 96 | } 97 | 98 | pub struct HwIf { 99 | port: RefCell>, 100 | name: String, 101 | tx_space_avaliable: u16, 102 | output: Arc>, 103 | } 104 | 105 | impl HwIf { 106 | pub fn new(port: Box, name: String, tx_space_avaliable: u16) -> Self { 107 | HwIf { 108 | port: RefCell::new(port), 109 | name: name, 110 | tx_space_avaliable: tx_space_avaliable, 111 | output: Arc::new(Mutex::new(String::from(""))), 112 | } 113 | } 114 | 115 | fn available_for_write(&self) -> u16 { 116 | self.tx_space_avaliable 117 | } 118 | 119 | fn tx(&self, byte: u8) { 120 | let mut output = self.output.lock().unwrap(); 121 | output.push_str(format!("0x{:02x} ", byte).as_str()); 122 | let mut port = self.port.borrow_mut(); 123 | match port.write(&[byte]) { 124 | Ok(_) => {}, 125 | Err(e) => { 126 | debug!(target: self.name.as_str(), "{}", e); 127 | }, 128 | } 129 | } 130 | 131 | pub fn read(&self, buf: &mut [u8]) -> Result { 132 | let mut port = self.port.borrow_mut(); 133 | match port.read(&mut buf[..]) { 134 | Ok(n) => Ok(n), 135 | _ => Err(()), 136 | } 137 | } 138 | } 139 | 140 | impl min::Interface for HwIf { 141 | fn tx_start(&self) { 142 | let mut output = self.output.lock().unwrap(); 143 | output.clear(); 144 | output.push_str(format!("send frame: [ ").as_str()); 145 | } 146 | 147 | fn tx_finished(&self) { 148 | let mut output = self.output.lock().unwrap(); 149 | output.push_str(format!("]").as_str()); 150 | trace!(target: self.name.as_str(), "{}", output); 151 | } 152 | fn tx_space(&self) -> u16 { 153 | self.available_for_write() 154 | } 155 | 156 | fn tx_byte(&self, _min_port: u8, byte: u8) { 157 | self.tx(byte); 158 | } 159 | } 160 | 161 | 162 | #[derive(Debug, Copy, Clone)] 163 | pub struct SpiIf; 164 | 165 | impl SpiIf { 166 | fn read_register(self, spi: &mut Spidev, addr: u8) -> io::Result { 167 | let mut rx_buf = [0_u8; 2]; 168 | let tx_buf = [addr & 0x7f, 0]; 169 | let mut transfer = SpidevTransfer::read_write(&tx_buf, &mut rx_buf); 170 | spi.transfer(&mut transfer)?; 171 | 172 | Ok(rx_buf[1]) 173 | } 174 | 175 | fn write_register(self, spi: &mut Spidev, addr: u8, value: u8) -> io::Result<()> { 176 | spi.write(&[addr | 0x80, value])?; 177 | 178 | Ok(()) 179 | } 180 | 181 | fn set_modem_config(self, spi: &mut Spidev, config: ModemConfigChoice) -> io::Result<()> { 182 | match config { 183 | ModemConfigChoice::Bw125Cr45Sf128 => { 184 | self.write_register(spi, REG_MODEM_CONFIG, 0x72)?; 185 | self.write_register(spi, REG_MODEM_CONFIG2, 0x74)?; 186 | self.write_register(spi, REG_MODEM_CONFIG3, 0x00)?; 187 | Ok(()) 188 | } 189 | ModemConfigChoice::Bw500Cr45Sf128 => { 190 | self.write_register(spi, REG_MODEM_CONFIG, 0x92)?; 191 | self.write_register(spi, REG_MODEM_CONFIG2, 0x74)?; 192 | self.write_register(spi, REG_MODEM_CONFIG3, 0x00)?; 193 | Ok(()) 194 | } 195 | ModemConfigChoice::Bw31_25Cr48Sf512 => { 196 | self.write_register(spi, REG_MODEM_CONFIG, 0x48)?; 197 | self.write_register(spi, REG_MODEM_CONFIG2, 0x94)?; 198 | self.write_register(spi, REG_MODEM_CONFIG3, 0x00)?; 199 | Ok(()) 200 | } 201 | ModemConfigChoice::Bw125Cr48Sf4096 => { 202 | self.write_register(spi, REG_MODEM_CONFIG, 0x78)?; 203 | self.write_register(spi, REG_MODEM_CONFIG2, 0xc4)?; 204 | self.write_register(spi, REG_MODEM_CONFIG3, 0x08)?; 205 | Ok(()) 206 | } 207 | } 208 | } 209 | 210 | fn set_preamble_length(self, spi: &mut Spidev, length: u16) -> io::Result<()> { 211 | self.write_register(spi, REG_PREAMBLE_MSB, (length >> 8) as u8)?; 212 | self.write_register(spi, REG_PREAMBLE_LSB, (length & 0xff) as u8)?; 213 | Ok(()) 214 | } 215 | 216 | fn set_frequency(self, spi: &mut Spidev, freq: u32) -> io::Result<()> { 217 | // Frf = FRF / FSTEP 218 | let frf: u64 = ((freq as u64) << 19) / 32000000; 219 | self.write_register(spi, REG_FRF_MSB, (frf >> 16) as u8)?; 220 | self.write_register(spi, REG_FRF_MID, (frf >> 8) as u8)?; 221 | self.write_register(spi, REG_FRF_LSB, frf as u8)?; 222 | 223 | Ok(()) 224 | } 225 | 226 | fn set_tx_power(self, spi: &mut Spidev, power: u8) -> io::Result<()> { 227 | let mut p = power; 228 | if power > 23 { 229 | p = 23; 230 | } 231 | if power < 5 { 232 | p = 5; 233 | } 234 | // For PA_DAC_ENABLE, manual says '+20dBm on PA_BOOST when OutputPower=0xf' 235 | // PA_DAC_ENABLE actually adds about 3dBm to all power levels. We will us it 236 | // for 21, 22 and 23dBm 237 | if p > 20 { 238 | self.write_register(spi, REG_PA_DAC, PA_DAC_ENABLE)?; 239 | p -= 3; 240 | } else { 241 | self.write_register(spi, REG_PA_DAC, PA_DAC_DISABLE)?; 242 | } 243 | 244 | self.write_register(spi, REG_PA_CONFIG, PA_SELECT | (p - 5))?; 245 | 246 | Ok(()) 247 | } 248 | 249 | fn set_mode_rx(self, spi: &mut Spidev) -> io::Result<()> { 250 | self.write_register(spi, REG_OPMODE, SX72_MODE_RX_CONTINUOS)?; 251 | self.write_register(spi, REG_DIO_MAPPING1, 0x00)?; 252 | 253 | Ok(()) 254 | } 255 | 256 | fn setup_lora(self, spi: &mut Spidev) -> io::Result<()> { 257 | let version = self.read_register(spi, REG_VERSION)?; 258 | match version { 259 | 0x22 => println!("SX1272 detected, starting."), 260 | 0x12 => println!("SX1276 detected, starting."), 261 | _ => { 262 | return Err(io::Error::new( 263 | io::ErrorKind::Unsupported, 264 | format!("Unrecognized transceiver(version: 0x{:02X})", version), 265 | )); 266 | } 267 | } 268 | 269 | // Set sleep mode, so we can also set LORA mode: 270 | self.write_register(spi, REG_OPMODE, SX72_MODE_SLEEP | SX72_LONG_RANGE_MODE)?; 271 | thread::sleep(std::time::Duration::from_millis(100)); 272 | let op_mode = self.read_register(spi, REG_OPMODE)?; 273 | if op_mode != (SX72_MODE_SLEEP | SX72_LONG_RANGE_MODE) { 274 | return Err(io::Error::new( 275 | io::ErrorKind::Other, 276 | format!("REG_OPMODE(0x{:02X}): 0x{:02X}", REG_OPMODE, op_mode), 277 | )); 278 | } 279 | 280 | // Set up FIFO 281 | // We configure so that we can use the entire 256 byte FIFO for either receive 282 | // or transmit, but not both at the same time 283 | self.write_register(spi, REG_FIFO_TX_BASE_AD, 0)?; 284 | self.write_register(spi, REG_FIFO_RX_BASE_AD, 0)?; 285 | 286 | self.write_register(spi, REG_OPMODE, SX72_MODE_STANDBY)?; 287 | 288 | // Bw = 125 kHz, Cr = 4/8, Sf = 4096chips/symbol, CRC on. Slow+long range 289 | self.set_modem_config(spi, ModemConfigChoice::Bw125Cr48Sf4096)?; 290 | 291 | self.set_preamble_length(spi, 8)?; 292 | self.set_frequency(spi, FREQ)?; 293 | self.set_tx_power(spi, 13)?; 294 | 295 | self.set_mode_rx(spi)?; 296 | 297 | Ok(()) 298 | } 299 | 300 | pub fn init(self, spi: &mut Spidev) -> io::Result<()> { 301 | self.setup_lora(spi)?; 302 | 303 | Ok(()) 304 | } 305 | 306 | pub fn read(self, spi: &mut Spidev) -> Result { 307 | let mut buffer: [u8; 256] = [0; 256]; 308 | if let Ok(_) = self.set_mode_rx(spi) { 309 | if let Ok(irq_flags) = self.read_register(spi, REG_IRQ_FLAGS) { 310 | if irq_flags & RX_DONE != 0 { 311 | if irq_flags & 0x20 == 0x20 { 312 | debug!("CRC error"); 313 | Err(()) 314 | } else { 315 | if let Ok(current_addr) = self.read_register(spi, REG_FIFO_RX_CURRENT_ADDR) 316 | { 317 | if let Ok(received_count) = self.read_register(spi, REG_RX_NB_BYTES) { 318 | if let Ok(_) = 319 | self.write_register(spi, REG_FIFO_ADDR_PTR, current_addr) 320 | { 321 | for i in 0..received_count as usize { 322 | buffer[i] = match self.read_register(spi, REG_FIFO) { 323 | Ok(b) => b, 324 | Err(e) => { 325 | debug!("{}", e); 326 | 0 327 | } 328 | } 329 | } 330 | // Clear all IRQ flags 331 | match self.write_register(spi, REG_IRQ_FLAGS, 0xff) { 332 | Ok(_) => {} 333 | Err(e) => { 334 | debug!("{}", e); 335 | } 336 | } 337 | if let Ok(s) = String::from_utf8( 338 | buffer[4..received_count as usize].to_vec(), 339 | ) { 340 | Ok(s) 341 | } else { 342 | debug!("get string error"); 343 | Err(()) 344 | } 345 | } else { 346 | debug!( 347 | "write REG_FIFO_ADDR_PTR(0x{:02X}) error", 348 | REG_FIFO_ADDR_PTR 349 | ); 350 | Err(()) 351 | } 352 | } else { 353 | debug!("read REG_RX_NB_BYTES(0x{:02X}) error", REG_RX_NB_BYTES); 354 | Err(()) 355 | } 356 | } else { 357 | debug!( 358 | "read REG_FIFO_RX_CURRENT_ADDR(0x{:02X}) error", 359 | REG_FIFO_RX_CURRENT_ADDR 360 | ); 361 | Err(()) 362 | } 363 | } 364 | } else { 365 | Err(()) 366 | } 367 | } else { 368 | debug!("read REG_IRQ_FLAGS(0x{:02X}) error", REG_IRQ_FLAGS); 369 | Err(()) 370 | } 371 | } else { 372 | debug!("set_mode_rx error"); 373 | Err(()) 374 | } 375 | } 376 | } 377 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate chrono; 2 | extern crate clap; 3 | extern crate data_template; 4 | extern crate json; 5 | extern crate log; 6 | extern crate log4rs; 7 | extern crate rusqlite; 8 | extern crate serde_derive; 9 | extern crate time; 10 | extern crate toml; 11 | extern crate uuid; 12 | extern crate shadow_rs; 13 | extern crate min_rs as min; 14 | mod mqtt; 15 | mod types; 16 | mod interface; 17 | mod data_manager; 18 | 19 | use types::{ClientConfig, TopicConfig, TlsFiles, MsgReceiver}; 20 | 21 | use chrono::{Local, DateTime}; 22 | use data_manager::data_management::{data_base, DeviceData}; 23 | use std::time::Duration; 24 | use shadow_rs::shadow; 25 | use clap::{App, Arg, crate_name, crate_version, crate_authors}; 26 | use data_template::{Model, Template, Value}; 27 | use serde_derive::Deserialize; 28 | use serialport::SerialPort; 29 | use std::io::prelude::*; 30 | use std::sync::mpsc; 31 | use std::{env, fs, str, thread}; 32 | #[cfg(feature = "ssl")] 33 | use std::path::Path; 34 | use interface::{FileIf, HwIf, SpiIf}; 35 | use std::fs::File; 36 | use spidev::{SpiModeFlags, Spidev, SpidevOptions}; 37 | use log::{error, warn, info, debug, LevelFilter}; 38 | use log4rs::{ 39 | append::{ 40 | console::{ConsoleAppender, Target}, 41 | rolling_file::{ 42 | RollingFileAppender, 43 | policy::compound::{ 44 | CompoundPolicy, 45 | roll::fixed_window::FixedWindowRoller, 46 | trigger::size::SizeTrigger, 47 | }, 48 | }, 49 | }, 50 | config::{Appender, Config, Root}, 51 | encode::pattern::PatternEncoder, 52 | }; 53 | 54 | shadow!(build); 55 | 56 | struct SensorInterface { 57 | text_file: String, 58 | serial_port: Option>, 59 | spi: Option, 60 | } 61 | 62 | #[derive(Debug)] 63 | enum DataIfError { 64 | DataIfOpenError, 65 | DataIfInitError, 66 | DataIfUnknownType, 67 | } 68 | 69 | enum DbOp { 70 | INSERT, 71 | QUERY, 72 | DELETE, 73 | } 74 | 75 | struct DbReq { 76 | operation: DbOp, 77 | id: u32, 78 | data: DeviceData, 79 | } 80 | 81 | #[derive(Debug)] 82 | enum DatumType { 83 | Message, 84 | Notice, 85 | } 86 | 87 | #[derive(Debug)] 88 | struct Datum { 89 | id: u32, 90 | datum_type: DatumType, 91 | value: DeviceData, 92 | } 93 | 94 | #[derive(Deserialize)] 95 | struct AppConfig { 96 | log: LogConfig, 97 | server: ServerConfig, 98 | #[cfg(feature = "ssl")] 99 | tls: TlsFiles, 100 | client: ClientConfig, 101 | topic: TopicConfig, 102 | msg: MsgConfig, 103 | database: DatabaseConfig, 104 | data_if: DataIfConfig, 105 | } 106 | 107 | #[derive(Deserialize)] 108 | struct LogConfig { 109 | file_path: String, 110 | file_path_pattern: String, 111 | level: String, 112 | count: u32, 113 | size: u64, 114 | } 115 | 116 | #[derive(Deserialize)] 117 | struct ServerConfig { 118 | address: String, 119 | } 120 | 121 | #[derive(Deserialize)] 122 | struct MsgConfig { 123 | example: String, 124 | template: String, 125 | } 126 | 127 | #[derive(Deserialize)] 128 | struct DatabaseConfig { 129 | path: String, 130 | name: String, 131 | } 132 | 133 | #[derive(Deserialize)] 134 | struct DataIfConfig { 135 | if_name: String, 136 | if_type: String, 137 | } 138 | 139 | fn init_app_log(log_config: &LogConfig) -> Result<(), ()> { 140 | let level = match &log_config.level[..] { 141 | "error" => LevelFilter::Error, 142 | "warn" => LevelFilter::Warn, 143 | "info" => LevelFilter::Info, 144 | "debug" => LevelFilter::Debug, 145 | _ => LevelFilter::Trace, 146 | }; 147 | 148 | // Build a stdout logger. 149 | let stdout = ConsoleAppender::builder() 150 | .encoder(Box::new(PatternEncoder::new( 151 | "[{d(%Y-%m-%d %H:%M:%S)} {l} {M}:{T}] {m}\n", 152 | ))) 153 | .target(Target::Stdout) 154 | .build(); 155 | 156 | // Logging to log file. 157 | let trigger = SizeTrigger::new(log_config.size); 158 | let roller = match FixedWindowRoller::builder() 159 | .build(&log_config.file_path_pattern, log_config.count){ 160 | Ok(roller) => roller, 161 | Err(_) => return Err(()), 162 | }; 163 | let policy = CompoundPolicy::new(Box::new(trigger), Box::new(roller)); 164 | let logfile = match RollingFileAppender::builder() 165 | .encoder(Box::new(PatternEncoder::new( 166 | "[{d(%Y-%m-%d %H:%M:%S)} {l} {M}:{T}] {m}\n", 167 | ))) 168 | .append(true) 169 | .build(&log_config.file_path, Box::new(policy)) 170 | { 171 | Ok(logfile) => logfile, 172 | Err(_) => return Err(()), 173 | }; 174 | 175 | // Log Trace level output to file where trace is the default level 176 | // and the programmatically specified level to stdout. 177 | let config = match Config::builder() 178 | .appender(Appender::builder().build("logfile", Box::new(logfile))) 179 | .appender(Appender::builder().build("stdout", Box::new(stdout))) 180 | .build( 181 | Root::builder() 182 | .appender("logfile") 183 | .appender("stdout") 184 | .build(level), 185 | ) { 186 | Ok(config) => config, 187 | Err(_) => return Err(()), 188 | }; 189 | 190 | if let Err(_) = log4rs::init_config(config) { 191 | return Err(()); 192 | } 193 | Ok(()) 194 | } 195 | 196 | fn init_data_base(path: &str, name: &str) -> Result { 197 | let db = data_base::open_data_base(path, name); 198 | 199 | let db = match db { 200 | Ok(database) => database, 201 | Err(err) => panic!("Problem opening the database: {:?}", err), 202 | }; 203 | 204 | if data_base::device_data_table_exsits(&db) { 205 | return Ok(db); 206 | } 207 | 208 | match data_base::create_device_data_table(&db) { 209 | Ok(_ok) => debug!("create DEVICE_DATA table successfully"), 210 | Err(err) => error!("create DEVICE_DATA table failed: {:?}", err), 211 | } 212 | 213 | Ok(db) 214 | } 215 | 216 | fn get_msg_from_data(data: &DeviceData) -> String { 217 | data.msg.clone() 218 | } 219 | 220 | fn format_msg(original: &str, template_str: &str) -> Result { 221 | let parsed = match json::parse(&original) { 222 | Ok(parsed) => parsed, 223 | Err(_err) => return Err(()), 224 | }; 225 | let template = Template::new(template_str); 226 | let mut msg = template_str.to_string(); 227 | match template.get_value_models() { 228 | Ok(value_models) => { 229 | for model in value_models { 230 | match model.get_label() { 231 | Ok(label) => { 232 | //replace 233 | let value_model = match model { 234 | Model::Value(value_model) => value_model, 235 | }; 236 | if parsed[&label].is_string() { 237 | let string = match parsed[&label].as_str() { 238 | Some(string) => string, 239 | None => "", 240 | }; 241 | msg = msg.replace(&value_model, &("\"".to_owned() + &string + "\"")); 242 | } else if parsed[&label].is_number() { 243 | let num = match parsed[&label].as_number() { 244 | Some(num) => num, 245 | None => { 246 | warn!("parse JSON number failed"); 247 | return Err(()); 248 | } 249 | }; 250 | msg = msg.replace(&value_model, &num.to_string()); 251 | } else { 252 | warn!("unsurported data type"); 253 | } 254 | } 255 | Err(err) => { 256 | warn!("get label from model {:#?} failed: {:#?}", model, err); 257 | } 258 | } 259 | } 260 | } 261 | Err(_err) => {} 262 | } 263 | match template.get_call_models() { 264 | Ok(call_models) => { 265 | for model in call_models { 266 | match model.get_call_result() { 267 | Ok(value) => { 268 | let call_model = match model { 269 | Model::Value(call_model) => call_model, 270 | }; 271 | match value { 272 | Value::Number(num) => { 273 | msg = msg.replace(&call_model, &num.to_string()); 274 | } 275 | Value::String(string) => { 276 | msg = msg.replace(&call_model, &string); 277 | } 278 | } 279 | } 280 | Err(err) => { 281 | error!("get call result failed: {:#?}", err); 282 | } 283 | } 284 | } 285 | } 286 | Err(_err) => {} 287 | } 288 | if let Err(_err) = json::parse(&msg) { 289 | error!("msg converted was not a JSON string: {}", msg); 290 | return Err(()); 291 | } 292 | Ok(msg) 293 | } 294 | 295 | fn init_data_interface(if_name: &str, if_type: &str) -> Result { 296 | if if_type.eq("serial_port") { 297 | let port = match serialport::new(if_name, 115200) 298 | .timeout(Duration::from_millis(10)) 299 | .open() 300 | { 301 | Ok(port) => port, 302 | Err(err) => { 303 | error!("Open {} failed: {}", if_name, err); 304 | return Err(DataIfError::DataIfOpenError); 305 | } 306 | }; 307 | return Ok(SensorInterface{ text_file: if_name.to_string(), serial_port: Some(port), spi: None}); 308 | } else if if_type.eq("text_file") { 309 | match File::open(if_name) { 310 | Ok(_file) => return Ok(SensorInterface{ text_file: if_name.to_string(), serial_port: None, spi: None}), 311 | Err(_err) => return Err(DataIfError::DataIfOpenError), 312 | } 313 | } else if if_type.eq("spi_sx1276") { 314 | let spi = match Spidev::open(if_name) { 315 | Ok(mut spi) => { 316 | let options = SpidevOptions::new() 317 | .bits_per_word(8) 318 | .max_speed_hz(1000_000) 319 | .mode(SpiModeFlags::SPI_MODE_0) 320 | .build(); 321 | match spi.configure(&options) { 322 | Ok(_) => { 323 | if let Ok(_) = SpiIf.init(&mut spi) { 324 | spi 325 | } else { 326 | error!("init spi device failed: {}", if_name); 327 | return Err(DataIfError::DataIfOpenError); 328 | } 329 | } 330 | Err(err) => { 331 | error!("Open {} failed: {}", if_name, err); 332 | return Err(DataIfError::DataIfOpenError); 333 | } 334 | } 335 | } 336 | Err(err) => { 337 | error!("Open {} failed: {}", if_name, err); 338 | return Err(DataIfError::DataIfOpenError); 339 | } 340 | }; 341 | return Ok(SensorInterface { 342 | text_file: if_name.to_string(), 343 | serial_port: None, 344 | spi: Some(spi), 345 | }); 346 | } else { 347 | error!("DataIf type unknown: {}", if_type); 348 | return Err(DataIfError::DataIfUnknownType); 349 | } 350 | } 351 | 352 | // 格式化 log 信息 353 | fn format_log(msg: &str) -> Result { 354 | let local: DateTime = Local::now(); // 本地时间 355 | let time_str = local.format("%Y-%m-%d %H:%M:%S").to_string(); 356 | let log: String; 357 | if msg.is_empty() { 358 | log = format!("{{\"LOGS\": \"[{}]\"}}", time_str); 359 | } else { 360 | log = format!("{{\"LOGS\": \"[{}]: {}\"}}", time_str, msg); 361 | } 362 | info!("{}", log); 363 | Ok(log) 364 | } 365 | 366 | fn main() { 367 | env::set_var( 368 | "RUST_LOG", 369 | env::var_os("RUST_LOG").unwrap_or_else(|| "info".into()), 370 | ); 371 | 372 | let matches = App::new(crate_name!()) 373 | .version(crate_version!()) 374 | .long_version(build::version().as_str()) 375 | .author(crate_authors!()) 376 | .arg( 377 | Arg::with_name("CONFIG_FILE") 378 | .short("c") 379 | .long("config-file") 380 | .takes_value(true) 381 | .required(true) 382 | .help("specify the broker config file."), 383 | ) 384 | .get_matches(); 385 | 386 | let config_file = matches.value_of("CONFIG_FILE").unwrap(); 387 | let toml_string = fs::read_to_string(&config_file).unwrap(); 388 | let config: AppConfig = toml::from_str(&toml_string).unwrap(); 389 | let app_log = config.log; 390 | let server_addr = config.server.address; 391 | #[cfg(feature = "ssl")] 392 | let tls = config.tls; 393 | #[cfg(not(feature = "ssl"))] 394 | let tls = TlsFiles{cafile: String::from(""), key_store: String::from("")}; // 如果没有启用 tsl,生成个空的。 395 | let client = config.client; 396 | let topic = config.topic; 397 | let database_path = config.database.path; 398 | let database_name = config.database.name; 399 | let template = config.msg.template; 400 | let msg_example = config.msg.example; 401 | let data_if_name = config.data_if.if_name; 402 | let data_if_type = config.data_if.if_type; 403 | 404 | init_app_log(&app_log).unwrap(); 405 | 406 | // 数据模板校验 407 | let check = match format_msg(&msg_example, &template) { 408 | Ok(string) => string, 409 | Err(err) => panic!( 410 | "please check msg.example and msg.template if config-file: {:#?}", 411 | err 412 | ), 413 | }; 414 | if let Err(_) = json::parse(&check) { 415 | panic!( 416 | "please check msg.example and msg.template config-file: {}", 417 | config_file 418 | ); 419 | } 420 | 421 | // 检查证书和密钥库是否存在 422 | #[cfg(feature = "ssl")] 423 | if !Path::new(&tls.cafile).exists() { 424 | panic!( 425 | "The trust store file does not exist: {}", &tls.cafile 426 | ); 427 | } else { 428 | if !Path::new(&tls.key_store).exists() { 429 | panic!( 430 | "The key store file does not exist: {}", &tls.key_store 431 | ); 432 | } 433 | } 434 | 435 | let sensor_if = match init_data_interface(&data_if_name, &data_if_type) { 436 | Ok(sensor_if) => sensor_if, 437 | Err(err) => panic!("Init data interface failed: {:#?}", err), 438 | }; 439 | 440 | let db = match init_data_base(&database_path, &database_name) { 441 | Ok(database) => database, 442 | Err(err) => panic!("Problem opening the database: {:?}", err), 443 | }; 444 | 445 | let (insert_req, db_handle) = mpsc::channel(); 446 | let query_req = mpsc::Sender::clone(&insert_req); 447 | let (db_query_rep_tx, db_query_rep_rx) = mpsc::channel(); 448 | let db_delete_req_tx = mpsc::Sender::clone(&insert_req); 449 | let (db_delete_rep_tx, db_delete_rep_rx) = mpsc::channel(); 450 | 451 | let original_data_pub_template = template.clone(); 452 | 453 | let (original_data_tx, original_data_rx) = mpsc::channel(); 454 | 455 | // 下行消息收发 456 | let (downstream_msg_tx, downstream_msg_rx): (mpsc::Sender, mpsc::Receiver) = mpsc::channel(); 457 | 458 | // 获取原始数据 459 | let original_data_read_thread_builder = thread::Builder::new().name("original_data_read_thread".into()); 460 | 461 | let original_data_read_thread = original_data_read_thread_builder 462 | .spawn(move || { 463 | let mut buf: Vec = (0..255).collect(); 464 | if data_if_type.eq("serial_port") { 465 | if let Some(port) = sensor_if.serial_port { 466 | let uart = HwIf::new(port, String::from("uart"), 128); 467 | let mut min = min::Context::new( 468 | String::from("min"), 469 | &uart, 470 | 0, 471 | false, 472 | ); 473 | loop { 474 | if let Ok(msg) = downstream_msg_rx.try_recv() { 475 | info!("{}", msg); 476 | if let Err(_) = min.send_frame(0, msg.as_bytes(), msg.len() as u8) 477 | { 478 | error!("Send msg to interface failed."); 479 | } 480 | } 481 | if let Ok(n) = min.hw_if.read(&mut buf[..]) { 482 | min.poll(&buf[0..n], n as u32); 483 | }; 484 | if let Ok(msg) = min.get_msg() { 485 | if let Ok(string) = String::from_utf8(msg.buf[0..msg.len as usize].to_vec()) { 486 | match original_data_tx.send(string) { 487 | _ => {} 488 | } 489 | } 490 | } 491 | thread::sleep(Duration::from_millis(100)); 492 | } 493 | }; 494 | } else if data_if_type.eq("spi_sx1276") { 495 | if let Some(mut spi) = sensor_if.spi { 496 | loop { 497 | if let Ok(msg) = downstream_msg_rx.try_recv() { 498 | info!("{}", msg); 499 | } 500 | match SpiIf.read(&mut spi) { 501 | Ok(sn_msg) => { 502 | if !sn_msg.is_empty() { 503 | match original_data_tx.send(String::from(sn_msg.trim())) { 504 | _ => {} 505 | } 506 | } 507 | } 508 | Err(_err) => continue, 509 | } 510 | thread::sleep(Duration::from_millis(100)); 511 | } 512 | } 513 | } else { 514 | loop { 515 | if let Ok(msg) = downstream_msg_rx.try_recv() { 516 | info!("{}", msg); 517 | } 518 | match FileIf.read(&sensor_if.text_file) { 519 | Ok(sn_msg) => { 520 | if !sn_msg.is_empty() { 521 | match original_data_tx.send(String::from(sn_msg.trim())) { 522 | _ => {} 523 | } 524 | } 525 | } 526 | Err(_err) => continue, 527 | } 528 | thread::sleep(Duration::from_secs(1)); 529 | } 530 | } 531 | }) 532 | .unwrap(); 533 | 534 | // 该通道既收发数据,又收发消息。如果是收发消息,id 为 0 表示网络断开,id 为 1 表示网络已连接。 535 | let (original_datum_sender, datum_receiver) = mpsc::channel(); 536 | let buffed_datum_sender = original_datum_sender.clone(); 537 | 538 | // 该通道用于将数据发送给数据上传线程 539 | let (datum_publish_sender, datum_publish_receiver): (mpsc::Sender>, mpsc::Receiver>) = mpsc::channel(); 540 | // 该通道用于向数据发送者返回数据上传结果 541 | let (publish_result_sender, publish_result_receiver) = mpsc::channel(); 542 | 543 | // 该通道用于将封装好的 MQTT 消息发送给数据上传线程 544 | let (mqtt_message_sender, mqtt_message_receiver): (mpsc::Sender, mpsc::Receiver) = mpsc::channel(); 545 | 546 | // 通过该通道向所有需要获知网络连接状态的线程发送网络连通或断开消息(连通:Some(0),断开:None) 547 | let (cloud_statue_announcement_sender, cloud_statue_announcement_receiver) = mpsc::channel(); 548 | let cloud_statue_announcement_sender_clone = cloud_statue_announcement_sender.clone(); 549 | let send_cloud_link_broken_msg_to_uploader = datum_publish_sender.clone(); 550 | let (cloud_link_broken_msg_sender, cloud_link_change_msg_receiver) = mpsc::channel(); 551 | let send_cloud_link_change_msg_to_data_manager = original_datum_sender.clone(); 552 | 553 | // 原始数据处理 554 | let original_data_handle_thread_builder = 555 | thread::Builder::new().name("original_data_handle_thread".into()); 556 | let original_data_handle_thread = original_data_handle_thread_builder 557 | .spawn(move || loop { 558 | match original_data_rx.recv() { 559 | Ok(sn_msg) => { 560 | match format_msg(&sn_msg, &original_data_pub_template) { 561 | Ok(formated_msg) => { 562 | match buffed_datum_sender.send(Datum { 563 | id: 0, 564 | datum_type: DatumType::Message, 565 | value: DeviceData::new(&formated_msg), 566 | }) { 567 | Err(err) => error!("send datum to data_manger failed: {}", err), 568 | _ => {} 569 | } 570 | } 571 | Err(_) => { 572 | error!("convert from data template failed: {}", sn_msg); 573 | continue; 574 | } 575 | }; 576 | } 577 | Err(_err) => {} 578 | } 579 | }) 580 | .unwrap(); 581 | 582 | // 离线数据处理 583 | let offine_data_handle_thread_builder = 584 | thread::Builder::new().name("offine_data_handle_thread".into()); 585 | let offine_data_handle_thread = offine_data_handle_thread_builder 586 | .spawn(move || { 587 | for msg in cloud_link_change_msg_receiver.iter() { 588 | if let Some(_msg) = msg { 589 | debug!("Cloud connected, query offline data..."); 590 | /* 收到联网消息 */ 591 | let db_req = DbReq { 592 | operation: DbOp::QUERY, 593 | id: 0, 594 | data: DeviceData::new(""), 595 | }; 596 | match query_req.send(db_req) { 597 | Ok(_ok) => match db_query_rep_rx.recv() { 598 | Ok(vec) => { 599 | for tuple in vec { 600 | let (id, device_data) = match tuple { 601 | Ok(t) => t, 602 | Err(err) => { 603 | error!("get data from data iter failed: {}", err); 604 | continue; 605 | } 606 | }; 607 | let sn_msg = get_msg_from_data(&device_data); 608 | match original_datum_sender.send(Datum { 609 | id: id, 610 | datum_type: DatumType::Message, 611 | value: DeviceData::new(&sn_msg), 612 | }) { 613 | Err(err) => { 614 | error!("send datum to data_manger failed: {}", err) 615 | } 616 | _ => {} 617 | } 618 | thread::sleep(Duration::from_millis(100)); 619 | } 620 | } 621 | Err(_err) => {} 622 | }, 623 | Err(_err) => {} 624 | } 625 | } 626 | } 627 | }) 628 | .unwrap(); 629 | 630 | // 数据库操作 631 | let db_handle_thread_builder = thread::Builder::new().name("db_handle_thread".into()); 632 | let db_handle_thread = db_handle_thread_builder 633 | .spawn(move || { 634 | loop { 635 | let db_req = match db_handle.recv() { 636 | Ok(req) => req, 637 | Err(_err) => continue, 638 | }; 639 | match db_req.operation { 640 | DbOp::INSERT => { 641 | //let sn_msg = get_msg_from_data(&db_req.data); 642 | //let device_data = match format_msg(&sn_msg, &db_handle_template) { 643 | // Ok(msg) => { 644 | // DeviceData::new(&msg) 645 | // }, 646 | // Err(err) => { 647 | // error!("convert from data template failed: {:#?}", err); 648 | // continue; 649 | // }, 650 | //}; 651 | match data_base::insert_data_to_device_data_table(&db, &db_req.data) { 652 | Ok(_ok) => { 653 | debug!("buffed data successfully"); 654 | } 655 | Err(err) => { 656 | error!("buffed data failed: {:?}", err); 657 | } 658 | } 659 | } 660 | DbOp::QUERY => { 661 | let mut stmt = match data_base::querry_device_data(&db) { 662 | Ok(stmt) => stmt, 663 | Err(_err) => { 664 | error!("querry database failed"); 665 | continue; 666 | } 667 | }; 668 | let data_iter = match stmt.query_map(rusqlite::params![], |row| { 669 | let id: u32 = row.get(0)?; 670 | let data = DeviceData { msg: row.get(1)? }; 671 | Ok((id, data)) 672 | }) { 673 | Ok(iter) => iter, 674 | Err(_err) => { 675 | error!("get data iter failed"); 676 | continue; 677 | } 678 | }; 679 | let mut vec = Vec::new(); 680 | for tuple in data_iter { 681 | vec.push(tuple); 682 | } 683 | match db_query_rep_tx.send(vec) { 684 | _ => {} 685 | } 686 | } 687 | DbOp::DELETE => match data_base::delete_device_data(&db, db_req.id) { 688 | Ok(_ok) => match db_delete_rep_tx.send(true) { 689 | Ok(_ok) => {} 690 | Err(err) => error!("send delete rep failed: {}", err), 691 | }, 692 | Err(_err) => match db_delete_rep_tx.send(false) { 693 | Ok(_ok) => {} 694 | Err(err) => error!("send delete rep failed: {}", err), 695 | }, 696 | }, 697 | } 698 | } 699 | }) 700 | .unwrap(); 701 | 702 | // 数据管理 703 | // 数据流: 704 | // 联网时,原始数据处理线程->数据管理线程->MQTT发布线程(发布),如果发布失败,向数据库操作线程请求保存该数据。 705 | // 网络断开时,原始数据处理线程->数据管理线程->数据库操作线程(离线保存)。 706 | // 网络恢复时,离线数据处理线程通过数据库操作线程取出缓存的消息,发送给数据管理线程,数据管理线程将数据发给MQTT发布线程,收到发送成功的反馈 707 | // 后,向数据库操作线程请求删除对应 id 的消息。 708 | let data_manager_builder = thread::Builder::new().name("data_manager".into()); 709 | let data_manager = data_manager_builder.spawn(move || { 710 | let mut id: u32; 711 | let mut cloud_is_connected = false; 712 | for datum in datum_receiver.iter() { 713 | info!("datum: {{ id: {}, type: {:?}, value: {:?} }}", datum.id, datum.datum_type, datum.value.msg); 714 | match datum.datum_type { 715 | DatumType::Notice => { 716 | match datum.id { 717 | 0 => cloud_is_connected = false, /* 网络断开 */ 718 | _ => { 719 | cloud_is_connected = true; /* 网络已连接 */ 720 | // 发送联网消息给离线数据处理线程 721 | if let Err(err) = cloud_link_broken_msg_sender.send(Some(0)) { 722 | error!("Error send cloud link broken msg: {}", err); 723 | } 724 | }, 725 | } 726 | continue; 727 | }, 728 | DatumType::Message => {}, 729 | } 730 | id = datum.id; 731 | if cloud_is_connected { /* 已联网,发布数据 */ 732 | match datum_publish_sender.send(Some(datum.value.msg.clone())) { 733 | Ok(_) => {}, 734 | Err(err) => error!("Error send datum to publish: {}", err), 735 | } 736 | match publish_result_receiver.recv() { 737 | Ok(r) => { 738 | if r == false { 739 | if id == 0 { 740 | // 原始数据 id 为 0,发布失败,需要存入数据库 741 | let db_req = DbReq{ 742 | operation: DbOp::INSERT, 743 | id: 0, 744 | data: datum.value, 745 | }; 746 | match insert_req.send(db_req) { 747 | Err(err) => { 748 | error!("send insert req failed: {}", err); 749 | }, 750 | _ => {}, 751 | } 752 | } 753 | // 离线数据原本就在数据库中,发布失败后不做处理 754 | } else { 755 | if id != 0 { 756 | let db_delete_req = DbReq{ 757 | operation: DbOp::DELETE, 758 | id: id, 759 | data: DeviceData::new(""), 760 | }; 761 | match db_delete_req_tx.send(db_delete_req) { 762 | Ok(_ok) => { 763 | match db_delete_rep_rx.recv() { 764 | Ok(r) => { 765 | if r { 766 | debug!("handle offline data successfully"); 767 | } else { 768 | error!("delete offline data after publishing failed"); 769 | } 770 | }, 771 | Err(_err) => {}, 772 | } 773 | }, 774 | Err(err) => { 775 | error!("send offline data delete req failed: {}", err); 776 | }, 777 | } 778 | } 779 | // 原始数据发布成功后不做处理 780 | } 781 | }, 782 | Err(err) => error!("Error receiving publish result: {}", err), 783 | } 784 | } else if id == 0 { /* 没联网,存入数据库(原始数据处理线程发过来的数据,id 为 0) */ 785 | let db_req = DbReq{ 786 | operation: DbOp::INSERT, 787 | id: 0, 788 | data: datum.value, 789 | }; 790 | match insert_req.send(db_req) { 791 | Err(err) => { 792 | error!("send insert req failed: {}", err); 793 | }, 794 | _ => {}, 795 | } 796 | } else { 797 | // 没有联网且 id 不为 0 的情况下,理论上没有这种情况。 798 | error!("Get datum(id: {}) from offine_data_handle_thread when device is offline!", id); 799 | } 800 | } 801 | }).unwrap(); 802 | 803 | let cloud_state_broadcaster_builder = 804 | thread::Builder::new().name("cloud_state_broadcaster".into()); 805 | let cloud_state_broadcaster = cloud_state_broadcaster_builder 806 | .spawn(move || { 807 | for msg in cloud_statue_announcement_receiver.iter() { 808 | if let Some(_msg) = msg { 809 | info!("Successfully connected."); 810 | // 需要接收联网消息的线程由数据管理模块和离线数据处理模块,为了确保离线数据操作线程在数据管理线程获知网络恢复的情况下才向数据管理线程 811 | // 发送数据,此处仅通知数据管理线程,数据管理模块再通知离线数据操作线程。 812 | // 发送联网消息给数据管理模块 813 | if let Err(err) = send_cloud_link_change_msg_to_data_manager.send(Datum { 814 | id: 1, 815 | datum_type: DatumType::Notice, 816 | value: DeviceData::new(""), 817 | }) { 818 | error!("Error send cloud link change msg to data manager: {}", err); 819 | } 820 | } else { 821 | error!("Cloud connection lost."); 822 | // 发送断网消息给数据发布线程 823 | if let Err(err) = send_cloud_link_broken_msg_to_uploader.send(None) { 824 | error!("Error send cloud link broken msg to uploader: {}", err); 825 | } 826 | // 发送断网消息给数据管理模块 827 | if let Err(err) = send_cloud_link_change_msg_to_data_manager.send(Datum { 828 | id: 0, 829 | datum_type: DatumType::Notice, 830 | value: DeviceData::new(""), 831 | }) { 832 | error!("Error send cloud link change msg to data manager: {}", err); 833 | } 834 | } 835 | } 836 | }) 837 | .unwrap(); 838 | 839 | // 处理 MQTT 连接 840 | let (tx, rx): (mpsc::Sender, mpsc::Receiver) = mpsc::channel(); 841 | let mqtt_pub_thread_builder = thread::Builder::new().name("mqtt_pub_thread".into()); 842 | let mqtt_pub_thread = mqtt_pub_thread_builder 843 | .spawn( 844 | mqtt::closure::pub_closure(client, topic, tls, cloud_statue_announcement_sender_clone, tx, datum_publish_receiver, format_log, 845 | publish_result_sender, mqtt_message_receiver, server_addr) 846 | ) 847 | .unwrap(); 848 | 849 | let mqtt_sub_thread_builder = thread::Builder::new().name("mqtt_sub_thread".into()); 850 | let mqtt_sub_thread = mqtt_sub_thread_builder 851 | .spawn(mqtt::closure::sub_closure(rx, downstream_msg_tx, cloud_statue_announcement_sender)) 852 | .unwrap(); 853 | 854 | original_data_read_thread.join().unwrap(); 855 | original_data_handle_thread.join().unwrap(); 856 | offine_data_handle_thread.join().unwrap(); 857 | db_handle_thread.join().unwrap(); 858 | data_manager.join().unwrap(); 859 | cloud_state_broadcaster.join().unwrap(); 860 | mqtt_pub_thread.join().unwrap(); 861 | mqtt_sub_thread.join().unwrap(); 862 | } 863 | -------------------------------------------------------------------------------- /src/mqtt.rs: -------------------------------------------------------------------------------- 1 | pub mod closure { 2 | extern crate paho_mqtt; 3 | use std::sync::mpsc::{Sender, Receiver}; 4 | use std::time::Duration; 5 | use std::{process, thread}; 6 | use crate::types::{ClientConfig, TopicConfig, MsgReceiver, TlsFiles}; 7 | use log::{error, warn, info, debug, LevelFilter}; 8 | 9 | pub fn pub_closure(client: ClientConfig, topic: TopicConfig, tls: TlsFiles, cloud_statue_announcement_sender: Sender>, 10 | tx: Sender, datum_publish_receiver: Receiver>, format_log: fn(msg: &str) -> Result, 11 | publish_result_sender: Sender, mqtt_message_receiver: Receiver, server_addr: String 12 | ) -> impl FnOnce() -> () { 13 | move || { 14 | let create_opts = paho_mqtt::CreateOptionsBuilder::new() 15 | .server_uri(server_addr) 16 | .client_id(client.id) 17 | .max_buffered_messages(1) // 离线时不缓存数据 18 | .finalize(); 19 | 20 | let mut cli = paho_mqtt::Client::new(create_opts).unwrap_or_else(|e| { 21 | error!("Error creating the client: {:?}", e); 22 | process::exit(1); 23 | }); 24 | 25 | cli.set_timeout(Duration::from_secs(5)); 26 | let sub_msg_receiver = cli.start_consuming(); 27 | 28 | #[cfg(feature = "ssl")] 29 | let ssl_opts = paho_mqtt::SslOptionsBuilder::new() 30 | .trust_store(&tls.cafile).unwrap() 31 | .key_store(&tls.key_store).unwrap() 32 | .finalize(); 33 | 34 | #[cfg(feature = "ssl")] 35 | let conn_opts = paho_mqtt::ConnectOptionsBuilder::new() 36 | .ssl_options(ssl_opts) 37 | .keep_alive_interval(Duration::from_secs(client.keep_alive.into())) 38 | .mqtt_version(paho_mqtt::MQTT_VERSION_3_1_1) 39 | .clean_session(true) 40 | .user_name(client.username) 41 | .finalize(); 42 | 43 | #[cfg(not(feature = "ssl"))] 44 | let conn_opts = paho_mqtt::ConnectOptionsBuilder::new() 45 | .keep_alive_interval(Duration::from_secs(client.keep_alive.into())) 46 | .mqtt_version(paho_mqtt::MQTT_VERSION_3_1_1) 47 | .clean_session(true) 48 | .user_name(client.username) 49 | //.automatic_reconnect(Duration::from_secs(1), Duration::from_secs(30)) 50 | .finalize(); 51 | 52 | info!("Connecting to the MQTT broker..."); 53 | match cli.connect(conn_opts) { 54 | Ok(rsp) => { 55 | if let Some(cr) = rsp.connect_response() { 56 | if let Err(err) = cloud_statue_announcement_sender.send(Some(0)) { 57 | error!("Error send cloud statue announcement: {}", err); 58 | } 59 | info!("Connected to: '{}' with MQTT version {}", cr.server_uri, cr.mqtt_version); 60 | // Register subscriptions on the server 61 | debug!("Subscribing to topics, with requested QoS: {:?}...", topic.qos); 62 | match cli.subscribe(&topic.sub_topic, topic.qos) { 63 | Ok(qosv) => debug!("QoS granted: {:?}", qosv), 64 | Err(e) => { 65 | debug!("Error subscribing to topics: {:?}", e); 66 | } 67 | } 68 | } 69 | } 70 | Err(e) => { 71 | error!("Error connecting to the broker: {:?}", e); 72 | loop { 73 | if cli.reconnect().is_ok() { 74 | if let Err(err) = cloud_statue_announcement_sender.send(Some(0)) { 75 | error!("Error send cloud statue announcement: {}", err); 76 | } 77 | // Register subscriptions on the server 78 | debug!("Subscribing to topics, with requested QoS: {:?}...", topic.qos); 79 | match cli.subscribe(&topic.sub_topic, topic.qos) { 80 | Ok(qosv) => debug!("QoS granted: {:?}", qosv), 81 | Err(e) => { 82 | debug!("Error subscribing to topics: {:?}", e); 83 | } 84 | } 85 | break; 86 | } else { 87 | thread::sleep(Duration::from_secs(10)); 88 | } 89 | } 90 | } 91 | } 92 | 93 | match tx.send(sub_msg_receiver) { 94 | Err(err) => error!("Send msg receiver failed: {}", err), 95 | _ => {} 96 | } 97 | loop { 98 | let publish_result: bool; 99 | match datum_publish_receiver.recv_timeout(Duration::from_millis(500)) { 100 | Ok(option) => if let Some(msg) = option { 101 | let message = paho_mqtt::Message::new(topic.pub_topic.clone(), msg, topic.qos); 102 | debug!("message: {}", message); 103 | if let Err(e) = cli.publish(message) { 104 | error!("Error publishing message: {:?}", e); 105 | publish_result = false; 106 | } else { 107 | publish_result = true; 108 | // 数据发布成功后发送 LOG 109 | match format_log("") { 110 | Ok(log) => { 111 | let log_msg = paho_mqtt::Message::new(topic.pub_log_topic.clone(), log, topic.qos); 112 | match cli.publish(log_msg) { 113 | Err(err) => error!("Error publishing log: {:?}", err), 114 | _ => {} 115 | } 116 | }, 117 | Err(err) => error!("Error formating log: {:?}", err), 118 | } 119 | } 120 | match publish_result_sender.send(publish_result) { 121 | Err(err) => error!("Error send publish result: {}", err), 122 | _ => {}, 123 | } 124 | } 125 | Err(_) => {}, 126 | } 127 | // 发布其他线程发过来的 MQTT 消息 128 | if let Ok(mqtt_message) = mqtt_message_receiver.try_recv() { 129 | if let Err(e) = cli.publish(mqtt_message) { 130 | error!("Error publishing message: {:?}", e); 131 | } 132 | } 133 | if !cli.is_connected() { 134 | if cli.reconnect().is_ok() { 135 | if let Err(err) = cloud_statue_announcement_sender.send(Some(0)) { 136 | error!("Error send cloud statue announcement: {}", err); 137 | } 138 | // Register subscriptions on the server 139 | debug!("Subscribing to topics, with requested QoS: {:?}...", topic.qos); 140 | match cli.subscribe(&topic.sub_topic, topic.qos) { 141 | Ok(qosv) => debug!("QoS granted: {:?}", qosv), 142 | Err(e) => { 143 | debug!("Error subscribing to topics: {:?}", e); 144 | } 145 | } 146 | } 147 | } 148 | } 149 | } 150 | } 151 | 152 | pub fn sub_closure(rx: Receiver, downstream_msg_tx: Sender, 153 | cloud_statue_announcement_sender: Sender>) -> impl FnOnce() -> () { 154 | move || loop { 155 | match rx.recv() { 156 | Ok(r) => { 157 | for msg in r.iter() { 158 | if let Some(msg) = msg { 159 | if let Err(err) = downstream_msg_tx.send(String::from(msg.payload_str())) 160 | { 161 | error!("Send downstream msg failed(err: {}, msg: {})", err, msg); 162 | } 163 | } else { 164 | if let Err(err) = cloud_statue_announcement_sender.send(None) { 165 | error!("Error send cloud statue announcement: {}", err); 166 | } 167 | } 168 | } 169 | } 170 | Err(err) => { 171 | error!("mqtt_sub_thread recv error: {}", err); 172 | } 173 | } 174 | } 175 | } 176 | 177 | } -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | extern crate paho_mqtt; 2 | use serde_derive::Deserialize; 3 | use std::sync::mpsc::Receiver; 4 | 5 | pub type MsgReceiver = Receiver>; 6 | 7 | #[derive(Deserialize)] 8 | pub struct ClientConfig { 9 | pub id: String, 10 | pub keep_alive: u16, 11 | pub username: String, 12 | } 13 | 14 | #[derive(Deserialize)] 15 | pub struct TopicConfig { 16 | pub sub_topic: String, 17 | pub pub_topic: String, 18 | pub pub_log_topic: String, 19 | pub qos: i32, 20 | } 21 | 22 | #[derive(Deserialize)] 23 | pub struct TlsFiles { 24 | pub cafile: String, 25 | pub key_store: String, 26 | } -------------------------------------------------------------------------------- /tools/build_ar9331.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | TARGET="mips-unknown-linux-uclibc" 3 | MODE="release" 4 | BIN=target/$TARGET/$MODE/gw 5 | TOOLCHAIN=~/wsl/OpenWRT/OpenWrt-SDK-ar71xx-for-linux-x86_64-gcc-4.8-linaro_uClibc-0.9.33.2/staging_dir/toolchain-mips_34kc_gcc-4.8-linaro_uClibc-0.9.33.2 6 | CC=mips-openwrt-linux-uclibc-gcc 7 | AR=mips-openwrt-linux-uclibc-ar 8 | STRIP=mips-openwrt-linux-uclibc-strip 9 | 10 | export PATH=$PATH:$TOOLCHAIN/bin 11 | export CC_mips_unknown_linux_uclibc=$CC 12 | export AR_mips_unknown_linux_uclibc=$AR 13 | export STAGING_DIR=$TOOLCHAIN 14 | 15 | # 切换为 mips-unknown-linux-uclibc 编译的工具链 16 | rustup default my_toolchain 17 | 18 | cd .. 19 | cargo build --$MODE --target=$TARGET -vv 20 | $STRIP $BIN 21 | echo "" 22 | ls -lh $BIN 23 | -------------------------------------------------------------------------------- /tools/build_f133.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TARGET="riscv64gc-unknown-linux-gnu" 4 | MODE="release" 5 | BIN=target/$TARGET/$MODE/gw 6 | CC="riscv64-unknown-linux-gnu-gcc" 7 | AR="riscv64-unknown-linux-gnu-ar" 8 | # 注意,需要改为实际工具链所在的路径 9 | TOOLCHAIN=~/Tina-Linux/out/f133-mq_r/staging_dir/toolchain 10 | # 注意,需要改为实际 libz.h 所在的路径 11 | CFLAGS="-I/home/dell/Tina-Linux/out/f133-mq_r/staging_dir/target/usr/include" 12 | STRIP=riscv64-unknown-linux-gnu-strip 13 | 14 | export CC_riscv64gc_unknown_linux_gnu=$CC 15 | export AR_riscv64gc_unknown_linux_gnu=$AR 16 | export CFLAGS_riscv64gc_unknown_linux_gnu=$CFLAGS 17 | export PATH=$PATH:$TOOLCHAIN/bin 18 | 19 | cd .. 20 | cargo build --$MODE --target=$TARGET -vv 21 | $STRIP $BIN 22 | echo "" 23 | ls -lh $BIN -------------------------------------------------------------------------------- /tools/build_mt76x8.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TARGET="mipsel-unknown-linux-musl" 4 | MODE="release" 5 | BIN=target/$TARGET/$MODE/gw 6 | TOOLCHAIN=~/wsl/OpenWRT/openwrt-sdk-ramips-mt76x8_gcc-7.5.0_musl.Linux-x86_64/staging_dir/toolchain-mipsel_24kc_gcc-7.5.0_musl 7 | CC=mipsel-openwrt-linux-gcc 8 | STRIP=mipsel-openwrt-linux-strip 9 | 10 | export PATH=$PATH:$TOOLCHAIN/bin 11 | export CC_mipsel_unknown_linux_musl=$CC 12 | export STAGING_DIR=$TOOLCHAIN 13 | 14 | cd .. 15 | cargo build --$MODE --target=$TARGET -vv 16 | $STRIP $BIN 17 | echo "" 18 | ls -lh $BIN 19 | -------------------------------------------------------------------------------- /tools/build_pi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TARGET="arm-unknown-linux-gnueabihf" 4 | MODE="release" 5 | BIN=target/$TARGET/$MODE/gw 6 | # 注意,需要改为实际工具链所在的路径 7 | TOOLCHAIN=~/wsl/tool/raspberrypi-tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64 8 | CC=arm-linux-gnueabihf-gcc 9 | # 注意,需要改为实际 libz.h 所在的路径 10 | CFLAGS="-I/home/dell/wsl/source/libz-1.2.1100+2/libz" 11 | STRIP=arm-linux-gnueabihf-strip 12 | 13 | export PATH=$PATH:$TOOLCHAIN/bin 14 | export CC_arm_unknown_linux_gnueabihf=$CC 15 | export CFLAGS_arm_unknown_linux_gnueabihf=$CFLAGS 16 | 17 | cd .. 18 | if [ -f "$BIN" ];then 19 | rm $BIN 20 | fi 21 | cargo build --$MODE --target=$TARGET -vv 22 | $STRIP $BIN 23 | echo "" 24 | ls -lh $BIN 25 | --------------------------------------------------------------------------------