diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a7427f918f6b5e776eb54734dc594801309f1fa6
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,40 @@
+image: "rust:latest"
+
+variables:
+  CARGO_HOME: $CI_PROJECT_DIR/.cargo
+
+cache:
+  key: $CI_COMMIT_REF_SLUG
+  paths:
+    - target/
+    - .cargo
+
+# Install C compiler in case we need it
+before_script:
+  - apt-get update -yqq
+  - apt-get install -yqq --no-install-recommends build-essential
+
+# Use cargo to test the project
+check:
+  before_script:
+    - rustc --version && cargo --version # Print version info for debugging
+  script:
+    - cargo check
+
+test:
+  before_script:
+    - rustc --version && cargo --version # Print version info for debugging
+  script:
+    - cargo test
+
+lint:
+  before_script:
+    - rustc --version && cargo --version # Print version info for debugging
+    - rustup component add rustfmt
+    - rustfmt --version
+    - rustup component add clippy
+    - cargo-clippy --version
+  script:
+    - cargo build
+    - cargo fmt --all -- --check
+    - cargo clippy -- -D warnings
diff --git a/Cargo.lock b/Cargo.lock
index 3768acf6b488fab24822ff2e740877564c355553..4a6ec268976011680bb514e0695de0a363dcfdd5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3,55 +3,40 @@
 version = 3
 
 [[package]]
-name = "CoreFoundation-sys"
-version = "0.1.4"
+name = "addr2line"
+version = "0.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0e9889e6db118d49d88d84728d0e964d973a5680befb5f85f55141beea5c20b"
+checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97"
 dependencies = [
- "libc",
- "mach 0.1.2",
+ "gimli",
 ]
 
 [[package]]
-name = "IOKit-sys"
-version = "0.1.5"
+name = "adler"
+version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99696c398cbaf669d2368076bdb3d627fb0ce51a26899d7c61228c5c0af3bf4a"
-dependencies = [
- "CoreFoundation-sys",
- "libc",
- "mach 0.1.2",
-]
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
 
 [[package]]
-name = "aho-corasick"
-version = "0.7.20"
+name = "backtrace"
+version = "0.3.67"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
+checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca"
 dependencies = [
- "memchr",
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
 ]
 
 [[package]]
-name = "autocfg"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
-
-[[package]]
-name = "bitflags"
-version = "1.3.2"
+name = "cc"
+version = "1.0.78"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
-
-[[package]]
-name = "block-buffer"
-version = "0.10.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
-dependencies = [
- "generic-array",
-]
+checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
 
 [[package]]
 name = "cfg-if"
@@ -60,38 +45,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
-name = "cpufeatures"
-version = "0.2.5"
+name = "color-eyre"
+version = "0.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
+checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204"
 dependencies = [
- "libc",
-]
-
-[[package]]
-name = "crc16"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "338089f42c427b86394a5ee60ff321da23a5c89c9d89514c829687b26359fcff"
-
-[[package]]
-name = "crypto-common"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
-dependencies = [
- "generic-array",
- "typenum",
+ "backtrace",
+ "color-spantrace",
+ "eyre",
+ "indenter",
+ "once_cell",
+ "owo-colors",
+ "tracing-error",
 ]
 
 [[package]]
-name = "digest"
-version = "0.10.6"
+name = "color-spantrace"
+version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
+checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce"
 dependencies = [
- "block-buffer",
- "crypto-common",
+ "once_cell",
+ "owo-colors",
+ "tracing-core",
+ "tracing-error",
 ]
 
 [[package]]
@@ -111,38 +88,32 @@ dependencies = [
 ]
 
 [[package]]
-name = "generic-array"
-version = "0.14.6"
+name = "eyre"
+version = "0.6.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
+checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb"
 dependencies = [
- "typenum",
- "version_check",
+ "indenter",
+ "once_cell",
 ]
 
 [[package]]
-name = "hermit-abi"
-version = "0.1.19"
+name = "gimli"
+version = "0.27.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
-dependencies = [
- "libc",
-]
+checksum = "dec7af912d60cdbd3677c1af9352ebae6fb8394d165568a2234df0fa00f87793"
 
 [[package]]
-name = "ioctl-rs"
-version = "0.1.6"
+name = "indenter"
+version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7970510895cee30b3e9128319f2cefd4bde883a39f38baa279567ba3a7eb97d"
-dependencies = [
- "libc",
-]
+checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
 
 [[package]]
-name = "leb128"
-version = "0.2.5"
+name = "lazy_static"
+version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 
 [[package]]
 name = "libc"
@@ -150,44 +121,6 @@ version = "0.2.138"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8"
 
