Compare commits
3 Commits
cd614c0956
...
8be762b0ef
Author | SHA1 | Date |
---|---|---|
|
8be762b0ef | |
|
5fb847aec6 | |
|
c9b3879b6c |
|
@ -78,8 +78,10 @@ dependencies = [
|
||||||
name = "atem-connection-rs"
|
name = "atem-connection-rs"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"defmt",
|
||||||
"derive-getters",
|
"derive-getters",
|
||||||
"derive-new",
|
"derive-new",
|
||||||
|
"embassy-net",
|
||||||
"enumflags2",
|
"enumflags2",
|
||||||
"itertools",
|
"itertools",
|
||||||
"log",
|
"log",
|
||||||
|
@ -138,6 +140,12 @@ version = "1.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
|
@ -187,7 +195,7 @@ dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.48",
|
"syn 2.0.104",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -229,6 +237,44 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "critical-section"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "defmt"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"defmt-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "defmt-macros"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e"
|
||||||
|
dependencies = [
|
||||||
|
"defmt-parser",
|
||||||
|
"proc-macro-error2",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.104",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "defmt-parser"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive-getters"
|
name = "derive-getters"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
@ -248,7 +294,16 @@ checksum = "d150dea618e920167e5973d70ae6ece4385b7164e0d799fe7c122dd0a5d912ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.48",
|
"syn 2.0.104",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "document-features"
|
||||||
|
version = "0.2.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d"
|
||||||
|
dependencies = [
|
||||||
|
"litrs",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -257,6 +312,127 @@ version = "1.15.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embassy-net"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "940c4b9fe5c1375b09a0c6722c0100d6b2ed46a717a34f632f26e8d7327c4383"
|
||||||
|
dependencies = [
|
||||||
|
"document-features",
|
||||||
|
"embassy-net-driver",
|
||||||
|
"embassy-sync",
|
||||||
|
"embassy-time",
|
||||||
|
"embedded-io-async",
|
||||||
|
"embedded-nal-async",
|
||||||
|
"heapless",
|
||||||
|
"managed",
|
||||||
|
"smoltcp",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embassy-net-driver"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "524eb3c489760508f71360112bca70f6e53173e6fe48fc5f0efd0f5ab217751d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embassy-sync"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8d2c8cdff05a7a51ba0087489ea44b0b1d97a296ca6b1d6d1a33ea7423d34049"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"critical-section",
|
||||||
|
"embedded-io-async",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-util",
|
||||||
|
"heapless",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embassy-time"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f820157f198ada183ad62e0a66f554c610cdcd1a9f27d4b316358103ced7a1f8"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"critical-section",
|
||||||
|
"document-features",
|
||||||
|
"embassy-time-driver",
|
||||||
|
"embedded-hal 0.2.7",
|
||||||
|
"embedded-hal 1.0.0",
|
||||||
|
"embedded-hal-async",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embassy-time-driver"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8d45f5d833b6d98bd2aab0c2de70b18bfaa10faf661a1578fd8e5dfb15eb7eba"
|
||||||
|
dependencies = [
|
||||||
|
"document-features",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-hal"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff"
|
||||||
|
dependencies = [
|
||||||
|
"nb 0.1.3",
|
||||||
|
"void",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-hal"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-hal-async"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884"
|
||||||
|
dependencies = [
|
||||||
|
"embedded-hal 1.0.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-io"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-io-async"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f"
|
||||||
|
dependencies = [
|
||||||
|
"embedded-io",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-nal"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c56a28be191a992f28f178ec338a0bf02f63d7803244add736d026a471e6ed77"
|
||||||
|
dependencies = [
|
||||||
|
"nb 1.1.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-nal-async"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "76959917cd2b86f40a98c28dd5624eddd1fa69d746241c8257eac428d83cb211"
|
||||||
|
dependencies = [
|
||||||
|
"embedded-io-async",
|
||||||
|
"embedded-nal",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "enumflags2"
|
name = "enumflags2"
|
||||||
version = "0.7.12"
|
version = "0.7.12"
|
||||||
|
@ -274,7 +450,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.48",
|
"syn 2.0.104",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -302,9 +478,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-core"
|
name = "futures-core"
|
||||||
version = "0.3.30"
|
version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
|
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-sink"
|
name = "futures-sink"
|
||||||
|
@ -312,12 +488,49 @@ version = "0.3.30"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
|
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-task"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-util"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"pin-project-lite",
|
||||||
|
"pin-utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gimli"
|
name = "gimli"
|
||||||
version = "0.25.0"
|
version = "0.25.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7"
|
checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hash32"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heapless"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
|
||||||
|
dependencies = [
|
||||||
|
"hash32",
|
||||||
|
"stable_deref_trait",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
|
@ -372,6 +585,12 @@ version = "0.2.153"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "litrs"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
|
@ -390,6 +609,12 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "managed"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.4.0"
|
version = "2.4.0"
|
||||||
|
@ -417,6 +642,21 @@ dependencies = [
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nb"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f"
|
||||||
|
dependencies = [
|
||||||
|
"nb 1.1.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nb"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num_cpus"
|
name = "num_cpus"
|
||||||
version = "1.16.0"
|
version = "1.16.0"
|
||||||
|
@ -478,10 +718,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
|
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "pin-utils"
|
||||||
version = "1.0.78"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error-attr2"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error2"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-error-attr2",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.104",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.95"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
@ -557,6 +825,19 @@ version = "1.13.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
|
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smoltcp"
|
||||||
|
version = "0.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dad095989c1533c1c266d9b1e8d70a1329dd3723c3edac6d03bbd67e7bf6f4bb"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"byteorder",
|
||||||
|
"cfg-if",
|
||||||
|
"heapless",
|
||||||
|
"managed",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.5.6"
|
version = "0.5.6"
|
||||||
|
@ -567,6 +848,12 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stable_deref_trait"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
|
@ -586,9 +873,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.48"
|
version = "2.0.104"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
|
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -604,6 +891,26 @@ dependencies = [
|
||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "2.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "2.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.104",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thread_local"
|
name = "thread_local"
|
||||||
version = "1.1.3"
|
version = "1.1.3"
|
||||||
|
@ -640,7 +947,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.48",
|
"syn 2.0.104",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -727,6 +1034,12 @@ version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "void"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.11.0+wasi-snapshot-preview1"
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
|
|
@ -11,8 +11,12 @@ log = "0.4.14"
|
||||||
tokio = { version = "1.13.0", features = ["full"], optional = true }
|
tokio = { version = "1.13.0", features = ["full"], optional = true }
|
||||||
tokio-util = { version = "0.7.10", optional = true }
|
tokio-util = { version = "0.7.10", optional = true }
|
||||||
enumflags2 = { version = "0.7.12", default-features = false }
|
enumflags2 = { version = "0.7.12", default-features = false }
|
||||||
|
embassy-net = { version = "0.7.0", optional = true, features = ["proto-ipv4", "medium-ip", "udp"]}
|
||||||
|
defmt = {version = "1.0.0", optional = true}
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["std"]
|
default = ["std", "embassy-net", "defmt"]
|
||||||
|
|
||||||
std = ["dep:tokio", "dep:tokio-util", "itertools/use_std"]
|
std = ["dep:tokio", "dep:tokio-util", "itertools/use_std"]
|
||||||
|
embassy-net = ["dep:embassy-net"]
|
||||||
|
defmt = ["dep:defmt"]
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
//! This module contains [`AtemPacket`] which is a zero*ish* copy abstraction over a [`AsRef`]`<[u8]>`.
|
||||||
use core::{fmt::Display, str};
|
use core::{fmt::Display, str};
|
||||||
use enumflags2::{BitFlags, bitflags};
|
use enumflags2::{BitFlags, bitflags};
|
||||||
|
|
||||||
|
@ -9,54 +10,159 @@ pub const COMMAND_CONNECT_HELLO: [u8; 20] = [
|
||||||
0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/// Maximum atem packet length. This is determined by the maximum value that can be stored
|
||||||
|
/// in the length field.
|
||||||
|
pub const MAX_LEN: usize = field::LEN_MASK as usize;
|
||||||
|
|
||||||
|
mod field {
|
||||||
|
use core::ops::{Range, RangeInclusive};
|
||||||
|
|
||||||
|
pub(crate) const FLAGS_LEN_H: usize = 0;
|
||||||
|
pub(crate) const FLAGS_MASK: u8 = 0b1111_1000;
|
||||||
|
pub(crate) const LEN_L: usize = 1;
|
||||||
|
pub(crate) const LEN: RangeInclusive<usize> = FLAGS_LEN_H..=LEN_L;
|
||||||
|
pub(crate) const LEN_MASK: u16 = 0x7ff;
|
||||||
|
|
||||||
|
pub(crate) const SESSION_ID: Range<usize> = 2..4;
|
||||||
|
pub(crate) const ACK_NUMBER: Range<usize> = 4..6;
|
||||||
|
pub(crate) const REMOTE_SEQ_NUM: Range<usize> = 8..10;
|
||||||
|
pub(crate) const LOCAL_SEQ_NUM: Range<usize> = 10..12;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An ATEM protocol packet
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AtemPacket<T: AsRef<[u8]>> {
|
pub struct AtemPacket<T: AsRef<[u8]>> {
|
||||||
buf: T,
|
buf: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TryFrom<&'a [u8]> for AtemPacket<&'a [u8]> {
|
impl<'a> TryFrom<&'a [u8]> for AtemPacket<&'a [u8]> {
|
||||||
type Error = AtemPacketErr;
|
type Error = AtemPacketParseError;
|
||||||
fn try_from(buf: &'a [u8]) -> Result<Self, Self::Error> {
|
fn try_from(buf: &'a [u8]) -> Result<Self, Self::Error> {
|
||||||
AtemPacket::new_checked(buf)
|
AtemPacket::new_checked(buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum AtemPacketErr {
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
/// The packet was too short
|
pub enum AtemPacketParseError {
|
||||||
TooShort {
|
/// The packet was shorter than any valid packet should be.
|
||||||
got: usize,
|
TooShort { got: usize },
|
||||||
},
|
|
||||||
/// The packet's stated and actual lengths were different
|
/// The packet's stated and actual lengths were different
|
||||||
LengthDiffers {
|
LengthDiffers { expected: u16, got: usize },
|
||||||
expected: u16,
|
/// The packet had invalid (unknown) flags set
|
||||||
got: usize,
|
|
||||||
},
|
|
||||||
InvalidFlags,
|
InvalidFlags,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ATEM packet flags
|
||||||
#[bitflags]
|
#[bitflags]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(PartialEq, Copy, Clone, Debug)]
|
#[derive(PartialEq, Copy, Clone, Debug)]
|
||||||
pub enum PacketFlag {
|
pub enum PacketFlag {
|
||||||
|
/// AKA "reliable". This packet should be ACKed by the other end
|
||||||
AckRequest = 0x1,
|
AckRequest = 0x1,
|
||||||
|
/// AKA "syn" - used in the connection handshake
|
||||||
NewSessionId = 0x2,
|
NewSessionId = 0x2,
|
||||||
|
/// This packet is a retransmit of a previous one
|
||||||
IsRetransmit = 0x4,
|
IsRetransmit = 0x4,
|
||||||
|
/// Request retransmission of a previous sequence ID
|
||||||
RetransmitRequest = 0x8,
|
RetransmitRequest = 0x8,
|
||||||
|
/// This packet is an ACK
|
||||||
AckReply = 0x10,
|
AckReply = 0x10,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An error while constructing a packet
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum AtemPacketInitError {
|
||||||
|
/// The provided buf was too small
|
||||||
|
BufTooSmall { need: usize, got: usize },
|
||||||
|
/// The provided payload was too long
|
||||||
|
DataTooLong { got: usize, max: usize },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> AtemPacket<&'a mut [u8]> {
|
||||||
|
/// Initialise an [`AtemPacket`] into the given `buf`, with optional payload copied from `data`.
|
||||||
|
/// Returns the constructed packet, and the remaining spare space in `buf`, if there was any.
|
||||||
|
pub fn init<'b>(
|
||||||
|
buf: &'a mut [u8],
|
||||||
|
flags: BitFlags<PacketFlag>,
|
||||||
|
session_id: u16,
|
||||||
|
local_seq_num: u16,
|
||||||
|
data: Option<&'b [u8]>,
|
||||||
|
) -> Result<(Self, &'a mut [u8]), AtemPacketInitError> {
|
||||||
|
let len = 12 + data.map_or(0, |d| d.len());
|
||||||
|
if len > buf.len() {
|
||||||
|
return Err(AtemPacketInitError::BufTooSmall {
|
||||||
|
need: len,
|
||||||
|
got: buf.len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if len > MAX_LEN {
|
||||||
|
return Err(AtemPacketInitError::DataTooLong {
|
||||||
|
got: len - 12,
|
||||||
|
max: MAX_LEN - 12,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let (pkt, rem) = buf.split_at_mut(len);
|
||||||
|
let mut p = AtemPacket { buf: pkt };
|
||||||
|
p.set_flags(flags)
|
||||||
|
.set_session_id(session_id)
|
||||||
|
.set_local_seq_num(local_seq_num)
|
||||||
|
.set_len(len.try_into().unwrap());
|
||||||
|
if let Some(d) = data {
|
||||||
|
p.set_data(d);
|
||||||
|
}
|
||||||
|
Ok((p, rem))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsRef<[u8]> + AsMut<[u8]>> AtemPacket<T> {
|
||||||
|
pub fn set_flags(&mut self, flags: BitFlags<PacketFlag>) -> &mut Self {
|
||||||
|
let prev = self.buf.as_ref()[field::FLAGS_LEN_H];
|
||||||
|
self.buf.as_mut()[field::FLAGS_LEN_H] = (prev & !field::FLAGS_MASK) | (flags.bits() << 3);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_len(&mut self, value: u16) -> &mut Self {
|
||||||
|
let v = value.to_be_bytes();
|
||||||
|
self.buf.as_mut()[field::FLAGS_LEN_H] &= v[0] | field::FLAGS_MASK;
|
||||||
|
self.buf.as_mut()[field::LEN_L] = v[1];
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_session_id(&mut self, value: u16) -> &mut Self {
|
||||||
|
self.buf.as_mut()[field::SESSION_ID].copy_from_slice(&value.to_be_bytes());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn set_local_seq_num(&mut self, value: u16) -> &mut Self {
|
||||||
|
self.buf.as_mut()[field::LOCAL_SEQ_NUM].copy_from_slice(&value.to_be_bytes());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn set_remote_seq_num(&mut self, value: u16) -> &mut Self {
|
||||||
|
self.buf.as_mut()[field::REMOTE_SEQ_NUM].copy_from_slice(&value.to_be_bytes());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn set_data<'a>(&mut self, value: &'a [u8]) -> &mut Self {
|
||||||
|
self.buf.as_mut()[12..].copy_from_slice(value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_ack_num(&mut self, value: u16) -> &mut Self {
|
||||||
|
self.buf.as_mut()[field::ACK_NUMBER].copy_from_slice(&value.to_be_bytes());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: AsRef<[u8]>> AtemPacket<T> {
|
impl<T: AsRef<[u8]>> AtemPacket<T> {
|
||||||
pub fn new_checked(buf: T) -> Result<Self, AtemPacketErr> {
|
pub fn new_checked(buf: T) -> Result<Self, AtemPacketParseError> {
|
||||||
let len = buf.as_ref().len();
|
let len = buf.as_ref().len();
|
||||||
if len < 12 {
|
if len < 12 {
|
||||||
return Err(AtemPacketErr::TooShort {
|
return Err(AtemPacketParseError::TooShort {
|
||||||
got: buf.as_ref().len(),
|
got: buf.as_ref().len(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let p = Self { buf };
|
let p = Self { buf };
|
||||||
if p.length() as usize != len {
|
if p.length() as usize != len {
|
||||||
return Err(AtemPacketErr::LengthDiffers {
|
return Err(AtemPacketParseError::LengthDiffers {
|
||||||
expected: p.length(),
|
expected: p.length(),
|
||||||
got: len,
|
got: len,
|
||||||
});
|
});
|
||||||
|
@ -65,15 +171,20 @@ impl<T: AsRef<[u8]>> AtemPacket<T> {
|
||||||
let _: BitFlags<PacketFlag> = p
|
let _: BitFlags<PacketFlag> = p
|
||||||
.flags_raw()
|
.flags_raw()
|
||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|_| AtemPacketErr::InvalidFlags)?;
|
.map_err(|_| AtemPacketParseError::InvalidFlags)?;
|
||||||
Ok(p)
|
Ok(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Consumes self, returning the inner packet buffer
|
||||||
|
pub fn inner(self) -> T {
|
||||||
|
self.buf
|
||||||
|
}
|
||||||
pub fn length(&self) -> u16 {
|
pub fn length(&self) -> u16 {
|
||||||
u16::from_be_bytes(self.buf.as_ref()[0..=1].try_into().unwrap()) & 0x07ff
|
u16::from_be_bytes(self.buf.as_ref()[field::LEN].try_into().unwrap()) & field::LEN_MASK
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flags_raw(&self) -> u8 {
|
fn flags_raw(&self) -> u8 {
|
||||||
self.buf.as_ref()[0] >> 3
|
self.buf.as_ref()[field::FLAGS_LEN_H] >> 3
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn flags(&self) -> BitFlags<PacketFlag> {
|
pub fn flags(&self) -> BitFlags<PacketFlag> {
|
||||||
|
@ -82,19 +193,19 @@ impl<T: AsRef<[u8]>> AtemPacket<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_id(&self) -> u16 {
|
pub fn session_id(&self) -> u16 {
|
||||||
u16::from_be_bytes(self.buf.as_ref()[2..=3].try_into().unwrap())
|
u16::from_be_bytes(self.buf.as_ref()[field::SESSION_ID].try_into().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ack_number(&self) -> u16 {
|
pub fn ack_number(&self) -> u16 {
|
||||||
u16::from_be_bytes(self.buf.as_ref()[4..=5].try_into().unwrap())
|
u16::from_be_bytes(self.buf.as_ref()[field::ACK_NUMBER].try_into().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remote_sequence_number(&self) -> u16 {
|
pub fn remote_sequence_number(&self) -> u16 {
|
||||||
u16::from_be_bytes(self.buf.as_ref()[9..=10].try_into().unwrap())
|
u16::from_be_bytes(self.buf.as_ref()[field::REMOTE_SEQ_NUM].try_into().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn local_sequence_number(&self) -> u16 {
|
pub fn local_sequence_number(&self) -> u16 {
|
||||||
u16::from_be_bytes(self.buf.as_ref()[10..=11].try_into().unwrap())
|
u16::from_be_bytes(self.buf.as_ref()[field::LOCAL_SEQ_NUM].try_into().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn retransmit_request(&self) -> Option<u16> {
|
pub fn retransmit_request(&self) -> Option<u16> {
|
||||||
|
@ -144,8 +255,8 @@ impl<T: AsRef<[u8]>> Display for AtemPacket<T> {
|
||||||
self.session_id(),
|
self.session_id(),
|
||||||
self.flags(),
|
self.flags(),
|
||||||
self.ack_number(),
|
self.ack_number(),
|
||||||
self.remote_sequence_number(),
|
|
||||||
self.local_sequence_number(),
|
self.local_sequence_number(),
|
||||||
|
self.remote_sequence_number(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,3 +294,21 @@ impl<'a> Iterator for RawFields<'a> {
|
||||||
Some(RawField { r#type, data })
|
Some(RawField { r#type, data })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_init() {
|
||||||
|
let mut buf = [0u8; 12];
|
||||||
|
let (mut p, _) =
|
||||||
|
AtemPacket::init(&mut buf, PacketFlag::AckReply.into(), 1234, 5678, None).unwrap();
|
||||||
|
p.set_remote_seq_num(9012);
|
||||||
|
assert!(p.flags().contains(PacketFlag::AckReply));
|
||||||
|
assert_eq!(p.session_id(), 1234);
|
||||||
|
assert_eq!(p.local_sequence_number(), 5678);
|
||||||
|
assert_eq!(p.remote_sequence_number(), 9012);
|
||||||
|
assert_eq!(p.length(), 12);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -18,11 +18,8 @@ use tokio::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
atem_lib::atem_packet::{self, AtemPacket, COMMAND_CONNECT_HELLO},
|
atem_lib::atem_packet::{AtemPacket, COMMAND_CONNECT_HELLO},
|
||||||
commands::{
|
commands::command_base::{BasicWritableCommand, DeserializedCommand},
|
||||||
command_base::{BasicWritableCommand, DeserializedCommand},
|
|
||||||
parse_commands::deserialize_commands,
|
|
||||||
},
|
|
||||||
enums::ProtocolVersion,
|
enums::ProtocolVersion,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -303,22 +300,18 @@ impl AtemSocket {
|
||||||
self.next_send_packet_id = 0;
|
self.next_send_packet_id = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let opcode = u16::from(PacketFlag::AckRequest as u8) << 11;
|
|
||||||
|
|
||||||
let mut buffer = vec![0; 20 + payload.len()];
|
let mut buffer = vec![0; 20 + payload.len()];
|
||||||
|
|
||||||
// Headers
|
let (p, _) = AtemPacket::init(
|
||||||
buffer[0..2].copy_from_slice(&u16::to_be_bytes(opcode | (payload.len() as u16 + 20)));
|
&mut buffer,
|
||||||
buffer[2..4].copy_from_slice(&u16::to_be_bytes(self.session_id));
|
PacketFlag::AckRequest.into(),
|
||||||
buffer[10..12].copy_from_slice(&u16::to_be_bytes(packet_id));
|
self.session_id,
|
||||||
|
packet_id,
|
||||||
|
Some([raw_name.as_bytes(), payload].concat().as_slice()),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Command
|
self.send_packet(&p.inner()).await;
|
||||||
buffer[12..14].copy_from_slice(&u16::to_be_bytes(payload.len() as u16 + 8));
|
|
||||||
buffer[16..20].copy_from_slice(raw_name.as_bytes());
|
|
||||||
|
|
||||||
// Body
|
|
||||||
buffer[20..20 + payload.len()].copy_from_slice(payload);
|
|
||||||
self.send_packet(&buffer).await;
|
|
||||||
|
|
||||||
self.in_flight.push(InFlightPacket {
|
self.in_flight.push(InFlightPacket {
|
||||||
packet_id,
|
packet_id,
|
||||||
|
@ -401,7 +394,7 @@ impl AtemSocket {
|
||||||
self.last_received_at = Instant::now();
|
self.last_received_at = Instant::now();
|
||||||
|
|
||||||
self.session_id = atem_packet.session_id();
|
self.session_id = atem_packet.session_id();
|
||||||
// TODO: naming seems rather off here
|
// TODO: bit of a naming clash here
|
||||||
let remote_packet_id = atem_packet.local_sequence_number();
|
let remote_packet_id = atem_packet.local_sequence_number();
|
||||||
|
|
||||||
if atem_packet.flags().contains(PacketFlag::NewSessionId) {
|
if atem_packet.flags().contains(PacketFlag::NewSessionId) {
|
||||||
|
@ -483,13 +476,17 @@ impl AtemSocket {
|
||||||
|
|
||||||
async fn send_ack(&mut self, packet_id: u16) {
|
async fn send_ack(&mut self, packet_id: u16) {
|
||||||
log::debug!("Sending ack for packet {:x?}", packet_id);
|
log::debug!("Sending ack for packet {:x?}", packet_id);
|
||||||
let flag: u8 = PacketFlag::AckReply as u8;
|
|
||||||
let opcode = u16::from(flag) << 11;
|
|
||||||
let mut buffer: [u8; ACK_PACKET_LENGTH as _] = [0; 12];
|
let mut buffer: [u8; ACK_PACKET_LENGTH as _] = [0; 12];
|
||||||
buffer[0..2].copy_from_slice(&u16::to_be_bytes(opcode | ACK_PACKET_LENGTH));
|
let (mut p, _) = AtemPacket::init(
|
||||||
buffer[2..4].copy_from_slice(&u16::to_be_bytes(self.session_id));
|
&mut buffer,
|
||||||
buffer[4..6].copy_from_slice(&u16::to_be_bytes(packet_id));
|
PacketFlag::AckReply.into(),
|
||||||
self.send_packet(&buffer).await;
|
self.session_id,
|
||||||
|
0,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
p.set_ack_num(packet_id);
|
||||||
|
self.send_packet(p.inner()).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn retransmit_from(&mut self, from_id: u16) {
|
async fn retransmit_from(&mut self, from_id: u16) {
|
||||||
|
|
|
@ -0,0 +1,285 @@
|
||||||
|
use core::net::{Ipv4Addr, SocketAddrV4};
|
||||||
|
|
||||||
|
use log::debug;
|
||||||
|
|
||||||
|
use super::atem_packet::{AtemPacket, AtemPacketParseError, PacketFlag};
|
||||||
|
|
||||||
|
pub const ATEM_PORT: u16 = 9910;
|
||||||
|
pub const SYN_PAYLOAD: [u8; 8] = [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum AtemSocketState {
|
||||||
|
Closed,
|
||||||
|
StateDump,
|
||||||
|
Established { session_id: u16 },
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An abstraction over an async UDP socket
|
||||||
|
pub trait SocketBackend {
|
||||||
|
type SendError;
|
||||||
|
type RecvError;
|
||||||
|
async fn send_to(&mut self, data: &[u8], addr: SocketAddrV4) -> Result<(), Self::SendError>;
|
||||||
|
async fn recv(&mut self, data: &mut [u8]) -> Result<usize, Self::RecvError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub mod tokio {
|
||||||
|
use core::net::Ipv4Addr;
|
||||||
|
|
||||||
|
use tokio::net::UdpSocket;
|
||||||
|
|
||||||
|
use super::{AtemSocket, SocketBackend};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TokioBackend {
|
||||||
|
sock: UdpSocket,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TokioBackend {
|
||||||
|
pub async fn new(addr: Ipv4Addr) -> Result<AtemSocket<Self>, std::io::Error> {
|
||||||
|
let b = Self {
|
||||||
|
sock: UdpSocket::bind("0.0.0.0:0").await?,
|
||||||
|
};
|
||||||
|
Ok(AtemSocket::new_with_backend(addr, b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SocketBackend for TokioBackend {
|
||||||
|
type SendError = std::io::Error;
|
||||||
|
type RecvError = std::io::Error;
|
||||||
|
|
||||||
|
async fn send_to(
|
||||||
|
&mut self,
|
||||||
|
data: &[u8],
|
||||||
|
addr: core::net::SocketAddrV4,
|
||||||
|
) -> Result<(), Self::SendError> {
|
||||||
|
self.sock.send_to(data, addr).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn recv(&mut self, data: &mut [u8]) -> Result<usize, Self::RecvError> {
|
||||||
|
let (cnt, _) = self.sock.recv_from(data).await?;
|
||||||
|
Ok(cnt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "embassy-net")]
|
||||||
|
pub mod enet {
|
||||||
|
use core::net::{Ipv4Addr, SocketAddrV4};
|
||||||
|
|
||||||
|
use embassy_net::udp::{RecvError, SendError, UdpSocket};
|
||||||
|
|
||||||
|
use super::{AtemSocket, SocketBackend};
|
||||||
|
|
||||||
|
pub struct EnetBackend<'a> {
|
||||||
|
sock: UdpSocket<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::fmt::Debug for EnetBackend<'_> {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
write!(f, "EnetBackend")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> EnetBackend<'a> {
|
||||||
|
pub fn new(addr: Ipv4Addr, sock: UdpSocket<'a>) -> AtemSocket<Self> {
|
||||||
|
let b = Self { sock };
|
||||||
|
AtemSocket::new_with_backend(addr, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SocketBackend for EnetBackend<'_> {
|
||||||
|
type SendError = SendError;
|
||||||
|
type RecvError = RecvError;
|
||||||
|
|
||||||
|
async fn send_to(
|
||||||
|
&mut self,
|
||||||
|
data: &[u8],
|
||||||
|
addr: SocketAddrV4,
|
||||||
|
) -> Result<(), Self::SendError> {
|
||||||
|
self.sock.send_to(data, addr).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn recv(&mut self, data: &mut [u8]) -> Result<usize, Self::RecvError> {
|
||||||
|
let (count, _) = self.sock.recv_from(data).await?;
|
||||||
|
Ok(count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum AtemError<I: SocketBackend> {
|
||||||
|
/// Tried to do something but the socket is closed
|
||||||
|
SocketClosed,
|
||||||
|
/// There was an error from the underlying socket while receiving
|
||||||
|
Recv(I::RecvError),
|
||||||
|
/// There was an error from the underlying socket while sending
|
||||||
|
Send(I::SendError),
|
||||||
|
/// We received an invalid ATEM protocol packet
|
||||||
|
BadPacket(AtemPacketParseError),
|
||||||
|
/// The switcher denied our connection attempt.
|
||||||
|
SwitcherDeniedConnect,
|
||||||
|
/// There was some nonspecific protocol error
|
||||||
|
ProtocolError,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I> From<AtemPacketParseError> for AtemError<I>
|
||||||
|
where
|
||||||
|
I: SocketBackend,
|
||||||
|
{
|
||||||
|
fn from(value: AtemPacketParseError) -> Self {
|
||||||
|
Self::BadPacket(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AtemSocket<I: SocketBackend> {
|
||||||
|
inner: I,
|
||||||
|
addr: SocketAddrV4,
|
||||||
|
state: AtemSocketState,
|
||||||
|
local_seq_num: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I> AtemSocket<I>
|
||||||
|
where
|
||||||
|
I: SocketBackend,
|
||||||
|
{
|
||||||
|
fn new_with_backend(addr: Ipv4Addr, backend: I) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: backend,
|
||||||
|
addr: SocketAddrV4::new(addr, ATEM_PORT),
|
||||||
|
state: AtemSocketState::Closed,
|
||||||
|
local_seq_num: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Connect to the ATEM, performing the connection handshake.
|
||||||
|
pub async fn connect(&mut self) -> Result<(), AtemError<I>> {
|
||||||
|
// Reset state
|
||||||
|
self.state = AtemSocketState::Closed;
|
||||||
|
self.local_seq_num = 0;
|
||||||
|
let mut buf = [0u8; 20];
|
||||||
|
// FIXME: this should be random
|
||||||
|
let initial_sid = 0x53ab;
|
||||||
|
|
||||||
|
// Step 1: Send the "SYN" with a random session id and magic payload
|
||||||
|
|
||||||
|
let (mut p, _) = AtemPacket::init(
|
||||||
|
&mut buf,
|
||||||
|
PacketFlag::NewSessionId.into(),
|
||||||
|
initial_sid,
|
||||||
|
self.local_seq_num,
|
||||||
|
Some(&SYN_PAYLOAD),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
p.set_remote_seq_num(0x3a);
|
||||||
|
|
||||||
|
self.inner
|
||||||
|
.send_to(p.inner(), self.addr)
|
||||||
|
.await
|
||||||
|
.map_err(|e| AtemError::Send(e))?;
|
||||||
|
|
||||||
|
debug!("Connect step 1: sent SYN");
|
||||||
|
|
||||||
|
// Step 2: Wait for the response from the ATEM.
|
||||||
|
// this should also have the SYN flag set and have the same session id that we sent.
|
||||||
|
// The first byte of the payload should be 0x02 - if it's 0x04 we should restart
|
||||||
|
// the connection
|
||||||
|
|
||||||
|
self.inner
|
||||||
|
.recv(&mut buf)
|
||||||
|
.await
|
||||||
|
.map_err(|e| AtemError::Recv(e))?;
|
||||||
|
|
||||||
|
let ack = AtemPacket::new_checked(buf)?;
|
||||||
|
debug!("RX: {}", ack);
|
||||||
|
if ack.flags() != PacketFlag::NewSessionId {
|
||||||
|
return Err(AtemError::ProtocolError);
|
||||||
|
}
|
||||||
|
if ack.session_id() != initial_sid {
|
||||||
|
return Err(AtemError::ProtocolError);
|
||||||
|
}
|
||||||
|
match ack.body()[0] {
|
||||||
|
0x02 => {
|
||||||
|
// all good
|
||||||
|
debug!("Connect step 2: got reply");
|
||||||
|
}
|
||||||
|
0x04 => {
|
||||||
|
// Restart connection
|
||||||
|
return Err(AtemError::SwitcherDeniedConnect);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(AtemError::ProtocolError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Send an ACK for the packet we just received.
|
||||||
|
self.ack(initial_sid, ack.local_sequence_number()).await?;
|
||||||
|
debug!("Connect step 3: sent ACK");
|
||||||
|
self.state = AtemSocketState::StateDump;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn ack(&mut self, sid: u16, ack_num: u16) -> Result<(), AtemError<I>> {
|
||||||
|
let mut buf = [0u8; 12];
|
||||||
|
let (mut p, _) = AtemPacket::init(
|
||||||
|
&mut buf,
|
||||||
|
PacketFlag::AckReply.into(),
|
||||||
|
sid,
|
||||||
|
// N.B: local seq num is set to 0 in ACKs
|
||||||
|
0,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
p.set_ack_num(ack_num);
|
||||||
|
debug!("TX: {}", p);
|
||||||
|
self.inner
|
||||||
|
.send_to(p.inner(), self.addr)
|
||||||
|
.await
|
||||||
|
.map_err(|e| AtemError::Send(e))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receive a packet from the ATEM. Automatically handles sending ACKs for received packets.
|
||||||
|
pub async fn recv<'a>(
|
||||||
|
&mut self,
|
||||||
|
buf: &'a mut [u8],
|
||||||
|
) -> Result<AtemPacket<&'a [u8]>, AtemError<I>> {
|
||||||
|
if self.state == AtemSocketState::Closed {
|
||||||
|
return Err(AtemError::SocketClosed);
|
||||||
|
}
|
||||||
|
let size = self.inner.recv(buf).await.map_err(|e| AtemError::Recv(e))?;
|
||||||
|
let p: AtemPacket<_> = buf[0..size].try_into()?;
|
||||||
|
debug!("RX: {}", p);
|
||||||
|
match self.state {
|
||||||
|
AtemSocketState::StateDump => {
|
||||||
|
// We've just connected and the state's being dumped to us.
|
||||||
|
if p.body().len() == 0 {
|
||||||
|
// State dump is done.
|
||||||
|
self.state = AtemSocketState::Established {
|
||||||
|
session_id: p.session_id(),
|
||||||
|
};
|
||||||
|
debug!("ATEM state dump done, session ID is {}", p.session_id());
|
||||||
|
self.ack(p.session_id(), p.local_sequence_number()).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AtemSocketState::Established { session_id } => {
|
||||||
|
if p.flags().contains(PacketFlag::AckRequest) {
|
||||||
|
self.ack(session_id, p.local_sequence_number()).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AtemSocketState::Closed => unreachable!(),
|
||||||
|
}
|
||||||
|
Ok(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a packet to the ATEM. The ATEM will respond with an ACK - it's up to the caller to handle packet retransmission
|
||||||
|
pub async fn send<T: AsRef<[u8]>>(&mut self, p: AtemPacket<T>) -> Result<(), AtemError<I>> {
|
||||||
|
self.inner
|
||||||
|
.send_to(p.inner().as_ref(), self.addr)
|
||||||
|
.await
|
||||||
|
.map_err(|e| AtemError::Send(e))
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,3 +2,4 @@ pub mod atem_field;
|
||||||
pub mod atem_packet;
|
pub mod atem_packet;
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
pub mod atem_socket;
|
pub mod atem_socket;
|
||||||
|
pub mod atem_socket_two;
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
use std::{
|
use std::{boxed::Box, collections::HashMap, fmt::Debug, string::String, sync::Arc, vec::Vec};
|
||||||
boxed::Box, collections::HashMap, fmt::Debug, process::Command, string::String, sync::Arc,
|
|
||||||
vec::Vec,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{enums::ProtocolVersion, state::AtemState};
|
use crate::{enums::ProtocolVersion, state::AtemState};
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::{boxed::Box, collections::VecDeque, sync::Arc};
|
use std::{boxed::Box, collections::VecDeque, sync::Arc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
atem_lib::atem_packet::{AtemPacket, RawFields},
|
atem_lib::atem_packet::RawFields,
|
||||||
commands::device_profile::version::{DESERIALIZE_VERSION_RAW_NAME, deserialize_version},
|
commands::device_profile::version::{DESERIALIZE_VERSION_RAW_NAME, deserialize_version},
|
||||||
enums::ProtocolVersion,
|
enums::ProtocolVersion,
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
name = "atem-test"
|
name = "atem-test"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
default-run = "main"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
atem-connection-rs = { path = "../atem-connection-rs" }
|
atem-connection-rs = { path = "../atem-connection-rs" }
|
||||||
|
|
|
@ -36,7 +36,7 @@ async fn main() {
|
||||||
let (atem_event_tx, atem_event_rx) = tokio::sync::mpsc::unbounded_channel();
|
let (atem_event_tx, atem_event_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||||
let cancel = CancellationToken::new();
|
let cancel = CancellationToken::new();
|
||||||
|
|
||||||
let mut atem_socket = AtemSocket::new(socket_message_rx, atem_event_tx);
|
let atem_socket = AtemSocket::new(socket_message_rx, atem_event_tx);
|
||||||
|
|
||||||
let atem = Arc::new(Atem::new(atem_socket, socket_message_tx));
|
let atem = Arc::new(Atem::new(atem_socket, socket_message_tx));
|
||||||
let atem_thread = atem.clone();
|
let atem_thread = atem.clone();
|
|
@ -0,0 +1,48 @@
|
||||||
|
use atem_connection_rs::atem_lib::{atem_field::PrgI, atem_socket_two::tokio::TokioBackend};
|
||||||
|
use clap::Parser;
|
||||||
|
use color_eyre::Report;
|
||||||
|
|
||||||
|
/// ATEM Rust Library Test App
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(author, version, about, long_about = None)]
|
||||||
|
struct Args {
|
||||||
|
/// IP of the ATEM to connect to
|
||||||
|
#[arg(short, long)]
|
||||||
|
ip: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
setup_logging().unwrap();
|
||||||
|
|
||||||
|
let mut s = TokioBackend::new(args.ip.parse().unwrap()).await.unwrap();
|
||||||
|
s.connect().await.expect("aa");
|
||||||
|
let mut buf = [0u8; 2048];
|
||||||
|
loop {
|
||||||
|
let p = s.recv(&mut buf).await.unwrap();
|
||||||
|
let prg: Vec<PrgI> = p.fields::<PrgI>().collect::<Result<_, _>>().unwrap();
|
||||||
|
if !prg.is_empty() {
|
||||||
|
println!("{:?}", prg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_logging() -> Result<(), Report> {
|
||||||
|
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
|
||||||
|
unsafe {
|
||||||
|
std::env::set_var("RUST_LIB_BACKTRACE", "1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
color_eyre::install()?;
|
||||||
|
|
||||||
|
if std::env::var("RUST_LOG").is_err() {
|
||||||
|
unsafe {
|
||||||
|
std::env::set_var("RUST_LOG", "debug");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in New Issue