Initial publish

This commit is contained in:
John Goerzen 2019-10-26 21:45:39 -05:00
parent 44f5fb8c3b
commit a5a9978281
18 changed files with 2184 additions and 6 deletions

460
Cargo.lock generated Normal file
View File

@ -0,0 +1,460 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "CoreFoundation-sys"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"mach 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "IOKit-sys"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"CoreFoundation-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"mach 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "aho-corasick"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "atty"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "autocfg"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cc"
version = "1.0.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "chrono"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "clap"
version = "2.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-channel"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-utils"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "format_escape_default"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "heck"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "hex"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libc"
version = "0.2.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libudev"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"libudev-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "libudev-sys"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "log"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "lorapipe"
version = "1.0.0"
dependencies = [
"crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"format_escape_default 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"serialport 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"simplelog 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)",
"structopt 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "mach"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "mach"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "memchr"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "nix"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-integer"
version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-traits"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "pkg-config"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "proc-macro-error"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "proc-macro2"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "quote"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "redox_syscall"
version = "0.1.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "regex"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex-syntax"
version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serialport"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"CoreFoundation-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"IOKit-sys 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libudev 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"mach 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "simplelog"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "structopt"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"structopt-derive 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "structopt-derive"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro-error 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "syn"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "thread_local"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "time"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "unicode-segmentation"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-width"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-xid"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "vec_map"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum CoreFoundation-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e9889e6db118d49d88d84728d0e964d973a5680befb5f85f55141beea5c20b"
"checksum IOKit-sys 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "99696c398cbaf669d2368076bdb3d627fb0ce51a26899d7c61228c5c0af3bf4a"
"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d"
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
"checksum cc 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)" = "0213d356d3c4ea2c18c40b037c3be23cd639825c18f25ee670ac7813beeef99c"
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
"checksum chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e8493056968583b0193c1bb04d6f7684586f3726992d6c573261941a895dbd68"
"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
"checksum crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c8ec7fcd21571dc78f96cc96243cab8d8f035247c3efd16c687be154c3fa9efa"
"checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6"
"checksum format_escape_default 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cdb2a22fc101e1c1be19e7401b58d502802839a4a7fd58ad35369a386b4639e9"
"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
"checksum hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "023b39be39e3a2da62a94feb433e91e8bcd37676fbc8bea371daf52b7a769a3e"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)" = "1a31a0627fdf1f6a39ec0dd577e101440b7db22672c0901fe00a9a6fbb5c24e8"
"checksum libudev 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea626d3bdf40a1c5aee3bcd4f40826970cae8d80a8fec934c82a63840094dcfe"
"checksum libudev-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324"
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
"checksum mach 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2fd13ee2dd61cc82833ba05ade5a30bb3d63f7ced605ef827063c63078302de9"
"checksum mach 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "86dd2487cdfea56def77b88438a2c915fb45113c5319bfe7e14306ca4cd0b0e1"
"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
"checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce"
"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09"
"checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32"
"checksum pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "72d5370d90f49f70bd033c3d75e87fc529fbfff9d6f7cccef07d6170079d91ea"
"checksum proc-macro-error 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "aeccfe4d5d8ea175d5f0e4a2ad0637e0f4121d63bd99d356fb1f39ab2e7c6097"
"checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27"
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
"checksum regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd"
"checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716"
"checksum serialport 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8d3ecaf58010bedccae17be55d4ed6f2ecde5646fc48ce8c66ea2d35a1419c"
"checksum simplelog 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "05a3e303ace6adb0a60a9e9e2fbc6a33e1749d1e43587e2125f7efa9c5e107c5"
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
"checksum structopt 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6d4f66a4c0ddf7aee4677995697366de0749b0139057342eccbb609b12d0affc"
"checksum structopt-derive 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8fe0c13e476b4e21ff7f5c4ace3818b6d7bdc16897c31c73862471bc1663acae"
"checksum syn 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0e7bedb3320d0f3035594b0b723c8a28d7d336a3eda3881db79e61d676fb644c"
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
"checksum unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9"
"checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20"
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@ -15,14 +15,18 @@
[package]
name = "lora"
version = "0.1.0"
name = "lorapipe"
version = "1.0.0"
authors = ["John Goerzen <jgoerzen@complete.org>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serial = "0.4"
serialport = "3.3.0"
log = "0.4"
simplelog = {version = "^0.7.4", default-features = false}
hex = "0.4.0"
crossbeam-channel = "0.3.9"
format_escape_default = "0.1.1"
structopt = "0.3"

29
README.md Normal file
View File

@ -0,0 +1,29 @@
# LoRa LoStik Pipe & Networking Tools
So you have a fantastic long-range LoRa device, such as a Lostik, and
you'd like to be able to do Unixy things with it -- maybe pipe stuff
across the radio, maybe run a network over it. That's what this is
for.
Please see the [comprehensive documentation](https://github.com/jgoerzen/lorapipe/blob/master/doc/lorapipe.1.md).
This requires a [LoStik](https://ronoth.com/lostik) or other Microchip
RN2903- or RN2483-based or device that has been updated to firmware 1.0.5. Details
in the documentation.
# Copyright
Copyright (C) 2019 John Goerzen <jgoerzen@complete.org>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

4
doc/Makefile Normal file
View File

@ -0,0 +1,4 @@
ALL: lorapipe.1
%.1: %.1.md
pandoc --standalone --to man $< -o $@

466
doc/lorapipe.1 Normal file
View File

@ -0,0 +1,466 @@
.\" Automatically generated by Pandoc 2.2.1
.\"
.TH "LORAPIPE" "1" "October 2019" "John Goerzen" "lorapipe Manual"
.hy
.SH NAME
.PP
lorapipe \- Transfer data and run a network over LoRa long\-range radios
.SH SYNOPSIS
.PP
\f[B]lorapipe\f[] [ \f[I]OPTIONS\f[] ] \f[B]PORT\f[] \f[B]COMMAND\f[] [
\f[I]command_options\f[] ]
.SH OVERVIEW
.PP
\f[B]lorapipe\f[] is designed to integrate LoRa long\-range radios into
a Unix/Linux system.
In particular, lorapipe can:
.IP \[bu] 2
Bidirectionally pipe data across a LoRa radio system
.IP \[bu] 2
Do an RF ping and report signal strength at each end
.IP \[bu] 2
Operate an AX.25 network using LoRa, and atop it, TCP/IP
.SH HARDWARE REQUIREMENTS
.PP
\f[B]lorapipe\f[] is designed to run with a Microchip RN2903/RN2483 as
implemented by LoStik.
.PP
Drivers for other hardware may be added in the future.
.PP
The Microchip firmware must be upgraded to 1.0.5 before running with
\f[B]lorapipe\f[].
Previous versions lacked the \f[C]radio\ rxstop\f[] command, which is a
severe limitation when receiving multiple packets rapidly.
.PP
See the documents tab for the
RN2093 (https://www.microchip.com/wwwproducts/en/RN2903) and the
firmware upgrade
guide (https://www.pocketmagic.net/rn2483-rn2903-firmware-upgrade-guide/)
\- note that the upgrade part is really finicky and you need the
\[lq]offset\[rq] file.
.SH PROTOCOL
.PP
The \f[B]lorapipe pipe\f[] command is the primary one of interest here.
It will receive data on stdin, break it up into LoRa\-sized packets (see
\f[B]\[en]maxpacketsize\f[]), and transmit it across the radio.
It also will receive data from the radio channel and send it to stdout.
No attempt at encryption or authentication is made; all packets
successfully decoded will be sent to stdout.
Authentication and filtering is left to other layers of the stack atop
\f[B]lorapipe\f[].
.PP
A thin layer atop \f[B]lorapipe pipe\f[] is \f[B]lorapipe kiss\f[],
which implements the AX.25 KISS protocol.
It transmits each KISS frame it receives as a LoRa frame, and
vice\-versa.
It performs rudimentary checking to ensure it is receiving valid KISS
data, and will not pass anything else to stdout.
This support can be used to build a TCP/IP network atop LoRa as will be
shown below.
Encryption and authentication could be added atop this by using tools
such as OpenVPN or SSH.
.PP
\f[B]lorapipe\f[] provides only the guarantees that LoRa itself does:
that raw LoRa frames which are decoded are intact, but not all frames
will be received.
It is somewhat akin to UDP in this sense.
Protocols such as UUCP, ZModem, or TCP can be layered atop
\f[B]lorapipe\f[] to transform this into a \[lq]reliable\[rq]
connection.
.SS Broadcast Use and Separate Frequencies
.PP
It is quite possible to use \f[B]lorapipe\f[] to broadcast data to
multiple listeners; an unlimited number of systems can run \f[B]lorapipe
pipe\f[] to receive data, and as long as there is nothing on stdin, they
will happily decode data received over the air without transmitting
anything.
.PP
Separate communication channels may be easily achieved by selecting
separate radio frequencies.
.SS Collision Mitigation
.PP
\f[B]lorapipe\f[] cannot provide collision detection or avoidance,
though it does impliement a collision mitigation strategy as described
below.
.PP
As LoRa radios are half\-duplex (they cannot receive while
transmitting), this poses challenges for quite a few applications that
expect full\-duplex communication or something like it.
In testing, a particular problem was observed with protocols that use
transmission windows and send data in packets.
These protocols send ACKs after a successful packet transmission, which
frequently collided with the next packet transmitted from the other
radio.
This caused serious performance degredations, and for some protocols,
complete failure.
.PP
There is no carrier detect signal from the LoRa radio.
Therefore, a turn\-based mechanism is implemented; with each frame
transmitted, a byte is prepended indicating whether the sender has more
data in queue to transmit or not.
The sender will continue transmitting until its transmit buffer is
empty.
When that condition is reached, the other end will begin transmitting
whatever is in its queue.
This enables protocols such as UUCP \[lq]g\[rq] and UUCP \[lq]i\[rq] to
work quite well.
.PP
A potential complication could arise if the \[lq]last\[rq] packet from
the transmitter never arrives at the receiver; the receiver might
therefore never take a turn to transmit.
To guard against this possibility, there is a timer, and after receiving
no packets for a certain amount of time, the receiver will assume it is
acceptable to transmit.
This timeout is set by the \f[B]\[en]eotwait\f[] option and defaults to
1000ms (1 second).
.PP
The signal about whether or not data remains in the queue takes the form
of a single byte prepended to every frame.
It is 0x00 if no data will follow immediately, and 0x01 if data exists
in the transmitters queue which will be sent immediately.
The receiving side processes this byte and strips it off before handing
the data to the application.
This byte is, however, visible under \f[B]\[en]debug\f[] mode, so you
can observe the protocol at this low level.
.SH RADIO PARAMETERS AND INITIALIZATION
.PP
The Microchip command reference, available at
<http://ww1.microchip.com/downloads/en/DeviceDoc/40001811A.pdf>,
describes the parameters available for the radio.
A LoRa data rate calculator is available at
<https://www.rfwireless-world.com/calculators/LoRa-Data-Rate-Calculator.html>
to give you a rough sense of the speed of different parameters.
In general, by sacrificing speed, you can increase range and robustness
of the signal.
The default initialization uses fairly slow and high\-range settings:
.IP
.nf
\f[C]
sys\ get\ ver
mac\ reset
mac\ pause
radio\ get\ mod
radio\ get\ freq
radio\ get\ pwr
radio\ get\ sf
radio\ get\ bw
radio\ get\ cr
radio\ get\ wdt
radio\ set\ pwr\ 20
radio\ set\ sf\ sf12
radio\ set\ bw\ 125
radio\ set\ cr\ 4/5
radio\ set\ wdt\ 60000
\f[]
.fi
.PP
The \f[C]get\f[] commands will cause the pre\-initialization settings to
be output to stderr if \f[C]\-\-debug\f[] is used.
A maximum speed init would look like this:
.IP
.nf
\f[C]
sys\ get\ ver
mac\ reset
mac\ pause
radio\ get\ mod
radio\ get\ freq
radio\ get\ pwr
radio\ get\ sf
radio\ get\ bw
radio\ get\ cr
radio\ get\ wdt
radio\ set\ pwr\ 20
radio\ set\ sf\ sf7
radio\ set\ bw\ 500
radio\ set\ cr\ 4/5
radio\ set\ wdt\ 60000
\f[]
.fi
.PP
You can craft your own parameters and pass them in with
\f[C]\-\-initfile\f[] to customize the performance of your RF link.
.PP
A particular hint: if \f[C]\-\-debug\f[] shows \f[C]radio_err\f[] after
a \f[C]radio\ rx\ 0\f[] command, the radio is seeing carrier but is
getting CRC errors decoding packets.
Increasing the code rate with \f[C]radio\ set\ cr\f[] to a higher value
such as \f[C]4/6\f[] or even \f[C]4/8\f[] will increase the FEC
redundancy and enable it to decode some of those packets.
Increasing code rate will not help if there is complete silence from the
radio during a transmission; for those situations, try decreasing
bandwidth or increasing the spreading factor.
Note that coderate \f[C]4/5\f[] to the radio is the same as \f[C]1\f[]
to the calculator, while \f[C]4/8\f[] is the same as \f[C]4\f[].
.SH PROTOCOL HINTS
.PP
Although \f[B]lorapipe pipe\f[] doesn't guarantee it preserves
application framing, in many cases it does.
For applications that have their own framing, it is highly desirable to
set their frame size to be less than the \f[B]lorapipe \&... pipe
\[en]maxpacketsize\f[] setting.
This will reduce the amount of data that would have to be retransmitted
due to lost frames.
.PP
As speed decreases, packet size should as well.
.SH APPLICATION HINTS
.SS SOCAT
.PP
The \f[B]socat\f[](1) program can be particularly helpful; it can
gateway TCP ports and various other sorts of things into
\f[B]lorapipe\f[].
This is helpful if the \f[B]lorapipe\f[] system is across a network from
the system you wish to run an application on.
\f[B]ssh\f[](1) can also be useful for this purpose.
.PP
A basic command might be like this:
.IP
.nf
\f[C]
socat\ TCP\-LISTEN:12345\ EXEC:\[aq]lorapipe\ /dev/ttyUSB0\ pipe\[aq]
\f[]
.fi
.PP
Some systems might require disabling buffering in some situations, or
using a pty.
In those instances, something like this may be in order:
.IP
.nf
\f[C]
socat\ TCP\-LISTEN:10104\ EXEC:\[aq]stdbuf\ \-i0\ \-o0\ \-e0\ lorapipe\ /dev/ttyUSB4\ pipe,pty,rawer\[aq]
\f[]
.fi
.SS UUCP
.PP
For UUCP, I recommend protocol \f[C]i\f[] with the default window\-size
setting.
Use as large of a packet size as you can; for slow links, perhaps 32, up
to around 100 for fast, high\-quality links.
(LoRa seems to not do well with packets above 100 bytes).
.PP
Protocol \f[C]g\f[] (or \f[C]G\f[] with a smaller packet size) can also
work, but won't work as well.
.PP
Make sure to specify \f[C]half\-duplex\ true\f[] in
\f[C]/etc/uucp/port\f[].
.PP
Here is an example of settings in \f[C]sys\f[]:
.IP
.nf
\f[C]
protocol\ i
protocol\-parameter\ i\ packet\-size\ 90
protocol\-parameter\ i\ timeout\ 30
chat\-timeout\ 60
\f[]
.fi
.PP
Note that UUCP protocol i adds 10 bytes of overhead per packet, so this
is designed to work with the default recommended packet size of 100.
.PP
Then in \f[C]/etc/uucp/port\f[]:
.IP
.nf
\f[C]
half\-duplex\ true
reliable\ false
\f[]
.fi
.SS YMODEM (and generic example of bidirectional pipe)
.PP
ZModem makes a poor fit for LoRa because its smallest block size is 1K.
YModem, however, uses a 128\-byte block size.
Here's an example of how to make it work.
Let's say we want to transmit /bin/true over the radio.
We could run this:
.IP
.nf
\f[C]
socat\ EXEC:\[aq]sz\ \-\-ymodem\ /bin/true\[aq]\ EXEC:\[aq]lorapipe\ /dev/ttyUSB0\ pipe\[aq]
\f[]
.fi
.PP
And on the receiving end:
.IP
.nf
\f[C]
socat\ EXEC:\[aq]rz\ \-\-ymodem\[aq]\ EXEC:\[aq]lorapipe\ /dev/ttyUSB0\ pipe\[aq]
\f[]
.fi
.PP
This approach can also be used with many other programs.
For instance, \f[C]uucico\ \-l\f[] for UUCP logins.
.SS KERMIT
.PP
Using the C\-kermit distribution (\f[B]apt\-get install ckermit\f[]),
you can configure for \f[B]lorapipe\f[] like this:
.IP
.nf
\f[C]
set\ receive\ packet\-length\ 90
set\ send\ packet\-length\ 90
set\ duplex\ half
set\ window\ 2
set\ receive\ timeout\ 10
set\ send\ timeout\ 10
\f[]
.fi
.PP
Then, on one side, run:
.IP
.nf
\f[C]
pipe\ lorapipe\ /dev/ttyUSB0\ pipe
Ctrl\-\\\ c
server
\f[]
.fi
.PP
And on the other:
.IP
.nf
\f[C]
pipe\ lorapipe\ /dev/ttyUSB0\ pipe
Ctrl\-\\\ c
\f[]
.fi
.PP
Now you can do things like \f[C]rdir\f[] (to see ls from the remote),
\f[C]get\f[], \f[C]put\f[], etc.
.SS DEBUGGING WITH CU
.PP
To interact directly with the modem, something like this will work:
.IP
.nf
\f[C]
cu\ \-h\ \-\-line\ /dev/ttyUSB0\ \-s\ 57600\ \-e\ \-o\ \-f\ \-\-nostop
\f[]
.fi
.SH INSTALLATION
.PP
\f[B]lorapipe\f[] is a Rust program and can be built by running
\f[B]\f[BC]cargo\ build\ \-\-release\f[B]\f[].
The executable will then be placed in \f[B]target/release/lorapipe\f[].
Rust can be easily installed from <https://www.rust-lang.org/>.
.SH INVOCATION
.PP
Every invocation of \f[B]lorapipe\f[] requires at least the name of a
serial port (for instance, \f[B]/dev/ttyUSB0\f[]) and a subcommand to
run.
.SH GLOBAL OPTIONS
.PP
These options may be specified for any command, and must be given before
the port and command on the command line.
.TP
.B \f[B]\-d\f[], \f[B]\[en]debug\f[]
Activate debug mode.
Details of program operation will be sent to stderr.
.RS
.RE
.TP
.B \f[B]\-h\f[], \f[B]\[en]help\f[]
Display brief help on program operation.
.RS
.RE
.TP
.B \f[B]\[en]readqual\f[]
Attempt to read and log information about the RF quality of incoming
packets after each successful packet received.
There are some corner cases where this is not possible.
The details will be logged with \f[B]lorapipe\f[]'s logging facility,
and are therefore only visible if \f[B]\[en]debug\f[] is also used.
.RS
.RE
.TP
.B \f[B]\-V\f[], \f[B]\[en]version\f[]
Display the version number of \f[B]lorapipe\f[].
.RS
.RE
.TP
.B \f[B]\[en]eotwait\f[] \f[I]TIME\f[]
The amount of time in milliseconds to wait after receiving a packet that
indicates more are coming before giving up on receiving an additional
packet and proceeding to transmit.
Ideally this would be at least the amount of time it takes to transmit 2
packets.
Default: 1000.
.RS
.RE
.TP
.B \f[B]\[en]initfile\f[] \f[I]FILE\f[]
A file listing commands to send to the radio to initialize it.
If not given, a default set will be used.
.RS
.RE
.TP
.B \f[B]\[en]txwait\f[] \f[I]TIME\f[]
Amount of time in milliseconds to pause before transmitting each packet.
Due to processing delays on the receiving end, packets cannot be
transmitted immediately back to back.
Increase this if you are seeing frequent receive errors for
back\-to\-back packets, which may be indicative of a late listen.
Experimentation has shown that a value of 120 is needed for very large
packets, and is the default.
You may be able to use 50ms or less if you are sending small packets.
In my testing, with 100\-byte packets, a txwait of 50 was generally
sufficient.
.RS
.RE
.TP
.B \f[I]PORT\f[]
The name of the serial port to which the radio is attached.
.RS
.RE
.TP
.B \f[I]COMMAND\f[]
The subcommand which will be executed.
.RS
.RE
.SH SUBCOMMANDS
.SS lorapipe \&... pipe
.PP
The \f[B]pipe\f[] subcommand is the main workhorse of the application
and is described extensively above.
It has one optional parameter:
.TP
.B \f[B]\[en]maxpacketsize\f[] \f[I]BYTES\f[]
The maximum frame size, in the range of 10 \- 250.
The actual frame transmitted over the air will be one byte larger due to
\f[B]lorapipe\f[] collision mitigation as described above.
Experimentation myself, and reports from others, suggests that LoRa
works best when this is 100 or less.
.RS
.RE
.SS lorapipe \&... ping
.PP
The \f[B]ping\f[] subcommand will transmit a simple line of text every
10 seconds including an increasing counter.
It can be displayed at the other end with \f[B]lorapipe \&... pipe\f[]
or reflected with \f[B]lorapipe \&... pong\f[].
.SS lorapipe \&... pong
.PP
The \f[B]pong\f[] subcommand receives packets and crafts a reply.
It is intended to be used with \f[B]lorapipe \&... ping\f[].
Its replies include the signal quality SNR and RSSI if available.
.SH AUTHOR
.PP
John Goerzen <jgoerzen@complete.org>
.SH COPYRIGHT AND LICENSE
.PP
Copyright (C) 2019 John Goerzen <jgoerzen\@complete.org
.PP
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
.PP
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
.PP
You should have received a copy of the GNU General Public License along
with this program.
If not, see <http://www.gnu.org/licenses/>.
.SH AUTHORS
John Goerzen.

413
doc/lorapipe.1.md Normal file
View File

@ -0,0 +1,413 @@
% LORAPIPE(1) John Goerzen | lorapipe Manual
% John Goerzen
% October 2019
# NAME
lorapipe - Transfer data and run a network over LoRa long-range radios
# SYNOPSIS
**lorapipe** [ *OPTIONS* ] **PORT** **COMMAND** [ *command_options* ]
# OVERVIEW
**lorapipe** is designed to integrate LoRa long-range radios into a
Unix/Linux system. In particular, lorapipe can:
- Bidirectionally pipe data across a LoRa radio system
- Do an RF ping and report signal strength at each end
- Operate an AX.25 network using LoRa, and atop it, TCP/IP
# HARDWARE REQUIREMENTS
**lorapipe** is designed to run with a Microchip RN2903/RN2483 as
implemented by LoStik.
Drivers for other hardware may be added in the future.
The Microchip firmware must be upgraded to 1.0.5 before running with
**lorapipe**. Previous versions lacked the `radio rxstop` command,
which is a severe limitation when receiving multiple packets rapidly.
See the documents tab for the
[RN2093](https://www.microchip.com/wwwproducts/en/RN2903) and the
[firmware upgrade
guide](https://www.pocketmagic.net/rn2483-rn2903-firmware-upgrade-guide/) -
note that the upgrade part is really finicky and you need the "offset" file.
# PROTOCOL
The **lorapipe pipe** command is the primary one of interest here. It
will receive data on stdin, break it up into LoRa-sized packets (see
**--maxpacketsize**), and transmit it across the radio. It also will
receive data from the radio channel and send it to stdout. No attempt
at encryption or authentication is made; all packets successfully
decoded will be sent to stdout. Authentication and filtering is left
to other layers of the stack atop **lorapipe**.
A thin layer atop **lorapipe pipe** is **lorapipe kiss**, which
implements the AX.25 KISS protocol. It transmits each KISS frame it
receives as a LoRa frame, and vice-versa. It performs rudimentary
checking to ensure it is receiving valid KISS data, and will not pass
anything else to stdout. This support can be used to build a TCP/IP
network atop LoRa as will be shown below. Encryption and
authentication could be added atop this by using tools such as OpenVPN
or SSH.
**lorapipe** provides only the guarantees that LoRa itself does: that
raw LoRa frames which are decoded are intact, but not all frames will
be received. It is somewhat akin to UDP in this sense. Protocols
such as UUCP, ZModem, or TCP can be layered atop **lorapipe** to
transform this into a "reliable" connection.
## Broadcast Use and Separate Frequencies
It is quite possible to use **lorapipe** to broadcast data to multiple
listeners; an unlimited number of systems can run **lorapipe pipe**
to receive data, and as long as there is nothing on stdin, they will
happily decode data received over the air without transmitting
anything.
Separate communication channels may be easily achieved by selecting
separate radio frequencies.
## Collision Mitigation
**lorapipe** cannot provide collision detection or avoidance, though
it does impliement a collision mitigation strategy as described below.
As LoRa radios are half-duplex (they cannot receive while
transmitting), this poses challenges for quite a few applications that
expect full-duplex communication or something like it. In testing, a
particular problem was observed with protocols that use transmission
windows and send data in packets. These protocols send ACKs after a
successful packet transmission, which frequently collided with the
next packet transmitted from the other radio. This caused serious
performance degredations, and for some protocols, complete failure.
There is no carrier detect signal from the LoRa radio. Therefore, a
turn-based mechanism is implemented; with each frame transmitted, a
byte is prepended indicating whether the sender has more data in queue
to transmit or not. The sender will continue transmitting until its
transmit buffer is empty. When that condition is reached, the
other end will begin transmitting whatever is in its queue. This
enables protocols such as UUCP "g" and UUCP "i" to work quite well.
A potential complication could arise if the "last" packet from the
transmitter never arrives at the receiver; the receiver might
therefore never take a turn to transmit. To guard against this
possibility, there is a timer, and after receiving no packets for a
certain amount of time, the receiver will assume it is acceptable to
transmit. This timeout is set by the **--eotwait** option and
defaults to 1000ms (1 second).
The signal about whether or not data remains in the queue takes the
form of a single byte prepended to every frame. It is 0x00 if no data
will follow immediately, and 0x01 if data exists in the transmitters
queue which will be sent immediately. The receiving side processes
this byte and strips it off before handing the data to the
application. This byte is, however, visible under **--debug** mode,
so you can observe the protocol at this low level.
# RADIO PARAMETERS AND INITIALIZATION
The Microchip command reference, available at
<http://ww1.microchip.com/downloads/en/DeviceDoc/40001811A.pdf>,
describes the parameters available for the radio. A LoRa data rate
calculator is available at
<https://www.rfwireless-world.com/calculators/LoRa-Data-Rate-Calculator.html>
to give you a rough sense of the speed of different parameters. In
general, by sacrificing speed, you can increase range and robustness
of the signal. The default initialization uses fairly slow and
high-range settings:
```
sys get ver
mac reset
mac pause
radio get mod
radio get freq
radio get pwr
radio get sf
radio get bw
radio get cr
radio get wdt
radio set pwr 20
radio set sf sf12
radio set bw 125
radio set cr 4/5
radio set wdt 60000
```
The `get` commands will cause the pre-initialization settings to be
output to stderr if `--debug` is used. A maximum speed init would
look like this:
```
sys get ver
mac reset
mac pause
radio get mod
radio get freq
radio get pwr
radio get sf
radio get bw
radio get cr
radio get wdt
radio set pwr 20
radio set sf sf7
radio set bw 500
radio set cr 4/5
radio set wdt 60000
```
You can craft your own parameters and pass them in with `--initfile`
to customize the performance of your RF link.
A particular hint: if `--debug` shows `radio_err` after a `radio rx 0`
command, the radio is seeing carrier but is getting CRC errors
decoding packets. Increasing the code rate with `radio set cr` to a
higher value such as `4/6` or even `4/8` will increase the FEC
redundancy and enable it to decode some of those packets. Increasing
code rate will not help if there is complete silence from the radio
during a transmission; for those situations, try decreasing bandwidth
or increasing the spreading factor. Note that coderate `4/5` to the
radio is the same as `1` to the calculator, while `4/8` is the same as
`4`.
# PROTOCOL HINTS
Although **lorapipe pipe** doesn't guarantee it preserves application
framing, in many cases it does. For applications that have their own
framing, it is highly desirable to set their frame size to be less
than the **lorapipe ... pipe --maxpacketsize** setting. This will
reduce the amount of data that would have to be retransmitted due to
lost frames.
As speed decreases, packet size should as well.
# APPLICATION HINTS
## SOCAT
The **socat**(1) program can be particularly helpful; it can gateway TCP
ports and various other sorts of things into **lorapipe**. This is
helpful if the **lorapipe** system is across a network from the system
you wish to run an application on. **ssh**(1) can also be useful for
this purpose.
A basic command might be like this:
```
socat TCP-LISTEN:12345 EXEC:'lorapipe /dev/ttyUSB0 pipe'
```
Some systems might require disabling buffering in some situations, or
using a pty. In those instances, something like this may be in order:
```
socat TCP-LISTEN:10104 EXEC:'stdbuf -i0 -o0 -e0 lorapipe /dev/ttyUSB4 pipe,pty,rawer'
```
## UUCP
For UUCP, I recommend protocol `i` with the default window-size
setting. Use as large of a packet size as you can; for slow links,
perhaps 32, up to around 100 for fast, high-quality links. (LoRa seems to
not do well with packets above 100 bytes).
Protocol `g` (or `G` with a smaller packet size) can also work, but
won't work as well.
Make sure to specify `half-duplex true` in `/etc/uucp/port`.
Here is an example of settings in `sys`:
```
protocol i
protocol-parameter i packet-size 90
protocol-parameter i timeout 30
chat-timeout 60
```
Note that UUCP protocol i adds 10 bytes of overhead per packet, so
this is designed to work with the default recommended packet size of
100.
Then in `/etc/uucp/port`:
```
half-duplex true
reliable false
```
## YMODEM (and generic example of bidirectional pipe)
ZModem makes a poor fit for LoRa because its smallest block size is
1K. YModem, however, uses a 128-byte block size. Here's an example
of how to make it work. Let's say we want to transmit /bin/true over
the radio. We could run this:
```
socat EXEC:'sz --ymodem /bin/true' EXEC:'lorapipe /dev/ttyUSB0 pipe'
```
And on the receiving end:
```
socat EXEC:'rz --ymodem' EXEC:'lorapipe /dev/ttyUSB0 pipe'
```
This approach can also be used with many other programs. For
instance, `uucico -l` for UUCP logins.
## KERMIT
Using the C-kermit distribution (**apt-get install ckermit**), you can
configure for **lorapipe** like this:
```
set receive packet-length 90
set send packet-length 90
set duplex half
set window 2
set receive timeout 10
set send timeout 10
```
Then, on one side, run:
```
pipe lorapipe /dev/ttyUSB0 pipe
Ctrl-\ c
server
```
And on the other:
```
pipe lorapipe /dev/ttyUSB0 pipe
Ctrl-\ c
```
Now you can do things like `rdir` (to see ls from the remote), `get`,
`put`, etc.
## DEBUGGING WITH CU
To interact directly with the modem, something like this will work:
```
cu -h --line /dev/ttyUSB0 -s 57600 -e -o -f --nostop
```
# INSTALLATION
**lorapipe** is a Rust program and can be built by running **`cargo
build --release`**. The executable will then be placed in
**target/release/lorapipe**. Rust can be easily installed from
<https://www.rust-lang.org/>.
# INVOCATION
Every invocation of **lorapipe** requires at least the name of a
serial port (for instance, **/dev/ttyUSB0**) and a subcommand to run.
# GLOBAL OPTIONS
These options may be specified for any command, and must be given
before the port and command on the command line.
**-d**, **--debug**
: Activate debug mode. Details of program operation will be sent to
stderr.
**-h**, **--help**
: Display brief help on program operation.
**--readqual**
: Attempt to read and log information about the RF quality of
incoming packets after each successful packet received. There are
some corner cases where this is not possible. The details will be
logged with **lorapipe**'s logging facility, and are therefore only
visible if **--debug** is also used.
**-V**, **--version**
: Display the version number of **lorapipe**.
**--eotwait** *TIME*
: The amount of time in milliseconds to wait after receiving a packet
that indicates more are coming before giving up on receiving an
additional packet and proceeding to transmit. Ideally this would
be at least the amount of time it takes to transmit 2 packets.
Default: 1000.
**--initfile** *FILE*
: A file listing commands to send to the radio to initialize it.
If not given, a default set will be used.
**--txwait** *TIME*
: Amount of time in milliseconds to pause before transmitting each
packet. Due to processing delays on the receiving end, packets
cannot be transmitted immediately back to back. Increase this if
you are seeing frequent receive errors for back-to-back packets,
which may be indicative of a late listen. Experimentation has
shown that a value of 120 is needed for very large packets, and is
the default. You may be able to use 50ms or less if you are
sending small packets. In my testing, with 100-byte packets,
a txwait of 50 was generally sufficient.
*PORT*
: The name of the serial port to which the radio is attached.
*COMMAND*
: The subcommand which will be executed.
# SUBCOMMANDS
## lorapipe ... pipe
The **pipe** subcommand is the main workhorse of the application and
is described extensively above. It has one optional parameter:
**--maxpacketsize** *BYTES*
: The maximum frame size, in the range of 10 - 250. The actual frame
transmitted over the air will be one byte larger due to
**lorapipe** collision mitigation as described above.
Experimentation myself, and reports from others, suggests that LoRa
works best when this is 100 or less.
## lorapipe ... ping
The **ping** subcommand will transmit a simple line of text every 10
seconds including an increasing counter. It can be displayed at the
other end with **lorapipe ... pipe** or reflected with **lorapipe
... pong**.
## lorapipe ... pong
The **pong** subcommand receives packets and crafts a reply. It is
intended to be used with **lorapipe ... ping**. Its replies include
the signal quality SNR and RSSI if available.
# AUTHOR
John Goerzen <jgoerzen@complete.org>
# COPYRIGHT AND LICENSE
Copyright (C) 2019 John Goerzen <jgoerzen@complete.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

15
init-fast.txt Normal file
View File

@ -0,0 +1,15 @@
sys get ver
mac reset
mac pause
radio get mod
radio get freq
radio get pwr
radio get sf
radio get bw
radio get cr
radio get wdt
radio set pwr 20
radio set sf sf7
radio set bw 500
radio set cr 4/5
radio set wdt 60000

15
init-slow.txt Normal file
View File

@ -0,0 +1,15 @@
sys get ver
mac reset
mac pause
radio get mod
radio get freq
radio get pwr
radio get sf
radio get bw
radio get cr
radio get wdt
radio set pwr 20
radio set sf sf12
radio set bw 125
radio set cr 4/5
radio set wdt 60000

15
init.txt Normal file
View File

@ -0,0 +1,15 @@
sys get ver
mac reset
mac pause
radio get mod
radio get freq
radio get pwr
radio get sf
radio get bw
radio get cr
radio get wdt
radio set pwr 20
radio set sf sf7
radio set bw 500
radio set cr 4/5
radio set wdt 60000

15
init3.txt Normal file
View File

@ -0,0 +1,15 @@
sys get ver
mac reset
mac pause
radio get mod
radio get freq
radio get pwr
radio get sf
radio get bw
radio get cr
radio get wdt
radio set pwr 20
radio set sf sf12
radio set bw 250
radio set cr 4/8
radio set wdt 60000

15
init4.txt Normal file
View File

@ -0,0 +1,15 @@
sys get ver
mac reset
mac pause
radio get mod
radio get freq
radio get pwr
radio get sf
radio get bw
radio get cr
radio get wdt
radio set pwr 20
radio set sf sf12
radio set bw 500
radio set cr 4/8
radio set wdt 60000

29
radio-default.txt Normal file
View File

@ -0,0 +1,29 @@
03:46:56 [ INFO] lora starting
03:46:56 [TRACE] (0) lora::ser: [src/ser.rs:63] /dev/ttyUSB4 SEROUT: sys get ver
03:46:56 [TRACE] (0) lora::ser: [src/ser.rs:56] /dev/ttyUSB4 SERIN: RN2903 1.0.5 Nov 06 2018 10:45:27
03:46:56 [TRACE] (0) lora::ser: [src/ser.rs:63] /dev/ttyUSB4 SEROUT: mac pause
03:46:56 [TRACE] (0) lora::ser: [src/ser.rs:56] /dev/ttyUSB4 SERIN: 4294967245
03:46:56 [TRACE] (0) lora::ser: [src/ser.rs:63] /dev/ttyUSB4 SEROUT: radio get mod
03:46:56 [TRACE] (0) lora::ser: [src/ser.rs:56] /dev/ttyUSB4 SERIN: lora
03:46:56 [TRACE] (0) lora::ser: [src/ser.rs:63] /dev/ttyUSB4 SEROUT: radio get freq
03:46:56 [TRACE] (0) lora::ser: [src/ser.rs:56] /dev/ttyUSB4 SERIN: 923300000
03:46:56 [TRACE] (0) lora::ser: [src/ser.rs:63] /dev/ttyUSB4 SEROUT: radio get pwr
03:46:56 [TRACE] (0) lora::ser: [src/ser.rs:56] /dev/ttyUSB4 SERIN: 2
03:46:56 [TRACE] (0) lora::ser: [src/ser.rs:63] /dev/ttyUSB4 SEROUT: radio get sf
03:46:56 [TRACE] (0) lora::ser: [src/ser.rs:56] /dev/ttyUSB4 SERIN: sf12
03:46:56 [TRACE] (0) lora::ser: [src/ser.rs:63] /dev/ttyUSB4 SEROUT: radio get bw
03:46:56 [TRACE] (0) lora::ser: [src/ser.rs:56] /dev/ttyUSB4 SERIN: 125
03:46:56 [TRACE] (0) lora::ser: [src/ser.rs:63] /dev/ttyUSB4 SEROUT: radio get cr
03:46:56 [TRACE] (0) lora::ser: [src/ser.rs:56] /dev/ttyUSB4 SERIN: 4/5
03:46:56 [TRACE] (0) lora::ser: [src/ser.rs:63] /dev/ttyUSB4 SEROUT: radio get wdt
03:46:56 [TRACE] (0) lora::ser: [src/ser.rs:56] /dev/ttyUSB4 SERIN: 15000
03:46:56 [TRACE] (0) lora::ser: [src/ser.rs:63] /dev/ttyUSB4 SEROUT: radio set pwr 20
03:46:56 [TRACE] (0) lora::ser: [src/ser.rs:56] /dev/ttyUSB4 SERIN: ok
03:46:56 [TRACE] (0) lora::ser: [src/ser.rs:63] /dev/ttyUSB4 SEROUT: radio set sf sf12
03:46:56 [TRACE] (0) lora::ser: [src/ser.rs:56] /dev/ttyUSB4 SERIN: ok
03:46:56 [TRACE] (0) lora::ser: [src/ser.rs:63] /dev/ttyUSB4 SEROUT: radio set bw 125
03:46:56 [TRACE] (0) lora::ser: [src/ser.rs:56] /dev/ttyUSB4 SERIN: ok
03:46:56 [TRACE] (0) lora::ser: [src/ser.rs:63] /dev/ttyUSB4 SEROUT: radio set cr 4/5
03:46:56 [TRACE] (0) lora::ser: [src/ser.rs:56] /dev/ttyUSB4 SERIN: ok
03:46:56 [TRACE] (0) lora::ser: [src/ser.rs:63] /dev/ttyUSB4 SEROUT: radio set wdt 60000
03:46:56 [TRACE] (0) lora::ser: [src/ser.rs:56] /dev/ttyUSB4 SERIN: ok

58
src/kiss.rs Normal file
View File

@ -0,0 +1,58 @@
/*
Copyright (C) 2019 John Goerzen <jgoerzen@complete.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use std::io;
use std::io::{Read, Write};
use crate::lorastik::{LoraStik, ReceivedFrames};
use crossbeam_channel;
const MAXFRAME: usize = 200;
const FEND: u8 = 0xC0;
const FESC: u8 = 0xDB;
const TFEND: u8 = 0xDC;
const TFESC: u8 = 0xDD;
/// A thread for stdin processing
pub fn stdintolora(ls: &mut LoraStik) -> io::Result<()> {
let stdin = io::stdin();
let mut br = io::BufReader::new(stdin);
let mut buf = vec![0u8; 8192];
loop {
let res = br.read(&mut buf)?;
if res == 0 {
// EOF
return Ok(());
}
for chunk in buf[0..res].chunks(MAXFRAME) {
ls.transmit(&chunk);
}
}
}
pub fn loratostdout(receiver: crossbeam_channel::Receiver<ReceivedFrames>) -> io::Result<()> {
let mut stdout = io::stdout();
loop {
let data = receiver.recv().unwrap();
stdout.write_all(&data.0)?;
stdout.flush()?;
}
}

365
src/lorastik.rs Normal file
View File

@ -0,0 +1,365 @@
/*
Copyright (C) 2019 John Goerzen <jgoerzen@complete.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use crate::ser::LoraSer;
use log::*;
use std::fs;
use std::io::{BufRead, BufReader, Error, ErrorKind};
use std::io;
use crossbeam_channel;
use hex;
use std::thread;
use std::time::{Duration, Instant};
// use format_escape_default::format_escape_default;
use std::path::PathBuf;
pub fn mkerror(msg: &str) -> Error {
Error::new(ErrorKind::Other, msg)
}
/// Received frames. The option is populated only if
/// readqual is true, and reflects the SNR and RSSI of the
/// received packet.
#[derive(Clone, Debug, PartialEq)]
pub struct ReceivedFrames(pub Vec<u8>, pub Option<(String, String)>);
#[derive(Clone)]
pub struct LoraStik {
ser: LoraSer,
// Lines coming from the radio
readerlinesrx: crossbeam_channel::Receiver<String>,
// Frames going to the app
readeroutput: crossbeam_channel::Sender<ReceivedFrames>,
// Blocks to transmit
txblockstx: crossbeam_channel::Sender<Vec<u8>>,
txblocksrx: crossbeam_channel::Receiver<Vec<u8>>,
// Whether or not to read quality data from the radio
readqual: bool,
// Whether we must delay before transmit. The Instant
// reflects the moment when the delay should end.
txdelay: Option<Instant>,
// The wait before transmitting. Initialized from
// [`txwait`].
txwait: Duration,
// The transmit prevention timeout. Initialized from
// [`eotwait`].
eotwait: Duration,
}
/// Reads the lines from the radio and sends them down the channel to
/// the processing bits.
fn readerlinesthread(mut ser: LoraSer, tx: crossbeam_channel::Sender<String>) {
loop {
let line = ser.readln().expect("Error reading line");
if let Some(l) = line {
tx.send(l).unwrap();
} else {
debug!("{:?}: EOF", ser.portname);
return;
}
}
}
/// Assert that a given response didn't indicate an EOF, and that it
/// matches the given text. Return an IOError if either of these
/// conditions aren't met. The response type is as given by
/// ['ser::LoraSer::readln'].
pub fn assert_response(resp: String, expected: String) -> io::Result<()> {
if resp == expected {
Ok(())
} else {
Err(mkerror(&format!("Unexpected response: got {}, expected {}", resp, expected)))
}
}
impl LoraStik {
/// Creates a new LoraStik. Returns an instance to be used for sending,
/// as well as a separate receiver to be used in a separate thread to handle
/// incoming frames. The bool specifies whether or not to read the quality
/// parameters after a read.
pub fn new(ser: LoraSer, readqual: bool, txwait: u64, eotwait: u64) -> (LoraStik, crossbeam_channel::Receiver<ReceivedFrames>) {
let (readerlinestx, readerlinesrx) = crossbeam_channel::unbounded();
let (txblockstx, txblocksrx) = crossbeam_channel::unbounded();
let (readeroutput, readeroutputreader) = crossbeam_channel::unbounded();
let ser2 = ser.clone();
thread::spawn(move || readerlinesthread(ser2, readerlinestx));
(LoraStik { readqual, ser, readeroutput, readerlinesrx, txblockstx, txblocksrx,
txdelay: None,
txwait: Duration::from_millis(txwait),
eotwait: Duration::from_millis(eotwait)}, readeroutputreader)
}
/// Utility to read the response from initialization
fn initresp(&mut self) -> io::Result<()> {
let line = self.readerlinesrx.recv().unwrap();
if line == "invalid_param" {
Err(mkerror("Bad response from radio during initialization"))
} else {
Ok(())
}
}
pub fn radiocfg(&mut self, initfile: Option<PathBuf>) -> io::Result<()> {
// First, send it an invalid command. Then, consume everything it sends back
self.ser.writeln(String::from("INVALIDCOMMAND"))?;
// Give it a chance to do its thing.
thread::sleep(Duration::from_secs(1));
// Consume all data.
while let Ok(_) = self.readerlinesrx.try_recv() {
}
debug!("Configuring radio");
let default = vec![
"sys get ver",
"mac reset",
"mac pause",
"radio get mod",
"radio get freq",
"radio get pwr",
"radio get sf",
"radio get bw",
"radio get cr",
"radio get wdt",
"radio set pwr 20",
"radio set sf sf12",
"radio set bw 125",
"radio set cr 4/5",
"radio set wdt 60000"];
let initlines: Vec<String> = if let Some(file) = initfile {
let f = fs::File::open(file)?;
let reader = BufReader::new(f);
reader.lines().map(|l| l.unwrap()).collect()
} else {
default.iter().map(|l| String::from(*l)).collect()
};
for line in initlines {
if line.len() > 0 {
self.ser.writeln(line)?;
self.initresp()?;
}
}
Ok(())
}
/// Utililty function to handle actual sending. Assumes radio is idle.
fn dosend(&mut self, data: Vec<u8>) -> io::Result<()> {
// Give receiver a change to process.
thread::sleep(self.txwait);
let mut flag: u8 = 0;
if !self.txblocksrx.is_empty() {
flag = 1;
}
// Now, send the mesage.
let txstr = format!("radio tx {}{}", hex::encode([flag]), hex::encode(data));
self.ser.writeln(txstr)?;
// We get two responses from this.... though sometimes a lingering radio_err also.
let mut resp = self.readerlinesrx.recv().unwrap();
if resp == String::from("radio_err") {
resp = self.readerlinesrx.recv().unwrap();
}
assert_response(resp, String::from("ok"))?;
// Second.
self.readerlinesrx.recv().unwrap(); // normally radio_tx_ok
Ok(())
}
// Receive a message from the incoming radio channel and process it.
fn handlerx(&mut self, msg: String, readqual: bool) -> io::Result<()> {
if msg.starts_with("radio_rx ") {
if let Ok(mut decoded) = hex::decode(&msg.as_bytes()[10..]) {
// trace!("DECODED: {}", format_escape_default(&decoded));
let radioqual = if readqual {
self.ser.writeln(String::from("radio get snr"))?;
let snr = self.readerlinesrx.recv().unwrap();
self.ser.writeln(String::from("radio get rssi"))?;
let rssi = self.readerlinesrx.recv().unwrap();
Some((snr, rssi))
} else {
None
};
let flag = decoded.remove(0); // Remove the flag from the vec
if flag == 1 {
// More data is coming
self.txdelay = Some(Instant::now() + self.eotwait);
} else {
self.txdelay = None;
}
debug!("handlerx: txdelay set to {:?}", self.txdelay);
self.readeroutput.send(ReceivedFrames(decoded, radioqual)).unwrap();
} else {
return Err(mkerror("Error with hex decoding"));
}
}
// Might get radio_err here. That's harmless.
Ok(())
}
// Whether or not a txdelay prevents transmit at this time. None if
// we are cleared to transmit; Some(Duration) gives the amount of time
// we'd have to wait otherwise.
fn txdelayrequired(&mut self) -> Option<Duration> {
debug!("txdelayrequired: self.txdelay = {:?}", self.txdelay);
match self.txdelay {
None => None,
Some(delayend) => {
let now = Instant::now();
if now >= delayend {
// We're past the delay. Clear it and return.
debug!("txdelayrequired: past the required delay");
self.txdelay = None;
None
} else {
// Indicate we're still blocked.
debug!("txdelayrequired: required delay {:?}", delayend - now);
Some(delayend - now)
}
}
}
}
fn enterrxmode(&mut self) -> io::Result<()> {
// Enter read mode
self.ser.writeln(String::from("radio rx 0"))?;
let mut response = self.readerlinesrx.recv().unwrap();
// For some reason, sometimes we get a radio_err here, then an OK. Ignore it.
if response == String::from("radio_err") {
response = self.readerlinesrx.recv().unwrap();
}
assert_response(response, String::from("ok"))?;
Ok(())
}
fn rxstop(&mut self) -> io::Result<()> {
self.ser.writeln(String::from("radio rxstop"))?;
let checkresp = self.readerlinesrx.recv().unwrap();
if checkresp.starts_with("radio_rx ") {
// We had a race. A packet was coming in. Decode and deal with it,
// then look for the 'ok' from rxstop. We can't try to read the quality in
// this scenario.
self.handlerx(checkresp, false)?;
self.readerlinesrx.recv().unwrap(); // used to pop this into checkresp, but no need now.
}
// Now, checkresp should hold 'ok'.
// It might not be; I sometimes see radio_err here. it's OK too.
// assert_response(checkresp, String::from("ok"))?;
Ok(())
}
pub fn readerthread(&mut self) -> io::Result<()> {
loop {
// First, check to see if we're allowed to transmit. If not, just
// try to read and ignore all else.
if let Some(delayamt) = self.txdelayrequired() {
// We can't transmit yet. Just read, but with a time box.
self.enterrxmode()?;
let res = self.readerlinesrx.recv_timeout(delayamt);
match res {
Ok(msg) => {
self.handlerx(msg, self.readqual)?;
continue;
},
Err(e) => {
if e.is_timeout() {
debug!("readerthread: txdelay timeout expired");
self.txdelay = None;
// Now we can fall through to the rest of the logic - already in read mode.
} else {
res.unwrap(); // disconnected - crash
}
}
}
} else {
// We are allowed to transmit.
// Do we have anything to send? Check at the top and keep checking
// here so we send as much as possible before going back into read
// mode.
let r = self.txblocksrx.try_recv();
match r {
Ok(data) => {
self.dosend(data)?;
continue;
},
Err(e) => {
if e.is_disconnected() {
// other threads crashed
r.unwrap();
}
// Otherwise - nothing to write, go on through.
}
}
self.enterrxmode()?;
}
// At this point, we're in rx mode with no timeout.
// Now we wait for either a write request or data.
let mut sel = crossbeam_channel::Select::new();
let readeridx = sel.recv(&self.readerlinesrx);
let blocksidx = sel.recv(&self.txblocksrx);
match sel.ready() {
i if i == readeridx => {
// We have data coming in from the radio.
let msg = self.readerlinesrx.recv().unwrap();
self.handlerx(msg, self.readqual)?;
},
i if i == blocksidx => {
// We have something to send. Stop the receiver and then go
// back to the top of the loop to handle it.
self.rxstop()?;
},
_ => panic!("Invalid response from sel.ready()"),
}
}
}
pub fn transmit(&mut self, data: &[u8]) {
self.txblockstx.send(data.to_vec()).unwrap();
}
}

View File

@ -16,6 +16,103 @@
*/
fn main() {
println!("Hello, world!");
use simplelog::*;
use std::io;
use log::*;
use std::thread;
mod ser;
mod lorastik;
mod pipe;
mod ping;
use std::path::PathBuf;
use structopt::StructOpt;
#[derive(Debug, StructOpt)]
#[structopt(name = "lorapipe", about = "Tools for LoRa radios", author = "John Goerzen <jgoerzen@complete.org>")]
struct Opt {
/// Activate debug mode
// short and long flags (-d, --debug) will be deduced from the field's name
#[structopt(short, long)]
debug: bool,
/// Read and log quality data after receiving packets
#[structopt(long)]
readqual: bool,
/// Radio initialization command file
#[structopt(long, parse(from_os_str))]
initfile: Option<PathBuf>,
/// Amount of time (ms) to pause before transmitting a packet
/* The
main purpose of this is to give the othe rradio a chance to finish
decoding the previous packet, send it to the OS, and re-enter RX mode.
A secondary purpose is to give the duplex logic a chance to see if
anything else is coming in. Given in ms.
*/
#[structopt(long, default_value = "120")]
txwait: u64,
/// Amount of time (ms) to wait for end-of-transmission signal before transmitting
/* The amount of time to wait before transmitting after receiving a
packet that indicated more data was forthcoming. The purpose of this is
to compensate for a situation in which the "last" incoming packet was lost,
to prevent the receiver from waiting forever for more packets before
transmitting. Given in ms. */
#[structopt(long, default_value = "1000")]
eotwait: u64,
#[structopt(parse(from_os_str))]
/// Serial port to use to communicate with radio
port: PathBuf,
#[structopt(subcommand)]
cmd: Command
}
#[derive(Debug, StructOpt)]
enum Command {
/// Pipe data across raios
Pipe {
/// Maximum frame size sent to radio [10..250]
#[structopt(long, default_value = "100")]
maxpacketsize: usize,
},
/// Transmit ping requests
Ping,
/// Receive ping requests and transmit pongs
Pong,
}
fn main() {
let opt = Opt::from_args();
if opt.debug {
WriteLogger::init(LevelFilter::Trace, Config::default(), io::stderr()).expect("Failed to init log");
}
info!("lora starting");
let loraser = ser::LoraSer::new(opt.port).expect("Failed to initialize serial port");
let (mut ls, radioreceiver) = lorastik::LoraStik::new(loraser, opt.readqual, opt.txwait, opt.eotwait);
ls.radiocfg(opt.initfile).expect("Failed to configure radio");
let mut ls2 = ls.clone();
thread::spawn(move || ls2.readerthread().expect("Failure in readerthread"));
match opt.cmd {
Command::Pipe{ maxpacketsize } => {
thread::spawn(move || pipe::stdintolora(&mut ls, maxpacketsize).expect("Failure in stdintolora"));
pipe::loratostdout(radioreceiver).expect("Failure in loratostdout");
},
Command::Ping => {
thread::spawn(move || ping::genpings(&mut ls).expect("Failure in genpings"));
pipe::loratostdout(radioreceiver).expect("Failure in loratostdout");
},
Command::Pong => {
ping::pong(&mut ls, radioreceiver).expect("Failure in loratostdout");
}
}
}

46
src/ping.rs Normal file
View File

@ -0,0 +1,46 @@
/*
Copyright (C) 2019 John Goerzen <jgoerzen@complete.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use std::io;
use crate::lorastik::{LoraStik, ReceivedFrames};
use crossbeam_channel;
use std::thread;
use std::time::Duration;
const INTERVAL: u64 = 5;
pub fn genpings(ls: &mut LoraStik) -> io::Result<()> {
let mut counter: u64 = 1;
loop {
let sendstr = format!("Ping {}", counter);
println!("SEND: {}", sendstr);
ls.transmit(&sendstr.as_bytes());
thread::sleep(Duration::from_secs(INTERVAL));
counter += 1;
}
}
/// Reply to pings
pub fn pong(ls: &mut LoraStik, receiver: crossbeam_channel::Receiver<ReceivedFrames>) -> io::Result<()> {
loop {
let data = receiver.recv().unwrap();
let resp = format!("Pong {}, {:?}", String::from_utf8_lossy(&data.0), data.1);
println!("SEND: {}", resp);
ls.transmit(resp.as_bytes());
}
}

52
src/pipe.rs Normal file
View File

@ -0,0 +1,52 @@
/*
Copyright (C) 2019 John Goerzen <jgoerzen@complete.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use std::io;
use std::io::{Read, Write};
use crate::lorastik::{LoraStik, ReceivedFrames};
use crossbeam_channel;
/// A thread for stdin processing
pub fn stdintolora(ls: &mut LoraStik, maxframesize: usize) -> io::Result<()> {
let stdin = io::stdin();
let mut br = io::BufReader::new(stdin);
let mut buf = vec![0u8; 8192];
loop {
let res = br.read(&mut buf)?;
if res == 0 {
// EOF
return Ok(());
}
for chunk in buf[0..res].chunks(maxframesize) {
ls.transmit(&chunk);
}
}
}
pub fn loratostdout(receiver: crossbeam_channel::Receiver<ReceivedFrames>) -> io::Result<()> {
let mut stdout = io::stdout();
loop {
let data = receiver.recv().unwrap();
stdout.write_all(&data.0)?;
stdout.flush()?;
}
}

80
src/ser.rs Normal file
View File

@ -0,0 +1,80 @@
/*
Copyright (C) 2019 John Goerzen <jgoerzen@complete.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use std::io;
use serialport::prelude::*;
use std::io::{BufReader, BufRead, Write};
use log::*;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use std::path::PathBuf;
#[derive(Clone)]
pub struct LoraSer {
// BufReader can't be cloned. Sigh.
pub br: Arc<Mutex<BufReader<Box<dyn SerialPort>>>>,
pub swrite: Arc<Mutex<Box<dyn SerialPort>>>,
pub portname: PathBuf
}
impl LoraSer {
/// Initialize the serial system, configuring the port.
pub fn new(portname: PathBuf) -> io::Result<LoraSer> {
let settings = SerialPortSettings {
baud_rate: 57600,
data_bits: DataBits::Eight,
flow_control: FlowControl::None,
parity: Parity::None,
stop_bits: StopBits::One,
timeout: Duration::new(60 * 60 * 24 * 365 * 20, 0),
};
let readport = serialport::open_with_settings(&portname, &settings)?;
let writeport = readport.try_clone()?;
Ok(LoraSer {br: Arc::new(Mutex::new(BufReader::new(readport))),
swrite: Arc::new(Mutex::new(writeport)),
portname: portname})
}
/// Read a line from the port. Return it with EOL characters removed.
/// None if EOF reached.
pub fn readln(&mut self) -> io::Result<Option<String>> {
let mut buf = String::new();
let size = self.br.lock().unwrap().read_line(&mut buf)?;
if size == 0 {
debug!("{:?}: Received EOF from serial port", self.portname);
Ok(None)
} else {
let buf = String::from(buf.trim());
trace!("{:?} SERIN: {}", self.portname, buf);
Ok(Some(buf))
}
}
/// Transmits a command with terminating EOL characters
pub fn writeln(&mut self, mut data: String) -> io::Result<()> {
trace!("{:?} SEROUT: {}", self.portname, data);
data.push_str("\r\n");
// Give the receiver a chance to process
self.swrite.lock().unwrap().write_all(data.as_bytes())?;
self.swrite.lock().unwrap().flush()
}
}