-[[package]]
-name = "libudev"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78b324152da65df7bb95acfcaab55e3097ceaab02fb19b228a9eb74d55f135e0"
-dependencies = [
- "libc",
- "libudev-sys",
-]
-
-[[package]]
-name = "libudev-sys"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324"
-dependencies = [
- "libc",
- "pkg-config",
-]
-
-[[package]]
-name = "mach"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2fd13ee2dd61cc82833ba05ade5a30bb3d63f7ced605ef827063c63078302de9"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "mach"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa"
-dependencies = [
- "libc",
-]
-
 [[package]]
 name = "memchr"
 version = "2.5.0"
@@ -195,120 +128,62 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
 
 [[package]]
-name = "nix"
-version = "0.24.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
-dependencies = [
- "bitflags",
- "cfg-if",
- "libc",
-]
-
-[[package]]
-name = "num_cpus"
-version = "1.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
-dependencies = [
- "hermit-abi",
- "libc",
-]
-
-[[package]]
-name = "once_cell"
-version = "1.16.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
-
-[[package]]
-name = "pin-project-lite"
-version = "0.2.9"
+name = "minimal-lexical"
+version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
 
 [[package]]
-name = "pkg-config"
-version = "0.3.26"
+name = "miniz_oxide"
+version = "0.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
-
-[[package]]
-name = "proc-macro2"
-version = "1.0.47"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
+checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
 dependencies = [
- "unicode-ident",
+ "adler",
 ]
 
 [[package]]
-name = "quote"
-version = "1.0.21"
+name = "nom"
+version = "7.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
+checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
 dependencies = [
- "proc-macro2",
+ "memchr",
+ "minimal-lexical",
 ]
 
 [[package]]
-name = "regex"
-version = "1.7.0"
+name = "object"
+version = "0.30.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
+checksum = "239da7f290cfa979f43f85a8efeee9a8a76d0827c356d37f9d3d7254d6b537fb"
 dependencies = [
- "aho-corasick",
  "memchr",
- "regex-syntax",
 ]
 
 [[package]]
-name = "regex-syntax"
-version = "0.6.28"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
-
-[[package]]
-name = "serial"
-version = "0.4.0"
+name = "once_cell"
+version = "1.16.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1237a96570fc377c13baa1b88c7589ab66edced652e43ffb17088f003db3e86"
-dependencies = [
- "serial-core",
- "serial-unix",
- "serial-windows",
-]
+checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
 
 [[package]]
-name = "serial-core"
-version = "0.4.0"
+name = "owo-colors"
+version = "3.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f46209b345401737ae2125fe5b19a77acce90cd53e1658cda928e4fe9a64581"
-dependencies = [
- "libc",
-]
+checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
 
 [[package]]
-name = "serial-unix"
-version = "0.4.0"
+name = "pin-project-lite"
+version = "0.2.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f03fbca4c9d866e24a459cbca71283f545a37f8e3e002ad8c70593871453cab7"
-dependencies = [
- "ioctl-rs",
- "libc",
- "serial-core",
- "termios",
-]
+checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
 
 [[package]]
-name = "serial-windows"
-version = "0.4.0"
+name = "rustc-demangle"
+version = "0.1.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "15c6d3b776267a75d31bbdfd5d36c0ca051251caafc285827052bc53bcdc8162"
-dependencies = [
- "libc",
- "serial-core",
-]
+checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
 
 [[package]]
 name = "serial2"
@@ -322,117 +197,90 @@ dependencies = [
 ]
 
 [[package]]
-name = "serialport"
-version = "4.2.0"
+name = "serial_enumerator"
+version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aab92efb5cf60ad310548bc3f16fa6b0d950019cb7ed8ff41968c3d03721cf12"
+checksum = "12db7426ede8268552c2370897ebe98e64520ec27969c65f7b95800aafe636a0"
 dependencies = [
- "CoreFoundation-sys",
- "IOKit-sys",
- "bitflags",
- "cfg-if",
- "libudev",
- "mach 0.3.2",
- "nix",
- "regex",
- "winapi",
+ "nom",
+ "windows",
 ]
 
 [[package]]
-name = "sha2"
-version = "0.10.6"
+name = "sharded-slab"
+version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
+checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
 dependencies = [
- "cfg-if",
- "cpufeatures",
- "digest",
+ "lazy_static",
 ]
 
 [[package]]
-name = "syn"
-version = "1.0.105"
+name = "thread_local"
+version = "1.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908"
+checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
 dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
+ "once_cell",
 ]
 
 [[package]]
-name = "termios"
-version = "0.2.2"
+name = "tracing"
+version = "0.1.37"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d5d9cf598a6d7ce700a4e6a9199da127e6819a61e64b68609683cc9a01b5683a"
+checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
 dependencies = [
- "libc",
+ "cfg-if",
+ "pin-project-lite",
+ "tracing-core",
 ]
 
 [[package]]
-name = "thiserror"
-version = "1.0.37"
+name = "tracing-core"
+version = "0.1.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
+checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
 dependencies = [
- "thiserror-impl",
+ "once_cell",
+ "valuable",
 ]
 
 [[package]]
