mirror of
https://github.com/jgoerzen/xbnet.git
synced 2025-01-13 07:48:29 -04:00
Initial publish
This commit is contained in:
parent
44f5fb8c3b
commit
a5a9978281
460
Cargo.lock
generated
Normal file
460
Cargo.lock
generated
Normal 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"
|
12
Cargo.toml
12
Cargo.toml
@ -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
29
README.md
Normal 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
4
doc/Makefile
Normal file
@ -0,0 +1,4 @@
|
||||
ALL: lorapipe.1
|
||||
|
||||
%.1: %.1.md
|
||||
pandoc --standalone --to man $< -o $@
|
466
doc/lorapipe.1
Normal file
466
doc/lorapipe.1
Normal 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
413
doc/lorapipe.1.md
Normal 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
15
init-fast.txt
Normal 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
15
init-slow.txt
Normal 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
15
init.txt
Normal 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
15
init3.txt
Normal 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
15
init4.txt
Normal 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
29
radio-default.txt
Normal 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
58
src/kiss.rs
Normal 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
365
src/lorastik.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
101
src/main.rs
101
src/main.rs
@ -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
46
src/ping.rs
Normal 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
52
src/pipe.rs
Normal 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
80
src/ser.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user