mirror of
https://github.com/Djeeberjr/fw-anwesenheit.git
synced 2026-04-30 18:49:09 +00:00
Compare commits
45 Commits
v1.0
...
bc4dc20012
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc4dc20012 | ||
|
|
d90376121e | ||
| 2a81499f7c | |||
|
|
4ff8ff0f77 | ||
|
|
781d27ae48 | ||
|
|
671fb0cbdd | ||
|
|
99d9cf306e | ||
|
|
b551f4521f | ||
|
|
adcbe87bd7 | ||
|
|
d96b3ed11a | ||
|
|
dcb4b14854 | ||
| fe90ca9aa9 | |||
|
|
b031a47e85 | ||
|
|
bf59b6eed3 | ||
|
|
59d87eb199 | ||
|
|
630fc4aaf9 | ||
| 21480cef4f | |||
|
|
fabb14de86 | ||
|
|
6a2d448f86 | ||
|
|
fc7bd8b089 | ||
|
|
3117c55b1c | ||
|
|
593d98df74 | ||
|
|
fa6d1f024c | ||
|
|
36dc52f464 | ||
|
|
6831d7776c | ||
| a015d6b983 | |||
|
|
1ae5250449 | ||
|
|
2f502e908e | ||
|
|
5950279dc4 | ||
| fe6540ca3d | |||
|
|
161ebf9bd2 | ||
|
|
c1b54920ff | ||
|
|
5a2beb1fb3 | ||
|
|
d5c20bf348 | ||
|
|
49027fed99 | ||
|
|
4dda9548d3 | ||
|
|
46e207bd2a | ||
|
|
8cb118e0ee | ||
|
|
9b4df77112 | ||
| 23bb1126a6 | |||
| a97e9c8080 | |||
| 4b39529a65 | |||
| c91d2f070f | |||
| 2e6094ea11 | |||
| 43e964b5a0 |
14
.cargo/config.toml
Normal file
14
.cargo/config.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[target.riscv32imac-unknown-none-elf]
|
||||
runner = "espflash flash --monitor --chip esp32c6"
|
||||
|
||||
[build]
|
||||
rustflags = [
|
||||
# Required to obtain backtraces (e.g. when using the "esp-backtrace" crate.)
|
||||
# NOTE: May negatively impact performance of produced code
|
||||
"-C", "force-frame-pointers",
|
||||
]
|
||||
|
||||
target = "riscv32imac-unknown-none-elf"
|
||||
|
||||
[unstable]
|
||||
build-std = ["alloc", "core"]
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,2 +1,4 @@
|
||||
/target
|
||||
/build
|
||||
|
||||
pcb/fw-anwesenheit-backups
|
||||
Binary file not shown.
Binary file not shown.
BIN
3d_print/LWL.3mf
BIN
3d_print/LWL.3mf
Binary file not shown.
Binary file not shown.
BIN
3d_print/enclouseure_printfile.3mf
Normal file
BIN
3d_print/enclouseure_printfile.3mf
Normal file
Binary file not shown.
BIN
3d_print/enclousure_top_ffw.3mf
Normal file
BIN
3d_print/enclousure_top_ffw.3mf
Normal file
Binary file not shown.
2429
Cargo.lock
generated
2429
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
95
Cargo.toml
95
Cargo.toml
@@ -3,24 +3,83 @@ name = "fw-anwesenheit"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
mock_pi = [] # Enable mocking of the rpi hardware
|
||||
[[bin]]
|
||||
name = "fw-anwesenheit"
|
||||
path = "./src/main.rs"
|
||||
test = false
|
||||
doctest = false
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4.40", features = ["serde"] }
|
||||
gpio = "0.4.1"
|
||||
regex = "1.11.1"
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.140"
|
||||
rocket = { version = "0.5.1", features = ["json"] }
|
||||
tokio = { version = "1.44.2", features = ["full"] }
|
||||
rust-embed = "8.7.0"
|
||||
log = "0.4.27"
|
||||
simplelog = "0.12.2"
|
||||
rppal = { version = "0.22.1", features = ["hal"] }
|
||||
smart-leds = "0.3"
|
||||
ws2812-spi = "0.3"
|
||||
rgb = "0.8.50"
|
||||
anyhow = "1.0.98"
|
||||
esp-bootloader-esp-idf = "0.1.0"
|
||||
embassy-net = { version = "0.7.0", features = [
|
||||
"dhcpv4",
|
||||
"medium-ethernet",
|
||||
"tcp",
|
||||
"udp",
|
||||
] }
|
||||
embedded-hal = "=1.0.0"
|
||||
embedded-io = "0.6.1"
|
||||
embedded-io-async = "0.6.1"
|
||||
esp-alloc = "0.8.0"
|
||||
esp-hal = { version = "1.0.0-beta.1", features = ["esp32c6", "unstable"] }
|
||||
smoltcp = { version = "0.12.0", default-features = false, features = [
|
||||
"medium-ethernet",
|
||||
"multicast",
|
||||
"proto-dhcpv4",
|
||||
"proto-dns",
|
||||
"proto-ipv4",
|
||||
"socket-dns",
|
||||
"socket-icmp",
|
||||
"socket-raw",
|
||||
"socket-tcp",
|
||||
"socket-udp",
|
||||
] }
|
||||
# for more networking protocol support see https://crates.io/crates/edge-net
|
||||
bleps = { git = "https://github.com/bjoernQ/bleps", package = "bleps", rev = "a5148d8ae679e021b78f53fd33afb8bb35d0b62e", features = [
|
||||
"async",
|
||||
"macros",
|
||||
] }
|
||||
critical-section = "1.2.0"
|
||||
embassy-executor = { version = "0.7.0", features = ["nightly"] }
|
||||
embassy-time = { version = "0.4.0", features = ["generic-queue-8"] }
|
||||
esp-hal-embassy = { version = "0.9.0", features = ["esp32c6"] }
|
||||
esp-wifi = { version = "0.15.0", features = [
|
||||
"wifi",
|
||||
"builtin-scheduler",
|
||||
"esp-alloc",
|
||||
"esp32c6",
|
||||
"log-04",
|
||||
] }
|
||||
heapless = { version = "0.8.0", default-features = false }
|
||||
static_cell = { version = "2.1.0", features = ["nightly"] }
|
||||
esp-println = { version = "0.15.0", features = ["esp32c6", "log-04"] }
|
||||
log = { version = "0.4" }
|
||||
edge-dhcp = { version = "0.6.0", features = ["log"] }
|
||||
edge-nal = "0.5.0"
|
||||
edge-nal-embassy = { version = "0.6.0", features = ["log"] }
|
||||
picoserve = { version = "0.16.0", features = ["embassy", "log"] }
|
||||
embassy-sync = { version = "0.7.0", features = ["log"] }
|
||||
ds3231 = { version = "0.3.0", features = ["async", "temperature_f32"] }
|
||||
chrono = { version = "0.4.41", default-features = false }
|
||||
dir-embed = "0.3.0"
|
||||
esp-hal-smartled = { git = "https://github.com/esp-rs/esp-hal-community.git", package = "esp-hal-smartled", branch = "main", features = ["esp32c6"]}
|
||||
smart-leds = "0.4.0"
|
||||
serde = { version = "1.0.219", default-features = false, features = ["derive", "alloc"] }
|
||||
embedded-sdmmc = "0.8.0"
|
||||
embedded-hal-bus = "0.3.0"
|
||||
serde_json = { version = "1.0.143", default-features = false, features = ["alloc"]}
|
||||
|
||||
[profile.dev]
|
||||
# Rust debug is too slow.
|
||||
# For debug builds always builds with some optimization
|
||||
opt-level = "s"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1 # LLVM can perform better optimizations using a single thread
|
||||
debug = 2
|
||||
debug-assertions = false
|
||||
incremental = false
|
||||
lto = 'fat'
|
||||
opt-level = 's'
|
||||
overflow-checks = false
|
||||
|
||||
61
Makefile
61
Makefile
@@ -1,61 +0,0 @@
|
||||
PACKAGE_NAME := fwa
|
||||
VERSION := 1.0
|
||||
ARCH := armhf
|
||||
BUILD_DIR := build
|
||||
DEB_DIR := $(BUILD_DIR)/$(PACKAGE_NAME)-$(VERSION)
|
||||
BIN_DIR := $(DEB_DIR)/usr/local/bin
|
||||
SERVICE_DIR := $(DEB_DIR)/lib/systemd/system
|
||||
CONFIG_DIR := $(DEB_DIR)/etc
|
||||
PM3_DIR := $(DEB_DIR)/usr/share/pm3
|
||||
|
||||
.PHONY: all build clean package prepare_package
|
||||
|
||||
all: package
|
||||
|
||||
build: $(BUILD_DIR)/fwa
|
||||
|
||||
$(BUILD_DIR)/fwa: web/dist
|
||||
cross build --release --target arm-unknown-linux-gnueabihf
|
||||
cp ./target/arm-unknown-linux-gnueabihf/release/fw-anwesenheit $@
|
||||
|
||||
prepare_package: $(DEB_DIR)/DEBIAN $(BIN_DIR)/fwa
|
||||
mkdir -p $(SERVICE_DIR)
|
||||
cp ./service/fwa.service $(SERVICE_DIR)/
|
||||
cp ./service/fwa-fail.service $(SERVICE_DIR)/
|
||||
|
||||
mkdir -p $(CONFIG_DIR)
|
||||
cp ./service/fwa.env $(CONFIG_DIR)/
|
||||
|
||||
mkdir -p $(PM3_DIR)
|
||||
cp -r ./pre-compiled/* $(PM3_DIR)/
|
||||
|
||||
mkdir -p $(DEB_DIR)/var/lib/fwa/
|
||||
|
||||
$(BIN_DIR)/fwa: $(BUILD_DIR)/fwa
|
||||
mkdir -p $(BIN_DIR)
|
||||
cp $< $@
|
||||
|
||||
|
||||
$(DEB_DIR)/DEBIAN:
|
||||
mkdir -p $(DEB_DIR)/DEBIAN
|
||||
echo "Package: $(PACKAGE_NAME)" > $(DEB_DIR)/DEBIAN/control
|
||||
echo "Version: $(VERSION)" >> $(DEB_DIR)/DEBIAN/control
|
||||
echo "Section: utils" >> $(DEB_DIR)/DEBIAN/control
|
||||
echo "Priority: optional" >> $(DEB_DIR)/DEBIAN/control
|
||||
echo "Architecture: $(ARCH)" >> $(DEB_DIR)/DEBIAN/control
|
||||
echo "Depends: libc6 (>= 2.28)" >> $(DEB_DIR)/DEBIAN/control
|
||||
echo "Maintainer: Niklas Kapelle <niklas@kapelle.org>" >> $(DEB_DIR)/DEBIAN/control
|
||||
echo "Description: Feuerwehr anwesenheit" >> $(DEB_DIR)/DEBIAN/control
|
||||
|
||||
echo "/etc/fwa.env" > $(DEB_DIR)/DEBIAN/conffiles
|
||||
|
||||
web/dist:
|
||||
(cd web && npm run build)
|
||||
|
||||
package: prepare_package
|
||||
dpkg-deb --build $(DEB_DIR)
|
||||
|
||||
clean:
|
||||
cargo clean
|
||||
rm -rf web/dist
|
||||
rm -rf $(BUILD_DIR)
|
||||
66
build.rs
Normal file
66
build.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
||||
fn main() {
|
||||
linker_be_nice();
|
||||
// make sure linkall.x is the last linker script (otherwise might cause problems with flip-link)
|
||||
println!("cargo:rustc-link-arg=-Tlinkall.x");
|
||||
save_build_time();
|
||||
}
|
||||
|
||||
fn save_build_time() {
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let dest_path = Path::new(&out_dir).join("build_time.rs");
|
||||
let system_time = std::time::SystemTime::now();
|
||||
let unix_time = system_time
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
println!("cargo:rustc-env=BUILD_TIME={}", unix_time);
|
||||
let content = format!(
|
||||
"/// compile time as UNIX-Timestamp (seconds since 1970-01-01)
|
||||
pub const BUILD_UNIX_TIME: u64 = {};",
|
||||
unix_time
|
||||
);
|
||||
let mut f = File::create(dest_path).unwrap();
|
||||
f.write_all(content.as_bytes()).unwrap();
|
||||
}
|
||||
|
||||
fn linker_be_nice() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
if args.len() > 1 {
|
||||
let kind = &args[1];
|
||||
let what = &args[2];
|
||||
|
||||
match kind.as_str() {
|
||||
"undefined-symbol" => match what.as_str() {
|
||||
"_defmt_timestamp" => {
|
||||
eprintln!();
|
||||
eprintln!(
|
||||
"💡 `defmt` not found - make sure `defmt.x` is added as a linker script and you have included `use defmt_rtt as _;`"
|
||||
);
|
||||
eprintln!();
|
||||
}
|
||||
"_stack_start" => {
|
||||
eprintln!();
|
||||
eprintln!("💡 Is the linker script `linkall.x` missing?");
|
||||
eprintln!();
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
// we don't have anything helpful for "missing-lib" yet
|
||||
_ => {
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
println!(
|
||||
"cargo:rustc-link-arg=--error-handling-script={}",
|
||||
std::env::current_exe().unwrap().display()
|
||||
);
|
||||
}
|
||||
24
buzzer.py
24
buzzer.py
@@ -1,24 +0,0 @@
|
||||
import RPi.GPIO as GPIO
|
||||
import time
|
||||
|
||||
BUZZER_PIN = 26
|
||||
|
||||
def beep(frequency, duration):
|
||||
pwm = GPIO.PWM(BUZZER_PIN, frequency)
|
||||
pwm.start(50) # 50 % duty cycle
|
||||
time.sleep(duration)
|
||||
pwm.stop()
|
||||
|
||||
def main():
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
GPIO.setup(BUZZER_PIN, GPIO.OUT)
|
||||
|
||||
try:
|
||||
beep(523, 0.3) # C5
|
||||
beep(659, 0.3) # E5
|
||||
beep(784, 0.3) # G5
|
||||
finally:
|
||||
GPIO.cleanup()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 13 KiB |
@@ -1,34 +0,0 @@
|
||||
@startuml
|
||||
actor user
|
||||
|
||||
|
||||
main -> pm3 :start
|
||||
loop
|
||||
pm3 -> pm3 : look for HITAG
|
||||
end
|
||||
|
||||
user -> pm3 :show HITAG
|
||||
|
||||
pm3 -> parser : parse ID
|
||||
parser -> pm3 : return ID
|
||||
|
||||
pm3 --> main : send ID
|
||||
|
||||
loop
|
||||
main -> main : look for IDs
|
||||
end
|
||||
|
||||
main -> idstore : send ID
|
||||
idstore -> System : ask for day
|
||||
alt
|
||||
System -> idstore : return attendence_day
|
||||
else
|
||||
create collections attendence_day
|
||||
idstore -> attendence_day : create new attendence_day
|
||||
|
||||
end
|
||||
|
||||
idstore -> attendence_day : add ID
|
||||
attendence_day -> system : save json
|
||||
|
||||
@enduml
|
||||
61
em4100_write.lua
Normal file
61
em4100_write.lua
Normal file
@@ -0,0 +1,61 @@
|
||||
local used_ids = {}
|
||||
local id_file_path = "used_ids.txt"
|
||||
|
||||
local function load_ids()
|
||||
local file = io.open(id_file_path, "r")
|
||||
if not file then return end
|
||||
for line in file:lines() do
|
||||
used_ids[line:lower()] = true
|
||||
end
|
||||
file:close()
|
||||
end
|
||||
|
||||
local function save_id(id)
|
||||
local file = io.open(id_file_path, "a")
|
||||
if file then
|
||||
file:write(id:lower() .. "\n")
|
||||
file:close()
|
||||
end
|
||||
end
|
||||
|
||||
local function gen_id()
|
||||
local id = ""
|
||||
for i = 1, 10 do
|
||||
id = id .. string.format("%x", math.random(0, 15))
|
||||
end
|
||||
return id
|
||||
end
|
||||
|
||||
local function get_new_id()
|
||||
local tries = 0
|
||||
while tries < 10000 do
|
||||
local id = gen_id()
|
||||
if not used_ids[id:lower()] then
|
||||
return id
|
||||
end
|
||||
tries = tries + 1
|
||||
end
|
||||
error("Could not generate a new unused ID after 10000 tries")
|
||||
end
|
||||
|
||||
local function write_new_card()
|
||||
local id = get_new_id()
|
||||
local cmd = string.format("lf em 410x clone --id %s", id)
|
||||
core.console(cmd)
|
||||
used_ids[id:lower()] = true
|
||||
save_id(id)
|
||||
print("Wrote new EM4100 card with ID:", id)
|
||||
end
|
||||
|
||||
local function write_new_card(id)
|
||||
local cmd = string.format("lf em 410x clone --id %s", id)
|
||||
core.console(cmd)
|
||||
used_ids[id:lower()] = true
|
||||
save_id(id)
|
||||
print("Wrote new EM4100 card with ID:", id)
|
||||
end
|
||||
|
||||
math.randomseed(os.time())
|
||||
load_ids()
|
||||
local id = get_new_id()
|
||||
write_new_card(id)
|
||||
0
pcb/FETCH_HEAD
Normal file
0
pcb/FETCH_HEAD
Normal file
4709
pcb/bom/ibom.html
Normal file
4709
pcb/bom/ibom.html
Normal file
File diff suppressed because one or more lines are too long
1
pcb/fabrication-toolkit-options.json
Normal file
1
pcb/fabrication-toolkit-options.json
Normal file
@@ -0,0 +1 @@
|
||||
{"ARCHIVE_NAME": "", "EXTRA_LAYERS": "", "ALL_ACTIVE_LAYERS": false, "EXTEND_EDGE_CUT": false, "ALTERNATIVE_EDGE_CUT": false, "AUTO TRANSLATE": true, "AUTO FILL": true, "EXCLUDE DNP": false}
|
||||
105757
pcb/fp-info-cache
Normal file
105757
pcb/fp-info-cache
Normal file
File diff suppressed because it is too large
Load Diff
BIN
pcb/fw-anwesenheit-backups/fw-anwesenheit-2025-09-21_232810.zip
Normal file
BIN
pcb/fw-anwesenheit-backups/fw-anwesenheit-2025-09-21_232810.zip
Normal file
Binary file not shown.
BIN
pcb/fw-anwesenheit-backups/fw-anwesenheit-2025-09-22_184259.zip
Normal file
BIN
pcb/fw-anwesenheit-backups/fw-anwesenheit-2025-09-22_184259.zip
Normal file
Binary file not shown.
BIN
pcb/fw-anwesenheit-backups/fw-anwesenheit-2025-09-30_164954.zip
Normal file
BIN
pcb/fw-anwesenheit-backups/fw-anwesenheit-2025-09-30_164954.zip
Normal file
Binary file not shown.
BIN
pcb/fw-anwesenheit-backups/fw-anwesenheit-2025-09-30_184632.zip
Normal file
BIN
pcb/fw-anwesenheit-backups/fw-anwesenheit-2025-09-30_184632.zip
Normal file
Binary file not shown.
27924
pcb/fw-anwesenheit.kicad_pcb
Normal file
27924
pcb/fw-anwesenheit.kicad_pcb
Normal file
File diff suppressed because it is too large
Load Diff
143
pcb/fw-anwesenheit.kicad_prl
Normal file
143
pcb/fw-anwesenheit.kicad_prl
Normal file
@@ -0,0 +1,143 @@
|
||||
{
|
||||
"board": {
|
||||
"active_layer": 6,
|
||||
"active_layer_preset": "",
|
||||
"auto_track_width": true,
|
||||
"hidden_netclasses": [],
|
||||
"hidden_nets": [],
|
||||
"high_contrast_mode": 0,
|
||||
"net_color_mode": 1,
|
||||
"opacity": {
|
||||
"images": 0.6,
|
||||
"pads": 1.0,
|
||||
"shapes": 1.0,
|
||||
"tracks": 1.0,
|
||||
"vias": 1.0,
|
||||
"zones": 0.6
|
||||
},
|
||||
"selection_filter": {
|
||||
"dimensions": true,
|
||||
"footprints": true,
|
||||
"graphics": true,
|
||||
"keepouts": true,
|
||||
"lockedItems": false,
|
||||
"otherItems": true,
|
||||
"pads": true,
|
||||
"text": true,
|
||||
"tracks": true,
|
||||
"vias": true,
|
||||
"zones": true
|
||||
},
|
||||
"visible_items": [
|
||||
"vias",
|
||||
"footprint_text",
|
||||
"footprint_anchors",
|
||||
"ratsnest",
|
||||
"grid",
|
||||
"footprints_front",
|
||||
"footprints_back",
|
||||
"footprint_values",
|
||||
"footprint_references",
|
||||
"tracks",
|
||||
"drc_errors",
|
||||
"drawing_sheet",
|
||||
"bitmaps",
|
||||
"pads",
|
||||
"zones",
|
||||
"drc_warnings",
|
||||
"locked_item_shadows",
|
||||
"conflict_shadows",
|
||||
"shapes"
|
||||
],
|
||||
"visible_layers": "00000000_00000000_0fffffff_fffff8ef",
|
||||
"zone_display_mode": 0
|
||||
},
|
||||
"git": {
|
||||
"repo_password": "",
|
||||
"repo_type": "",
|
||||
"repo_username": "",
|
||||
"ssh_key": ""
|
||||
},
|
||||
"meta": {
|
||||
"filename": "fw-anwesenheit.kicad_prl",
|
||||
"version": 5
|
||||
},
|
||||
"net_inspector_panel": {
|
||||
"col_hidden": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
],
|
||||
"col_order": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13
|
||||
],
|
||||
"col_widths": [
|
||||
162,
|
||||
147,
|
||||
91,
|
||||
72,
|
||||
91,
|
||||
100,
|
||||
91,
|
||||
76,
|
||||
91,
|
||||
91,
|
||||
91,
|
||||
91,
|
||||
91,
|
||||
91
|
||||
],
|
||||
"custom_group_rules": [],
|
||||
"expanded_rows": [],
|
||||
"filter_by_net_name": true,
|
||||
"filter_by_netclass": true,
|
||||
"filter_text": "",
|
||||
"group_by_constraint": false,
|
||||
"group_by_netclass": false,
|
||||
"show_unconnected_nets": false,
|
||||
"show_zero_pad_nets": false,
|
||||
"sort_ascending": true,
|
||||
"sorting_column": 0
|
||||
},
|
||||
"open_jobsets": [],
|
||||
"project": {
|
||||
"files": []
|
||||
},
|
||||
"schematic": {
|
||||
"selection_filter": {
|
||||
"graphics": true,
|
||||
"images": true,
|
||||
"labels": true,
|
||||
"lockedItems": false,
|
||||
"otherItems": true,
|
||||
"pins": true,
|
||||
"symbols": true,
|
||||
"text": true,
|
||||
"wires": true
|
||||
}
|
||||
}
|
||||
}
|
||||
633
pcb/fw-anwesenheit.kicad_pro
Normal file
633
pcb/fw-anwesenheit.kicad_pro
Normal file
@@ -0,0 +1,633 @@
|
||||
{
|
||||
"board": {
|
||||
"3dviewports": [],
|
||||
"design_settings": {
|
||||
"defaults": {
|
||||
"apply_defaults_to_fp_fields": false,
|
||||
"apply_defaults_to_fp_shapes": false,
|
||||
"apply_defaults_to_fp_text": false,
|
||||
"board_outline_line_width": 0.05,
|
||||
"copper_line_width": 0.2,
|
||||
"copper_text_italic": false,
|
||||
"copper_text_size_h": 1.5,
|
||||
"copper_text_size_v": 1.5,
|
||||
"copper_text_thickness": 0.3,
|
||||
"copper_text_upright": false,
|
||||
"courtyard_line_width": 0.05,
|
||||
"dimension_precision": 4,
|
||||
"dimension_units": 3,
|
||||
"dimensions": {
|
||||
"arrow_length": 1270000,
|
||||
"extension_offset": 500000,
|
||||
"keep_text_aligned": true,
|
||||
"suppress_zeroes": true,
|
||||
"text_position": 0,
|
||||
"units_format": 0
|
||||
},
|
||||
"fab_line_width": 0.1,
|
||||
"fab_text_italic": false,
|
||||
"fab_text_size_h": 1.0,
|
||||
"fab_text_size_v": 1.0,
|
||||
"fab_text_thickness": 0.15,
|
||||
"fab_text_upright": false,
|
||||
"other_line_width": 0.1,
|
||||
"other_text_italic": false,
|
||||
"other_text_size_h": 1.0,
|
||||
"other_text_size_v": 1.0,
|
||||
"other_text_thickness": 0.15,
|
||||
"other_text_upright": false,
|
||||
"pads": {
|
||||
"drill": 0.0,
|
||||
"height": 1.7,
|
||||
"width": 1.7
|
||||
},
|
||||
"silk_line_width": 0.1,
|
||||
"silk_text_italic": false,
|
||||
"silk_text_size_h": 1.0,
|
||||
"silk_text_size_v": 1.0,
|
||||
"silk_text_thickness": 0.1,
|
||||
"silk_text_upright": false,
|
||||
"zones": {
|
||||
"min_clearance": 0.5
|
||||
}
|
||||
},
|
||||
"diff_pair_dimensions": [
|
||||
{
|
||||
"gap": 0.0,
|
||||
"via_gap": 0.0,
|
||||
"width": 0.0
|
||||
}
|
||||
],
|
||||
"drc_exclusions": [],
|
||||
"meta": {
|
||||
"version": 2
|
||||
},
|
||||
"rule_severities": {
|
||||
"annular_width": "error",
|
||||
"clearance": "error",
|
||||
"connection_width": "warning",
|
||||
"copper_edge_clearance": "error",
|
||||
"copper_sliver": "warning",
|
||||
"courtyards_overlap": "error",
|
||||
"creepage": "error",
|
||||
"diff_pair_gap_out_of_range": "error",
|
||||
"diff_pair_uncoupled_length_too_long": "error",
|
||||
"drill_out_of_range": "error",
|
||||
"duplicate_footprints": "warning",
|
||||
"extra_footprint": "warning",
|
||||
"footprint": "error",
|
||||
"footprint_filters_mismatch": "ignore",
|
||||
"footprint_symbol_mismatch": "warning",
|
||||
"footprint_type_mismatch": "ignore",
|
||||
"hole_clearance": "error",
|
||||
"hole_near_hole": "error",
|
||||
"hole_to_hole": "error",
|
||||
"holes_co_located": "warning",
|
||||
"invalid_outline": "error",
|
||||
"isolated_copper": "warning",
|
||||
"item_on_disabled_layer": "error",
|
||||
"items_not_allowed": "error",
|
||||
"length_out_of_range": "error",
|
||||
"lib_footprint_issues": "warning",
|
||||
"lib_footprint_mismatch": "warning",
|
||||
"malformed_courtyard": "error",
|
||||
"microvia_drill_out_of_range": "error",
|
||||
"mirrored_text_on_front_layer": "warning",
|
||||
"missing_courtyard": "ignore",
|
||||
"missing_footprint": "warning",
|
||||
"net_conflict": "warning",
|
||||
"nonmirrored_text_on_back_layer": "warning",
|
||||
"npth_inside_courtyard": "ignore",
|
||||
"padstack": "warning",
|
||||
"pth_inside_courtyard": "ignore",
|
||||
"shorting_items": "error",
|
||||
"silk_edge_clearance": "warning",
|
||||
"silk_over_copper": "warning",
|
||||
"silk_overlap": "warning",
|
||||
"skew_out_of_range": "error",
|
||||
"solder_mask_bridge": "error",
|
||||
"starved_thermal": "error",
|
||||
"text_height": "warning",
|
||||
"text_on_edge_cuts": "error",
|
||||
"text_thickness": "warning",
|
||||
"through_hole_pad_without_hole": "error",
|
||||
"too_many_vias": "error",
|
||||
"track_angle": "error",
|
||||
"track_dangling": "warning",
|
||||
"track_segment_length": "error",
|
||||
"track_width": "error",
|
||||
"tracks_crossing": "error",
|
||||
"unconnected_items": "error",
|
||||
"unresolved_variable": "error",
|
||||
"via_dangling": "warning",
|
||||
"zones_intersect": "error"
|
||||
},
|
||||
"rules": {
|
||||
"max_error": 0.005,
|
||||
"min_clearance": 0.2,
|
||||
"min_connection": 0.0,
|
||||
"min_copper_edge_clearance": 0.5,
|
||||
"min_groove_width": 0.0,
|
||||
"min_hole_clearance": 0.25,
|
||||
"min_hole_to_hole": 0.2,
|
||||
"min_microvia_diameter": 0.45,
|
||||
"min_microvia_drill": 0.3,
|
||||
"min_resolved_spokes": 2,
|
||||
"min_silk_clearance": 0.0,
|
||||
"min_text_height": 0.8,
|
||||
"min_text_thickness": 0.08,
|
||||
"min_through_hole_diameter": 0.3,
|
||||
"min_track_width": 0.16,
|
||||
"min_via_annular_width": 0.075,
|
||||
"min_via_diameter": 0.45,
|
||||
"solder_mask_to_copper_clearance": 0.005,
|
||||
"use_height_for_length_calcs": true
|
||||
},
|
||||
"teardrop_options": [
|
||||
{
|
||||
"td_onpthpad": true,
|
||||
"td_onroundshapesonly": false,
|
||||
"td_onsmdpad": true,
|
||||
"td_ontrackend": false,
|
||||
"td_onvia": true
|
||||
}
|
||||
],
|
||||
"teardrop_parameters": [
|
||||
{
|
||||
"td_allow_use_two_tracks": true,
|
||||
"td_curve_segcount": 0,
|
||||
"td_height_ratio": 1.0,
|
||||
"td_length_ratio": 0.5,
|
||||
"td_maxheight": 2.0,
|
||||
"td_maxlen": 1.0,
|
||||
"td_on_pad_in_zone": false,
|
||||
"td_target_name": "td_round_shape",
|
||||
"td_width_to_size_filter_ratio": 0.9
|
||||
},
|
||||
{
|
||||
"td_allow_use_two_tracks": true,
|
||||
"td_curve_segcount": 0,
|
||||
"td_height_ratio": 1.0,
|
||||
"td_length_ratio": 0.5,
|
||||
"td_maxheight": 2.0,
|
||||
"td_maxlen": 1.0,
|
||||
"td_on_pad_in_zone": false,
|
||||
"td_target_name": "td_rect_shape",
|
||||
"td_width_to_size_filter_ratio": 0.9
|
||||
},
|
||||
{
|
||||
"td_allow_use_two_tracks": true,
|
||||
"td_curve_segcount": 0,
|
||||
"td_height_ratio": 1.0,
|
||||
"td_length_ratio": 0.5,
|
||||
"td_maxheight": 2.0,
|
||||
"td_maxlen": 1.0,
|
||||
"td_on_pad_in_zone": false,
|
||||
"td_target_name": "td_track_end",
|
||||
"td_width_to_size_filter_ratio": 0.9
|
||||
}
|
||||
],
|
||||
"track_widths": [
|
||||
0.0
|
||||
],
|
||||
"tuning_pattern_settings": {
|
||||
"diff_pair_defaults": {
|
||||
"corner_radius_percentage": 80,
|
||||
"corner_style": 1,
|
||||
"max_amplitude": 1.0,
|
||||
"min_amplitude": 0.2,
|
||||
"single_sided": false,
|
||||
"spacing": 1.0
|
||||
},
|
||||
"diff_pair_skew_defaults": {
|
||||
"corner_radius_percentage": 80,
|
||||
"corner_style": 1,
|
||||
"max_amplitude": 1.0,
|
||||
"min_amplitude": 0.2,
|
||||
"single_sided": false,
|
||||
"spacing": 0.6
|
||||
},
|
||||
"single_track_defaults": {
|
||||
"corner_radius_percentage": 80,
|
||||
"corner_style": 1,
|
||||
"max_amplitude": 1.0,
|
||||
"min_amplitude": 0.2,
|
||||
"single_sided": false,
|
||||
"spacing": 0.6
|
||||
}
|
||||
},
|
||||
"via_dimensions": [
|
||||
{
|
||||
"diameter": 0.0,
|
||||
"drill": 0.0
|
||||
}
|
||||
],
|
||||
"zones_allow_external_fillets": false
|
||||
},
|
||||
"ipc2581": {
|
||||
"dist": "",
|
||||
"distpn": "",
|
||||
"internal_id": "",
|
||||
"mfg": "",
|
||||
"mpn": ""
|
||||
},
|
||||
"layer_pairs": [],
|
||||
"layer_presets": [],
|
||||
"viewports": []
|
||||
},
|
||||
"boards": [],
|
||||
"cvpcb": {
|
||||
"equivalence_files": []
|
||||
},
|
||||
"erc": {
|
||||
"erc_exclusions": [],
|
||||
"meta": {
|
||||
"version": 0
|
||||
},
|
||||
"pin_map": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2
|
||||
]
|
||||
],
|
||||
"rule_severities": {
|
||||
"bus_definition_conflict": "error",
|
||||
"bus_entry_needed": "error",
|
||||
"bus_to_bus_conflict": "error",
|
||||
"bus_to_net_conflict": "error",
|
||||
"different_unit_footprint": "error",
|
||||
"different_unit_net": "error",
|
||||
"duplicate_reference": "error",
|
||||
"duplicate_sheet_names": "error",
|
||||
"endpoint_off_grid": "warning",
|
||||
"extra_units": "error",
|
||||
"footprint_filter": "ignore",
|
||||
"footprint_link_issues": "warning",
|
||||
"four_way_junction": "ignore",
|
||||
"global_label_dangling": "warning",
|
||||
"hier_label_mismatch": "error",
|
||||
"label_dangling": "error",
|
||||
"label_multiple_wires": "warning",
|
||||
"lib_symbol_issues": "warning",
|
||||
"lib_symbol_mismatch": "warning",
|
||||
"missing_bidi_pin": "warning",
|
||||
"missing_input_pin": "warning",
|
||||
"missing_power_pin": "error",
|
||||
"missing_unit": "warning",
|
||||
"multiple_net_names": "warning",
|
||||
"net_not_bus_member": "warning",
|
||||
"no_connect_connected": "warning",
|
||||
"no_connect_dangling": "warning",
|
||||
"pin_not_connected": "error",
|
||||
"pin_not_driven": "error",
|
||||
"pin_to_pin": "warning",
|
||||
"power_pin_not_driven": "error",
|
||||
"same_local_global_label": "warning",
|
||||
"similar_label_and_power": "warning",
|
||||
"similar_labels": "warning",
|
||||
"similar_power": "warning",
|
||||
"simulation_model_issue": "ignore",
|
||||
"single_global_label": "ignore",
|
||||
"unannotated": "error",
|
||||
"unconnected_wire_endpoint": "warning",
|
||||
"undefined_netclass": "error",
|
||||
"unit_value_mismatch": "error",
|
||||
"unresolved_variable": "error",
|
||||
"wire_dangling": "error"
|
||||
}
|
||||
},
|
||||
"libraries": {
|
||||
"pinned_footprint_libs": [],
|
||||
"pinned_symbol_libs": []
|
||||
},
|
||||
"meta": {
|
||||
"filename": "fw-anwesenheit.kicad_pro",
|
||||
"version": 3
|
||||
},
|
||||
"net_settings": {
|
||||
"classes": [
|
||||
{
|
||||
"bus_width": 12,
|
||||
"clearance": 0.2,
|
||||
"diff_pair_gap": 0.25,
|
||||
"diff_pair_via_gap": 0.25,
|
||||
"diff_pair_width": 0.2,
|
||||
"line_style": 0,
|
||||
"microvia_diameter": 0.3,
|
||||
"microvia_drill": 0.1,
|
||||
"name": "Default",
|
||||
"pcb_color": "rgba(0, 0, 0, 0.000)",
|
||||
"priority": 2147483647,
|
||||
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
||||
"track_width": 0.2,
|
||||
"via_diameter": 0.6,
|
||||
"via_drill": 0.3,
|
||||
"wire_width": 6
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"version": 4
|
||||
},
|
||||
"net_colors": null,
|
||||
"netclass_assignments": null,
|
||||
"netclass_patterns": []
|
||||
},
|
||||
"pcbnew": {
|
||||
"last_paths": {
|
||||
"gencad": "",
|
||||
"idf": "",
|
||||
"netlist": "",
|
||||
"plot": "production/",
|
||||
"pos_files": "",
|
||||
"specctra_dsn": "",
|
||||
"step": "fw-anwesenheit.step",
|
||||
"svg": "",
|
||||
"vrml": ""
|
||||
},
|
||||
"page_layout_descr_file": ""
|
||||
},
|
||||
"schematic": {
|
||||
"annotate_start_num": 0,
|
||||
"bom_export_filename": "${PROJECTNAME}.csv",
|
||||
"bom_fmt_presets": [],
|
||||
"bom_fmt_settings": {
|
||||
"field_delimiter": ",",
|
||||
"keep_line_breaks": false,
|
||||
"keep_tabs": false,
|
||||
"name": "CSV",
|
||||
"ref_delimiter": ",",
|
||||
"ref_range_delimiter": "",
|
||||
"string_delimiter": "\""
|
||||
},
|
||||
"bom_presets": [],
|
||||
"bom_settings": {
|
||||
"exclude_dnp": false,
|
||||
"fields_ordered": [
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Reference",
|
||||
"name": "Reference",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Qty",
|
||||
"name": "${QUANTITY}",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "Value",
|
||||
"name": "Value",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "DNP",
|
||||
"name": "${DNP}",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "Exclude from BOM",
|
||||
"name": "${EXCLUDE_FROM_BOM}",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "Exclude from Board",
|
||||
"name": "${EXCLUDE_FROM_BOARD}",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "Footprint",
|
||||
"name": "Footprint",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Datasheet",
|
||||
"name": "Datasheet",
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"filter_string": "",
|
||||
"group_symbols": true,
|
||||
"include_excluded_from_bom": true,
|
||||
"name": "Default Editing",
|
||||
"sort_asc": true,
|
||||
"sort_field": "Referenz"
|
||||
},
|
||||
"connection_grid_size": 50.0,
|
||||
"drawing": {
|
||||
"dashed_lines_dash_length_ratio": 12.0,
|
||||
"dashed_lines_gap_length_ratio": 3.0,
|
||||
"default_line_thickness": 6.0,
|
||||
"default_text_size": 50.0,
|
||||
"field_names": [],
|
||||
"intersheets_ref_own_page": false,
|
||||
"intersheets_ref_prefix": "",
|
||||
"intersheets_ref_short": false,
|
||||
"intersheets_ref_show": false,
|
||||
"intersheets_ref_suffix": "",
|
||||
"junction_size_choice": 3,
|
||||
"label_size_ratio": 0.375,
|
||||
"operating_point_overlay_i_precision": 3,
|
||||
"operating_point_overlay_i_range": "~A",
|
||||
"operating_point_overlay_v_precision": 3,
|
||||
"operating_point_overlay_v_range": "~V",
|
||||
"overbar_offset_ratio": 1.23,
|
||||
"pin_symbol_size": 25.0,
|
||||
"text_offset_ratio": 0.15
|
||||
},
|
||||
"legacy_lib_dir": "",
|
||||
"legacy_lib_list": [],
|
||||
"meta": {
|
||||
"version": 1
|
||||
},
|
||||
"net_format_name": "",
|
||||
"page_layout_descr_file": "",
|
||||
"plot_directory": "",
|
||||
"space_save_all_events": true,
|
||||
"spice_current_sheet_as_root": false,
|
||||
"spice_external_command": "spice \"%I\"",
|
||||
"spice_model_current_sheet_as_root": true,
|
||||
"spice_save_all_currents": false,
|
||||
"spice_save_all_dissipations": false,
|
||||
"spice_save_all_voltages": false,
|
||||
"subpart_first_id": 65,
|
||||
"subpart_id_separator": 0
|
||||
},
|
||||
"sheets": [
|
||||
[
|
||||
"ccbf1fda-befd-42da-bcb2-5d3829184012",
|
||||
"Root"
|
||||
]
|
||||
],
|
||||
"text_variables": {}
|
||||
}
|
||||
11815
pcb/fw-anwesenheit.kicad_sch
Normal file
11815
pcb/fw-anwesenheit.kicad_sch
Normal file
File diff suppressed because it is too large
Load Diff
122063
pcb/fw-anwesenheit.step
Normal file
122063
pcb/fw-anwesenheit.step
Normal file
File diff suppressed because it is too large
Load Diff
374733
pcb/fw-anwesenheit.stl
Normal file
374733
pcb/fw-anwesenheit.stl
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
2
pcb/fw-anwesenheit/fw-anwesenheit.kicad_pcb
Normal file
2
pcb/fw-anwesenheit/fw-anwesenheit.kicad_pcb
Normal file
@@ -0,0 +1,2 @@
|
||||
(kicad_pcb (version 20241229) (generator "pcbnew") (generator_version "9.0")
|
||||
)
|
||||
98
pcb/fw-anwesenheit/fw-anwesenheit.kicad_prl
Normal file
98
pcb/fw-anwesenheit/fw-anwesenheit.kicad_prl
Normal file
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"board": {
|
||||
"active_layer": 0,
|
||||
"active_layer_preset": "",
|
||||
"auto_track_width": true,
|
||||
"hidden_netclasses": [],
|
||||
"hidden_nets": [],
|
||||
"high_contrast_mode": 0,
|
||||
"net_color_mode": 1,
|
||||
"opacity": {
|
||||
"images": 0.6,
|
||||
"pads": 1.0,
|
||||
"shapes": 1.0,
|
||||
"tracks": 1.0,
|
||||
"vias": 1.0,
|
||||
"zones": 0.6
|
||||
},
|
||||
"selection_filter": {
|
||||
"dimensions": true,
|
||||
"footprints": true,
|
||||
"graphics": true,
|
||||
"keepouts": true,
|
||||
"lockedItems": false,
|
||||
"otherItems": true,
|
||||
"pads": true,
|
||||
"text": true,
|
||||
"tracks": true,
|
||||
"vias": true,
|
||||
"zones": true
|
||||
},
|
||||
"visible_items": [
|
||||
"vias",
|
||||
"footprint_text",
|
||||
"footprint_anchors",
|
||||
"ratsnest",
|
||||
"grid",
|
||||
"footprints_front",
|
||||
"footprints_back",
|
||||
"footprint_values",
|
||||
"footprint_references",
|
||||
"tracks",
|
||||
"drc_errors",
|
||||
"drawing_sheet",
|
||||
"bitmaps",
|
||||
"pads",
|
||||
"zones",
|
||||
"drc_warnings",
|
||||
"drc_exclusions",
|
||||
"locked_item_shadows",
|
||||
"conflict_shadows",
|
||||
"shapes"
|
||||
],
|
||||
"visible_layers": "ffffffff_ffffffff_ffffffff_ffffffff",
|
||||
"zone_display_mode": 0
|
||||
},
|
||||
"git": {
|
||||
"repo_type": "",
|
||||
"repo_username": "",
|
||||
"ssh_key": ""
|
||||
},
|
||||
"meta": {
|
||||
"filename": "fw-anwesenheit.kicad_prl",
|
||||
"version": 5
|
||||
},
|
||||
"net_inspector_panel": {
|
||||
"col_hidden": [],
|
||||
"col_order": [],
|
||||
"col_widths": [],
|
||||
"custom_group_rules": [],
|
||||
"expanded_rows": [],
|
||||
"filter_by_net_name": true,
|
||||
"filter_by_netclass": true,
|
||||
"filter_text": "",
|
||||
"group_by_constraint": false,
|
||||
"group_by_netclass": false,
|
||||
"show_unconnected_nets": false,
|
||||
"show_zero_pad_nets": false,
|
||||
"sort_ascending": true,
|
||||
"sorting_column": -1
|
||||
},
|
||||
"open_jobsets": [],
|
||||
"project": {
|
||||
"files": []
|
||||
},
|
||||
"schematic": {
|
||||
"selection_filter": {
|
||||
"graphics": true,
|
||||
"images": true,
|
||||
"labels": true,
|
||||
"lockedItems": false,
|
||||
"otherItems": true,
|
||||
"pins": true,
|
||||
"symbols": true,
|
||||
"text": true,
|
||||
"wires": true
|
||||
}
|
||||
}
|
||||
}
|
||||
413
pcb/fw-anwesenheit/fw-anwesenheit.kicad_pro
Normal file
413
pcb/fw-anwesenheit/fw-anwesenheit.kicad_pro
Normal file
@@ -0,0 +1,413 @@
|
||||
{
|
||||
"board": {
|
||||
"3dviewports": [],
|
||||
"design_settings": {
|
||||
"defaults": {},
|
||||
"diff_pair_dimensions": [],
|
||||
"drc_exclusions": [],
|
||||
"rules": {},
|
||||
"track_widths": [],
|
||||
"via_dimensions": []
|
||||
},
|
||||
"ipc2581": {
|
||||
"dist": "",
|
||||
"distpn": "",
|
||||
"internal_id": "",
|
||||
"mfg": "",
|
||||
"mpn": ""
|
||||
},
|
||||
"layer_pairs": [],
|
||||
"layer_presets": [],
|
||||
"viewports": []
|
||||
},
|
||||
"boards": [],
|
||||
"cvpcb": {
|
||||
"equivalence_files": []
|
||||
},
|
||||
"erc": {
|
||||
"erc_exclusions": [],
|
||||
"meta": {
|
||||
"version": 0
|
||||
},
|
||||
"pin_map": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2
|
||||
]
|
||||
],
|
||||
"rule_severities": {
|
||||
"bus_definition_conflict": "error",
|
||||
"bus_entry_needed": "error",
|
||||
"bus_to_bus_conflict": "error",
|
||||
"bus_to_net_conflict": "error",
|
||||
"different_unit_footprint": "error",
|
||||
"different_unit_net": "error",
|
||||
"duplicate_reference": "error",
|
||||
"duplicate_sheet_names": "error",
|
||||
"endpoint_off_grid": "warning",
|
||||
"extra_units": "error",
|
||||
"footprint_filter": "ignore",
|
||||
"footprint_link_issues": "warning",
|
||||
"four_way_junction": "ignore",
|
||||
"global_label_dangling": "warning",
|
||||
"hier_label_mismatch": "error",
|
||||
"label_dangling": "error",
|
||||
"label_multiple_wires": "warning",
|
||||
"lib_symbol_issues": "warning",
|
||||
"lib_symbol_mismatch": "warning",
|
||||
"missing_bidi_pin": "warning",
|
||||
"missing_input_pin": "warning",
|
||||
"missing_power_pin": "error",
|
||||
"missing_unit": "warning",
|
||||
"multiple_net_names": "warning",
|
||||
"net_not_bus_member": "warning",
|
||||
"no_connect_connected": "warning",
|
||||
"no_connect_dangling": "warning",
|
||||
"pin_not_connected": "error",
|
||||
"pin_not_driven": "error",
|
||||
"pin_to_pin": "warning",
|
||||
"power_pin_not_driven": "error",
|
||||
"same_local_global_label": "warning",
|
||||
"similar_label_and_power": "warning",
|
||||
"similar_labels": "warning",
|
||||
"similar_power": "warning",
|
||||
"simulation_model_issue": "ignore",
|
||||
"single_global_label": "ignore",
|
||||
"unannotated": "error",
|
||||
"unconnected_wire_endpoint": "warning",
|
||||
"undefined_netclass": "error",
|
||||
"unit_value_mismatch": "error",
|
||||
"unresolved_variable": "error",
|
||||
"wire_dangling": "error"
|
||||
}
|
||||
},
|
||||
"libraries": {
|
||||
"pinned_footprint_libs": [],
|
||||
"pinned_symbol_libs": []
|
||||
},
|
||||
"meta": {
|
||||
"filename": "fw-anwesenheit.kicad_pro",
|
||||
"version": 3
|
||||
},
|
||||
"net_settings": {
|
||||
"classes": [
|
||||
{
|
||||
"bus_width": 12,
|
||||
"clearance": 0.2,
|
||||
"diff_pair_gap": 0.25,
|
||||
"diff_pair_via_gap": 0.25,
|
||||
"diff_pair_width": 0.2,
|
||||
"line_style": 0,
|
||||
"microvia_diameter": 0.3,
|
||||
"microvia_drill": 0.1,
|
||||
"name": "Default",
|
||||
"pcb_color": "rgba(0, 0, 0, 0.000)",
|
||||
"priority": 2147483647,
|
||||
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
||||
"track_width": 0.2,
|
||||
"via_diameter": 0.6,
|
||||
"via_drill": 0.3,
|
||||
"wire_width": 6
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"version": 4
|
||||
},
|
||||
"net_colors": null,
|
||||
"netclass_assignments": null,
|
||||
"netclass_patterns": []
|
||||
},
|
||||
"pcbnew": {
|
||||
"last_paths": {
|
||||
"gencad": "",
|
||||
"idf": "",
|
||||
"netlist": "",
|
||||
"plot": "",
|
||||
"pos_files": "",
|
||||
"specctra_dsn": "",
|
||||
"step": "",
|
||||
"svg": "",
|
||||
"vrml": ""
|
||||
},
|
||||
"page_layout_descr_file": ""
|
||||
},
|
||||
"schematic": {
|
||||
"annotate_start_num": 0,
|
||||
"bom_export_filename": "${PROJECTNAME}.csv",
|
||||
"bom_fmt_presets": [],
|
||||
"bom_fmt_settings": {
|
||||
"field_delimiter": ",",
|
||||
"keep_line_breaks": false,
|
||||
"keep_tabs": false,
|
||||
"name": "CSV",
|
||||
"ref_delimiter": ",",
|
||||
"ref_range_delimiter": "",
|
||||
"string_delimiter": "\""
|
||||
},
|
||||
"bom_presets": [],
|
||||
"bom_settings": {
|
||||
"exclude_dnp": false,
|
||||
"fields_ordered": [
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Reference",
|
||||
"name": "Reference",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Qty",
|
||||
"name": "${QUANTITY}",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "Value",
|
||||
"name": "Value",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "DNP",
|
||||
"name": "${DNP}",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "Exclude from BOM",
|
||||
"name": "${EXCLUDE_FROM_BOM}",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "Exclude from Board",
|
||||
"name": "${EXCLUDE_FROM_BOARD}",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "Footprint",
|
||||
"name": "Footprint",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Datasheet",
|
||||
"name": "Datasheet",
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"filter_string": "",
|
||||
"group_symbols": true,
|
||||
"include_excluded_from_bom": true,
|
||||
"name": "Default Editing",
|
||||
"sort_asc": true,
|
||||
"sort_field": "Reference"
|
||||
},
|
||||
"connection_grid_size": 50.0,
|
||||
"drawing": {
|
||||
"dashed_lines_dash_length_ratio": 12.0,
|
||||
"dashed_lines_gap_length_ratio": 3.0,
|
||||
"default_line_thickness": 6.0,
|
||||
"default_text_size": 50.0,
|
||||
"field_names": [],
|
||||
"intersheets_ref_own_page": false,
|
||||
"intersheets_ref_prefix": "",
|
||||
"intersheets_ref_short": false,
|
||||
"intersheets_ref_show": false,
|
||||
"intersheets_ref_suffix": "",
|
||||
"junction_size_choice": 3,
|
||||
"label_size_ratio": 0.375,
|
||||
"operating_point_overlay_i_precision": 3,
|
||||
"operating_point_overlay_i_range": "~A",
|
||||
"operating_point_overlay_v_precision": 3,
|
||||
"operating_point_overlay_v_range": "~V",
|
||||
"overbar_offset_ratio": 1.23,
|
||||
"pin_symbol_size": 25.0,
|
||||
"text_offset_ratio": 0.15
|
||||
},
|
||||
"legacy_lib_dir": "",
|
||||
"legacy_lib_list": [],
|
||||
"meta": {
|
||||
"version": 1
|
||||
},
|
||||
"net_format_name": "",
|
||||
"page_layout_descr_file": "",
|
||||
"plot_directory": "",
|
||||
"space_save_all_events": true,
|
||||
"spice_current_sheet_as_root": false,
|
||||
"spice_external_command": "spice \"%I\"",
|
||||
"spice_model_current_sheet_as_root": true,
|
||||
"spice_save_all_currents": false,
|
||||
"spice_save_all_dissipations": false,
|
||||
"spice_save_all_voltages": false,
|
||||
"subpart_first_id": 65,
|
||||
"subpart_id_separator": 0
|
||||
},
|
||||
"sheets": [],
|
||||
"text_variables": {}
|
||||
}
|
||||
14
pcb/fw-anwesenheit/fw-anwesenheit.kicad_sch
Normal file
14
pcb/fw-anwesenheit/fw-anwesenheit.kicad_sch
Normal file
@@ -0,0 +1,14 @@
|
||||
(kicad_sch
|
||||
(version 20250114)
|
||||
(generator "eeschema")
|
||||
(generator_version "9.0")
|
||||
(uuid 35cd442a-c7c9-4bc2-bfa5-9414b343d8e4)
|
||||
(paper "A4")
|
||||
(lib_symbols)
|
||||
(sheet_instances
|
||||
(path "/"
|
||||
(page "1")
|
||||
)
|
||||
)
|
||||
(embedded_fonts no)
|
||||
)
|
||||
BIN
pcb/production/backups/fw-anwesenheit_2025-09-01_15-16-16.zip
Normal file
BIN
pcb/production/backups/fw-anwesenheit_2025-09-01_15-16-16.zip
Normal file
Binary file not shown.
BIN
pcb/production/backups/fw-anwesenheit_2025-09-01_15-58-06.zip
Normal file
BIN
pcb/production/backups/fw-anwesenheit_2025-09-01_15-58-06.zip
Normal file
Binary file not shown.
BIN
pcb/production/backups/fw-anwesenheit_2025-09-21_03-36-00.zip
Normal file
BIN
pcb/production/backups/fw-anwesenheit_2025-09-21_03-36-00.zip
Normal file
Binary file not shown.
BIN
pcb/production/backups/fw-anwesenheit_2025-09-21_23-31-28.zip
Normal file
BIN
pcb/production/backups/fw-anwesenheit_2025-09-21_23-31-28.zip
Normal file
Binary file not shown.
BIN
pcb/production/backups/fw-anwesenheit_2025-09-30_16-52-16.zip
Normal file
BIN
pcb/production/backups/fw-anwesenheit_2025-09-30_16-52-16.zip
Normal file
Binary file not shown.
35
pcb/production/bom.csv
Normal file
35
pcb/production/bom.csv
Normal file
@@ -0,0 +1,35 @@
|
||||
Designator,Footprint,Quantity,Value,LCSC Part #
|
||||
BT1,Battery_Panasonic_CR2032-HFN_Horizontal_CircularHoles,1,Battery_Cell,
|
||||
BZ1,PinSocket_1x02_P2.54mm_Vertical,1,Buzzer,
|
||||
"C1, C4",0603,2,10µF,
|
||||
"C2, C3, C5",0603,3,100nF,
|
||||
D2,0603,1,LED,
|
||||
J1,PinHeader_1x02_P2.54mm_Vertical,1,Conn_01x02_Pin,
|
||||
J2,PinHeader_1x04_P2.54mm_Vertical,1,I2C,
|
||||
J3,PinHeader_1x03_P2.54mm_Vertical,1,LED,
|
||||
J4,WURTH_693071020811,1,MicroSD,
|
||||
R1,0603,1,150R,
|
||||
"R10, R11, R12, R13, R14, R15, R9",0603,7,47k,
|
||||
R2,0603,1,100R,
|
||||
R3,0603,1,10K,
|
||||
R4,0603,1,20k,
|
||||
"R5, R6",0603,2,4k7,
|
||||
"R7, R8",0603,2,NC,
|
||||
RDM1,RDM6300,1,RDM6300,
|
||||
"TP1, TP10, TP9",TestPoint_Pad_D1.5mm,3,TestPoint,
|
||||
TP11,TestPoint_Pad_D1.5mm,1,MOSI,
|
||||
TP12,TestPoint_Pad_D1.5mm,1,MISO,
|
||||
TP13,TestPoint_Pad_D1.5mm,1,SPI SCL,
|
||||
TP14,TestPoint_Pad_D1.5mm,1,DAT1,
|
||||
TP15,TestPoint_Pad_D1.5mm,1,DAT2,
|
||||
TP2,TestPoint_Pad_D1.5mm,1,+5V,
|
||||
TP3,TestPoint_Pad_D1.5mm,1,GND,
|
||||
TP4,TestPoint_Pad_D1.5mm,1,"3,3V",
|
||||
TP5_2,TestPoint_Pad_D1.5mm,1,Din,
|
||||
TP5,TestPoint_Pad_D1.5mm,1,CS,
|
||||
TP6,TestPoint_Pad_D1.5mm,1,SD_DECT,
|
||||
TP7,TestPoint_Pad_D1.5mm,1,UART_RX,
|
||||
TP8,TestPoint_Pad_D1.5mm,1,UART_TX,
|
||||
U1,XIAO-ESP32C6-SMD,1,XIAO-ESP32-S3-SMD,
|
||||
U2,SOT95P280X145-5N,1,SN74AHCT1G125DBVT,SN74AHCT1G125DBVT
|
||||
U3,SOIC-16W_7.5x10.3mm_P1.27mm,1,DS3231M,
|
||||
|
47
pcb/production/designators.csv
Normal file
47
pcb/production/designators.csv
Normal file
@@ -0,0 +1,47 @@
|
||||
BT1:1
|
||||
BZ1:1
|
||||
C1:1
|
||||
C2:1
|
||||
C3:1
|
||||
C4:1
|
||||
C5:1
|
||||
D2:1
|
||||
J1:1
|
||||
J2:1
|
||||
J3:1
|
||||
J4:1
|
||||
JP1:1
|
||||
R1:1
|
||||
R10:1
|
||||
R11:1
|
||||
R12:1
|
||||
R13:1
|
||||
R14:1
|
||||
R15:1
|
||||
R2:1
|
||||
R3:1
|
||||
R4:1
|
||||
R5:1
|
||||
R6:1
|
||||
R7:1
|
||||
R8:1
|
||||
R9:1
|
||||
RDM1:1
|
||||
TP1:1
|
||||
TP10:1
|
||||
TP11:1
|
||||
TP12:1
|
||||
TP13:1
|
||||
TP14:1
|
||||
TP15:1
|
||||
TP2:1
|
||||
TP3:1
|
||||
TP4:1
|
||||
TP5:2
|
||||
TP6:1
|
||||
TP7:1
|
||||
TP8:1
|
||||
TP9:1
|
||||
U1:1
|
||||
U2:1
|
||||
U3:1
|
||||
|
BIN
pcb/production/fw-anwesenheit.zip
Normal file
BIN
pcb/production/fw-anwesenheit.zip
Normal file
Binary file not shown.
354
pcb/production/netlist.ipc
Normal file
354
pcb/production/netlist.ipc
Normal file
@@ -0,0 +1,354 @@
|
||||
P CODE 00
|
||||
P UNITS CUST 0
|
||||
P arrayDim N
|
||||
317GND VIA MD0118PA00X+035433Y-045241X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+047750Y-050550X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+046550Y-043700X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+046950Y-031750X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+044450Y-033050X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+043600Y-040300X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+050950Y-031750X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+038800Y-048850X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+034150Y-031750X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+040550Y-050550X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+044550Y-031750X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+040120Y-047880X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+048850Y-037100X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+049500Y-036550X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+036500Y-038600X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+042300Y-042500X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+040900Y-042500X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+048750Y-038400X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+041150Y-049250X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+038800Y-049100X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+034850Y-040600X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+051100Y-045600X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+047150Y-036350X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+048100Y-050150X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+034950Y-031750X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+033051Y-044472X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+043600Y-041800X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+044650Y-036950X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+046340Y-045450X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+038450Y-048600X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+047750Y-031750X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+049600Y-045500X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+050787Y-045776X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+034050Y-039300X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+049350Y-031750X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+038950Y-050550X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+042850Y-047400X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+038600Y-041300X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+050500Y-045950X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+048100Y-049850X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+050950Y-050550X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+037200Y-048900X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+035750Y-031750X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+038600Y-041800X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+033200Y-049950X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+049950Y-038400X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+050150Y-031750X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+033350Y-031750X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+044550Y-050550X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+043600Y-039800X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+043600Y-041300X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+042950Y-031750X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+047250Y-039150X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+046340Y-044709X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+041350Y-031750X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+041200Y-038450X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+048400Y-049850X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+037350Y-031750X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+037350Y-050550X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+050750Y-036850X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+032550Y-031750X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+037550Y-046600X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+045450Y-036150X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+046950Y-050550X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+048420Y-050150X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+038950Y-031750X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+043850Y-042500X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+038150Y-031750X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+045350Y-031750X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+046150Y-031750X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+038100Y-048600X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+046650Y-039000X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+043750Y-050550X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+048550Y-031750X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+045350Y-050550X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+043750Y-031750X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+050150Y-050550X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+051100Y-045950X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+046150Y-050550X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+038800Y-048600X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+048050Y-039000X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+043500Y-045350X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+048550Y-050550X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+040550Y-031750X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+036550Y-031750X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+049350Y-050550X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+050650Y-033050X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+041350Y-044650X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+042150Y-031750X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+038600Y-040300X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+043600Y-040800X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+038100Y-048850X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+038150Y-050550X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+041350Y-044300X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+047250Y-046950X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+042950Y-050550X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+041526Y-041339X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+050500Y-045600X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+048850Y-037400X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+039750Y-031750X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+037645Y-038800X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+039750Y-050550X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+038450Y-049100X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+038600Y-040800X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+041640Y-047740X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+038450Y-048850X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+048228Y-046885X0177Y0000R000S1628436659
|
||||
317GND VIA MD0118PA00X+038100Y-049100X0177Y0000R000S1628436659
|
||||
317+5V VIA MD0118PA00X+050480Y-044960X0177Y0000R000S1628436659
|
||||
317+5V VIA MD0118PA00X+038820Y-050080X0177Y0000R000S1628436659
|
||||
317+5V VIA MD0118PA00X+050480Y-044600X0177Y0000R000S1628436659
|
||||
317+5V VIA MD0118PA00X+038460Y-050080X0177Y0000R000S1628436659
|
||||
317+5V VIA MD0118PA00X+051040Y-044620X0177Y0000R000S1628436659
|
||||
317+5V VIA MD0118PA00X+047360Y-044720X0177Y0000R000S1628436659
|
||||
317+5V VIA MD0118PA00X+038160Y-049860X0177Y0000R000S1628436659
|
||||
317+5V VIA MD0118PA00X+037200Y-049500X0177Y0000R000S1628436659
|
||||
317+5V VIA MD0118PA00X+038820Y-049640X0177Y0000R000S1628436659
|
||||
317+5V VIA MD0118PA00X+034451Y-049801X0177Y0000R000S1628436659
|
||||
317+5V VIA MD0118PA00X+051040Y-044980X0177Y0000R000S1628436659
|
||||
317+5V VIA MD0118PA00X+038820Y-049860X0177Y0000R000S1628436659
|
||||
317+5V VIA MD0118PA00X+047150Y-043700X0177Y0000R000S1628436659
|
||||
317+5V VIA MD0118PA00X+038460Y-049640X0177Y0000R000S1628436659
|
||||
317+5V VIA MD0118PA00X+038460Y-049860X0177Y0000R000S1628436659
|
||||
317+5V VIA MD0118PA00X+049600Y-044900X0177Y0000R000S1628436659
|
||||
317+5V VIA MD0118PA00X+050780Y-044780X0177Y0000R000S1628436659
|
||||
317+5V VIA MD0118PA00X+038160Y-050080X0177Y0000R000S1628436659
|
||||
317+5V VIA MD0118PA00X+038160Y-049640X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+041050Y-047400X0177Y0000R000S1628436659
|
||||
317+5V VIA MD0118PA00X+032874Y-036304X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+049606Y-040320X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+038255Y-038800X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+040400Y-037150X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+047244Y-047638X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+035595Y-038976X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+048000Y-037450X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+046457Y-040320X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+043350Y-048650X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+040450Y-048550X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+038800Y-037150X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+046350Y-048650X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+048150Y-044050X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+041300Y-047400X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+048031Y-040320X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+038450Y-047600X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+048000Y-037100X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+041300Y-047150X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+046600Y-048650X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+038800Y-047850X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+041050Y-047150X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+048100Y-038400X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+050394Y-040354X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+043650Y-048650X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+038450Y-048100X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+038450Y-047850X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+038800Y-048100X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+045669Y-040320X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+040350Y-038800X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+032750Y-043500X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+035595Y-038189X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+048819Y-040320X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+047244Y-040320X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+038800Y-047600X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+038100Y-048100X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+048228Y-047603X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+038100Y-047850X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+036100Y-050350X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+038100Y-047600X0177Y0000R000S1628436659
|
||||
317+3.3V VIA MD0118PA00X+048150Y-045550X0177Y0000R000S1628436659
|
||||
317NET-(R3-PAD1) VIA MD0118PA00X+035433Y-042948X0177Y0000R000S1628436659
|
||||
317/SD_DECT VIA MD0118PA00X+050138Y-037264X0177Y0000R000S1628436659
|
||||
317/SD_DECT VIA MD0118PA00X+044800Y-049900X0177Y0000R000S1628436659
|
||||
317/MISO VIA MD0118PA00X+038450Y-045800X0177Y0000R000S1628436659
|
||||
317/MISO VIA MD0118PA00X+049272Y-037264X0177Y0000R000S1628436659
|
||||
317/SPI_SCL VIA MD0118PA00X+048400Y-037264X0177Y0000R000S1628436659
|
||||
317/SPI_SCL VIA MD0118PA00X+038450Y-044900X0177Y0000R000S1628436659
|
||||
317/SPI_SCL VIA MD0118PA00X+048031Y-039601X0177Y0000R000S1628436659
|
||||
317/MOSI VIA MD0118PA00X+037500Y-044550X0177Y0000R000S1628436659
|
||||
317/MOSI VIA MD0118PA00X+047539Y-037264X0177Y0000R000S1628436659
|
||||
317/SPI_CS VIA MD0118PA00X+046250Y-046000X0177Y0000R000S1628436659
|
||||
317/SPI_CS VIA MD0118PA00X+047106Y-037264X0177Y0000R000S1628436659
|
||||
317/BUZZER VIA MD0118PA00X+044450Y-046650X0177Y0000R000S1628436659
|
||||
317-GPIO16_D6_TX) VIA MD0118PA00X+036480Y-047320X0177Y0000R000S1628436659
|
||||
317-GPIO16_D6_TX) VIA MD0118PA00X+045700Y-046650X0177Y0000R000S1628436659
|
||||
317/I2C_SCL VIA MD0118PA00X+043000Y-038300X0177Y0000R000S1628436659
|
||||
317/I2C_SCL VIA MD0118PA00X+045600Y-046100X0177Y0000R000S1628436659
|
||||
317/I2C_SCL VIA MD0118PA00X+043225Y-043575X0177Y0000R000S1628436659
|
||||
317/I2C_SCL VIA MD0118PA00X+034680Y-046240X0177Y0000R000S1628436659
|
||||
317/I2C_SDA VIA MD0118PA00X+042900Y-044100X0177Y0000R000S1628436659
|
||||
317/I2C_SDA VIA MD0118PA00X+042900Y-038800X0177Y0000R000S1628436659
|
||||
317/I2C_SDA VIA MD0118PA00X+044812Y-045842X0177Y0000R000S1628436659
|
||||
317/I2C_SDA VIA MD0118PA00X+033300Y-046050X0177Y0000R000S1628436659
|
||||
317/DAT2 VIA MD0118PA00X+046673Y-037264X0177Y0000R000S1628436659
|
||||
317/DAT1 VIA MD0118PA00X+049705Y-037264X0177Y0000R000S1628436659
|
||||
317NET-(J1-PIN_1) VIA MD0118PA00X+047100Y-049800X0177Y0000R000S1628436659
|
||||
317NET-(J1-PIN_1) VIA MD0118PA00X+047100Y-050000X0177Y0000R000S1628436659
|
||||
317NET-(J1-PIN_1) VIA MD0118PA00X+047100Y-050200X0177Y0000R000S1628436659
|
||||
317ET-(U3-~{RST}) VIA MD0118PA00X+033850Y-038500X0177Y0000R000S1628436659
|
||||
317ET-(U3-~{RST}) VIA MD0118PA00X+037750Y-039800X0177Y0000R000S1628436659
|
||||
317NET-(JP1-A) VIA MD0118PA00X+042450Y-045000X0177Y0000R000S1628436659
|
||||
317NET-(JP1-A) VIA MD0118PA00X+045800Y-049600X0177Y0000R000S1628436659
|
||||
317NET-(JP1-A) VIA MD0118PA00X+042250Y-045000X0177Y0000R000S1628436659
|
||||
317NET-(JP1-A) VIA MD0118PA00X+042250Y-044750X0177Y0000R000S1628436659
|
||||
317NET-(JP1-A) VIA MD0118PA00X+042450Y-044450X0177Y0000R000S1628436659
|
||||
317NET-(JP1-A) VIA MD0118PA00X+046050Y-049350X0177Y0000R000S1628436659
|
||||
317NET-(JP1-A) VIA MD0118PA00X+042250Y-044450X0177Y0000R000S1628436659
|
||||
317NET-(JP1-A) VIA MD0118PA00X+045850Y-049400X0177Y0000R000S1628436659
|
||||
317NET-(JP1-A) VIA MD0118PA00X+042450Y-044750X0177Y0000R000S1628436659
|
||||
327GND C5 -1 A01X+048730Y-038386X0354Y0374R180S2
|
||||
327+3.3V C5 -2 A01X+048120Y-038386X0354Y0374R180S2
|
||||
327/LED_DIN J3 -1 A01X+050700Y-043800X0984Y0669R000S2
|
||||
327+5V J3 -2 A01X+050700Y-044800X0984Y0669R000S2
|
||||
327GND J3 -3 A01X+050700Y-045800X0984Y0669R000S2
|
||||
327/SD_DECT U1 -1 A01X+044812Y-049842X1083Y0787R180S2
|
||||
327/LED_DRIVER U1 -2 A01X+044812Y-048842X1083Y0787R180S2
|
||||
327/SPI_CS U1 -3 A01X+044812Y-047842X1083Y0787R180S2
|
||||
327/BUZZER U1 -4 A01X+044812Y-046842X1083Y0787R180S2
|
||||
327/I2C_SDA U1 -5 A01X+044812Y-045842X1083Y0787R180S2
|
||||
327/I2C_SCL U1 -6 A01X+044812Y-044842X1083Y0787R180S2
|
||||
327-GPIO16_D6_TX) U1 -7 A01X+044812Y-043842X1083Y0787R180S2
|
||||
327/UART_RX U1 -8 A01X+038447Y-043842X1083Y0787R180S2
|
||||
327/SPI_SCL U1 -9 A01X+038447Y-044842X1083Y0787R180S2
|
||||
327/MISO U1 -10 A01X+038447Y-045842X1083Y0787R180S2
|
||||
327/MOSI U1 -11 A01X+038447Y-046842X1083Y0787R180S2
|
||||
327+3.3V U1 -12 A01X+038447Y-047842X1083Y0787R180S2
|
||||
327GND U1 -13 A01X+038447Y-048842X1083Y0787R180S2
|
||||
327+5V U1 -14 A01X+038447Y-049842X1083Y0787R180S2
|
||||
327NET-(JP1-A) U1 -15 A01X+042342Y-044664X0984Y0433R270S2
|
||||
327GND U1 -16 A01X+041342Y-044664X0984Y0433R270S2
|
||||
327U1-MTDI-PAD17) U1 -17 A01X+042142Y-050244X0669Y0000R180S2
|
||||
327U1-MTDO-PAD18) U1 -18 A01X+041142Y-050244X0669Y0000R180S2
|
||||
327CHIP_EN-PAD19) U1 -19 A01X+042142Y-049244X0669Y0000R180S2
|
||||
327GND U1 -20 A01X+041142Y-049244X0669Y0000R180S2
|
||||
327U1-MTMS-PAD21) U1 -21 A01X+042142Y-048244X0669Y0000R180S2
|
||||
327U1-MTCK-PAD22) U1 -22 A01X+041142Y-048244X0669Y0000R180S2
|
||||
327U1-BOOT-PAD23) U1 -23 A01X+042142Y-047244X0669Y0000R180S2
|
||||
327+3.3V U1 -24 A01X+041178Y-047266X0669Y0000R180S2
|
||||
327GND TP3 -1 A01X+032677Y-050197X0591Y0000R000S2
|
||||
327NET-(D2-K) D2 -1 A01X+032874Y-036924X0344Y0374R270S2
|
||||
327+5V D2 -2 A01X+032874Y-036304X0344Y0374R270S2
|
||||
327+3.3V R14 -1 A01X+047244Y-040320X0384Y0374R270S2
|
||||
327/MOSI R14 -2 A01X+047244Y-039601X0384Y0374R270S2
|
||||
327+3.3V R13 -1 A01X+048031Y-040320X0384Y0374R270S2
|
||||
327/SPI_SCL R13 -2 A01X+048031Y-039601X0384Y0374R270S2
|
||||
327NET-(R3-PAD1) R3 -1 A01X+035433Y-042948X0384Y0374R090S2
|
||||
327/UART_RX R3 -2 A01X+035433Y-043666X0384Y0374R090S2
|
||||
327GND C2 -1 A01X+037645Y-038800X0354Y0374R000S2
|
||||
327+3.3V C2 -2 A01X+038255Y-038800X0354Y0374R000S2
|
||||
327NT}{SLASH}SQW) TP1 -1 A01X+033071Y-040748X0591Y0000R000S2
|
||||
327NET-(U3-32KHZ) U3 -1 A01X+039319Y-038300X0807Y0236R000S2
|
||||
327+3.3V U3 -2 A01X+039319Y-038800X0807Y0236R000S2
|
||||
327NT}{SLASH}SQW) U3 -3 A01X+039319Y-039300X0807Y0236R000S2
|
||||
327ET-(U3-~{RST}) U3 -4 A01X+039319Y-039800X0807Y0236R000S2
|
||||
327GND U3 -5 A01X+039319Y-040300X0807Y0236R000S2
|
||||
327GND U3 -6 A01X+039319Y-040800X0807Y0236R000S2
|
||||
327GND U3 -7 A01X+039319Y-041300X0807Y0236R000S2
|
||||
327GND U3 -8 A01X+039319Y-041800X0807Y0236R000S2
|
||||
327GND U3 -9 A01X+042981Y-041800X0807Y0236R000S2
|
||||
327GND U3 -10 A01X+042981Y-041300X0807Y0236R000S2
|
||||
327GND U3 -11 A01X+042981Y-040800X0807Y0236R000S2
|
||||
327GND U3 -12 A01X+042981Y-040300X0807Y0236R000S2
|
||||
327GND U3 -13 A01X+042981Y-039800X0807Y0236R000S2
|
||||
327/BATTERY_CELL U3 -14 A01X+042981Y-039300X0807Y0236R000S2
|
||||
327/I2C_SDA U3 -15 A01X+042981Y-038800X0807Y0236R000S2
|
||||
327/I2C_SCL U3 -16 A01X+042981Y-038300X0807Y0236R000S2
|
||||
317/BUZZER BZ1 -1 D0394PA00X+035400Y-046806X0669Y0669R000S0
|
||||
317GND BZ1 -2 D0394PA00X+035400Y-047806X0669Y0000R000S0
|
||||
327+3.3V R15 -1 A01X+045669Y-040320X0384Y0374R270S2
|
||||
327/DAT2 R15 -2 A01X+045669Y-039601X0384Y0374R270S2
|
||||
327-GPIO16_D6_TX) TP8 -1 A01X+033927Y-046604X0591Y0000R000S2
|
||||
327NET-(JP1-A) JP1 -1 A01X+046450Y-050000X0118Y0118R000S2
|
||||
327NET-(J1-PIN_1) JP1 -2 A01X+047021Y-050000X0118Y0118R000S2
|
||||
317(RDM1-PADANT1) RDM1 -ANT1 D0551PA00X+035236Y-023433X0827Y0000R090S0
|
||||
317(RDM1-PADANT2) RDM1 -ANT2 D0551PA00X+035236Y-024433X0827Y0827R090S0
|
||||
317D-(RDM1-PADD0) RDM1 -D0 D0551PA00X+041236Y-034433X0827Y0000R090S0
|
||||
317GND RDM1 -GND D0551PA00X+041236Y-033433X0827Y0000R090S0
|
||||
317GND RDM1 -GND1 D0551PA00X+035236Y-034433X0827Y0000R090S0
|
||||
317NET-(R1-PAD1) RDM1 -LED D0551PA00X+035236Y-036433X0827Y0827R090S0
|
||||
317D-(RDM1-PADRX) RDM1 -RX D0551PA00X+041236Y-035433X0827Y0000R090S0
|
||||
317NET-(R3-PAD1) RDM1 -TX D0551PA00X+041236Y-036433X0827Y0827R090S0
|
||||
317+5V RDM1 -VCC D0551PA00X+041236Y-032433X0827Y0000R090S0
|
||||
317+5V RDM1 -VCC1 D0551PA00X+035236Y-035433X0827Y0000R090S0
|
||||
327+3.3V R7 -1 A01X+035595Y-038189X0384Y0374R180S2
|
||||
327NET-(U3-32KHZ) R7 -2 A01X+034877Y-038189X0384Y0374R180S2
|
||||
327+3.3V R8 -1 A01X+035595Y-038976X0384Y0374R180S2
|
||||
327NT}{SLASH}SQW) R8 -2 A01X+034877Y-038976X0384Y0374R180S2
|
||||
327GND U2 -1 A01X+046340Y-044709X0500Y0220R000S2
|
||||
327/LED_DRIVER U2 -2 A01X+046340Y-045079X0500Y0220R000S2
|
||||
327GND U2 -3 A01X+046340Y-045449X0500Y0220R000S2
|
||||
327NET-(U2-Y) U2 -4 A01X+047360Y-045449X0500Y0220R000S2
|
||||
327+5V U2 -5 A01X+047360Y-044709X0500Y0220R000S2
|
||||
327GND C3 -1 A01X+046545Y-043701X0354Y0374R000S2
|
||||
327+5V C3 -2 A01X+047156Y-043701X0354Y0374R000S2
|
||||
327NET-(R1-PAD1) R1 -1 A01X+033858Y-036255X0384Y0374R090S2
|
||||
327NET-(D2-K) R1 -2 A01X+033858Y-036973X0384Y0374R090S2
|
||||
327NET-(U2-Y) R2 -1 A01X+048460Y-043701X0384Y0374R000S2
|
||||
327/LED_DIN R2 -2 A01X+049178Y-043701X0384Y0374R000S2
|
||||
327+3.3V R12 -1 A01X+048819Y-040320X0384Y0374R270S2
|
||||
327/MISO R12 -2 A01X+048819Y-039601X0384Y0374R270S2
|
||||
327NET-(U3-32KHZ) TP10 -1 A01X+033071Y-039567X0591Y0000R000S2
|
||||
327+5V TP2 -1 A01X+034055Y-050197X0591Y0000R000S2
|
||||
327+3.3V TP4 -1 A01X+035433Y-050197X0591Y0000R000S2
|
||||
327+3.3V R11 -1 A01X+050394Y-040354X0384Y0374R270S2
|
||||
327/SD_DECT R11 -2 A01X+050394Y-039636X0384Y0374R270S2
|
||||
327/LED_DIN TP5 -1 A01X+049016Y-042717X0591Y0000R000S2
|
||||
327+3.3V R9 -1 A01X+046457Y-040320X0384Y0374R270S2
|
||||
327/SPI_CS R9 -2 A01X+046457Y-039601X0384Y0374R270S2
|
||||
327GND C4 -1 A01X+049600Y-045505X0354Y0374R270S2
|
||||
327+5V C4 -2 A01X+049600Y-044895X0354Y0374R270S2
|
||||
327+3.3V R6 -1 A01X+048228Y-047603X0384Y0374R270S2
|
||||
327GND R6 -2 A01X+048228Y-046885X0384Y0374R270S2
|
||||
327/UART_RX R4 -1 A01X+035433Y-044523X0384Y0374R090S2
|
||||
327GND R4 -2 A01X+035433Y-045241X0384Y0374R090S2
|
||||
327ET-(U3-~{RST}) TP9 -1 A01X+033071Y-038386X0591Y0000R270S2
|
||||
367N/C J4 D0394UA00X+049508Y-032913X0394Y0000R180S0
|
||||
367N/C J4 D0394UA00X+046358Y-032913X0394Y0000R180S0
|
||||
327/DAT2 J4 -1 A01X+046673Y-037264X0276Y0748R180S2
|
||||
327/SPI_CS J4 -2 A01X+047106Y-037264X0276Y0748R180S2
|
||||
327/MOSI J4 -3 A01X+047539Y-037264X0276Y0748R180S2
|
||||
327+3.3V J4 -4 A01X+047972Y-037264X0276Y0748R180S2
|
||||
327/SPI_SCL J4 -5 A01X+048406Y-037264X0276Y0748R180S2
|
||||
327GND J4 -6 A01X+048839Y-037264X0276Y0748R180S2
|
||||
327/MISO J4 -7 A01X+049272Y-037264X0276Y0748R180S2
|
||||
327/DAT1 J4 -8 A01X+049705Y-037264X0276Y0748R180S2
|
||||
327/SD_DECT J4 -9 A01X+050138Y-037264X0276Y0748R180S2
|
||||
327GND J4 -10 A01X+050748Y-036850X0669Y0709R180S2
|
||||
327GND J4 -11 A01X+044646Y-036850X0669Y0709R180S2
|
||||
327GND J4 -12 A01X+050650Y-033071X0551Y1024R180S2
|
||||
327GND J4 -13 A01X+044469Y-033071X0551Y1024R180S2
|
||||
327+3.3V R10 -1 A01X+049606Y-040320X0384Y0374R270S2
|
||||
327/DAT1 R10 -2 A01X+049606Y-039601X0384Y0374R270S2
|
||||
327/UART_RX TP7 -1 A01X+033927Y-045422X0591Y0000R000S2
|
||||
327+3.3V R5 -1 A01X+047244Y-047609X0384Y0374R270S2
|
||||
327GND R5 -2 A01X+047244Y-046891X0384Y0374R270S2
|
||||
327GND C1 -1 A01X+037205Y-048907X0354Y0374R090S2
|
||||
327+5V C1 -2 A01X+037205Y-049518X0354Y0374R090S2
|
||||
327/DAT2 TP15 -1 A06X+042323Y-035433X0591Y0000R000S1
|
||||
327/SPI_CS TP5 -1 A06X+043701Y-035433X0591Y0000R180S1
|
||||
327/SD_DECT TP6 -1 A06X+050591Y-035433X0591Y0000R180S1
|
||||
317/BATTERY_CELL BT1 -1 D0335PA00X+044650Y-040700X0630Y0787R180S0
|
||||
317GND BT1 -2 D0335PA00X+036579Y-040700X0630Y0787R180S0
|
||||
327/DAT1 TP14 -1 A06X+049213Y-035433X0591Y0000R180S1
|
||||
327NET-(J1-PIN_1) J1 -1 A06X+047248Y-050000X0669Y0669R090S1
|
||||
327GND J1 -2 A06X+048248Y-050000X0669Y0000R090S1
|
||||
327/MOSI TP11 -1 A06X+045079Y-035433X0591Y0000R180S1
|
||||
327+3.3V J2 -1 A06X+032750Y-043500X0669Y0669R180S1
|
||||
327GND J2 -2 A06X+032750Y-044500X0669Y0000R180S1
|
||||
327/I2C_SDA J2 -3 A06X+032750Y-045500X0669Y0000R180S1
|
||||
327/I2C_SCL J2 -4 A06X+032750Y-046500X0669Y0000R180S1
|
||||
327/MISO TP12 -1 A06X+047835Y-035433X0591Y0000R180S1
|
||||
327/SPI_SCL TP13 -1 A06X+046457Y-035433X0591Y0000R180S1
|
||||
999
|
||||
29
pcb/production/positions.csv
Normal file
29
pcb/production/positions.csv
Normal file
@@ -0,0 +1,29 @@
|
||||
Designator,Mid X,Mid Y,Rotation,Layer
|
||||
BT1,103.161,-103.378,0.0,bottom
|
||||
BZ1,89.916,-120.158,0.0,top
|
||||
C1,94.5,-125.0,270.0,top
|
||||
C2,96.393,-98.552,0.0,top
|
||||
C3,119.0,-111.0,0.0,top
|
||||
C4,125.984,-114.808,90.0,top
|
||||
C5,123.0,-97.5,180.0,top
|
||||
D2,83.5,-93.0,90.0,top
|
||||
J1,120.01,-127.0,270.0,bottom
|
||||
J2,83.185,-110.49,0.0,bottom
|
||||
J4,120.8,-83.6,180.0,top
|
||||
R1,86.0,-93.0,270.0,top
|
||||
R10,126.0,-101.5,90.0,top
|
||||
R11,128.0,-101.5875,90.0,top
|
||||
R12,124.0,-101.5,90.0,top
|
||||
R13,122.0,-101.5,90.0,top
|
||||
R14,120.0,-101.5,90.0,top
|
||||
R15,116.0,-101.5,90.0,top
|
||||
R2,124.0,-111.0,0.0,top
|
||||
R3,90.0,-110.0,270.0,top
|
||||
R4,90.0,-114.0,270.0,top
|
||||
R5,120.0,-120.015,90.0,top
|
||||
R6,122.5,-120.0,90.0,top
|
||||
R9,118.0,-101.5,90.0,top
|
||||
RDM1,97.12,-76.03,270.0,top
|
||||
U1,105.7391,-119.41425,180.0,top
|
||||
U2,119.0,-114.5,0.0,top
|
||||
U3,104.521,-101.727,270.0,top
|
||||
|
23
pm3_mock.sh
23
pm3_mock.sh
@@ -1,23 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
generate_random_hex() {
|
||||
openssl rand -hex 4 | tr '[:lower:]' '[:upper:]'
|
||||
}
|
||||
|
||||
output_hotspot_id() {
|
||||
echo "[+] UID.... C1532B57"
|
||||
}
|
||||
|
||||
trap 'output_hotspot_id' SIGUSR1
|
||||
|
||||
echo "Proxmark 3 mock script"
|
||||
echo "Outputs a random ID every 1 to 5 seconds"
|
||||
|
||||
while true; do
|
||||
random_id=$(generate_random_hex)
|
||||
echo "[+] UID.... $random_id"
|
||||
if (( RANDOM % 2 == 0 )); then
|
||||
echo "[+] UID.... $random_id"
|
||||
fi
|
||||
sleep "$((RANDOM % 5 + 1))"
|
||||
done
|
||||
Binary file not shown.
Binary file not shown.
550
pre-compiled/pm3
550
pre-compiled/pm3
@@ -1,550 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Usage: run option -h to get help
|
||||
|
||||
# BT auto detection
|
||||
# Shall we look for white HC-06-USB dongle ?
|
||||
FINDBTDONGLE=true
|
||||
# Shall we look for rfcomm interface ?
|
||||
FINDBTRFCOMM=true
|
||||
# Shall we look for registered BT device ? (Linux only)
|
||||
FINDBTDIRECT=true
|
||||
|
||||
PM3PATH=$(dirname "$0")
|
||||
EVALENV=""
|
||||
FULLIMAGE="fullimage.elf"
|
||||
BOOTIMAGE="bootrom.elf"
|
||||
|
||||
#Skip check if --list is used
|
||||
if [ ! "$1" == "--list" ]; then
|
||||
# try pm3 dirs in current repo workdir
|
||||
if [ -d "$PM3PATH/client/" ]; then
|
||||
if [ -x "$PM3PATH/client/proxmark3" ]; then
|
||||
CLIENT="$PM3PATH/client/proxmark3"
|
||||
elif [ -x "$PM3PATH/client/build/proxmark3" ]; then
|
||||
CLIENT="$PM3PATH/client/build/proxmark3"
|
||||
else
|
||||
echo >&2 "[!!] In devel workdir but no executable found, did you compile it?"
|
||||
exit 1
|
||||
fi
|
||||
# try install dir
|
||||
elif [ -x "$PM3PATH/proxmark3" ]; then
|
||||
CLIENT="$PM3PATH/proxmark3"
|
||||
else
|
||||
# hope it's installed somehow, still not sure where fw images and pm3.py are...
|
||||
CLIENT="proxmark3"
|
||||
fi
|
||||
fi
|
||||
|
||||
# LeakSanitizer suppressions
|
||||
if [ -e .lsan_suppressions ]; then
|
||||
EVALENV+=" LSAN_OPTIONS=suppressions=.lsan_suppressions"
|
||||
fi
|
||||
if [ "$EVALENV" != "" ]; then
|
||||
EVALENV="export $EVALENV"
|
||||
fi
|
||||
PM3LIST=()
|
||||
SHOWLIST=false
|
||||
|
||||
function get_pm3_list_Linux {
|
||||
N=$1
|
||||
PM3LIST=()
|
||||
if [ ! -c "/dev/tty0" ]; then
|
||||
echo >&2 "[!!] Script cannot access /dev/ttyXXX files, insufficient privileges"
|
||||
exit 1
|
||||
fi
|
||||
for DEV in $(find /dev/ttyACM* 2>/dev/null); do
|
||||
if command -v udevadm >/dev/null; then
|
||||
# WSL1 detection
|
||||
if udevadm info -q property -n "$DEV" | grep -q "ID_VENDOR=proxmark.org"; then
|
||||
PM3LIST+=("$DEV")
|
||||
if [ ${#PM3LIST[*]} -ge "$N" ]; then
|
||||
return
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
# WSL2 with usbipd detection - doesn't report same things as WSL1
|
||||
if grep -q "proxmark.org" "/sys/class/tty/${DEV#/dev/}/../../../manufacturer" 2>/dev/null; then
|
||||
if echo "${PM3LIST[*]}" | grep -qv "${DEV}"; then
|
||||
PM3LIST+=("$DEV")
|
||||
if [ ${#PM3LIST[*]} -ge "$N" ]; then
|
||||
return
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
if $FINDBTDONGLE; then
|
||||
# check if the HC-06-USB white dongle is present (still, that doesn't tell us if it's paired with a Proxmark3...)
|
||||
for DEV in $(find /dev/ttyUSB* 2>/dev/null); do
|
||||
if command -v udevadm >/dev/null; then
|
||||
if udevadm info -q property -n "$DEV" | grep -q "ID_MODEL=CP2104_USB_to_UART_Bridge_Controller"; then
|
||||
PM3LIST+=("$DEV")
|
||||
if [ ${#PM3LIST[*]} -ge "$N" ]; then
|
||||
return
|
||||
fi
|
||||
fi
|
||||
else
|
||||
if grep -q "DRIVER=cp210x" "/sys/class/tty/${DEV#/dev/}/../../uevent" 2>/dev/null; then
|
||||
PM3LIST+=("$DEV")
|
||||
if [ ${#PM3LIST[*]} -ge "$N" ]; then
|
||||
return
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
if $FINDBTRFCOMM; then
|
||||
# check if the MAC of a Proxmark3 was bound to a local rfcomm interface
|
||||
# (on OSes without deprecated rfcomm and hcitool, the loop will be simply skipped)
|
||||
for DEVMAC in $(rfcomm -a 2>/dev/null | grep " 20:19:0[45]" | sed 's/^\(.*\): \([0-9:]*\) .*/\1@\2/'); do
|
||||
DEV=${DEVMAC/@*/}
|
||||
MAC=${DEVMAC/*@/}
|
||||
# check which are Proxmark3 and, side-effect, if they're actually present
|
||||
if hcitool name "$MAC" | grep -q "PM3"; then
|
||||
PM3LIST+=("/dev/$DEV")
|
||||
if [ ${#PM3LIST[*]} -ge "$N" ]; then
|
||||
return
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
if $FINDBTDIRECT; then
|
||||
# check if the MAC of a Proxmark3 was registered in the known devices
|
||||
for MAC in $(dbus-send --system --print-reply --type=method_call --dest='org.bluez' '/' org.freedesktop.DBus.ObjectManager.GetManagedObjects 2>/dev/null|\
|
||||
awk '/"Address"/{getline;gsub(/"/,"",$3);a=$3}/Name/{getline;if (/PM3_RDV4/ || /Proxmark3 SE/) print a}'); do
|
||||
PM3LIST+=("bt:$MAC")
|
||||
done
|
||||
# we don't probe the device so there is no guarantee the device is actually present
|
||||
fi
|
||||
}
|
||||
|
||||
function get_pm3_list_macOS {
|
||||
N=$1
|
||||
PM3LIST=()
|
||||
for DEV in $(ioreg -r -c "IOUSBHostDevice" -l | awk -F '"' '
|
||||
$2=="USB Vendor Name"{b=($4=="proxmark.org")}
|
||||
b==1 && $2=="IODialinDevice"{print $4}'); do
|
||||
PM3LIST+=("$DEV")
|
||||
if [ ${#PM3LIST[*]} -ge "$N" ]; then
|
||||
return
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
function get_pm3_list_Windows {
|
||||
N=$1
|
||||
PM3LIST=()
|
||||
|
||||
# Normal SERIAL PORTS (COM)
|
||||
for DEV in $(wmic /locale:ms_409 path Win32_SerialPort Where "PNPDeviceID LIKE '%VID_9AC4&PID_4B8F%' Or PNPDeviceID LIKE '%VID_2D2D&PID_504D%'" Get DeviceID 2>/dev/null | awk -b '/^COM/{print $1}'); do
|
||||
DEV=${DEV/ */}
|
||||
#prevent soft bricking when using pm3-flash-all on an outdated bootloader
|
||||
if [ $(basename -- "$0") = "pm3-flash-all" ]; then
|
||||
line=$(wmic /locale:ms_409 path Win32_SerialPort Where "DeviceID='$DEV'" Get PNPDeviceID 2>/dev/null | awk -b '/^USB/{print $1}');
|
||||
if [[ ! $line =~ ^"USB\VID_9AC4&PID_4B8F\ICEMAN" ]]; then
|
||||
echo -e "\033[0;31m[!] Using pm3-flash-all on an oudated bootloader, use pm3-flash-bootrom first!"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
PM3LIST+=("$DEV")
|
||||
if [ ${#PM3LIST[*]} -ge "$N" ]; then
|
||||
return
|
||||
fi
|
||||
done
|
||||
|
||||
#BT direct SERIAL PORTS (COM)
|
||||
if $FINDBTRFCOMM; then
|
||||
for DEV in $(wmic /locale:ms_409 path Win32_PnPEntity Where "Caption LIKE '%Bluetooth%(COM%'" Get Name 2> /dev/null | awk -b 'match($0,/(COM[0-9]+)/,m){print m[1]}'); do
|
||||
DEV=${DEV/ */}
|
||||
PM3LIST+=("$DEV")
|
||||
if [ ${#PM3LIST[*]} -ge "$N" ]; then
|
||||
return
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
#white BT dongle SERIAL PORTS (COM)
|
||||
if $FINDBTDONGLE; then
|
||||
for DEV in $(wmic /locale:ms_409 path Win32_SerialPort Where "PNPDeviceID LIKE '%VID_10C4&PID_EA60%'" Get DeviceID 2>/dev/null | awk -b '/^COM/{print $1}'); do
|
||||
DEV=${DEV/ */}
|
||||
PM3LIST+=("$DEV")
|
||||
if [ ${#PM3LIST[*]} -ge "$N" ]; then
|
||||
return
|
||||
fi
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
function get_pm3_list_WSL {
|
||||
N=$1
|
||||
PM3LIST=()
|
||||
|
||||
# Normal SERIAL PORTS (COM)
|
||||
for DEV in $($PSHEXE -command "Get-CimInstance -ClassName Win32_serialport | Where-Object {\$_.PNPDeviceID -like '*VID_9AC4&PID_4B8F*' -or \$_.PNPDeviceID -like '*VID_2D2D&PID_504D*'} | Select -expandproperty DeviceID" 2>/dev/null); do
|
||||
DEV=$(echo $DEV | tr -dc '[:print:]')
|
||||
_comport=$DEV
|
||||
DEV=$(echo $DEV | sed -nr 's#^COM([0-9]+)\b#/dev/ttyS\1#p')
|
||||
# ttyS counterpart takes some more time to appear
|
||||
if [ -e "$DEV" ]; then
|
||||
|
||||
#prevent soft bricking when using pm3-flash-all on an outdated bootloader
|
||||
if [ $(basename -- "$0") = "pm3-flash-all" ]; then
|
||||
line=$($PSHEXE -command "Get-CimInstance -ClassName Win32_serialport | Where-Object {\$_.DeviceID -eq '$_comport'} | Select -expandproperty PNPDeviceID" 2>/dev/null | tr -dc '[:print:]');
|
||||
if [[ ! $line =~ ^"USB\VID_9AC4&PID_4B8F\ICEMAN" ]]; then
|
||||
echo -e "\033[0;31m[!] Using pm3-flash-all on an oudated bootloader, use pm3-flash-bootrom first!"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
PM3LIST+=("$DEV")
|
||||
if [ ! -w "$DEV" ]; then
|
||||
echo "[!] Let's give users read/write access to $DEV"
|
||||
sudo chmod 666 "$DEV"
|
||||
fi
|
||||
if [ ${#PM3LIST[*]} -ge "$N" ]; then
|
||||
return
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
#BT direct SERIAL PORTS (COM)
|
||||
if $FINDBTRFCOMM; then
|
||||
for DEV in $($PSHEXE -command "Get-CimInstance -ClassName Win32_PnPEntity | Where-Object Caption -like 'Standard Serial over Bluetooth link (COM*' | Select Name" 2> /dev/null | sed -nr 's#.*\bCOM([0-9]+)\b.*#/dev/ttyS\1#p'); do
|
||||
# ttyS counterpart takes some more time to appear
|
||||
if [ -e "$DEV" ]; then
|
||||
PM3LIST+=("$DEV")
|
||||
if [ ! -w "$DEV" ]; then
|
||||
echo "[!] Let's give users read/write access to $DEV"
|
||||
sudo chmod 666 "$DEV"
|
||||
fi
|
||||
if [ ${#PM3LIST[*]} -ge "$N" ]; then
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
done
|
||||
fi
|
||||
|
||||
#white BT dongle SERIAL PORTS (COM)
|
||||
if $FINDBTDONGLE; then
|
||||
for DEV in $($PSHEXE -command "Get-CimInstance -ClassName Win32_serialport | Where-Object PNPDeviceID -like '*VID_10C4&PID_EA60*' | Select DeviceID" 2>/dev/null | sed -nr 's#^COM([0-9]+)\b#/dev/ttyS\1#p'); do
|
||||
# ttyS counterpart takes some more time to appear
|
||||
if [ -e "$DEV" ]; then
|
||||
PM3LIST+=("$DEV")
|
||||
if [ ! -w "$DEV" ]; then
|
||||
echo "[!] Let's give users read/write access to $DEV"
|
||||
sudo chmod 666 "$DEV"
|
||||
fi
|
||||
if [ ${#PM3LIST[*]} -ge "$N" ]; then
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
SCRIPT=$(basename -- "$0")
|
||||
|
||||
if [ "$SCRIPT" = "pm3" ]; then
|
||||
CMD() { eval "$EVALENV"; $CLIENT "$@"; }
|
||||
HELP() {
|
||||
cat << EOF
|
||||
|
||||
Quick helper script for proxmark3 client when working with a Proxmark3 device
|
||||
|
||||
Description:
|
||||
The usage is the same as for the proxmark3 client, with the following differences:
|
||||
* the correct port name will be automatically guessed;
|
||||
* the script will wait for a Proxmark3 to be connected (same as option -w of the client).
|
||||
You can also specify a first option -n N to access the Nth Proxmark3 connected.
|
||||
To see a list of available ports, use --list.
|
||||
|
||||
Usage:
|
||||
$SCRIPT [-n <N>] [<any other proxmark3 client option>]
|
||||
$SCRIPT [--list] [-h|--help] [-hh|--helpclient]
|
||||
$SCRIPT [-o|--offline]
|
||||
|
||||
|
||||
Arguments:
|
||||
-h/--help this help
|
||||
-hh/--helpclient proxmark3 client help (the script will forward these options)
|
||||
--list list all detected com ports
|
||||
-n <N> connect device referred to the N:th number on the --list output
|
||||
-o/--offline shortcut to use directly the proxmark3 client without guessing ports
|
||||
|
||||
Samples:
|
||||
./$SCRIPT -- Auto detect/ select com port in the following order BT, USB/CDC, BT DONGLE
|
||||
./$SCRIPT -p /dev/ttyACM0 -- connect to port /dev/ttyACM0
|
||||
./$SCRIPT -n 2 -- use second item from the --list output
|
||||
./$SCRIPT -c 'lf search' -i -- run command and stay in client once completed
|
||||
|
||||
|
||||
EOF
|
||||
}
|
||||
elif [ "$SCRIPT" = "pm3-flash" ]; then
|
||||
FINDBTDONGLE=false
|
||||
FINDBTRFCOMM=false
|
||||
FINDBTDIRECT=false
|
||||
CMD() {
|
||||
ARGS=("--port" "$1" "--flash")
|
||||
shift;
|
||||
while [ "$1" != "" ]; do
|
||||
if [ "$1" == "-b" ]; then
|
||||
ARGS+=("--unlock-bootloader")
|
||||
elif [ "$1" == "--force" ]; then
|
||||
ARGS+=("--force")
|
||||
else
|
||||
ARGS+=("--image" "$1")
|
||||
fi
|
||||
shift;
|
||||
done
|
||||
$CLIENT "${ARGS[@]}";
|
||||
}
|
||||
HELP() {
|
||||
cat << EOF
|
||||
Quick helper script for flashing a Proxmark3 device via USB
|
||||
|
||||
Description:
|
||||
The usage is similar to the old proxmark3-flasher binary, except that the correct port name will be automatically guessed.
|
||||
You can also specify a first option -n N to access the Nth Proxmark3 connected on USB.
|
||||
If this doesn't work, you'll have to use manually the proxmark3 client, see "$CLIENT -h".
|
||||
To see a list of available ports, use --list.
|
||||
|
||||
Usage:
|
||||
$SCRIPT [-n <N>] [-b] image.elf [image.elf...]
|
||||
$SCRIPT --list
|
||||
|
||||
Options:
|
||||
-b Enable flashing of bootloader area (DANGEROUS)
|
||||
|
||||
Example:
|
||||
$SCRIPT -b bootrom.elf fullimage.elf
|
||||
EOF
|
||||
}
|
||||
elif [ "$SCRIPT" = "pm3-flash-all" ]; then
|
||||
FINDBTDONGLE=false
|
||||
FINDBTRFCOMM=false
|
||||
FINDBTDIRECT=false
|
||||
|
||||
|
||||
CMD() {
|
||||
ARGS=("--port" "$1" "--flash" "--unlock-bootloader" "--image" "$BOOTIMAGE" "--image" "$FULLIMAGE")
|
||||
shift;
|
||||
while [ "$1" != "" ]; do
|
||||
if [ "$1" == "--force" ]; then
|
||||
ARGS+=("--force")
|
||||
fi
|
||||
shift;
|
||||
done
|
||||
$CLIENT "${ARGS[@]}";
|
||||
}
|
||||
HELP() {
|
||||
cat << EOF
|
||||
Quick helper script for flashing a Proxmark3 device via USB
|
||||
|
||||
Description:
|
||||
The correct port name will be automatically guessed and the stock bootloader and firmware image will be flashed.
|
||||
You can also specify a first option -n N to access the Nth Proxmark3 connected on USB.
|
||||
If this doesn't work, you'll have to use manually the proxmark3 client, see "$CLIENT -h".
|
||||
To see a list of available ports, use --list.
|
||||
|
||||
Usage:
|
||||
$SCRIPT [-n <N>]
|
||||
$SCRIPT --list
|
||||
EOF
|
||||
}
|
||||
elif [ "$SCRIPT" = "pm3-flash-fullimage" ]; then
|
||||
FINDBTDONGLE=false
|
||||
FINDBTRFCOMM=false
|
||||
FINDBTDIRECT=false
|
||||
CMD() {
|
||||
ARGS=("--port" "$1" "--flash" "--image" "$FULLIMAGE")
|
||||
shift;
|
||||
while [ "$1" != "" ]; do
|
||||
if [ "$1" == "--force" ]; then
|
||||
ARGS+=("--force")
|
||||
fi
|
||||
shift;
|
||||
done
|
||||
$CLIENT "${ARGS[@]}";
|
||||
}
|
||||
HELP() {
|
||||
cat << EOF
|
||||
Quick helper script for flashing a Proxmark3 device via USB
|
||||
|
||||
Description:
|
||||
The correct port name will be automatically guessed and the stock firmware image will be flashed.
|
||||
You can also specify a first option -n N to access the Nth Proxmark3 connected on USB.
|
||||
If this doesn't work, you'll have to use manually the proxmark3 client, see "$CLIENT -h".
|
||||
To see a list of available ports, use --list.
|
||||
|
||||
Usage:
|
||||
$SCRIPT [-n <N>]
|
||||
$SCRIPT --list
|
||||
EOF
|
||||
}
|
||||
elif [ "$SCRIPT" = "pm3-flash-bootrom" ]; then
|
||||
FINDBTDONGLE=false
|
||||
FINDBTRFCOMM=false
|
||||
FINDBTDIRECT=false
|
||||
CMD() {
|
||||
ARGS=("--port" "$1" "--flash" "--unlock-bootloader" "--image" "$BOOTIMAGE")
|
||||
shift;
|
||||
while [ "$1" != "" ]; do
|
||||
if [ "$1" == "--force" ]; then
|
||||
ARGS+=("--force")
|
||||
fi
|
||||
shift;
|
||||
done
|
||||
$CLIENT "${ARGS[@]}";
|
||||
}
|
||||
HELP() {
|
||||
cat << EOF
|
||||
Quick helper script for flashing a Proxmark3 device via USB
|
||||
|
||||
Description:
|
||||
The correct port name will be automatically guessed and the stock bootloader will be flashed.
|
||||
You can also specify a first option -n N to access the Nth Proxmark3 connected on USB.
|
||||
If this doesn't work, you'll have to use manually the proxmark3 client, see "$CLIENT -h".
|
||||
To see a list of available ports, use --list.
|
||||
|
||||
Usage:
|
||||
$SCRIPT [-n <N>]
|
||||
$SCRIPT --list
|
||||
EOF
|
||||
}
|
||||
else
|
||||
echo >&2 "[!!] Script ran under unknown name, abort: $SCRIPT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# priority to the help options
|
||||
for ARG; do
|
||||
if [ "$ARG" == "-h" ] || [ "$ARG" == "--help" ]; then
|
||||
HELP
|
||||
exit 0
|
||||
fi
|
||||
if [ "$ARG" == "-hh" ] || [ "$ARG" == "--helpclient" ]; then
|
||||
CMD "-h"
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
|
||||
# if offline, bypass the script and forward all other args
|
||||
for ARG; do
|
||||
shift
|
||||
if [ "$ARG" == "-o" ] || [ "$ARG" == "--offline" ]; then
|
||||
CMD "$@"
|
||||
exit $?
|
||||
fi
|
||||
set -- "$@" "$ARG"
|
||||
done
|
||||
|
||||
# if a port is already provided, let's just run the command as such
|
||||
for ARG; do
|
||||
shift
|
||||
if [ "$ARG" == "-p" ]; then
|
||||
CMD "$@"
|
||||
exit $?
|
||||
fi
|
||||
set -- "$@" "$ARG"
|
||||
done
|
||||
|
||||
if [ "$1" == "--list" ]; then
|
||||
shift
|
||||
if [ "$1" != "" ]; then
|
||||
echo >&2 "[!!] Option --list must be used alone"
|
||||
exit 1
|
||||
fi
|
||||
SHOWLIST=true
|
||||
fi
|
||||
|
||||
# Number of the Proxmark3 we're interested in
|
||||
N=1
|
||||
if [ "$1" == "-n" ]; then
|
||||
shift
|
||||
if [ "$1" -ge 1 ] && [ "$1" -lt 10 ]; then
|
||||
N=$1
|
||||
shift
|
||||
else
|
||||
echo >&2 "[!!] Option -n requires a number between 1 and 9, got \"$1\""
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
HOSTOS=$(uname | awk '{print toupper($0)}')
|
||||
if [ "$HOSTOS" = "LINUX" ]; then
|
||||
# Detect when running under WSL1 (but exclude WSL2)
|
||||
if uname -a | grep -qi Microsoft && uname -a | grep -qvi WSL2; then
|
||||
# First try finding it using the PATH environment variable
|
||||
PSHEXE=$(command -v powershell.exe 2>/dev/null)
|
||||
|
||||
# If it fails (such as if WSLENV is not set), try using the default installation path
|
||||
if [ -z "$PSHEXE" ]; then
|
||||
PSHEXE=/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe
|
||||
fi
|
||||
|
||||
# Finally test if PowerShell is working
|
||||
if ! "$PSHEXE" exit >/dev/null 2>&1; then
|
||||
echo >&2 "[!!] Cannot run powershell.exe, are you sure your WSL is authorized to run Windows processes? (cf WSL interop flag)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
GETPM3LIST=get_pm3_list_WSL
|
||||
else
|
||||
GETPM3LIST=get_pm3_list_Linux
|
||||
fi
|
||||
elif [ "$HOSTOS" = "DARWIN" ]; then
|
||||
GETPM3LIST=get_pm3_list_macOS
|
||||
elif [[ "$HOSTOS" =~ MINGW(32|64)_NT* ]]; then
|
||||
GETPM3LIST=get_pm3_list_Windows
|
||||
else
|
||||
echo >&2 "[!!] Host OS not recognized, abort: $HOSTOS"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if $SHOWLIST; then
|
||||
# Probe for up to 9 devs
|
||||
$GETPM3LIST 9
|
||||
if [ ${#PM3LIST} -lt 1 ]; then
|
||||
echo >&2 "[!!] No port found"
|
||||
exit 1
|
||||
fi
|
||||
n=1
|
||||
for DEV in "${PM3LIST[@]}"
|
||||
do
|
||||
echo "$n: $DEV"
|
||||
n=$((n+1))
|
||||
done
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Wait till we get at least N Proxmark3 devices
|
||||
$GETPM3LIST "$N"
|
||||
if [ ${#PM3LIST} -lt "$N" ]; then
|
||||
echo >&2 "[=] Waiting for Proxmark3 to appear..."
|
||||
fi
|
||||
while true; do
|
||||
if [ ${#PM3LIST[*]} -ge "$N" ]; then
|
||||
break
|
||||
fi
|
||||
sleep .1
|
||||
$GETPM3LIST "$N"
|
||||
done
|
||||
|
||||
if [ ${#PM3LIST} -lt "$N" ]; then
|
||||
HELP() {
|
||||
cat << EOF
|
||||
[!!] No port found, abort
|
||||
|
||||
[?] Hint: try '$SCRIPT --list' to see list of available ports, and use the -n command like below
|
||||
[?] $SCRIPT [-n <N>]
|
||||
|
||||
EOF
|
||||
}
|
||||
HELP
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CMD "${PM3LIST[$((N-1))]}" "$@"
|
||||
exit $?
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
PM3PATH=$(dirname "$0")
|
||||
. "$PM3PATH/pm3"
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
PM3PATH=$(dirname "$0")
|
||||
. "$PM3PATH/pm3"
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
PM3PATH=$(dirname "$0")
|
||||
. "$PM3PATH/pm3"
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
PM3PATH=$(dirname "$0")
|
||||
. "$PM3PATH/pm3"
|
||||
Binary file not shown.
4
rust-toolchain.toml
Normal file
4
rust-toolchain.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
components = ["rust-src"]
|
||||
targets = ["riscv32imac-unknown-none-elf"]
|
||||
@@ -1,19 +0,0 @@
|
||||
[Unit]
|
||||
Description=Failure state for fwa.service
|
||||
Requires=local-fs.target
|
||||
After=local-fs.target
|
||||
StartLimitIntervalSec=500
|
||||
StartLimitBurst=5
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/local/bin/fwa --error
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
User=root
|
||||
Group=root
|
||||
WorkingDirectory=/var/lib/fwa
|
||||
EnvironmentFile=/etc/fwa.env
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1,5 +0,0 @@
|
||||
PM3_BIN=/usr/share/pm3/pm3
|
||||
LOG_LEVEL=warn
|
||||
HOTSPOT_IDS=578B5DF2;c1532b57
|
||||
HOTSPOT_SSID=fwa
|
||||
HOTSPOT_PW=a9LG2kUVrsRRVUo1
|
||||
@@ -1,20 +0,0 @@
|
||||
[Unit]
|
||||
Description=Feuerwehr Anwesenheit Service
|
||||
Requires=local-fs.target
|
||||
After=local-fs.target
|
||||
StartLimitIntervalSec=500
|
||||
StartLimitBurst=5
|
||||
OnFailure= fwa-fail.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/local/bin/fwa
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
User=root
|
||||
Group=root
|
||||
WorkingDirectory=/var/lib/fwa
|
||||
EnvironmentFile=/etc/fwa.env
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
3
src/drivers.rs
Normal file
3
src/drivers.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod nfc_reader;
|
||||
pub mod rtc;
|
||||
pub mod buzzer;
|
||||
0
src/drivers/buzzer.rs
Normal file
0
src/drivers/buzzer.rs
Normal file
9
src/drivers/fram.rs
Normal file
9
src/drivers/fram.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
const DEVICE_TYPE_CODE: u8 = 0b10100000;
|
||||
|
||||
const DEVICE_ADDRESS_CODE: u8 = 0b000000; // 3 bits for device address | default A0 = 0 A1 = 0 A2 = 0
|
||||
|
||||
const WRITE_CODE: u8 = 0b00000000; // 0 for write
|
||||
const READ_CODE: u8 = 0b00000001; // 1 for read
|
||||
|
||||
const DEVICE_ADDRESS_WRITE: u8 = DEVICE_TYPE_CODE | DEVICE_ADDRESS_CODE | WRITE_CODE; // I2C address write for FRAM
|
||||
const DEVICE_ADDRESS_READ: u8 = DEVICE_TYPE_CODE | DEVICE_ADDRESS_CODE | READ_CODE; // I2C address read for FRAM
|
||||
28
src/drivers/nfc_reader.rs
Normal file
28
src/drivers/nfc_reader.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use embassy_time::{Duration, Timer};
|
||||
use esp_hal::{Async, uart::Uart};
|
||||
use log::{debug, info};
|
||||
|
||||
use crate::TallyPublisher;
|
||||
|
||||
#[embassy_executor::task]
|
||||
pub async fn rfid_reader_task(mut uart_device: Uart<'static, Async>, chan: TallyPublisher) {
|
||||
let mut uart_buffer = [0u8; 64];
|
||||
|
||||
loop {
|
||||
debug!("Looking for NFC...");
|
||||
match uart_device.read_async(&mut uart_buffer).await {
|
||||
Ok(n) => {
|
||||
let mut hex_str = heapless::String::<64>::new();
|
||||
for byte in &uart_buffer[..n] {
|
||||
core::fmt::Write::write_fmt(&mut hex_str, format_args!("{:02X} ", byte)).ok();
|
||||
}
|
||||
info!("Read {n} bytes from UART: {hex_str}");
|
||||
chan.publish([1, 0, 2, 5, 0, 8, 12, 15]).await;
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Error reading from UART: {e}");
|
||||
}
|
||||
}
|
||||
Timer::after(Duration::from_millis(200)).await;
|
||||
}
|
||||
}
|
||||
96
src/drivers/rtc.rs
Normal file
96
src/drivers/rtc.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
use ds3231::{
|
||||
Config, DS3231, DS3231Error, InterruptControl, Oscillator, SquareWaveFrequency,
|
||||
TimeRepresentation,
|
||||
};
|
||||
use esp_hal::{
|
||||
Async,
|
||||
i2c::{self, master::I2c},
|
||||
};
|
||||
use log::{debug, error, info};
|
||||
|
||||
use crate::{FEEDBACK_STATE, drivers, feedback};
|
||||
use chrono::{TimeZone, Utc};
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/build_time.rs"));
|
||||
|
||||
const RTC_ADDRESS: u8 = 0x68;
|
||||
|
||||
pub struct RTCClock {
|
||||
dev: DS3231<I2c<'static, Async>>,
|
||||
}
|
||||
|
||||
impl RTCClock {
|
||||
pub async fn new(i2c: i2c::master::I2c<'static, Async>) -> Self {
|
||||
debug!("configuring rtc...");
|
||||
let rtc = drivers::rtc::rtc_config(i2c).await;
|
||||
debug!("rtc up");
|
||||
|
||||
RTCClock { dev: rtc }
|
||||
}
|
||||
|
||||
pub async fn get_time(&mut self) -> u64 {
|
||||
match self.dev.datetime().await {
|
||||
Ok(datetime) => {
|
||||
let utc_time = datetime.and_utc().timestamp() as u64;
|
||||
utc_time
|
||||
}
|
||||
Err(e) => {
|
||||
FEEDBACK_STATE.signal(feedback::FeedbackState::Error);
|
||||
error!("Failed to read RTC datetime: {:?}", e);
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn rtc_config(i2c: I2c<'static, Async>) -> DS3231<I2c<'static, Async>> {
|
||||
let mut rtc: DS3231<I2c<'static, Async>> = DS3231::new(i2c, RTC_ADDRESS);
|
||||
let naive_dt = Utc
|
||||
.timestamp_opt(BUILD_UNIX_TIME as i64, 0)
|
||||
.single()
|
||||
.unwrap()
|
||||
.naive_utc();
|
||||
|
||||
let rtc_config = Config {
|
||||
time_representation: TimeRepresentation::TwentyFourHour,
|
||||
square_wave_frequency: SquareWaveFrequency::Hz1,
|
||||
interrupt_control: InterruptControl::Interrupt, // Enable interrupt mode
|
||||
battery_backed_square_wave: false,
|
||||
oscillator_enable: Oscillator::Disabled,
|
||||
};
|
||||
|
||||
match rtc.configure(&rtc_config).await {
|
||||
Ok(_) => info!("DS3231 configured successfully"),
|
||||
Err(e) => {
|
||||
info!("Failed to configure DS3231: {:?}", e);
|
||||
panic!("DS3231 configuration failed");
|
||||
}
|
||||
}
|
||||
|
||||
rtc.set_datetime(&naive_dt).await.unwrap_or_else(|e| {
|
||||
FEEDBACK_STATE.signal(feedback::FeedbackState::Error);
|
||||
error!("Failed to set RTC datetime: {:?}", e);
|
||||
});
|
||||
info!("RTC datetime set to: {}", naive_dt);
|
||||
|
||||
match rtc.status().await {
|
||||
Ok(mut status) => {
|
||||
status.set_alarm1_flag(false);
|
||||
status.set_alarm2_flag(false);
|
||||
match rtc.set_status(status).await {
|
||||
Ok(_) => info!("Alarm flags cleared"),
|
||||
Err(e) => info!("Failed to clear alarm flags: {:?}", e),
|
||||
}
|
||||
}
|
||||
Err(e) => info!("Failed to read status: {:?}", e),
|
||||
}
|
||||
|
||||
rtc
|
||||
}
|
||||
|
||||
pub async fn read_rtc_time<'a>(
|
||||
rtc: &'a mut DS3231<I2c<'static, Async>>,
|
||||
) -> Result<u64, DS3231Error<esp_hal::i2c::master::Error>> {
|
||||
let timestamp_result = rtc.datetime().await?;
|
||||
Ok(timestamp_result.and_utc().timestamp() as u64)
|
||||
}
|
||||
235
src/feedback.rs
235
src/feedback.rs
@@ -1,74 +1,160 @@
|
||||
use anyhow::Result;
|
||||
use log::error;
|
||||
use rgb::RGB8;
|
||||
use smart_leds::colors::{GREEN, RED};
|
||||
use std::time::Duration;
|
||||
use tokio::{join, time::sleep};
|
||||
use embassy_time::{Delay, Duration, Timer};
|
||||
use esp_hal::{delay, gpio::Output, peripherals, rmt::ConstChannelAccess};
|
||||
use esp_hal_smartled::SmartLedsAdapterAsync;
|
||||
use init::hardware;
|
||||
use log::{debug, error, info};
|
||||
use smart_leds::SmartLedsWriteAsync;
|
||||
use smart_leds::colors::{BLACK, GREEN, RED, YELLOW};
|
||||
use smart_leds::{brightness, colors::BLUE};
|
||||
|
||||
use crate::hardware::{Buzzer, StatusLed};
|
||||
use crate::{FEEDBACK_STATE, init};
|
||||
|
||||
#[cfg(not(feature = "mock_pi"))]
|
||||
use crate::{hardware::GPIOBuzzer, hardware::SpiLed};
|
||||
|
||||
#[cfg(feature = "mock_pi")]
|
||||
use crate::hardware::{MockBuzzer, MockLed};
|
||||
|
||||
const LED_BLINK_DURATION: Duration = Duration::from_secs(1);
|
||||
|
||||
pub enum DeviceStatus {
|
||||
NotReady,
|
||||
Ready,
|
||||
HotspotEnabled,
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum FeedbackState {
|
||||
Ack,
|
||||
Nack,
|
||||
Error,
|
||||
Startup,
|
||||
WIFI,
|
||||
Idle,
|
||||
}
|
||||
|
||||
impl DeviceStatus {
|
||||
pub fn color(&self) -> RGB8 {
|
||||
match self {
|
||||
Self::NotReady => RGB8::new(0, 0, 0),
|
||||
Self::Ready => RGB8::new(0, 50, 0),
|
||||
Self::HotspotEnabled => RGB8::new(0, 0, 50),
|
||||
const LED_LEVEL: u8 = 255;
|
||||
|
||||
//TODO ERROR STATE: 1 Blink = unknows error, 3 Blink = no sd card
|
||||
|
||||
#[embassy_executor::task]
|
||||
pub async fn feedback_task(
|
||||
mut led: SmartLedsAdapterAsync<
|
||||
ConstChannelAccess<esp_hal::rmt::Tx, 0>,
|
||||
{ init::hardware::LED_BUFFER_SIZE },
|
||||
>,
|
||||
buzzer: peripherals::GPIO21<'static>,
|
||||
) {
|
||||
debug!("Starting feedback task");
|
||||
let mut buzzer = init::hardware::setup_buzzer(buzzer);
|
||||
loop {
|
||||
let feedback_state = FEEDBACK_STATE.wait().await;
|
||||
match feedback_state {
|
||||
FeedbackState::Ack => {
|
||||
led.write(brightness(
|
||||
[GREEN; init::hardware::NUM_LEDS].into_iter(),
|
||||
LED_LEVEL,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
buzzer.set_high();
|
||||
Timer::after(Duration::from_millis(100)).await;
|
||||
buzzer.set_low();
|
||||
Timer::after(Duration::from_millis(50)).await;
|
||||
}
|
||||
FeedbackState::Nack => {
|
||||
led.write(brightness(
|
||||
[YELLOW; init::hardware::NUM_LEDS].into_iter(),
|
||||
LED_LEVEL,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
buzzer.set_high();
|
||||
Timer::after(Duration::from_millis(100)).await;
|
||||
buzzer.set_low();
|
||||
Timer::after(Duration::from_millis(100)).await;
|
||||
buzzer.set_high();
|
||||
Timer::after(Duration::from_millis(100)).await;
|
||||
buzzer.set_low();
|
||||
led.write(brightness(
|
||||
[BLACK; init::hardware::NUM_LEDS].into_iter(),
|
||||
LED_LEVEL,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
FeedbackState::Error => {
|
||||
led.write(brightness(
|
||||
[RED; init::hardware::NUM_LEDS].into_iter(),
|
||||
LED_LEVEL,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
buzzer.set_high();
|
||||
Timer::after(Duration::from_millis(500)).await;
|
||||
buzzer.set_low();
|
||||
Timer::after(Duration::from_millis(500)).await;
|
||||
buzzer.set_high();
|
||||
Timer::after(Duration::from_millis(500)).await;
|
||||
buzzer.set_low();
|
||||
}
|
||||
FeedbackState::Startup => {
|
||||
led.write(brightness(
|
||||
[GREEN; init::hardware::NUM_LEDS].into_iter(),
|
||||
LED_LEVEL,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
buzzer.set_high();
|
||||
Timer::after(Duration::from_millis(10)).await;
|
||||
buzzer.set_low();
|
||||
Timer::after(Duration::from_millis(10)).await;
|
||||
buzzer.set_high();
|
||||
Timer::after(Duration::from_millis(10)).await;
|
||||
buzzer.set_low();
|
||||
Timer::after(Duration::from_millis(50)).await;
|
||||
buzzer.set_high();
|
||||
Timer::after(Duration::from_millis(100)).await;
|
||||
buzzer.set_low();
|
||||
led.write(brightness(
|
||||
[BLACK; init::hardware::NUM_LEDS].into_iter(),
|
||||
LED_LEVEL,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
FeedbackState::WIFI => {
|
||||
led.write(brightness(
|
||||
[BLUE; init::hardware::NUM_LEDS].into_iter(),
|
||||
LED_LEVEL,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
FeedbackState::Idle => {
|
||||
led.write(brightness(
|
||||
[GREEN; init::hardware::NUM_LEDS].into_iter(),
|
||||
LED_LEVEL,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
};
|
||||
debug!("Feedback state: {:?}", feedback_state);
|
||||
}
|
||||
}
|
||||
pub struct Feedback<B: Buzzer, L: StatusLed> {
|
||||
device_status: DeviceStatus,
|
||||
buzzer: B,
|
||||
led: L,
|
||||
}
|
||||
|
||||
impl<B: Buzzer, L: StatusLed> Feedback<B, L> {
|
||||
pub async fn success(&mut self) {
|
||||
let buzzer_handle = Self::beep_ack(&mut self.buzzer);
|
||||
let led_handle = Self::flash_led_for_duration(&mut self.led, GREEN, LED_BLINK_DURATION);
|
||||
let (buzzer_result, _) = join!(buzzer_handle, led_handle);
|
||||
// async fn beep_ack() {
|
||||
// buzzer.set_high();
|
||||
// buzzer.set_low();
|
||||
// //Timer::after(Duration::from_millis(100)).await;
|
||||
// }
|
||||
|
||||
buzzer_result.unwrap_or_else(|err| {
|
||||
error!("Failed to buzz: {err}");
|
||||
});
|
||||
|
||||
let _ = self.led_to_status();
|
||||
}
|
||||
|
||||
pub async fn failure(&mut self) {
|
||||
/* pub async fn failure(&mut self) {
|
||||
let buzzer_handle = Self::beep_nak(&mut self.buzzer);
|
||||
let led_handle = Self::flash_led_for_duration(&mut self.led, RED, LED_BLINK_DURATION);
|
||||
|
||||
let (buzzer_result, _) = join!(buzzer_handle, led_handle);
|
||||
|
||||
buzzer_result.unwrap_or_else(|err| {
|
||||
error!("Failed to buzz: {err}");
|
||||
buzzer_result.unwrap_or_else(|err| { error!("Failed to buzz: {err}");
|
||||
});
|
||||
|
||||
let _ = self.led_to_status();
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn activate_error_state(&mut self) -> Result<()> {
|
||||
pub async fn activate_error_state(&mut self) -> Result<()> {
|
||||
self.led.turn_on(RED)?;
|
||||
Self::beep_nak(&mut self.buzzer).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn startup(&mut self){
|
||||
pub async fn startup(&mut self){
|
||||
self.device_status = DeviceStatus::Ready;
|
||||
|
||||
let led_handle = Self::flash_led_for_duration(&mut self.led, GREEN, Duration::from_secs(1));
|
||||
@@ -85,18 +171,10 @@ impl<B: Buzzer, L: StatusLed> Feedback<B, L> {
|
||||
});
|
||||
|
||||
let _ = self.led_to_status();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_device_status(&mut self, status: DeviceStatus){
|
||||
self.device_status = status;
|
||||
let _ = self.led_to_status();
|
||||
}
|
||||
|
||||
fn led_to_status(&mut self) -> Result<()> {
|
||||
self.led.turn_on(self.device_status.color())
|
||||
}
|
||||
|
||||
async fn flash_led_for_duration(led: &mut L, color: RGB8, duration: Duration) -> Result<()> {
|
||||
async fn flash_led_for_duration(led: &mut L, color: RGB8, duration: Duration) -> Result<()> {
|
||||
led.turn_on(color)?;
|
||||
|
||||
sleep(duration).await;
|
||||
@@ -104,9 +182,9 @@ impl<B: Buzzer, L: StatusLed> Feedback<B, L> {
|
||||
led.turn_off()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn beep_ack(buzzer: &mut B) -> Result<()> {
|
||||
async fn beep_ack(buzzer: &mut B) -> Result<()> {
|
||||
buzzer
|
||||
.modulated_tone(1200.0, Duration::from_millis(100))
|
||||
.await?;
|
||||
@@ -115,9 +193,9 @@ impl<B: Buzzer, L: StatusLed> Feedback<B, L> {
|
||||
.modulated_tone(2000.0, Duration::from_millis(50))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn beep_nak(buzzer: &mut B) -> Result<()> {
|
||||
async fn beep_nak(buzzer: &mut B) -> Result<()> {
|
||||
buzzer
|
||||
.modulated_tone(600.0, Duration::from_millis(150))
|
||||
.await?;
|
||||
@@ -126,9 +204,9 @@ impl<B: Buzzer, L: StatusLed> Feedback<B, L> {
|
||||
.modulated_tone(600.0, Duration::from_millis(150))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn beep_startup(buzzer: &mut B) -> Result<()> {
|
||||
async fn beep_startup(buzzer: &mut B) -> Result<()> {
|
||||
buzzer
|
||||
.modulated_tone(523.0, Duration::from_millis(150))
|
||||
.await?;
|
||||
@@ -151,31 +229,6 @@ impl<B: Buzzer, L: StatusLed> Feedback<B, L> {
|
||||
.modulated_tone(880.0, Duration::from_millis(200))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mock_pi")]
|
||||
pub type FeedbackImpl = Feedback<MockBuzzer, MockLed>;
|
||||
#[cfg(not(feature = "mock_pi"))]
|
||||
pub type FeedbackImpl = Feedback<GPIOBuzzer, SpiLed>;
|
||||
|
||||
impl FeedbackImpl {
|
||||
pub fn new() -> Result<Self> {
|
||||
#[cfg(feature = "mock_pi")]
|
||||
{
|
||||
Ok(Feedback {
|
||||
device_status: DeviceStatus::NotReady,
|
||||
buzzer: MockBuzzer {},
|
||||
led: MockLed {},
|
||||
})
|
||||
}
|
||||
#[cfg(not(feature = "mock_pi"))]
|
||||
{
|
||||
Ok(Feedback {
|
||||
device_status: DeviceStatus::NotReady,
|
||||
buzzer: GPIOBuzzer::new_default()?,
|
||||
led: SpiLed::new()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
use anyhow::Result;
|
||||
use rppal::pwm::{Channel, Polarity, Pwm};
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
|
||||
use crate::hardware::Buzzer;
|
||||
|
||||
const DEFAULT_PWM_CHANNEL_BUZZER: Channel = Channel::Pwm0; //PWM0 = GPIO18/Physical pin 12
|
||||
|
||||
pub struct GPIOBuzzer {
|
||||
pwm: Pwm,
|
||||
}
|
||||
|
||||
impl GPIOBuzzer {
|
||||
pub fn new_from_channel(channel: Channel) -> Result<Self, rppal::pwm::Error> {
|
||||
// Enable with dummy values; we'll set frequency/duty in the tone method
|
||||
let duty_cycle: f64 = 0.5;
|
||||
let pwm = Pwm::with_frequency(channel, 1000.0, duty_cycle, Polarity::Normal, true)?;
|
||||
pwm.disable()?;
|
||||
|
||||
Ok(GPIOBuzzer { pwm })
|
||||
}
|
||||
|
||||
pub fn new_default() -> Result<Self, rppal::pwm::Error> {
|
||||
Self::new_from_channel(DEFAULT_PWM_CHANNEL_BUZZER)
|
||||
}
|
||||
}
|
||||
|
||||
impl Buzzer for GPIOBuzzer {
|
||||
async fn modulated_tone(&mut self, frequency_hz: f64, duration: Duration) -> Result<()> {
|
||||
self.pwm.set_frequency(frequency_hz, 0.5)?; // 50% duty cycle (square wave)
|
||||
self.pwm.enable()?;
|
||||
sleep(duration).await;
|
||||
self.pwm.disable()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
use anyhow::{Result, anyhow};
|
||||
use log::{trace, warn};
|
||||
use std::env;
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::hardware::Hotspot;
|
||||
|
||||
const SSID: &str = "fwa";
|
||||
const CON_NAME: &str = "fwa-hotspot";
|
||||
const PASSWORD: &str = "a9LG2kUVrsRRVUo1";
|
||||
const IPV4_ADDRES: &str = "192.168.4.1/24";
|
||||
|
||||
/// NetworkManager Hotspot
|
||||
pub struct NMHotspot {
|
||||
ssid: String,
|
||||
con_name: String,
|
||||
password: String,
|
||||
ipv4: String,
|
||||
}
|
||||
|
||||
impl NMHotspot {
|
||||
pub fn new_from_env() -> Result<Self> {
|
||||
let ssid = env::var("HOTSPOT_SSID").unwrap_or(SSID.to_owned());
|
||||
let password = env::var("HOTSPOT_PW").unwrap_or_else(|_| {
|
||||
warn!("HOTSPOT_PW not set. Using default password");
|
||||
PASSWORD.to_owned()
|
||||
});
|
||||
|
||||
if password.len() < 8 {
|
||||
return Err(anyhow!("Hotspot password to short"));
|
||||
}
|
||||
|
||||
Ok(NMHotspot {
|
||||
ssid,
|
||||
con_name: CON_NAME.to_owned(),
|
||||
password,
|
||||
ipv4: IPV4_ADDRES.to_owned(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn create_hotspot(&self) -> Result<()> {
|
||||
let cmd = Command::new("nmcli")
|
||||
.args(["device", "wifi", "hotspot"])
|
||||
.arg("con-name")
|
||||
.arg(&self.con_name)
|
||||
.arg("ssid")
|
||||
.arg(&self.ssid)
|
||||
.arg("password")
|
||||
.arg(&self.password)
|
||||
.output()
|
||||
.await?;
|
||||
|
||||
trace!("nmcli (std): {}", String::from_utf8_lossy(&cmd.stdout));
|
||||
trace!("nmcli (err): {}", String::from_utf8_lossy(&cmd.stderr));
|
||||
|
||||
if !cmd.status.success() {
|
||||
return Err(anyhow!("nmcli command had non-zero exit code"));
|
||||
}
|
||||
|
||||
let cmd = Command::new("nmcli")
|
||||
.arg("connection")
|
||||
.arg("modify")
|
||||
.arg(&self.con_name)
|
||||
.arg("ipv4.method")
|
||||
.arg("shared")
|
||||
.arg("ipv4.addresses")
|
||||
.arg(&self.ipv4)
|
||||
.output()
|
||||
.await?;
|
||||
|
||||
if !cmd.status.success() {
|
||||
return Err(anyhow!("nmcli command had non-zero exit code"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Checks if the connection already exists
|
||||
async fn exists(&self) -> Result<bool> {
|
||||
let cmd = Command::new("nmcli")
|
||||
.args(["connection", "show"])
|
||||
.arg(&self.con_name)
|
||||
.output()
|
||||
.await?;
|
||||
|
||||
trace!("nmcli (std): {}", String::from_utf8_lossy(&cmd.stdout));
|
||||
trace!("nmcli (err): {}", String::from_utf8_lossy(&cmd.stderr));
|
||||
|
||||
Ok(cmd.status.success())
|
||||
}
|
||||
}
|
||||
|
||||
impl Hotspot for NMHotspot {
|
||||
async fn enable_hotspot(&self) -> Result<()> {
|
||||
if !self.exists().await? {
|
||||
self.create_hotspot().await?;
|
||||
}
|
||||
|
||||
let cmd = Command::new("nmcli")
|
||||
.args(["connection", "up"])
|
||||
.arg(&self.con_name)
|
||||
.output()
|
||||
.await?;
|
||||
|
||||
trace!("nmcli (std): {}", String::from_utf8_lossy(&cmd.stdout));
|
||||
trace!("nmcli (err): {}", String::from_utf8_lossy(&cmd.stderr));
|
||||
|
||||
if !cmd.status.success() {
|
||||
return Err(anyhow!("nmcli command had non-zero exit code"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn disable_hotspot(&self) -> Result<()> {
|
||||
let cmd = Command::new("nmcli")
|
||||
.args(["connection", "down"])
|
||||
.arg(&self.con_name)
|
||||
.output()
|
||||
.await?;
|
||||
|
||||
trace!("nmcli (std): {}", String::from_utf8_lossy(&cmd.stdout));
|
||||
trace!("nmcli (err): {}", String::from_utf8_lossy(&cmd.stderr));
|
||||
|
||||
if !cmd.status.success() {
|
||||
return Err(anyhow!("nmcli command had non-zero exit code"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
use anyhow::Result;
|
||||
use std::time::Duration;
|
||||
|
||||
mod gpio_buzzer;
|
||||
mod hotspot;
|
||||
mod mock;
|
||||
mod spi_led;
|
||||
|
||||
pub use gpio_buzzer::GPIOBuzzer;
|
||||
pub use mock::{MockBuzzer, MockHotspot, MockLed};
|
||||
pub use spi_led::SpiLed;
|
||||
|
||||
pub trait StatusLed {
|
||||
fn turn_off(&mut self) -> Result<()>;
|
||||
|
||||
fn turn_on(&mut self, color: rgb::RGB8) -> Result<()>;
|
||||
}
|
||||
|
||||
pub trait Buzzer {
|
||||
fn modulated_tone(
|
||||
&mut self,
|
||||
frequency_hz: f64,
|
||||
duration: Duration,
|
||||
) -> impl Future<Output = Result<()>> + std::marker::Send;
|
||||
}
|
||||
|
||||
pub trait Hotspot {
|
||||
fn enable_hotspot(&self) -> impl std::future::Future<Output = Result<()>> + std::marker::Send;
|
||||
|
||||
fn disable_hotspot(&self) -> impl std::future::Future<Output = Result<()>> + std::marker::Send;
|
||||
}
|
||||
|
||||
/// Create a struct to manage the hotspot
|
||||
/// Respects the `mock_pi` flag.
|
||||
pub fn create_hotspot() -> Result<impl Hotspot> {
|
||||
#[cfg(feature = "mock_pi")]
|
||||
{
|
||||
Ok(mock::MockHotspot {})
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock_pi"))]
|
||||
{
|
||||
hotspot::NMHotspot::new_from_env()
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
use anyhow::Result;
|
||||
use rppal::spi::{Bus, Mode, SlaveSelect, Spi};
|
||||
use smart_leds::SmartLedsWrite;
|
||||
use ws2812_spi::Ws2812;
|
||||
|
||||
use crate::hardware::StatusLed;
|
||||
|
||||
const SPI_CLOCK_SPEED: u32 = 3_800_000;
|
||||
|
||||
pub struct SpiLed {
|
||||
controller: Ws2812<Spi>,
|
||||
}
|
||||
|
||||
impl SpiLed {
|
||||
pub fn new() -> Result<Self, rppal::spi::Error> {
|
||||
let spi = Spi::new(Bus::Spi0, SlaveSelect::Ss0, SPI_CLOCK_SPEED, Mode::Mode0)?;
|
||||
let controller = Ws2812::new(spi);
|
||||
Ok(SpiLed { controller })
|
||||
}
|
||||
}
|
||||
|
||||
impl StatusLed for SpiLed {
|
||||
fn turn_off(&mut self) -> Result<()> {
|
||||
self.controller
|
||||
.write(vec![rgb::RGB8::new(0, 0, 0)].into_iter())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn turn_on(&mut self, color: rgb::RGB8) -> Result<()> {
|
||||
self.controller.write(vec![color].into_iter())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
4
src/init.rs
Normal file
4
src/init.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod hardware;
|
||||
pub mod network;
|
||||
pub mod wifi;
|
||||
pub mod sd_card;
|
||||
215
src/init/hardware.rs
Normal file
215
src/init/hardware.rs
Normal file
@@ -0,0 +1,215 @@
|
||||
use core::cell::RefCell;
|
||||
|
||||
use bleps::att::Att;
|
||||
use critical_section::Mutex;
|
||||
use ds3231::InterruptControl;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_net::Stack;
|
||||
|
||||
use embassy_time::{Duration, Timer};
|
||||
use esp_hal::gpio::{Input, InputConfig};
|
||||
use esp_hal::i2c::master::Config;
|
||||
use esp_hal::peripherals::{
|
||||
GPIO0, GPIO1, GPIO16, GPIO17, GPIO18, GPIO19, GPIO20, GPIO21, GPIO22, GPIO23, I2C0, RMT, SPI2,
|
||||
UART1,
|
||||
};
|
||||
use esp_hal::rmt::{ConstChannelAccess, Rmt};
|
||||
use esp_hal::spi::master::{Config as Spi_config, Spi};
|
||||
|
||||
use esp_hal::Blocking;
|
||||
use esp_hal::time::Rate;
|
||||
use esp_hal::timer::timg::TimerGroup;
|
||||
use esp_hal::{
|
||||
Async,
|
||||
clock::CpuClock,
|
||||
gpio::{Output, OutputConfig},
|
||||
i2c::master::I2c,
|
||||
timer::systimer::SystemTimer,
|
||||
uart::Uart,
|
||||
};
|
||||
use esp_hal_smartled::{SmartLedsAdapterAsync, buffer_size_async};
|
||||
use esp_println::dbg;
|
||||
use esp_println::logger::init_logger;
|
||||
use log::{debug, error, info};
|
||||
|
||||
use crate::FEEDBACK_STATE;
|
||||
use crate::init::network;
|
||||
use crate::init::sd_card::setup_sdcard;
|
||||
use crate::init::wifi;
|
||||
use crate::store::AttendanceDay;
|
||||
use crate::store::persistence::Persistence;
|
||||
|
||||
/*************************************************
|
||||
* GPIO Pinout Xiao Esp32c6
|
||||
*
|
||||
* D0 -> GPIO0 -> SD DECT
|
||||
* D1 -> GPIO1 -> Level Shifter A0 -> LED
|
||||
* D2 -> GPIO2 -> SPI/CS
|
||||
* D3 -> GPIO21 -> Buzzer
|
||||
* D4 -> GPIO22 -> I2C/SDA
|
||||
* D5 -> GPIO23 -> I2C/SCL
|
||||
* D6 -> GPIO16 -> UART/TX
|
||||
* D7 -> GPIO17 -> UART/RX -> Level Shifter A1 -> NFC Reader
|
||||
* D8 -> GPIO19 -> SPI/SCLK
|
||||
* D9 -> GPIO20 -> SPI/MISO
|
||||
* D10 -> GPIO18 -> SPI/MOSI
|
||||
*
|
||||
*************************************************/
|
||||
|
||||
pub const NUM_LEDS: usize = 66;
|
||||
pub const LED_BUFFER_SIZE: usize = NUM_LEDS * 25;
|
||||
|
||||
static SD_DET: Mutex<RefCell<Option<Input>>> = Mutex::new(RefCell::new(None));
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(info: &core::panic::PanicInfo) -> ! {
|
||||
loop {
|
||||
error!("PANIC: {info}");
|
||||
}
|
||||
}
|
||||
|
||||
esp_bootloader_esp_idf::esp_app_desc!();
|
||||
|
||||
pub async fn hardware_init(
|
||||
spawner: &mut Spawner,
|
||||
) -> (
|
||||
Uart<'static, Async>,
|
||||
Stack<'static>,
|
||||
I2c<'static, Async>,
|
||||
SmartLedsAdapterAsync<ConstChannelAccess<esp_hal::rmt::Tx, 0>, LED_BUFFER_SIZE>,
|
||||
GPIO21<'static>,
|
||||
GPIO0<'static>,
|
||||
) {
|
||||
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
|
||||
let peripherals = esp_hal::init(config);
|
||||
|
||||
esp_alloc::heap_allocator!(size: 72 * 1024);
|
||||
|
||||
let timer0 = SystemTimer::new(peripherals.SYSTIMER);
|
||||
esp_hal_embassy::init(timer0.alarm0);
|
||||
|
||||
init_logger(log::LevelFilter::Debug);
|
||||
|
||||
let timer1 = TimerGroup::new(peripherals.TIMG0);
|
||||
let mut rng = esp_hal::rng::Rng::new(peripherals.RNG);
|
||||
let network_seed = (rng.random() as u64) << 32 | rng.random() as u64;
|
||||
|
||||
wifi::set_antenna_mode(peripherals.GPIO3, peripherals.GPIO14).await;
|
||||
let interfaces = wifi::setup_wifi(timer1.timer0, rng, peripherals.WIFI, spawner);
|
||||
let stack = network::setup_network(network_seed, interfaces.ap, spawner);
|
||||
|
||||
Timer::after(Duration::from_millis(1)).await;
|
||||
|
||||
let uart_device = setup_uart(peripherals.UART1, peripherals.GPIO16, peripherals.GPIO17);
|
||||
|
||||
let i2c_device = setup_i2c(peripherals.I2C0, peripherals.GPIO22, peripherals.GPIO23);
|
||||
|
||||
let mut sd_det_gpio = peripherals.GPIO0;
|
||||
|
||||
let spi_bus = setup_spi(
|
||||
peripherals.SPI2,
|
||||
peripherals.GPIO19,
|
||||
peripherals.GPIO20,
|
||||
peripherals.GPIO18,
|
||||
);
|
||||
|
||||
let sd_cs_pin = Output::new(
|
||||
peripherals.GPIO2,
|
||||
esp_hal::gpio::Level::High,
|
||||
OutputConfig::default(),
|
||||
);
|
||||
|
||||
let mut vol_mgr = setup_sdcard(spi_bus, sd_cs_pin);
|
||||
|
||||
let buzzer_gpio = peripherals.GPIO21;
|
||||
|
||||
Timer::after(Duration::from_millis(500)).await;
|
||||
|
||||
let led = setup_led(peripherals.RMT, peripherals.GPIO1);
|
||||
|
||||
debug!("hardware init done");
|
||||
|
||||
(
|
||||
uart_device,
|
||||
stack,
|
||||
i2c_device,
|
||||
led,
|
||||
buzzer_gpio,
|
||||
sd_det_gpio,
|
||||
)
|
||||
}
|
||||
|
||||
fn setup_uart(
|
||||
uart1: UART1<'static>,
|
||||
uart_tx: GPIO16<'static>,
|
||||
uart_rx: GPIO17<'static>,
|
||||
) -> Uart<'static, Async> {
|
||||
let uard_device = Uart::new(uart1, esp_hal::uart::Config::default().with_baudrate(9600));
|
||||
|
||||
match uard_device {
|
||||
Ok(block) => block.with_rx(uart_rx).with_tx(uart_tx).into_async(),
|
||||
Err(e) => {
|
||||
error!("Failed to initialize UART: {e}");
|
||||
panic!(); //TODO panic!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_i2c(
|
||||
i2c0: I2C0<'static>,
|
||||
sda: GPIO22<'static>,
|
||||
scl: GPIO23<'static>,
|
||||
) -> I2c<'static, Async> {
|
||||
debug!("init I2C");
|
||||
let config = Config::default().with_frequency(Rate::from_khz(400));
|
||||
let i2c = match I2c::new(i2c0, config) {
|
||||
Ok(i2c) => i2c.with_sda(sda).with_scl(scl).into_async(),
|
||||
Err(e) => {
|
||||
error!("Failed to initialize I2C: {:?}", e);
|
||||
panic!(); //TODO panic!
|
||||
}
|
||||
};
|
||||
i2c
|
||||
}
|
||||
|
||||
fn setup_spi(
|
||||
spi2: SPI2<'static>,
|
||||
sck: GPIO19<'static>,
|
||||
miso: GPIO20<'static>,
|
||||
mosi: GPIO18<'static>,
|
||||
) -> Spi<'static, Blocking> {
|
||||
let spi = match Spi::new(spi2, Spi_config::default()) {
|
||||
Ok(spi) => spi.with_sck(sck).with_miso(miso).with_mosi(mosi),
|
||||
Err(e) => panic!("Failed to initialize SPI: {:?}", e),
|
||||
};
|
||||
spi
|
||||
}
|
||||
|
||||
pub fn setup_buzzer(buzzer_gpio: GPIO21<'static>) -> Output<'static> {
|
||||
let config = esp_hal::gpio::OutputConfig::default()
|
||||
.with_drive_strength(esp_hal::gpio::DriveStrength::_40mA);
|
||||
let buzzer = Output::new(buzzer_gpio, esp_hal::gpio::Level::Low, config);
|
||||
|
||||
buzzer
|
||||
}
|
||||
|
||||
fn setup_led(
|
||||
rmt: RMT<'static>,
|
||||
led_gpio: GPIO1<'static>,
|
||||
) -> SmartLedsAdapterAsync<ConstChannelAccess<esp_hal::rmt::Tx, 0>, LED_BUFFER_SIZE> {
|
||||
debug!("setup led");
|
||||
let rmt: Rmt<'_, esp_hal::Async> = {
|
||||
let frequency: Rate = Rate::from_mhz(80);
|
||||
Rmt::new(rmt, frequency)
|
||||
}
|
||||
.expect("Failed to initialize RMT")
|
||||
.into_async();
|
||||
|
||||
let rmt_channel = rmt.channel0;
|
||||
let rmt_buffer = [0_u32; buffer_size_async(NUM_LEDS)];
|
||||
|
||||
let led: SmartLedsAdapterAsync<_, LED_BUFFER_SIZE> =
|
||||
SmartLedsAdapterAsync::new(rmt_channel, led_gpio, rmt_buffer);
|
||||
|
||||
led
|
||||
}
|
||||
72
src/init/network.rs
Normal file
72
src/init/network.rs
Normal file
@@ -0,0 +1,72 @@
|
||||
use core::{net::Ipv4Addr, str::FromStr};
|
||||
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_net::{Ipv4Cidr, Runner, Stack, StackResources, StaticConfigV4};
|
||||
use embassy_time::{Duration, Timer};
|
||||
use esp_wifi::wifi::WifiDevice;
|
||||
use static_cell::make_static;
|
||||
|
||||
|
||||
pub fn setup_network<'a>(seed: u64, wifi: WifiDevice<'static>, spawner: &mut Spawner) -> Stack<'a> {
|
||||
let gw_ip_addr_str = "192.168.2.1";
|
||||
let gw_ip_addr = Ipv4Addr::from_str(gw_ip_addr_str).expect("failed to parse gateway ip");
|
||||
let config = embassy_net::Config::ipv4_static(StaticConfigV4 {
|
||||
address: Ipv4Cidr::new(gw_ip_addr, 24),
|
||||
gateway: Some(gw_ip_addr),
|
||||
dns_servers: Default::default(),
|
||||
});
|
||||
|
||||
let (stack, runner) =
|
||||
embassy_net::new(wifi, config, make_static!(StackResources::<3>::new()), seed);
|
||||
|
||||
spawner.must_spawn(net_task(runner));
|
||||
spawner.must_spawn(run_dhcp(stack, gw_ip_addr_str));
|
||||
|
||||
stack
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn run_dhcp(stack: Stack<'static>, gw_ip_addr: &'static str) {
|
||||
use core::net::{Ipv4Addr, SocketAddrV4};
|
||||
|
||||
use edge_dhcp::{
|
||||
io::{self, DEFAULT_SERVER_PORT},
|
||||
server::{Server, ServerOptions},
|
||||
};
|
||||
use edge_nal::UdpBind;
|
||||
use edge_nal_embassy::{Udp, UdpBuffers};
|
||||
|
||||
let ip = Ipv4Addr::from_str(gw_ip_addr).expect("dhcp task failed to parse gw ip");
|
||||
|
||||
let mut buf = [0u8; 1500];
|
||||
|
||||
let mut gw_buf = [Ipv4Addr::UNSPECIFIED];
|
||||
|
||||
let buffers = UdpBuffers::<3, 1024, 1024, 10>::new();
|
||||
let unbound_socket = Udp::new(stack, &buffers);
|
||||
let mut bound_socket = unbound_socket
|
||||
.bind(core::net::SocketAddr::V4(SocketAddrV4::new(
|
||||
Ipv4Addr::UNSPECIFIED,
|
||||
DEFAULT_SERVER_PORT,
|
||||
)))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
loop {
|
||||
_ = io::server::run(
|
||||
&mut Server::<_, 64>::new_with_et(ip),
|
||||
&ServerOptions::new(ip, Some(&mut gw_buf)),
|
||||
&mut bound_socket,
|
||||
&mut buf,
|
||||
)
|
||||
.await
|
||||
.inspect_err(|e| log::warn!("DHCP server error: {e:?}"));
|
||||
Timer::after(Duration::from_millis(500)).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn net_task(mut runner: Runner<'static, WifiDevice<'static>>) {
|
||||
runner.run().await;
|
||||
}
|
||||
|
||||
92
src/init/sd_card.rs
Normal file
92
src/init/sd_card.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
use alloc::vec::Vec;
|
||||
use embassy_time::Delay;
|
||||
use embedded_hal_bus::spi::ExclusiveDevice;
|
||||
use embedded_sdmmc::{SdCard, TimeSource, Timestamp, VolumeIdx, VolumeManager};
|
||||
use esp_hal::{Blocking, gpio::Output, spi::master::Spi};
|
||||
|
||||
use crate::store::{AttendanceDay, Date, persistence::Persistence};
|
||||
|
||||
pub struct DummyTimesource;
|
||||
|
||||
impl TimeSource for DummyTimesource {
|
||||
fn get_timestamp(&self) -> Timestamp {
|
||||
Timestamp {
|
||||
year_since_1970: 0,
|
||||
zero_indexed_month: 0,
|
||||
zero_indexed_day: 0,
|
||||
hours: 0,
|
||||
minutes: 0,
|
||||
seconds: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type VolMgr = VolumeManager<
|
||||
SdCard<ExclusiveDevice<Spi<'static, Blocking>, Output<'static>, Delay>, Delay>,
|
||||
DummyTimesource,
|
||||
>;
|
||||
|
||||
pub fn setup_sdcard(spi_bus: Spi<'static, Blocking>, cs_pin: Output<'static>) -> SDCardPersistence {
|
||||
let spi_device = ExclusiveDevice::new(spi_bus, cs_pin, Delay).unwrap();
|
||||
let sd_card = SdCard::new(spi_device, Delay);
|
||||
let vol_mgr = VolumeManager::new(sd_card, DummyTimesource);
|
||||
|
||||
SDCardPersistence { vol_mgr }
|
||||
}
|
||||
|
||||
pub struct SDCardPersistence {
|
||||
vol_mgr: VolMgr,
|
||||
}
|
||||
|
||||
impl Persistence for SDCardPersistence {
|
||||
async fn load_day(&mut self, day: crate::store::Date) -> Option<AttendanceDay> {
|
||||
let mut vol_0 = self.vol_mgr.open_volume(VolumeIdx(0)).unwrap();
|
||||
let mut root_dir = vol_0.open_root_dir().unwrap();
|
||||
let mut file = root_dir
|
||||
.open_file_in_dir("day.jsn", embedded_sdmmc::Mode::ReadOnly)
|
||||
.unwrap();
|
||||
|
||||
let mut read_buffer: [u8; 1024] = [0; 1024];
|
||||
let read = file.read(&mut read_buffer).unwrap();
|
||||
file.close().unwrap();
|
||||
|
||||
let day: AttendanceDay = serde_json::from_slice(&read_buffer[..read]).unwrap();
|
||||
|
||||
Some(day)
|
||||
}
|
||||
|
||||
async fn save_day(&mut self, day: Date, data: &AttendanceDay) {
|
||||
let mut vol_0 = self.vol_mgr.open_volume(VolumeIdx(0)).unwrap();
|
||||
let mut root_dir = vol_0.open_root_dir().unwrap();
|
||||
|
||||
let mut file = root_dir
|
||||
.open_file_in_dir("day.jsn", embedded_sdmmc::Mode::ReadWriteCreateOrTruncate)
|
||||
.unwrap();
|
||||
file.write(&serde_json::to_vec(data).unwrap()).unwrap();
|
||||
file.flush();
|
||||
file.close();
|
||||
}
|
||||
|
||||
async fn load_mapping(&mut self) -> Option<crate::store::IDMapping> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn save_mapping(&mut self, data: &crate::store::IDMapping) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn list_days(&mut self) -> Vec<Date> {
|
||||
let mut vol_0 = self.vol_mgr.open_volume(VolumeIdx(0)).unwrap();
|
||||
let mut root_dir = vol_0.open_root_dir().unwrap();
|
||||
let mut days_dir = root_dir.open_dir("days").unwrap();
|
||||
|
||||
let mut days = Vec::new();
|
||||
days_dir
|
||||
.iterate_dir(|e| {
|
||||
days.push(1);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
days
|
||||
}
|
||||
}
|
||||
56
src/init/wifi.rs
Normal file
56
src/init/wifi.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_time::{Duration, Timer};
|
||||
use esp_hal::gpio::{Output, OutputConfig};
|
||||
use esp_hal::peripherals::{GPIO3, GPIO14, WIFI};
|
||||
use esp_wifi::wifi::{AccessPointConfiguration, Configuration, WifiController, WifiEvent, WifiState};
|
||||
use esp_wifi::{EspWifiRngSource, EspWifiTimerSource, wifi::Interfaces};
|
||||
use static_cell::make_static;
|
||||
|
||||
pub async fn set_antenna_mode(gpio3: GPIO3<'static>, gpio14: GPIO14<'static>) {
|
||||
let mut rf_switch = Output::new(gpio3, esp_hal::gpio::Level::Low, OutputConfig::default());
|
||||
|
||||
rf_switch.set_low();
|
||||
|
||||
Timer::after_millis(150).await;
|
||||
|
||||
let mut antenna_mode = Output::new(gpio14, esp_hal::gpio::Level::Low, OutputConfig::default());
|
||||
|
||||
antenna_mode.set_low();
|
||||
}
|
||||
|
||||
pub fn setup_wifi<'d: 'static>(
|
||||
timer: impl EspWifiTimerSource + 'd,
|
||||
rng: impl EspWifiRngSource + 'd,
|
||||
wifi: WIFI<'static>,
|
||||
spawner: &mut Spawner,
|
||||
) -> Interfaces<'d> {
|
||||
let esp_wifi_ctrl = make_static!(esp_wifi::init(timer, rng).unwrap());
|
||||
|
||||
let (controller, interfaces) = esp_wifi::wifi::new(esp_wifi_ctrl, wifi).unwrap();
|
||||
|
||||
spawner.must_spawn(connection(controller));
|
||||
|
||||
interfaces
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn connection(mut controller: WifiController<'static>) {
|
||||
loop {
|
||||
match esp_wifi::wifi::wifi_state() {
|
||||
WifiState::ApStarted => {
|
||||
// wait until we're no longer connected
|
||||
controller.wait_for_event(WifiEvent::ApStop).await;
|
||||
Timer::after(Duration::from_millis(5000)).await
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if !matches!(controller.is_started(), Ok(true)) {
|
||||
let client_config = Configuration::AccessPoint(AccessPointConfiguration {
|
||||
ssid: "esp-wifi".try_into().unwrap(),
|
||||
..Default::default()
|
||||
});
|
||||
controller.set_configuration(&client_config).unwrap();
|
||||
controller.start_async().await.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
1
src/lib.rs
Normal file
1
src/lib.rs
Normal file
@@ -0,0 +1 @@
|
||||
#![no_std]
|
||||
@@ -1,25 +0,0 @@
|
||||
use std::env;
|
||||
|
||||
use log::LevelFilter;
|
||||
use simplelog::{ConfigBuilder, SimpleLogger};
|
||||
|
||||
pub fn setup_logger() {
|
||||
let log_level = env::var("LOG_LEVEL")
|
||||
.ok()
|
||||
.and_then(|level| level.parse::<LevelFilter>().ok())
|
||||
.unwrap_or({
|
||||
if cfg!(debug_assertions) {
|
||||
LevelFilter::Debug
|
||||
} else {
|
||||
LevelFilter::Warn
|
||||
}
|
||||
});
|
||||
|
||||
let config = ConfigBuilder::new()
|
||||
.set_target_level(LevelFilter::Off)
|
||||
.set_location_level(LevelFilter::Off)
|
||||
.set_thread_level(LevelFilter::Off)
|
||||
.build();
|
||||
|
||||
let _ = SimpleLogger::init(log_level, config);
|
||||
}
|
||||
265
src/main.rs
265
src/main.rs
@@ -1,192 +1,121 @@
|
||||
#![allow(dead_code)]
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
#![feature(impl_trait_in_assoc_type)]
|
||||
|
||||
use anyhow::Result;
|
||||
use feedback::{Feedback, FeedbackImpl};
|
||||
use log::{error, info, warn};
|
||||
use std::{
|
||||
env::{self, args},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use tally_id::TallyID;
|
||||
use tokio::{
|
||||
fs,
|
||||
signal::unix::{SignalKind, signal},
|
||||
sync::{
|
||||
Mutex,
|
||||
broadcast::{self, Receiver, Sender},
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_net::Stack;
|
||||
use embassy_sync::{
|
||||
blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex},
|
||||
channel::Channel,
|
||||
pubsub::{
|
||||
PubSubChannel, Publisher,
|
||||
WaitResult::{Lagged, Message},
|
||||
},
|
||||
try_join,
|
||||
signal::Signal,
|
||||
};
|
||||
use webserver::start_webserver;
|
||||
use embassy_time::{Duration, Timer};
|
||||
use esp_hal::gpio::Input;
|
||||
use esp_hal::{gpio::InputConfig, peripherals};
|
||||
use log::{debug, info};
|
||||
use static_cell::make_static;
|
||||
|
||||
use crate::{hardware::{create_hotspot, Hotspot}, pm3::run_pm3, store::IDStore, webserver::{spawn_idle_watcher, ActivityNotifier}};
|
||||
use crate::store::TallyID;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
mod drivers;
|
||||
mod feedback;
|
||||
mod hardware;
|
||||
mod pm3;
|
||||
mod logger;
|
||||
mod tally_id;
|
||||
mod webserver;
|
||||
mod init;
|
||||
mod store;
|
||||
//mod webserver;
|
||||
|
||||
const STORE_PATH: &str = "./data.json";
|
||||
static FEEDBACK_STATE: Signal<CriticalSectionRawMutex, feedback::FeedbackState> = Signal::new();
|
||||
|
||||
async fn run_webserver<H>(
|
||||
store: Arc<Mutex<IDStore>>,
|
||||
id_channel: Sender<String>,
|
||||
hotspot: Arc<Mutex<H>>,
|
||||
user_feedback: Arc<Mutex<FeedbackImpl>>,
|
||||
) -> Result<()>
|
||||
where
|
||||
H: Hotspot + Send + Sync + 'static,
|
||||
{
|
||||
let activity_channel = spawn_idle_watcher(Duration::from_secs(60 * 30), move || {
|
||||
info!("No activity on webserver. Disabling hotspot");
|
||||
let cloned_hotspot = hotspot.clone();
|
||||
let cloned_user_feedback = user_feedback.clone();
|
||||
tokio::spawn(async move {
|
||||
let _ = cloned_hotspot.lock().await.disable_hotspot().await;
|
||||
cloned_user_feedback
|
||||
.lock()
|
||||
.await
|
||||
.set_device_status(feedback::DeviceStatus::Ready);
|
||||
});
|
||||
});
|
||||
type TallyChannel = PubSubChannel<NoopRawMutex, TallyID, 8, 2, 1>;
|
||||
type TallyPublisher = Publisher<'static, NoopRawMutex, TallyID, 8, 2, 1>;
|
||||
|
||||
let notifier = ActivityNotifier {
|
||||
sender: activity_channel,
|
||||
};
|
||||
#[esp_hal_embassy::main]
|
||||
async fn main(mut spawner: Spawner) {
|
||||
let (uart_device, stack, _i2c, _led, buzzer_gpio, sd_det_gpio) =
|
||||
init::hardware::hardware_init(&mut spawner).await;
|
||||
|
||||
start_webserver(store, id_channel, notifier).await?;
|
||||
wait_for_stack_up(stack).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
info!("Starting up...");
|
||||
|
||||
async fn load_or_create_store() -> Result<IDStore> {
|
||||
if fs::try_exists(STORE_PATH).await? {
|
||||
info!("Loading data from file");
|
||||
IDStore::new_from_json(STORE_PATH).await
|
||||
} else {
|
||||
info!("No data file found. Creating empty one.");
|
||||
Ok(IDStore::new())
|
||||
let chan: &'static mut TallyChannel = make_static!(PubSubChannel::new());
|
||||
|
||||
//start_webserver(&mut spawner, stack);
|
||||
|
||||
let publisher = chan.publisher().unwrap();
|
||||
|
||||
let mut rtc = drivers::rtc::RTCClock::new(_i2c).await;
|
||||
|
||||
/****************************** Spawning tasks ***********************************/
|
||||
debug!("spawing NFC reader task...");
|
||||
spawner.must_spawn(drivers::nfc_reader::rfid_reader_task(
|
||||
uart_device,
|
||||
publisher,
|
||||
));
|
||||
|
||||
debug!("spawing feedback task..");
|
||||
spawner.must_spawn(feedback::feedback_task(_led, buzzer_gpio));
|
||||
|
||||
debug!("spawn sd detect task");
|
||||
spawner.must_spawn(sd_detect_task(sd_det_gpio));
|
||||
/******************************************************************************/
|
||||
|
||||
let mut sub = chan.subscriber().unwrap();
|
||||
|
||||
debug!("everything spawned");
|
||||
FEEDBACK_STATE.signal(feedback::FeedbackState::Startup);
|
||||
|
||||
loop {
|
||||
rtc.get_time().await;
|
||||
info!("Current RTC time: {}", rtc.get_time().await);
|
||||
Timer::after(Duration::from_millis(1000)).await;
|
||||
|
||||
// let wait_result = sub.next_message().await;
|
||||
// match wait_result {
|
||||
// Lagged(_) => debug!("Lagged"),
|
||||
// Message(msg) => debug!("Got message: {msg:?}"),
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
fn get_hotspot_enable_ids() -> Vec<TallyID> {
|
||||
let hotspot_ids: Vec<TallyID> = env::var("HOTSPOT_IDS")
|
||||
.map(|ids| ids.split(";").map(|id| TallyID(id.to_owned())).collect())
|
||||
.unwrap_or_default();
|
||||
#[embassy_executor::task]
|
||||
async fn sd_detect_task(sd_det_gpio: peripherals::GPIO0<'static>) {
|
||||
let mut sd_det = Input::new(sd_det_gpio, InputConfig::default());
|
||||
sd_det.wait_for(esp_hal::gpio::Event::AnyEdge);
|
||||
|
||||
if hotspot_ids.is_empty() {
|
||||
warn!(
|
||||
"HOTSPOT_IDS is not set or empty. You will not be able to activate the hotspot via a tally!"
|
||||
);
|
||||
loop {
|
||||
sd_det.wait_for_any_edge().await;
|
||||
{
|
||||
if sd_det.is_high() {
|
||||
FEEDBACK_STATE.signal(feedback::FeedbackState::Ack);
|
||||
debug!("card insert");
|
||||
}
|
||||
//card is not insert on low
|
||||
else {
|
||||
FEEDBACK_STATE.signal(feedback::FeedbackState::Nack);
|
||||
debug!("card removed");
|
||||
}
|
||||
}
|
||||
//debounce time
|
||||
Timer::after(Duration::from_millis(100)).await;
|
||||
}
|
||||
|
||||
hotspot_ids
|
||||
}
|
||||
|
||||
async fn handle_ids_loop(
|
||||
mut id_channel: Receiver<String>,
|
||||
hotspot_enable_ids: Vec<TallyID>,
|
||||
id_store: Arc<Mutex<IDStore>>,
|
||||
hotspot: Arc<Mutex<impl Hotspot>>,
|
||||
user_feedback: Arc<Mutex<FeedbackImpl>>,
|
||||
) -> Result<()> {
|
||||
while let Ok(tally_id_string) = id_channel.recv().await {
|
||||
let tally_id = TallyID(tally_id_string);
|
||||
|
||||
if hotspot_enable_ids.contains(&tally_id) {
|
||||
info!("Enableing hotspot");
|
||||
let hotspot_enable_result = hotspot.lock().await.enable_hotspot().await;
|
||||
|
||||
match hotspot_enable_result {
|
||||
Ok(_) => {
|
||||
user_feedback
|
||||
.lock()
|
||||
.await
|
||||
.set_device_status(feedback::DeviceStatus::HotspotEnabled);
|
||||
async fn wait_for_stack_up(stack: Stack<'static>) {
|
||||
loop {
|
||||
if stack.is_link_up() {
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Hotspot: {e}");
|
||||
Timer::after(Duration::from_millis(500)).await;
|
||||
if stack.is_config_up() {
|
||||
break;
|
||||
}
|
||||
Timer::after(Duration::from_millis(500)).await;
|
||||
}
|
||||
|
||||
// TODO: Should the ID be added anyway or ignored ?
|
||||
}
|
||||
|
||||
if id_store.lock().await.add_id(tally_id) {
|
||||
info!("Added new id to current day");
|
||||
|
||||
user_feedback.lock().await.success().await;
|
||||
|
||||
if let Err(e) = id_store.lock().await.export_json(STORE_PATH).await {
|
||||
error!("Failed to save id store to file: {e}");
|
||||
user_feedback.lock().await.failure().await;
|
||||
// TODO: How to handle a failure to save ?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn enter_error_state(feedback: Arc<Mutex<FeedbackImpl>>, hotspot: Arc<Mutex<impl Hotspot>>) {
|
||||
let _ = feedback.lock().await.activate_error_state().await;
|
||||
let _ = hotspot.lock().await.enable_hotspot().await;
|
||||
|
||||
let mut sigterm = signal(SignalKind::terminate()).unwrap();
|
||||
sigterm.recv().await;
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
logger::setup_logger();
|
||||
|
||||
info!("Starting application");
|
||||
|
||||
let user_feedback = Arc::new(Mutex::new(Feedback::new()?));
|
||||
let hotspot = Arc::new(Mutex::new(create_hotspot()?));
|
||||
|
||||
let error_flag_set = args().any(|e| e == "--error" || e == "-e");
|
||||
if error_flag_set {
|
||||
error!("Error flag set. Entering error state");
|
||||
enter_error_state(user_feedback.clone(), hotspot).await;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let store: Arc<Mutex<IDStore>> = Arc::new(Mutex::new(load_or_create_store().await?));
|
||||
let hotspot_enable_ids = get_hotspot_enable_ids();
|
||||
|
||||
let (tx, rx) = broadcast::channel::<String>(32);
|
||||
let sse_tx = tx.clone();
|
||||
|
||||
let pm3_handle = run_pm3(tx);
|
||||
|
||||
user_feedback.lock().await.startup().await;
|
||||
|
||||
let loop_handle = handle_ids_loop(
|
||||
rx,
|
||||
hotspot_enable_ids,
|
||||
store.clone(),
|
||||
hotspot.clone(),
|
||||
user_feedback.clone(),
|
||||
);
|
||||
|
||||
let webserver_handle = run_webserver(
|
||||
store.clone(),
|
||||
sse_tx,
|
||||
hotspot.clone(),
|
||||
user_feedback.clone(),
|
||||
);
|
||||
|
||||
let run_result = try_join!(pm3_handle, loop_handle, webserver_handle);
|
||||
|
||||
if let Err(e) = run_result {
|
||||
error!("Failed to run application: {e}");
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
mod runner;
|
||||
mod parser;
|
||||
|
||||
pub use runner::run_pm3;
|
||||
@@ -1,10 +0,0 @@
|
||||
use regex::Regex;
|
||||
|
||||
/// Parses the output of PM3 finds the read IDs
|
||||
/// Example input: `[+] UID.... 3112B710`
|
||||
pub fn parse_line(line: &str) -> Option<String> {
|
||||
let regex = Regex::new(r"(?m)^\[\+\] UID.... (.*)$").unwrap();
|
||||
let result = regex.captures(line);
|
||||
|
||||
result.map(|c| c.get(1).unwrap().as_str().to_owned())
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
use anyhow::{Result, anyhow};
|
||||
use log::{debug, info, trace, warn};
|
||||
use std::env;
|
||||
use std::process::Stdio;
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||
use tokio::process::Command;
|
||||
use tokio::select;
|
||||
use tokio::signal::unix::{SignalKind, signal};
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
/// Runs the pm3 binary and monitors it's output
|
||||
/// The pm3 binary is ether set in the env var PM3_BIN or found in the path
|
||||
/// The ouput is parsed and send via the `tx` channel
|
||||
pub async fn run_pm3(tx: broadcast::Sender<String>) -> Result<()> {
|
||||
kill_orphans().await;
|
||||
|
||||
let pm3_path = match env::var("PM3_BIN") {
|
||||
Ok(path) => path,
|
||||
Err(_) => {
|
||||
info!("PM3_BIN not set. Using default value");
|
||||
"pm3".to_owned()
|
||||
}
|
||||
};
|
||||
|
||||
let mut cmd = Command::new("stdbuf")
|
||||
.arg("-oL")
|
||||
.arg(pm3_path)
|
||||
.arg("-c")
|
||||
.arg("lf hitag reader -@")
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::null())
|
||||
.stdin(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
let stdout = cmd.stdout.take().ok_or(anyhow!("Failed to get stdout"))?;
|
||||
let mut stdin = cmd.stdin.take().ok_or(anyhow!("Failed to get stdin"))?;
|
||||
|
||||
let mut reader = BufReader::new(stdout).lines();
|
||||
|
||||
let mut sigterm = signal(SignalKind::terminate())?;
|
||||
|
||||
let child_handle = tokio::spawn(async move {
|
||||
let mut last_id: String = "".to_owned();
|
||||
while let Some(line) = reader.next_line().await.unwrap_or(None) {
|
||||
trace!("PM3: {line}");
|
||||
if let Some(uid) = super::parser::parse_line(&line) {
|
||||
if last_id == uid {
|
||||
let _ = tx.send(uid.clone());
|
||||
}
|
||||
last_id = uid;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
select! {
|
||||
_ = child_handle => {}
|
||||
_ = sigterm.recv() => {
|
||||
debug!("Graceful shutdown of PM3");
|
||||
let _ = stdin.write_all(b"\n").await;
|
||||
let _ = stdin.flush().await;
|
||||
}
|
||||
};
|
||||
|
||||
let status = cmd.wait().await?;
|
||||
|
||||
// We use the exit code here because status.success() is false if the child was terminated by a
|
||||
// signal
|
||||
let code = status.code().unwrap_or(0);
|
||||
|
||||
if code == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!("PM3 exited with a non-zero exit code: {code}"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Kills any open pm3 instances
|
||||
/// Also funny name. hehehe.
|
||||
async fn kill_orphans() {
|
||||
let kill_result = Command::new("pkill")
|
||||
.arg("-KILL")
|
||||
.arg("-x")
|
||||
.arg("proxmark3")
|
||||
.output()
|
||||
.await;
|
||||
|
||||
match kill_result {
|
||||
Ok(_) => {
|
||||
debug!("Successfully killed orphaned pm3 instances");
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Failed to kill pm3 orphans: {e} Continuing anyway");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,23 @@
|
||||
use crate::tally_id::TallyID;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use super::TallyID;
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::string::String;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct Name {
|
||||
pub first: String,
|
||||
pub last: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct IDMapping {
|
||||
id_map: HashMap<TallyID, Name>,
|
||||
id_map: BTreeMap<TallyID, Name>,
|
||||
}
|
||||
|
||||
impl IDMapping {
|
||||
pub fn new() -> Self {
|
||||
IDMapping {
|
||||
id_map: HashMap::new(),
|
||||
id_map: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,49 +29,3 @@ impl IDMapping {
|
||||
self.id_map.insert(id, name);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn basic() {
|
||||
let mut map = IDMapping::new();
|
||||
let id1 = TallyID("A2Fb44".to_owned());
|
||||
let name1 = Name {
|
||||
first: "Max".to_owned(),
|
||||
last: "Mustermann".to_owned(),
|
||||
};
|
||||
|
||||
map.add_mapping(id1.clone(), name1.clone());
|
||||
|
||||
let res = map.map(&id1);
|
||||
|
||||
assert_eq!(res, Some(&name1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple() {
|
||||
let mut map = IDMapping::new();
|
||||
let id1 = TallyID("A2Fb44".to_owned());
|
||||
let name1 = Name {
|
||||
first: "Max".to_owned(),
|
||||
last: "Mustermann".to_owned(),
|
||||
};
|
||||
|
||||
let id2 = TallyID("7D3DF5B5".to_owned());
|
||||
let name2 = Name {
|
||||
first: "First".to_owned(),
|
||||
last: "Last".to_owned(),
|
||||
};
|
||||
|
||||
map.add_mapping(id1.clone(), name1.clone());
|
||||
map.add_mapping(id2.clone(), name2.clone());
|
||||
|
||||
let res = map.map(&id1);
|
||||
assert_eq!(res, Some(&name1));
|
||||
|
||||
let res = map.map(&id2);
|
||||
assert_eq!(res, Some(&name2));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,123 +1,22 @@
|
||||
use anyhow::{Result, anyhow};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use tokio::fs;
|
||||
use crate::store::persistence::Persistence;
|
||||
|
||||
use crate::{store::IDMapping, tally_id::TallyID};
|
||||
use super::Date;
|
||||
use super::IDMapping;
|
||||
use super::TallyID;
|
||||
use alloc::vec::Vec;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
/// Represents a single day that IDs can attend
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||
pub struct AttendanceDay {
|
||||
date: String,
|
||||
date: Date,
|
||||
ids: Vec<TallyID>,
|
||||
}
|
||||
|
||||
/// Stores all the days
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct IDStore {
|
||||
days: HashMap<String, AttendanceDay>,
|
||||
pub mapping: IDMapping,
|
||||
}
|
||||
|
||||
impl IDStore {
|
||||
pub fn new() -> Self {
|
||||
IDStore {
|
||||
days: HashMap::new(),
|
||||
mapping: IDMapping::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creats a new `IDStore` from a json file
|
||||
pub async fn new_from_json(filepath: &str) -> Result<Self> {
|
||||
let read_string = fs::read_to_string(filepath).await?;
|
||||
Ok(serde_json::from_str(&read_string)?)
|
||||
}
|
||||
|
||||
/// Add a new id for the current day
|
||||
/// Returns false if ID is already present at the current day.
|
||||
pub fn add_id(&mut self, id: TallyID) -> bool {
|
||||
self.get_current_day().add_id(id)
|
||||
}
|
||||
|
||||
/// Get the `AttendanceDay` of the current day
|
||||
/// Creates a new if not exists
|
||||
pub fn get_current_day(&mut self) -> &mut AttendanceDay {
|
||||
let current_day = get_day_str();
|
||||
|
||||
if self.days.contains_key(¤t_day) {
|
||||
return self.days.get_mut(¤t_day).unwrap();
|
||||
}
|
||||
|
||||
self.days.insert(
|
||||
current_day.clone(),
|
||||
AttendanceDay::new(¤t_day.clone()),
|
||||
);
|
||||
|
||||
self.days.get_mut(¤t_day.clone()).unwrap()
|
||||
}
|
||||
|
||||
/// Writes the store to a json file
|
||||
pub async fn export_json(&self, filepath: &str) -> Result<()> {
|
||||
fs::write(filepath, serde_json::to_string(&self)?).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Export the store to a csv file.
|
||||
/// With days in the rows and IDs in the collum.
|
||||
pub fn export_csv(&self) -> Result<String> {
|
||||
let mut csv = String::new();
|
||||
let seperator = ";";
|
||||
let mut user_ids: HashSet<TallyID> = HashSet::new();
|
||||
|
||||
for day in self.days.values() {
|
||||
for id in day.ids.iter() {
|
||||
user_ids.insert(id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let mut user_ids: Vec<TallyID> = user_ids.into_iter().collect();
|
||||
user_ids.sort();
|
||||
|
||||
let mut days: Vec<String> = self.days.keys().cloned().collect();
|
||||
days.sort();
|
||||
|
||||
let header = days.join(seperator);
|
||||
csv.push_str(&format!(
|
||||
"ID{seperator}Nachname{seperator}Vorname{seperator}{header}\n"
|
||||
));
|
||||
|
||||
for user_id in user_ids.iter() {
|
||||
let id = &user_id.0.to_string();
|
||||
let name = self.mapping.map(user_id);
|
||||
|
||||
let firstname = name.map(|e| e.first.clone()).unwrap_or("".to_owned());
|
||||
let lastname = name.map(|e| e.last.clone()).unwrap_or("".to_owned());
|
||||
|
||||
csv.push_str(&format!("{id}{seperator}{lastname}{seperator}{firstname}"));
|
||||
for day in days.iter() {
|
||||
let was_there: bool = self
|
||||
.days
|
||||
.get(day)
|
||||
.ok_or(anyhow!("Failed to access day"))?
|
||||
.ids
|
||||
.contains(user_id);
|
||||
|
||||
if was_there {
|
||||
csv.push_str(&format!("{seperator}x"));
|
||||
} else {
|
||||
csv.push_str(seperator);
|
||||
}
|
||||
}
|
||||
csv.push('\n');
|
||||
}
|
||||
Ok(csv)
|
||||
}
|
||||
}
|
||||
|
||||
impl AttendanceDay {
|
||||
fn new(day: &str) -> Self {
|
||||
pub fn new(date: Date) -> Self {
|
||||
Self {
|
||||
date: day.to_owned(),
|
||||
date,
|
||||
ids: Vec::new(),
|
||||
}
|
||||
}
|
||||
@@ -133,7 +32,64 @@ impl AttendanceDay {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_day_str() -> String {
|
||||
let now = chrono::offset::Local::now();
|
||||
now.format("%Y-%m-%d").to_string()
|
||||
#[derive(Clone)]
|
||||
pub struct IDStore<T: Persistence> {
|
||||
pub current_day: AttendanceDay,
|
||||
pub mapping: IDMapping,
|
||||
persistence_layer: T,
|
||||
}
|
||||
|
||||
impl<T: Persistence> IDStore<T> {
|
||||
pub async fn new_from_storage(mut persistence_layer: T) -> Self {
|
||||
let mapping = match persistence_layer.load_mapping().await {
|
||||
Some(map) => map,
|
||||
None => IDMapping::new(),
|
||||
};
|
||||
|
||||
let current_date: Date = 1;
|
||||
|
||||
let day = persistence_layer
|
||||
.load_day(current_date)
|
||||
.await
|
||||
.unwrap_or(AttendanceDay::new(current_date));
|
||||
|
||||
Self {
|
||||
current_day: day,
|
||||
mapping,
|
||||
persistence_layer,
|
||||
}
|
||||
}
|
||||
|
||||
async fn persist_day(&mut self) {
|
||||
self.persistence_layer
|
||||
.save_day(self.current_day.date, &self.current_day)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn persist_mapping(&mut self) {
|
||||
self.persistence_layer.save_mapping(&self.mapping).await
|
||||
}
|
||||
|
||||
/// Add a new id for the current day
|
||||
/// Returns false if ID is already present at the current day.
|
||||
pub async fn add_id(&mut self, id: TallyID) -> bool {
|
||||
let current_date: Date = 1;
|
||||
|
||||
if self.current_day.date == current_date {
|
||||
let changed = self.current_day.add_id(id);
|
||||
if changed {
|
||||
self.persist_day().await;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
let new_day = AttendanceDay::new(current_date);
|
||||
self.current_day = new_day;
|
||||
|
||||
let changed = self.current_day.add_id(id);
|
||||
if changed {
|
||||
self.persist_day().await;
|
||||
}
|
||||
changed
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
mod id_store;
|
||||
mod id_mapping;
|
||||
pub mod persistence;
|
||||
mod id_store;
|
||||
|
||||
pub use id_store::IDStore;
|
||||
pub use id_mapping::{IDMapping,Name};
|
||||
pub use id_mapping::{IDMapping, Name};
|
||||
pub use id_store::{IDStore,AttendanceDay};
|
||||
|
||||
pub type TallyID = [u8; 8];
|
||||
pub type Date = u64;
|
||||
|
||||
12
src/store/persistence.rs
Normal file
12
src/store/persistence.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::store::{Date, IDMapping, id_store::AttendanceDay};
|
||||
|
||||
pub trait Persistence {
|
||||
async fn load_day(&mut self, day: Date) -> Option<AttendanceDay>;
|
||||
async fn save_day(&mut self, day: Date, data: &AttendanceDay);
|
||||
async fn list_days(&mut self) -> Vec<Date>;
|
||||
|
||||
async fn load_mapping(&mut self) -> Option<IDMapping>;
|
||||
async fn save_mapping(&mut self, data: &IDMapping);
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
fmt::Display,
|
||||
hash::{Hash, Hasher},
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Represents the ID that is stored on the Tally
|
||||
/// Is case-insensitive.
|
||||
/// While any string can be a ID, most IDs are going to be a hex string.
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
pub struct TallyID(pub String);
|
||||
|
||||
impl PartialEq for TallyID {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0.eq_ignore_ascii_case(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for TallyID {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.0.to_uppercase().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for TallyID {}
|
||||
|
||||
impl Ord for TallyID {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.0.to_uppercase().cmp(&other.0.to_uppercase())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for TallyID {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for TallyID {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0.to_uppercase())
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use log::error;
|
||||
use rocket::{
|
||||
Data, Request,
|
||||
fairing::{Fairing, Info, Kind},
|
||||
};
|
||||
use tokio::{sync::mpsc, time::timeout};
|
||||
|
||||
pub struct ActivityNotifier {
|
||||
pub sender: mpsc::Sender<()>,
|
||||
}
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl Fairing for ActivityNotifier {
|
||||
fn info(&self) -> Info {
|
||||
Info {
|
||||
name: "Keeps track of time since the last request",
|
||||
kind: Kind::Request | Kind::Response,
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_request(&self, _: &mut Request<'_>, _: &mut Data<'_>) {
|
||||
error!("on_request");
|
||||
let _ = self.sender.try_send(());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn_idle_watcher<F>(idle_duration: Duration, mut on_idle: F) -> mpsc::Sender<()>
|
||||
where
|
||||
F: FnMut() + Send + 'static,
|
||||
{
|
||||
let (tx, mut rx) = mpsc::channel::<()>(100);
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
let idle = timeout(idle_duration, rx.recv()).await;
|
||||
if idle.is_err() {
|
||||
// No activity received in the duration
|
||||
on_idle();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tx
|
||||
}
|
||||
77
src/webserver/assets.rs
Normal file
77
src/webserver/assets.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
use dir_embed::Embed;
|
||||
use picoserve::response::Content;
|
||||
|
||||
#[derive(Embed)]
|
||||
#[dir = "../../web/dist"]
|
||||
#[mode = "mime"]
|
||||
pub struct Assets;
|
||||
|
||||
impl<State, CurrentPathParameters>
|
||||
picoserve::routing::PathRouterService<State, CurrentPathParameters> for Assets
|
||||
{
|
||||
async fn call_request_handler_service<
|
||||
R: picoserve::io::embedded_io_async::Read,
|
||||
W: picoserve::response::ResponseWriter<Error = R::Error>,
|
||||
>(
|
||||
&self,
|
||||
state: &State,
|
||||
current_path_parameters: CurrentPathParameters,
|
||||
path: picoserve::request::Path<'_>,
|
||||
request: picoserve::request::Request<'_, R>,
|
||||
response_writer: W,
|
||||
) -> Result<picoserve::ResponseSent, W::Error> {
|
||||
let requested_path = path.encoded();
|
||||
|
||||
let requested_file = if requested_path == "/" {
|
||||
Self::get("index.html")
|
||||
} else if let Some(striped_path) = requested_path.strip_prefix("/") {
|
||||
Self::get(striped_path)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match requested_file {
|
||||
Some(content) => {
|
||||
let response = picoserve::response::Response::new(
|
||||
picoserve::response::StatusCode::OK,
|
||||
StaticAsset(content.0, content.1),
|
||||
);
|
||||
|
||||
response_writer
|
||||
.write_response(request.body_connection.finalize().await.unwrap(), response)
|
||||
.await
|
||||
}
|
||||
None => {
|
||||
use picoserve::routing::PathRouter;
|
||||
picoserve::routing::NotFound
|
||||
.call_path_router(
|
||||
state,
|
||||
current_path_parameters,
|
||||
path,
|
||||
request,
|
||||
response_writer,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct StaticAsset(pub &'static [u8], pub &'static str);
|
||||
|
||||
impl Content for StaticAsset {
|
||||
fn content_type(&self) -> &'static str {
|
||||
self.1
|
||||
}
|
||||
|
||||
fn content_length(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
async fn write_content<W: embedded_io_async::Write>(
|
||||
self,
|
||||
mut writer: W,
|
||||
) -> Result<(), W::Error> {
|
||||
writer.write_all(self.0).await
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,54 @@
|
||||
mod server;
|
||||
mod activity_fairing;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_net::Stack;
|
||||
use embassy_time::Duration;
|
||||
use picoserve::{AppBuilder, AppRouter, routing::get};
|
||||
use static_cell::make_static;
|
||||
|
||||
pub use activity_fairing::{ActivityNotifier,spawn_idle_watcher};
|
||||
pub use server::start_webserver;
|
||||
mod assets;
|
||||
|
||||
pub fn start_webserver(spawner: &mut Spawner, stack: Stack<'static>) {
|
||||
let app = make_static!(AppProps.build_app());
|
||||
|
||||
let config = make_static!(picoserve::Config::new(picoserve::Timeouts {
|
||||
start_read_request: Some(Duration::from_secs(5)),
|
||||
persistent_start_read_request: Some(Duration::from_secs(1)),
|
||||
read_request: Some(Duration::from_secs(1)),
|
||||
write: Some(Duration::from_secs(1)),
|
||||
}));
|
||||
|
||||
let _ = spawner.spawn(webserver_task(0, stack, app, config));
|
||||
}
|
||||
|
||||
struct AppProps;
|
||||
|
||||
impl AppBuilder for AppProps {
|
||||
type PathRouter = impl picoserve::routing::PathRouter;
|
||||
|
||||
fn build_app(self) -> picoserve::Router<Self::PathRouter> {
|
||||
picoserve::Router::from_service(assets::Assets).route("/api/a", get(async move || "Hello"))
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn webserver_task(
|
||||
id: usize,
|
||||
stack: embassy_net::Stack<'static>,
|
||||
app: &'static AppRouter<AppProps>,
|
||||
config: &'static picoserve::Config<Duration>,
|
||||
) -> ! {
|
||||
let mut tcp_rx_buffer = [0u8; 1024];
|
||||
let mut tcp_tx_buffer = [0u8; 1024];
|
||||
let mut http_buffer = [0u8; 2048];
|
||||
|
||||
picoserve::listen_and_serve(
|
||||
id,
|
||||
app,
|
||||
config,
|
||||
stack,
|
||||
80,
|
||||
&mut tcp_rx_buffer,
|
||||
&mut tcp_tx_buffer,
|
||||
&mut http_buffer,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
use log::{error, info, warn};
|
||||
use rocket::http::Status;
|
||||
use rocket::response::stream::{Event, EventStream};
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::{Config, Shutdown, State, post};
|
||||
use rocket::{get, http::ContentType, response::content::RawHtml, routes};
|
||||
use rust_embed::Embed;
|
||||
use serde::Deserialize;
|
||||
use std::borrow::Cow;
|
||||
use std::env;
|
||||
use std::ffi::OsStr;
|
||||
use std::sync::Arc;
|
||||
use tokio::select;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::sync::broadcast::Sender;
|
||||
|
||||
use crate::store::{IDMapping, IDStore, Name};
|
||||
use crate::tally_id::TallyID;
|
||||
use crate::webserver::ActivityNotifier;
|
||||
|
||||
#[derive(Embed)]
|
||||
#[folder = "web/dist"]
|
||||
struct Asset;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct NewMapping {
|
||||
id: String,
|
||||
name: Name,
|
||||
}
|
||||
|
||||
pub async fn start_webserver(
|
||||
store: Arc<Mutex<IDStore>>,
|
||||
sse_broadcaster: Sender<String>,
|
||||
fairing: ActivityNotifier,
|
||||
) -> Result<(), rocket::Error> {
|
||||
let port = match env::var("HTTP_PORT") {
|
||||
Ok(port) => port.parse().unwrap_or_else(|_| {
|
||||
warn!("Failed to parse HTTP_PORT. Using default 80");
|
||||
80
|
||||
}),
|
||||
Err(_) => 80,
|
||||
};
|
||||
|
||||
let config = Config {
|
||||
address: "0.0.0.0".parse().unwrap(), // Listen on all interfaces
|
||||
port,
|
||||
..Config::default()
|
||||
};
|
||||
|
||||
rocket::custom(config)
|
||||
.attach(fairing)
|
||||
.mount(
|
||||
"/",
|
||||
routes![
|
||||
static_files,
|
||||
index,
|
||||
export_csv,
|
||||
id_event,
|
||||
get_mapping,
|
||||
add_mapping
|
||||
],
|
||||
)
|
||||
.manage(store)
|
||||
.manage(sse_broadcaster)
|
||||
.launch()
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
fn index() -> Option<RawHtml<Cow<'static, [u8]>>> {
|
||||
let asset = Asset::get("index.html")?;
|
||||
Some(RawHtml(asset.data))
|
||||
}
|
||||
|
||||
#[get("/<file..>")]
|
||||
fn static_files(file: std::path::PathBuf) -> Option<(ContentType, Vec<u8>)> {
|
||||
let filename = file.display().to_string();
|
||||
let asset = Asset::get(&filename)?;
|
||||
let content_type = file
|
||||
.extension()
|
||||
.and_then(OsStr::to_str)
|
||||
.and_then(ContentType::from_extension)
|
||||
.unwrap_or(ContentType::Bytes);
|
||||
|
||||
Some((content_type, asset.data.into_owned()))
|
||||
}
|
||||
|
||||
#[get("/api/idevent")]
|
||||
fn id_event(sse_broadcaster: &State<Sender<String>>, shutdown: Shutdown) -> EventStream![] {
|
||||
let mut rx = sse_broadcaster.subscribe();
|
||||
EventStream! {
|
||||
loop {
|
||||
select! {
|
||||
msg = rx.recv() => {
|
||||
if let Ok(id) = msg {
|
||||
yield Event::data(id);
|
||||
}
|
||||
}
|
||||
_ = &mut shutdown.clone() => {
|
||||
// Shutdown signal received, exit the loop
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/api/csv")]
|
||||
async fn export_csv(manager: &State<Arc<Mutex<IDStore>>>) -> Result<String, Status> {
|
||||
info!("Exporting CSV");
|
||||
match manager.lock().await.export_csv() {
|
||||
Ok(csv) => Ok(csv),
|
||||
Err(e) => {
|
||||
error!("Failed to generate csv: {e}");
|
||||
Err(Status::InternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/api/mapping")]
|
||||
async fn get_mapping(store: &State<Arc<Mutex<IDStore>>>) -> Json<IDMapping> {
|
||||
Json(store.lock().await.mapping.clone())
|
||||
}
|
||||
|
||||
#[post("/api/mapping", format = "json", data = "<new_mapping>")]
|
||||
async fn add_mapping(store: &State<Arc<Mutex<IDStore>>>, new_mapping: Json<NewMapping>) -> Status {
|
||||
if new_mapping.id.is_empty()
|
||||
|| new_mapping.name.first.is_empty()
|
||||
|| new_mapping.name.last.is_empty()
|
||||
{
|
||||
return Status::BadRequest;
|
||||
}
|
||||
|
||||
store
|
||||
.lock()
|
||||
.await
|
||||
.mapping
|
||||
.add_mapping(TallyID(new_mapping.id.clone()), new_mapping.name.clone());
|
||||
|
||||
Status::Created
|
||||
}
|
||||
88
used_ids.txt
Normal file
88
used_ids.txt
Normal file
@@ -0,0 +1,88 @@
|
||||
8f801f988c
|
||||
6fc90dd450
|
||||
edf3f0793a
|
||||
df39cf9566
|
||||
b9f998924d
|
||||
f7686ed090
|
||||
c4dc09d5fa
|
||||
78908e3baa
|
||||
da874600cd
|
||||
66beb3dfad
|
||||
b5758c2ada
|
||||
aee543f099
|
||||
ea912de917
|
||||
d309e7a0da
|
||||
54d1fbd27a
|
||||
a2f95c6f50
|
||||
b663118bfd
|
||||
8d639f381c
|
||||
25ec58dace
|
||||
a0ecef7443
|
||||
cab4672699
|
||||
f8a021b691
|
||||
5314cc63d8
|
||||
ad4a8a3882
|
||||
927ead1dec
|
||||
743a8e8162
|
||||
f54694666a
|
||||
38f8ff49c4
|
||||
6da025be13
|
||||
afe671009f
|
||||
8a18526cc5
|
||||
fe6ead39e7
|
||||
07b0391c5b
|
||||
aaf6d9cef5
|
||||
ee12fe5bbf
|
||||
96b833ccc2
|
||||
690eec798e
|
||||
142c3cb709
|
||||
cc585eea85
|
||||
1b426bf077
|
||||
3df69e83bc
|
||||
8fba107bbc
|
||||
79e24823d2
|
||||
3ec6e52678
|
||||
e1e0d87659
|
||||
4c12460af8
|
||||
7d506534de
|
||||
c4946d9a72
|
||||
80d2b13291
|
||||
0c36d4a7a7
|
||||
776cef50a2
|
||||
1cc64b5158
|
||||
ee78890172
|
||||
63fa57ad63
|
||||
9072b8fad8
|
||||
b3c407a858
|
||||
833c54a7a5
|
||||
99d2c32c35
|
||||
4f5e5357e2
|
||||
82cea5924d
|
||||
fec8fa57ef
|
||||
11b49b1b2b
|
||||
e40a8e6e3f
|
||||
2fbe63bb85
|
||||
f76830b226
|
||||
76544233e5
|
||||
2fc8c544ef
|
||||
2a4cc77d6b
|
||||
f52eebdd85
|
||||
508e07aca5
|
||||
936aed7997
|
||||
0fbf70c4c6
|
||||
bf47dfd6b7
|
||||
81e7d42454
|
||||
96b701ef5d
|
||||
11d3ecfa1b
|
||||
e0bd39c427
|
||||
6fa914114e
|
||||
d7a3b89055
|
||||
e417131533
|
||||
1fef16c2ce
|
||||
1af12ecd77
|
||||
37e1a8f1dc
|
||||
65e4521004
|
||||
a0be6cc4fa
|
||||
90bcf9dbaa
|
||||
8f169642c4
|
||||
ac5b109c5b
|
||||
Reference in New Issue
Block a user