-name = "thiserror-impl"
-version = "1.0.37"
+name = "tracing-error"
+version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
+checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e"
 dependencies = [
- "proc-macro2",
- "quote",
- "syn",
+ "tracing",
+ "tracing-subscriber",
 ]
 
 [[package]]
-name = "tokio"
-version = "1.23.0"
+name = "tracing-subscriber"
+version = "0.3.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46"
+checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70"
 dependencies = [
- "autocfg",
- "num_cpus",
- "pin-project-lite",
- "windows-sys",
+ "sharded-slab",
+ "thread_local",
+ "tracing-core",
 ]
 
 [[package]]
 name = "tudelft-serial-upload"
 version = "0.1.0"
 dependencies = [
- "crc16",
+ "color-eyre",
  "expect-test",
- "leb128",
- "serial",
  "serial2",
- "serialport",
- "sha2",
- "thiserror",
- "tokio",
+ "serial_enumerator",
 ]
 
 [[package]]
-name = "typenum"
-version = "1.16.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
-
-[[package]]
-name = "unicode-ident"
-version = "1.0.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
-
-[[package]]
-name = "version_check"
-version = "0.9.4"
+name = "valuable"
+version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
 
 [[package]]
 name = "winapi"
@@ -457,58 +305,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
 [[package]]
-name = "windows-sys"
-version = "0.42.0"
+name = "windows"
+version = "0.29.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
+checksum = "aac7fef12f4b59cd0a29339406cc9203ab44e440ddff6b3f5a41455349fa9cf3"
 dependencies = [
- "windows_aarch64_gnullvm",
  "windows_aarch64_msvc",
  "windows_i686_gnu",
  "windows_i686_msvc",
  "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
  "windows_x86_64_msvc",
 ]
 
-[[package]]
-name = "windows_aarch64_gnullvm"
-version = "0.42.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
-
 [[package]]
 name = "windows_aarch64_msvc"
-version = "0.42.0"
+version = "0.29.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
+checksum = "c3d027175d00b01e0cbeb97d6ab6ebe03b12330a35786cbaca5252b1c4bf5d9b"
 
 [[package]]
 name = "windows_i686_gnu"
-version = "0.42.0"
+version = "0.29.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
+checksum = "8793f59f7b8e8b01eda1a652b2697d87b93097198ae85f823b969ca5b89bba58"
 
 [[package]]
 name = "windows_i686_msvc"
-version = "0.42.0"
+version = "0.29.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
+checksum = "8602f6c418b67024be2996c512f5f995de3ba417f4c75af68401ab8756796ae4"
 
 [[package]]
 name = "windows_x86_64_gnu"
-version = "0.42.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
-
-[[package]]
-name = "windows_x86_64_gnullvm"
-version = "0.42.0"
+version = "0.29.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
+checksum = "f3d615f419543e0bd7d2b3323af0d86ff19cbc4f816e6453f36a2c2ce889c354"
 
 [[package]]
 name = "windows_x86_64_msvc"
-version = "0.42.0"
+version = "0.29.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
+checksum = "11d95421d9ed3672c280884da53201a5c46b7b2765ca6faf34b0d71cf34a3561"
diff --git a/Cargo.toml b/Cargo.toml
index a99f1174f96cd2bdf86a4dead104ca7824b6b3e8..7c61dda476c37b32e3273e467a51f4815924b773 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,14 +6,9 @@ edition = "2021"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-sha2 = "0.10.6"
-leb128 = "0.2.5"
-serial = "0.4.0"
 serial2 = "0.1.7"
-serialport = "4.2.0"
-crc16 = "0.4.0"
-thiserror = "1.0.37"
-tokio = {version="1.23.0", features=["time", "rt-multi-thread"]}
+serial_enumerator = "0.2.5"
+color-eyre = "0.6.2"
 
 [dev-dependencies]
 expect-test = "1.4.0"
diff --git a/README.md b/README.md
index f200ec120cde6d2c74e526aada52cd97ff11c550..8e48de0bb93ea5de834c662a29d3797550bc3919 100644
--- a/README.md
+++ b/README.md
@@ -3,5 +3,3 @@
 
 Serial upload library for the quadrupel drone project
 of the Embedded Systems Lab at the TU Delft (`CESE4030`)
-
-Modified version of <https://github.com/ferrous-systems/nrfdfu-rs>, in library form.
diff --git a/legacy/fcb_software b/legacy/fcb_software
deleted file mode 160000
index ae7862257f18f29a727a1c07ac60b7a42d3822e3..0000000000000000000000000000000000000000
--- a/legacy/fcb_software
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit ae7862257f18f29a727a1c07ac60b7a42d3822e3
diff --git a/nrfdfu-rs b/nrfdfu-rs
deleted file mode 160000
index f44c4b0307fd8924b5e6805a8b5db412c8bd406e..0000000000000000000000000000000000000000
--- a/nrfdfu-rs
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit f44c4b0307fd8924b5e6805a8b5db412c8bd406e
diff --git a/src/crc.rs b/src/crc.rs
index 7905b44eccbb1d725b4a62fafa1c15ef5105f593..2e4cd10166285333cda31c542c108552179b9b0e 100644
--- a/src/crc.rs
+++ b/src/crc.rs
@@ -10,7 +10,8 @@ pub fn calc_crc16(data: &[u8], start: Option<u16>) -> u16 {
         crc ^= (crc << 8) << 4;
         crc ^= ((crc & 0x00FF) << 4) << 1;
     }
-    return crc;
+
+    crc
 }
 
 pub fn calc_crc16_default(data: &[u8]) -> u16 {
diff --git a/src/error.rs b/src/error.rs
deleted file mode 100644
index 1132b28a43a8d8226e484c1f3f8c75a4f0b6aacb..0000000000000000000000000000000000000000
--- a/src/error.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-use thiserror::Error;
-
-#[derive(Debug, Error)]
-pub enum UploadError {
-    #[error("failed to upload due to the following errors:\n{}", _0.iter().map(|i| i.to_string()).collect::<Vec<_>>().join("\n"))]
-    Multiple(Vec<UploadError>),
-
-    #[error("errored while listing available serial ports: {0}")]
-    ListSerialPort(serialport::Error),
-
-    #[error("errored while opening serial port at {0}: {1}")]
-    OpenPortError(String, serialport::Error),
-
-    #[error("errored while writing to serial port: {0}")]
-    WriteError(std::io::Error),
-
-    #[error("errored while reading from serial port: {0}")]
-    ReadError(std::io::Error),
-
-    #[error("errored while reading from file to upload: {0}")]
-    ReadFile(String),
-
-    #[error("wait for ack timed out")]
-    TimeoutError,
-
-    #[error("malformed response: escape byte followed by either nothing or an invalid byte: {0:?}")]
-    InvalidOrNoByteAfterEscape(Option<u8>)
-}
-
-pub type Result<T, E = UploadError> = std::result::Result<T, E>;
diff --git a/src/lib.rs b/src/lib.rs
index 94f6522415ea7d11bf7613795308b197bde5d56e..303e7d8934b63c3907cd7c98236ba863b5f68015 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,15 +1,14 @@
 extern crate core;
 
+mod crc;
 mod selector;
-mod upload;
-mod error;
 mod serial;
-mod crc;
+mod upload;
 
 use std::time::Duration;
 
+pub use color_eyre;
 pub use selector::PortSelector;
-pub use error::UploadError;
-pub use upload::{upload, upload_or_stop, async_upload, upload_file_or_stop, upload_file};
+pub use upload::{upload, upload_file, upload_file_or_stop, upload_or_stop};
 
-const SERIAL_TIMEOUT: Duration = Duration::from_secs(5);
+const SERIAL_TIMEOUT: Duration = Duration::from_secs(1);
diff --git a/src/selector.rs b/src/selector.rs
index b0e9136349f7809c0a4857a306668687b0e5e0d5..260bcca68dd2b8bf592555e8165f798b31629e31 100644
--- a/src/selector.rs
+++ b/src/selector.rs
@@ -1,6 +1,5 @@
-use serialport::{available_ports, SerialPortType};
-use crate::error::Result;
-use crate::UploadError::ListSerialPort;
+use color_eyre::Result;
+use serial_enumerator::get_serial_list;
 
 #[derive(Default)]
 // TODO: manufacturer
@@ -23,7 +22,7 @@ pub enum PortSelector<'a> {
     /// Choose to a specific, named serial port
     /// Note that a conversion from strings exists for this
     /// variant, so you can just write `upload("/dev/ttyUSB0", ...)` for example.
-    Named(&'a str)
+    Named(&'a str),
 }
 
 impl<'a, T: AsRef<str>> From<&'a T> for PortSelector<'a> {
@@ -33,11 +32,8 @@ impl<'a, T: AsRef<str>> From<&'a T> for PortSelector<'a> {
 }
 
 pub fn find_available_serial_ports() -> Result<impl Iterator<Item = String>> {
-    Ok(
-        available_ports()
-            .map_err(ListSerialPort)?
-            .into_iter()
-            .filter(|i| matches!(i.port_type, SerialPortType::UsbPort(_)))
-            .map(|i| i.port_name)
-    )
+    Ok(get_serial_list()
+        .into_iter()
+        .filter(|i| i.usb_info.is_some())
+        .map(|i| i.name))
 }
diff --git a/src/serial.rs b/src/serial.rs
index cf861bd2b35543b667c3ab850f6d7c8c9b665463..345dc7f7173a0bd1f38ec72b0172aa65fa6187d7 100644
--- a/src/serial.rs
+++ b/src/serial.rs
@@ -1,14 +1,13 @@
-use std::io::{Read, stdout, Write};
-use std::process::exit;
+use color_eyre::eyre::{bail, WrapErr};
+use serial2::{FlowControl, SerialPort, Settings};
+use std::io::{stdout, Read, Write};
+use std::sync::mpsc::channel;
+use std::thread::{sleep, spawn};
 use std::time::Duration;
-use serial2::{SerialPort, Settings, FlowControl};
-use serial::core::SerialDevice;
-use tokio::time::{sleep, timeout};
+
 use crate::crc::calc_crc16_default;
-use crate::UploadError::{InvalidOrNoByteAfterEscape, ReadError, TimeoutError, WriteError};
-use crate::error::Result;
 use crate::SERIAL_TIMEOUT;
-
+use color_eyre::Result;
 
 const DFU_INIT_PACKET: u32 = 1;
 const DFU_START_PACKET: u32 = 3;
@@ -20,20 +19,26 @@ const SEND_INIT_PACKET_WAIT_TIME: Duration = Duration::from_secs(1);
 
 pub struct Serial {
     port: SerialPort,
+    pub(crate) path: String,
     sequence_number: u8,
 }
 
 impl Serial {
-    pub fn open(name: String) -> Result<Self> {
-        let mut port = SerialPort::open(&name, |mut s: Settings| {
+    pub fn open(path: String) -> Result<Self> {
+        let mut port = SerialPort::open(&path, |mut s: Settings| {
             s.set_flow_control(FlowControl::RtsCts);
             s.set_baud_rate(921600)?;
             Ok(s)
-        }).unwrap();
-        port.set_read_timeout(SERIAL_TIMEOUT).unwrap();
+        })
+        .wrap_err("failed to open serial port")?;
+
+        // port.set_read_timeout(SERIAL_TIMEOUT).wrap_err("failed to set read timeout")?;
+        port.set_write_timeout(SERIAL_TIMEOUT)
+            .wrap_err("failed to set write timeout")?;
 
         Ok(Self {
             port,
+            path,
             sequence_number: 0,
         })
     }
@@ -45,7 +50,7 @@ impl Serial {
 
     /// For a description of the SLIP header go to:
     /// http://developer.nordicsemi.com/nRF51_SDK/doc/7.2.0/s110/html/a00093.html
-    fn create_slip_header(&mut self, pkt_len: usize) -> [u8; 4] {
+    fn create_slip_header(&mut self, pkt_len: usize) -> ([u8; 4], u8) {
         assert!(pkt_len < 0x1000);
 
         // sequence number
@@ -55,8 +60,6 @@ impl Serial {
         // reliable packet (yes, our (USB) connection is reliable)
         let rp = true as u8;
 
-        println!("SLIP: seq={seq}, pkt_len={pkt_len}");
-
         // we always send HCI packet, pkt type 14.
         let pkt_type = 14;
 
@@ -64,31 +67,33 @@ impl Serial {
         let b2 = pkt_type | ((pkt_len & 0x00f) << 4) as u8;
         let b3 = ((pkt_len & 0xff0) >> 4) as u8;
 
-        println!("b1={b1} b2={b2} b3={b3}");
-
-        [
-            b1,
-            b2,
-            b3,
-            (!b1.wrapping_add(b2).wrapping_add(b3)).wrapping_add(1),
-        ]
+        (
+            [
+                b1,
+                b2,
+                b3,
+                (!b1.wrapping_add(b2).wrapping_add(b3)).wrapping_add(1),
+            ],
+            seq,
+        )
     }
 
     fn encode_int(i: u32) -> [u8; 4] {
         i.to_le_bytes()
     }
 
-    fn create_packet(&mut self, data: &[u8]) -> Vec<u8> {
+    fn create_packet(&mut self, data: &[u8]) -> (Vec<u8>, u8) {
         let mut temp_res = Vec::new();
 
+        let (bytes, seq_nr) = self.create_slip_header(data.len());
         // create header
-        temp_res.extend_from_slice(&self.create_slip_header(data.len()));
+        temp_res.extend_from_slice(&bytes);
         // add data
         temp_res.extend_from_slice(data);
         // add crc
         temp_res.extend_from_slice(&calc_crc16_default(&temp_res).to_le_bytes());
 
-        Self::escape(&temp_res)
+        (Self::escape(&temp_res), seq_nr)
     }
 
     fn escape(unescaped: &[u8]) -> Vec<u8> {
@@ -96,7 +101,7 @@ impl Serial {
         for &i in unescaped {
             match i {
                 0xc0 => res.extend_from_slice(&[0xdb, 0xdc]),
-                0xdb => res.extend_from_slice(&[0xc0, 0xdd]),
+                0xdb => res.extend_from_slice(&[0xdb, 0xdd]),
                 a => res.push(a),
             }
         }
@@ -113,46 +118,58 @@ impl Serial {
                 0xdb => match iter.next() {
                     Some(0xdc) => 0xc0,
                     Some(0xdd) => 0xdb,
-                    i => return Err(InvalidOrNoByteAfterEscape(i.copied()))
-                }
-                i => i
+                    i => bail!("encountered invalid byte '{i:?}' after escape character"),
+                },
+                i => i,
             });
         }
 
         Ok(res)
     }
 
-    pub async fn send_data(&mut self, data: &[u8]) -> Result<()> {
-        let packet = self.create_packet(data);
+    pub fn send_data(&mut self, data: &[u8]) -> Result<()> {
+        let (packet, seq_nr) = self.create_packet(data);
 
-        println!("send: {packet:?}");
+        // println!("send: {:?}", packet.iter().map(|i| format!("{:02x}", i).chars().collect::<Vec<_>>()).flatten().collect::<String>());
 
-        self.port.write_all(&packet)
-            .map_err(WriteError)?;
-        sleep(Duration::from_millis(500)).await;
+        self.port
+            .write_all(&packet)
+            .wrap_err("failed to write to serial port")?;
+        sleep(Duration::from_millis(40));
 
-        if let Ok(i) = timeout(
-            SERIAL_TIMEOUT,
-            self.wait_for_ack(),
-        ).await {
-            i.map(|_| ())
-        } else {
-            Err(TimeoutError)
+        let res = self.wait_for_ack()
+            .wrap_err("waiting for message acknowledgement. If this is due to a timeout, try resetting your board, or turning it off and on again")?;
+
+        if res != (seq_nr + 1) % 8 {
+            bail!("received invalid sequence number, retry transmission")
         }
+
+        Ok(())
     }
 
-    pub async fn wait_for_ack(&mut self) -> Result<u8> {
+    pub fn wait_for_ack(&mut self) -> Result<u8> {
+        let (tx, rx) = channel();
+
+        spawn(move || {
+            if rx.recv_timeout(SERIAL_TIMEOUT).is_err() {
+                println!("Your read operation seems to be timing out. Make sure you reset your board before uploading a program");
+                println!("and try turning it off and on again. We'll keep trying to send data, but most likely the upload has failed now.");
+            }
+        });
+
         let mut response = Vec::new();
 
         while response.iter().filter(|&&i| i == 0xc0).count() < 2 {
             let mut temp = [0u8; 6];
-            self.port.read_exact(&mut temp)
-                .map_err(ReadError)?;
-            println!("got bytes: {:?}", temp);
-
+            self.port
+                .read_exact(&mut temp)
+                .wrap_err("failed to read from serial port")?;
             response.extend_from_slice(&temp);
         }
 
+        // ignore error, if the thread died then that's too bad.
+        let _ = tx.send(());
+
         let unescaped = Self::unescape(&response)?;
 
         // remove 0xc0 at the start and end
@@ -161,88 +178,94 @@ impl Serial {
         Ok(message[0] >> 3 & 0x07)
     }
 
-    pub async fn send_start_dfu(&mut self, file_size: u32) -> Result<()> {
+    pub fn send_start_dfu(&mut self, file_size: u32) -> Result<()> {
         let mut res = Vec::new();
 
-
         res.extend_from_slice(&Self::encode_int(DFU_START_PACKET));
         res.extend_from_slice(&Self::encode_int(4));
         res.extend_from_slice(&Self::encode_int(0));
         res.extend_from_slice(&Self::encode_int(0));
         res.extend_from_slice(&Self::encode_int(file_size));
 
-        println!("packet: {res:?}");
-        self.send_data(&res).await?;
+        self.send_data(&res)?;
 
         Ok(())
     }
 
-    pub async fn send_init_packet(&mut self, file: &[u8]) -> Result<()> {
+    pub fn send_init_packet(&mut self, file: &[u8]) -> Result<()> {
         let mut res = vec![];
 
         res.extend_from_slice(&Self::encode_int(DFU_INIT_PACKET));
-        res.extend_from_slice(&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0xfe, 0xff]);
+        res.extend_from_slice(&[
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0xfe, 0xff,
+        ]);
         res.extend_from_slice(&calc_crc16_default(file).to_le_bytes());
         // padding required as per the python reference implementation. No further docs found on this
         res.extend_from_slice(&[0, 0]);
 
-        self.send_data(&res).await?;
+        self.send_data(&res)?;
 
         Ok(())
     }
 
-    pub async fn send_stop_packet(&mut self) -> Result<()> {
+    pub fn send_stop_packet(&mut self) -> Result<()> {
         let mut res = vec![];
 
         res.extend_from_slice(&Self::encode_int(DFU_STOP_DATA_PACKET));
-        self.send_data(&res).await?;
+        self.send_data(&res)?;
 
         Ok(())
     }
 
-    pub async fn send_data_packet(&mut self, data: &[u8]) -> Result<()> {
+    pub fn send_data_packet(&mut self, data: &[u8]) -> Result<()> {
         let mut res = vec![];
 
         res.extend_from_slice(&Self::encode_int(DFU_DATA_PACKET));
-        res.extend_from_slice(&data);
+        res.extend_from_slice(data);
 
-        self.send_data(&res).await?;
+        self.send_data(&res)?;
 
         Ok(())
     }
 
-    pub async fn try_do_upload(&mut self, file: &[u8]) -> Result<()> {
+    pub fn try_do_upload(&mut self, file: &[u8]) -> Result<()> {
         println!("starting connection...");
-        self.send_start_dfu(file.len() as u32).await?;
+        self.send_start_dfu(file.len() as u32)?;
         // wait before we actually send data to the board after
         // we send the start_dfu message
-        sleep(SEND_START_DFU_WAIT_TIME).await;
+        sleep(SEND_START_DFU_WAIT_TIME);
 
-        println!("intitializing upload...");
-        self.send_init_packet(file).await?;
+        println!("initializing upload...");
+        self.send_init_packet(file)?;
 
         // wait before we actually send data to the board after
         // we send the init_packet message
-        sleep(SEND_INIT_PACKET_WAIT_TIME).await;
+        sleep(SEND_INIT_PACKET_WAIT_TIME);
 
         let total_chunks = (file.len() + DFU_MAX_PACKET_SIZE - 1) / DFU_MAX_PACKET_SIZE;
 
-        println!("uploading in {total_chunks} chunks ({}kb)...", file.len() as f64 / 1024.0);
+        println!(
+            "uploading in {total_chunks} chunks ({}kb)...",
+            file.len() as f64 / 1024.0
+        );
         for (index, i) in file.chunks(DFU_MAX_PACKET_SIZE).enumerate() {
-            if let Err(e) = self.send_data_packet(i).await {
+            if let Err(e) = self.send_data_packet(i) {
                 println!();
                 return Err(e);
             }
-            print!("\rframes uploaded: {index}/{total_chunks} = {:.1}%", (index as f64 / total_chunks as f64) * 100.0);
+            print!(
+                "\rframes uploaded: {}/{total_chunks} = {:.1}%",
+                index + 1,
+                ((index + 1) as f64 / total_chunks as f64) * 100.0
+            );
             stdout().flush().unwrap();
         }
         println!();
 
-        println!("finalizing upload..");
-        self.send_stop_packet().await?;
+        println!("finalizing upload...");
+        self.send_stop_packet()?;
 
         println!("done");
         Ok(())
     }
 }
-
diff --git a/src/upload.rs b/src/upload.rs
index 77eca3b9941b27b98af0de961dfdba21c952c4ad..f959a406bf2dc657a6bc1ee1dfd0e1761329c566 100644
--- a/src/upload.rs
+++ b/src/upload.rs
@@ -1,33 +1,39 @@
+use crate::serial::Serial;
+use crate::{selector, PortSelector};
+use color_eyre::eyre::{bail, eyre, Context};
+use color_eyre::Result;
 use std::fs::read;
 use std::path::Path;
-use std::process::{Command, exit};
-use crate::{error, PortSelector, selector, UploadError};
-use crate::serial::Serial;
-use crate::UploadError::ReadFile;
+use std::process::{exit, Command};
 
-fn copy_object(source: &Path, target: &Path) -> Result<(), String> {
+fn copy_object(source: &Path, target: &Path) -> Result<()> {
     if Command::new("rust-objcopy").output().is_err() {
-        return Err("rust-objcopy not found, try installing cargo-binutils or refer to the course website".to_string());
+        bail!(
+            "rust-objcopy not found, try installing cargo-binutils or refer to the course website"
+        );
     }
 
     let op = Command::new("rust-objcopy")
         .arg("-O")
         .arg("binary")
-        .arg(&source)
-        .arg(&target)
+        .arg(source)
+        .arg(target)
         .output()
-        .map_err(|e| e.to_string())?;
+        .wrap_err("failed to run rust-objcopy")?;
 
     println!("creating binary file at {target:?}");
 
     if !op.status.success() {
-        return Err(format!("running rust-objcopy failed: {}", String::from_utf8_lossy(&op.stderr)));
+        bail!(
+            "running rust-objcopy failed: {}",
+            String::from_utf8_lossy(&op.stderr)
+        );
     }
 
     Ok(())
 }
 
-fn read_file(file: &Path) -> Result<Vec<u8>, String> {
+fn read_file(file: &Path) -> Result<Vec<u8>> {
     let mut target = file.to_path_buf();
     target.set_extension("bin");
 
@@ -35,17 +41,19 @@ fn read_file(file: &Path) -> Result<Vec<u8>, String> {
     copy_object(file, &target)?;
 
     println!("reading binary file");
-    read(target).map_err(|e| e.to_string())
+    read(target).wrap_err("failed to read converted binary file to send to board")
 }
 
 /// Upload a file to a connected board. Select which serial port the board is on with the [`PortSelector`].
 /// The file is expected to be the compiled `.elf` file created by cargo/rustc
 /// Exit with an exit code of 1 when the upload fails.
-pub fn upload_file_or_stop<'a>(port: PortSelector<'a>, file: impl AsRef<Path>) {
-    match read_file(file.as_ref()) {
+pub fn upload_file_or_stop(port: PortSelector, file: impl AsRef<Path>) {
+    match read_file(file.as_ref())
+        .wrap_err_with(|| format!("failed to read from file {:?}", file.as_ref()))
+    {
         Ok(i) => upload_or_stop(port, i),
         Err(e) => {
-            eprintln!("{e}");
+            eprintln!("{e:?}");
             exit(1);
         }
     }
@@ -54,17 +62,21 @@ pub fn upload_file_or_stop<'a>(port: PortSelector<'a>, file: impl AsRef<Path>) {
 /// Upload a file to a connected board. Select which serial port the board is on with the [`PortSelector`]
 /// The file is expected to be the compiled `.elf` file created by cargo/rustc
 /// Returns an error when the upload fails.
-pub fn upload_file<'a>(port: PortSelector<'a>, file: impl AsRef<Path>) -> error::Result<()> {
-    upload(port, read_file(file.as_ref()).map_err(ReadFile)?)
+pub fn upload_file(port: PortSelector, file: impl AsRef<Path>) -> Result<()> {
+    upload(
+        port,
+        read_file(file.as_ref())
+            .wrap_err_with(|| format!("failed to read from file {:?}", file.as_ref()))?,
+    )
 }
 
 /// Upload (already read) bytes to a connected board. Select which serial port the board is on with the [`PortSelector`]
 /// The bytes are the exact bytes that are uploaded to the board. That means it should be a binary file, and *not* contain
 /// ELF headers or similar
 /// Exit with an exit code of 1 when the upload fails.
-pub fn upload_or_stop<'a>(port: PortSelector<'a>, file: impl AsRef<[u8]>) {
-    if let Err(e) = upload(port.into(), file.as_ref()) {
-        eprintln!("{e}");
+pub fn upload_or_stop(port: PortSelector, file: impl AsRef<[u8]>) {
+    if let Err(e) = upload(port, file.as_ref()) {
+        eprintln!("{e:?}");
         exit(1);
     }
 }
@@ -73,37 +85,23 @@ pub fn upload_or_stop<'a>(port: PortSelector<'a>, file: impl AsRef<[u8]>) {
 /// The bytes are the exact bytes that are uploaded to the board. That means it should be a binary file, and *not* contain
 /// ELF headers or similar
 /// Returns an error when the upload fails.
-pub fn upload<'a>(port: PortSelector<'a>, file: impl AsRef<[u8]>) -> error::Result<()> {
-    let rt = tokio::runtime::Builder::new_multi_thread()
-        .enable_all()
-        .build()
-        .unwrap();
-
-    rt.block_on(async {
-        upload_internal(port, file.as_ref()).await
-    })
-}
-
-/// Async version of [`upload`], so you can supply your own `tokio` runtime. No other runtimes are supported.
-pub async fn async_upload<'a>(port: PortSelector<'a>, file: impl AsRef<[u8]>) -> error::Result<()> {
-    upload_internal(port, file.as_ref()).await
+pub fn upload(port: PortSelector, file: impl AsRef<[u8]>) -> Result<()> {
+    upload_internal(port, file.as_ref())
 }
 
-async fn upload_internal(port: PortSelector<'_>, file: &[u8]) -> error::Result<()> {
-    let (ports_to_try, stop_after_first_error): (Vec<error::Result<Serial>>, bool) = match port {
-        PortSelector::SearchFirst => {
-            (
-                selector::find_available_serial_ports()?
-                    .map(Serial::open)
-                    .collect(),
-                true
-            )
-        },
+fn upload_internal(port: PortSelector<'_>, file: &[u8]) -> Result<()> {
+    let (ports_to_try, stop_after_first_error): (Vec<Result<Serial>>, bool) = match port {
+        PortSelector::SearchFirst => (
+            selector::find_available_serial_ports()?
+                .map(Serial::open)
+                .collect(),
+            true,
+        ),
         PortSelector::SearchAll => (
             selector::find_available_serial_ports()?
-            .map(Serial::open)
-            .collect(),
-            false
+                .map(Serial::open)
+                .collect(),
+            false,
         ),
         PortSelector::ChooseInteractive => todo!(),
         PortSelector::Named(n) => (vec![Serial::open(n.to_string())], false),
@@ -127,22 +125,27 @@ async fn upload_internal(port: PortSelector<'_>, file: &[u8]) -> error::Result<(
             }
         };
 
-        if let Err(e) = port.try_do_upload(file).await {
+        if let Err(e) = port
+            .try_do_upload(file)
+            .wrap_err_with(|| format!("failed to upload to port {}", port.path))
+        {
             if stop_after_first_error || num_ports == 1 {
-                return Err(e)
+                return Err(e);
             } else {
                 eprintln!("WARNING: {e}");
                 errors.push(e);
-                continue
+                continue;
             }
         }
 
-        break
+        break;
     }
 
     if errors.is_empty() {
         Ok(())
     } else {
-        Err(UploadError::Multiple(errors))
+        Err(eyre!(
+            "uploading failed because none of the ports tried worked (see previous warnings)"
+        ))
     }
 }