feat(firmware): add open lock
This commit is contained in:
@@ -22,7 +22,13 @@ The format is based on Keep a Changelog and this project follows Semantic Versio
|
||||
- Firmware component documentation.
|
||||
- Firmware debugging documentation.
|
||||
- Firmware buzzer documentation.
|
||||
- Firmware lock control documentation.
|
||||
- English architecture documentation in `docs/architecture.md`.
|
||||
- Persistent six-digit access code storage.
|
||||
- Lock control pulse on XIAO pin `D9`.
|
||||
- Lock state feedback detection using KR-S79 `COM/NC` on XIAO `D7` / `D8`.
|
||||
- Long buzzer success beep for accepted unlock codes.
|
||||
- Door opened and door closed UART messages.
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -36,6 +42,8 @@ The format is based on Keep a Changelog and this project follows Semantic Versio
|
||||
- Aligned hardware documentation with the active buzzer transistor driver.
|
||||
- Aligned firmware documentation with C17 and the current Zephyr build tools.
|
||||
- Updated project tracking documents to reflect completed RGB LED, GPIO expander, keypad, and active buzzer work.
|
||||
- Updated keypad unlock user feedback with off-at-rest LEDs, green-open indication, red invalid-code feedback, and `B` entry cancellation.
|
||||
- Updated open-lock reminder beep interval to 2 seconds.
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -46,6 +54,7 @@ The format is based on Keep a Changelog and this project follows Semantic Versio
|
||||
- Removed obsolete documentation references to PWM buzzer control.
|
||||
- Removed stale development-environment references from project documentation.
|
||||
- Removed corrupted text encoding artifacts from Markdown documentation.
|
||||
- Prevented unintended lock command pulses at startup or after flashing by declaring D9 in Devicetree and configuring it directly inactive.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -27,18 +27,24 @@ Completed or validated:
|
||||
- PCF8574 GPIO expander driver
|
||||
- Development keypad scanning
|
||||
- Active buzzer feedback
|
||||
- Persistent six-digit access codes
|
||||
- Keypad unlock flow with default development code `784512`
|
||||
- Lock command pulse on XIAO `D9`
|
||||
- KR-S79 `COM/NC` lock state feedback on XIAO `D7` / `D8`
|
||||
- Green-open, off-closed, red-invalid LED feedback
|
||||
- Short key beeps, long valid-code beep, invalid-code beep sequence, and 2s open reminder beep
|
||||
- Door opened / closed serial debug messages
|
||||
- Firmware debug output over serial console
|
||||
|
||||
In progress:
|
||||
|
||||
- Original hardware reverse engineering
|
||||
- Lock driver
|
||||
- Production keypad mapping
|
||||
- Hardware validation
|
||||
|
||||
Planned:
|
||||
|
||||
- Access control
|
||||
- Administrator access management
|
||||
- NFC
|
||||
- Battery management
|
||||
- Zigbee
|
||||
@@ -79,6 +85,9 @@ Current firmware modules:
|
||||
- PCF8574 GPIO expander
|
||||
- Matrix keypad
|
||||
- Active buzzer
|
||||
- Lock control
|
||||
- Lock state feedback
|
||||
- Persistent access codes
|
||||
|
||||
Firmware documentation is available in:
|
||||
|
||||
@@ -94,6 +103,9 @@ Current development platform:
|
||||
- Seeed Studio XIAO BLE nRF52840
|
||||
- PCF8574 GPIO expander
|
||||
- Freenove 4x4 matrix keypad for development validation
|
||||
- KR-S79 lock command on XIAO `D9`
|
||||
- KR-S79 `NC` feedback on XIAO `D7`
|
||||
- KR-S79 `COM` feedback reference on XIAO `D8`
|
||||
|
||||
The production hardware is planned around a custom PCB and the original parcel box mechanical parts.
|
||||
|
||||
|
||||
+28
-4
@@ -39,7 +39,9 @@ The firmware is based on Zephyr RTOS and follows a modular architecture to simpl
|
||||
| GPIO expander driver | Done |
|
||||
| Matrix keypad driver | Done |
|
||||
| Active buzzer driver | Done |
|
||||
| Lock driver | Pending |
|
||||
| Lock driver | Done |
|
||||
| Door or lock state feedback | Done |
|
||||
| Persistent access code storage | Done |
|
||||
| Battery driver | Pending |
|
||||
| NFC driver | Pending |
|
||||
| Power management | Pending |
|
||||
@@ -99,6 +101,9 @@ Firmware modules:
|
||||
- GPIO expander.
|
||||
- Matrix keypad.
|
||||
- Active buzzer.
|
||||
- Lock control.
|
||||
- Door or lock state feedback.
|
||||
- Persistent access codes.
|
||||
|
||||
Status: in progress.
|
||||
|
||||
@@ -110,13 +115,21 @@ Objective: validate every reusable or newly added hardware component before inte
|
||||
|
||||
Components:
|
||||
|
||||
- Lock.
|
||||
- Door or lock state feedback.
|
||||
- Lock command pulse.
|
||||
- Door or lock state feedback through the KR-S79 `COM/NC` contact.
|
||||
- Battery monitoring.
|
||||
- NFC antenna.
|
||||
- Buzzer.
|
||||
- External LEDs.
|
||||
|
||||
Validated so far:
|
||||
|
||||
- KR-S79 opening command through XIAO `D9`.
|
||||
- KR-S79 `COM/NC` state detection through XIAO `D7` / `D8`.
|
||||
- Door opened and door closed UART events.
|
||||
- Open-state reminder beep every 2 seconds.
|
||||
- Valid-code long beep and invalid-code beep sequence.
|
||||
|
||||
Status: in progress.
|
||||
|
||||
---
|
||||
@@ -132,6 +145,7 @@ Components:
|
||||
- Keypad.
|
||||
- Lock.
|
||||
- Door or lock state feedback.
|
||||
- Persistent six-digit access codes.
|
||||
- Battery.
|
||||
- Buzzer.
|
||||
- NFC.
|
||||
@@ -139,11 +153,12 @@ Components:
|
||||
Security:
|
||||
|
||||
- Administrator PIN.
|
||||
- Development six-digit unlock code.
|
||||
- PIN timeout.
|
||||
- Anti brute-force logic.
|
||||
- Configuration storage.
|
||||
|
||||
Status: planned.
|
||||
Status: in progress.
|
||||
|
||||
---
|
||||
|
||||
@@ -195,6 +210,15 @@ Required features:
|
||||
- No cloud.
|
||||
- No subscription.
|
||||
|
||||
Implemented MVP pieces:
|
||||
|
||||
- Keypad-based unlock flow.
|
||||
- Persistent six-digit code table.
|
||||
- Default development unlock code.
|
||||
- Lock command pulse.
|
||||
- Lock state feedback.
|
||||
- Audible and visual feedback for valid codes, invalid codes, and open state.
|
||||
|
||||
At this stage, the original proprietary electronics are no longer required.
|
||||
|
||||
---
|
||||
|
||||
@@ -69,8 +69,8 @@ Tasks are grouped by development phase and updated throughout the project.
|
||||
- [x] GPIO expander driver.
|
||||
- [x] Matrix keypad driver.
|
||||
- [x] Active buzzer driver.
|
||||
- [ ] Lock driver.
|
||||
- [ ] Door or lock state driver.
|
||||
- [x] Lock driver.
|
||||
- [x] Door or lock state driver.
|
||||
- [ ] Battery driver.
|
||||
- [ ] NFC driver.
|
||||
|
||||
@@ -81,7 +81,7 @@ Tasks are grouped by development phase and updated throughout the project.
|
||||
- [x] buzzer.md.
|
||||
- [x] debug.md.
|
||||
- [ ] led.md.
|
||||
- [ ] lock.md.
|
||||
- [x] lock.md.
|
||||
- [ ] battery.md.
|
||||
- [ ] nfc.md.
|
||||
|
||||
@@ -91,16 +91,16 @@ Tasks are grouped by development phase and updated throughout the project.
|
||||
|
||||
### Lock
|
||||
|
||||
- [ ] Drive lock through the selected circuit.
|
||||
- [ ] Validate opening pulse.
|
||||
- [ ] Validate opening detection.
|
||||
- [x] Drive lock through the selected circuit.
|
||||
- [x] Validate opening pulse.
|
||||
- [x] Validate opening detection.
|
||||
- [ ] Validate timeout.
|
||||
- [ ] Validate error handling.
|
||||
|
||||
### Door or Lock State
|
||||
|
||||
- [ ] Read feedback contact.
|
||||
- [ ] Validate state changes.
|
||||
- [x] Read feedback contact.
|
||||
- [x] Validate state changes.
|
||||
- [ ] Validate debounce.
|
||||
|
||||
### Battery
|
||||
@@ -120,8 +120,8 @@ Tasks are grouped by development phase and updated throughout the project.
|
||||
|
||||
- [x] Single keypad beep.
|
||||
- [ ] Double beep.
|
||||
- [ ] Error beep.
|
||||
- [ ] Success beep.
|
||||
- [x] Error beep.
|
||||
- [x] Success beep.
|
||||
|
||||
---
|
||||
|
||||
@@ -136,15 +136,15 @@ Tasks are grouped by development phase and updated throughout the project.
|
||||
### Access Control
|
||||
|
||||
- [ ] Administrator PIN.
|
||||
- [ ] PIN verification.
|
||||
- [x] PIN verification.
|
||||
- [ ] PIN timeout.
|
||||
- [ ] Anti brute-force logic.
|
||||
- [ ] Lock opening sequence.
|
||||
- [ ] Door or lock state validation.
|
||||
- [x] Lock opening sequence.
|
||||
- [x] Door or lock state validation.
|
||||
|
||||
### Configuration
|
||||
|
||||
- [ ] Persistent storage.
|
||||
- [x] Persistent storage.
|
||||
- [ ] Factory reset.
|
||||
- [ ] Version information.
|
||||
|
||||
@@ -165,12 +165,12 @@ Tasks are grouped by development phase and updated throughout the project.
|
||||
## MVP - Standalone Firmware
|
||||
|
||||
- [ ] Complete firmware startup.
|
||||
- [ ] Lock opening.
|
||||
- [ ] Door or lock feedback.
|
||||
- [x] Lock opening.
|
||||
- [x] Door or lock feedback.
|
||||
- [ ] Battery monitoring.
|
||||
- [ ] Administrator PIN.
|
||||
- [ ] Administrator NFC badge.
|
||||
- [ ] Audible feedback.
|
||||
- [x] Audible feedback.
|
||||
- [ ] Standalone operation.
|
||||
- [ ] Battery-powered operation.
|
||||
- [ ] No cloud.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Purpose
|
||||
|
||||
This module provides short audible feedback for accepted keypad presses.
|
||||
This module provides audible feedback for keypad presses and access-code events.
|
||||
|
||||
The current hardware uses an active buzzer. The firmware does not generate PWM; it only drives a GPIO high for a short pulse.
|
||||
|
||||
@@ -28,6 +28,8 @@ void buzzer_on(void);
|
||||
|
||||
void buzzer_beep_key(void);
|
||||
|
||||
void buzzer_beep_success(void);
|
||||
|
||||
void buzzer_off(void);
|
||||
```
|
||||
|
||||
@@ -37,7 +39,10 @@ void buzzer_off(void);
|
||||
|
||||
- `buzzer_init()` configures the buzzer control GPIO and leaves the buzzer off.
|
||||
- `buzzer_beep_key()` emits a short blocking beep.
|
||||
- `buzzer_beep_success()` emits a longer blocking beep when a valid access code is accepted.
|
||||
- The keypad calls `buzzer_beep_key()` only when a new key press is accepted, so holding a key does not produce a continuous beep.
|
||||
- Invalid six-digit access codes are signaled by a higher-level sequence of three short GPIO pulses.
|
||||
- While the lock state feedback reports open, the main loop emits one short reminder beep every 2 seconds.
|
||||
|
||||
---
|
||||
|
||||
@@ -45,4 +50,4 @@ void buzzer_off(void);
|
||||
|
||||
Because the buzzer is active, tone generation is handled by the buzzer itself.
|
||||
|
||||
Future sound patterns, such as success and error feedback, should be implemented as higher-level sequences built from GPIO on/off pulses.
|
||||
Future sound patterns should stay as higher-level sequences built from GPIO on/off pulses.
|
||||
|
||||
+23
-21
@@ -45,25 +45,21 @@ The onboard RGB LED is used to quickly identify the firmware state.
|
||||
|
||||
## Startup
|
||||
|
||||
| Color | Meaning |
|
||||
| ------- | ----------------------------------- |
|
||||
| Red | Firmware initialization in progress |
|
||||
| Green | Firmware initialized successfully |
|
||||
| Magenta | Initialization failed |
|
||||
| Color | Meaning |
|
||||
| ------- | ----------------------- |
|
||||
| Off | Startup in progress |
|
||||
| Magenta | Initialization failed |
|
||||
|
||||
---
|
||||
|
||||
## Runtime
|
||||
|
||||
| Color | Meaning |
|
||||
| ------- | ------------------------- |
|
||||
| Green | Idle / Ready |
|
||||
| Blue | User interaction detected |
|
||||
| Yellow | Warning |
|
||||
| Cyan | Reserved |
|
||||
| White | Reserved |
|
||||
| Red | Reserved |
|
||||
| Magenta | Fatal error |
|
||||
| Color | Meaning |
|
||||
| ------- | ----------------------------------- |
|
||||
| Off | Idle / lock closed |
|
||||
| Green | Lock state feedback reports open |
|
||||
| Red | Invalid six-digit access code |
|
||||
| Magenta | Fatal initialization error |
|
||||
|
||||
---
|
||||
|
||||
@@ -72,14 +68,18 @@ The onboard RGB LED is used to quickly identify the firmware state.
|
||||
Current startup sequence:
|
||||
|
||||
1. Initialize RGB LED
|
||||
2. Turn LED Red
|
||||
2. Keep LED off
|
||||
3. Initialize peripherals
|
||||
4. If initialization fails:
|
||||
- Turn LED Magenta
|
||||
- Stop execution
|
||||
5. Turn LED Green
|
||||
5. Show green only if the lock state feedback already reports open
|
||||
6. Start main application loop
|
||||
|
||||
While the lock state feedback reports open, the firmware emits one short reminder beep every 2 seconds.
|
||||
|
||||
A valid six-digit access code triggers one long success beep before the opening pulse.
|
||||
|
||||
---
|
||||
|
||||
# UART Examples
|
||||
@@ -89,14 +89,16 @@ Keypad:
|
||||
```text
|
||||
Key pressed: 1
|
||||
Key pressed: 5
|
||||
Key pressed: #
|
||||
Key pressed: 6
|
||||
Received valid access code
|
||||
Door opened
|
||||
```
|
||||
|
||||
Future lock module:
|
||||
Lock state:
|
||||
|
||||
```text
|
||||
Opening lock...
|
||||
Lock opened.
|
||||
Door opened
|
||||
Door closed
|
||||
```
|
||||
|
||||
Future NFC module:
|
||||
@@ -158,4 +160,4 @@ Planned debugging features:
|
||||
|
||||
Debugging features should remain lightweight to minimize firmware size and power consumption.
|
||||
|
||||
Development-only diagnostics should be removable without affecting the application logic.
|
||||
Development-only diagnostics should be removable without affecting the application logic.
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# Lock Control
|
||||
|
||||
## Purpose
|
||||
|
||||
This module commands the KR-S79 lock opening input.
|
||||
|
||||
The lock itself remains powered by the battery pack. The firmware only drives the control stage connected to the lock `SIG` input.
|
||||
|
||||
---
|
||||
|
||||
## Hardware
|
||||
|
||||
- Seeed Studio XIAO nRF52840.
|
||||
- Control signal: XIAO pin `D9`.
|
||||
- D9 mapping in Zephyr: `GPIO1 pin 14`.
|
||||
- D9 is declared in the application Devicetree overlay as `GPIO_ACTIVE_HIGH`.
|
||||
- External S8050/S8550 driver stage.
|
||||
- Active-high signal.
|
||||
|
||||
The GPIO is configured directly with `GPIO_OUTPUT_INACTIVE`. The idle state is low to avoid accidental opening at startup or after flashing.
|
||||
|
||||
---
|
||||
|
||||
## Public API
|
||||
|
||||
```c
|
||||
int lock_control_init(void);
|
||||
|
||||
int lock_control_open(void);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Current Behavior
|
||||
|
||||
- `lock_control_init()` configures D9 directly as an inactive GPIO output.
|
||||
- `lock_control_init()` never sends an unlock pulse.
|
||||
- `lock_control_open()` drives D9 high for 100 ms, then immediately returns it inactive with `gpio_pin_set_dt(..., 0)`.
|
||||
- All GPIO return codes used by the lock control path are logged.
|
||||
- No PWM is used.
|
||||
|
||||
---
|
||||
|
||||
## Access Code Flow
|
||||
|
||||
The main loop collects numeric keypad input.
|
||||
|
||||
- Codes are six digits long.
|
||||
- Up to 10 codes can be stored.
|
||||
- Codes are persisted through Zephyr settings with the NVS backend.
|
||||
- The development default code is `784512`.
|
||||
- A valid six-digit entry is the only current path that triggers `lock_control_open()`.
|
||||
- `*`, `#`, and `B` clear the current entry.
|
||||
- A valid six-digit entry triggers one long success beep before the opening pulse.
|
||||
- An invalid six-digit entry triggers three short close beeps and keeps the red LED on for 1 second.
|
||||
- The green LED stays on while the lock state feedback reports the lock open.
|
||||
- A short reminder beep is emitted every 2 seconds while the lock state feedback reports open.
|
||||
- Door state changes are reported over UART as `Door opened` and `Door closed`.
|
||||
- LEDs stay off during startup and while the lock is closed.
|
||||
@@ -0,0 +1,138 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1600" height="1000" viewBox="0 0 1600 1000">
|
||||
<defs>
|
||||
<style>
|
||||
.wire { stroke:#111; stroke-width:4; fill:none; }
|
||||
.power { stroke:#b00020; stroke-width:5; fill:none; }
|
||||
.signal { stroke:#c47a00; stroke-width:5; fill:none; }
|
||||
.logic { stroke:#006b3c; stroke-width:5; fill:none; }
|
||||
.thin { stroke:#111; stroke-width:3; fill:none; }
|
||||
.box { fill:#fff; stroke:#111; stroke-width:3; rx:16; }
|
||||
.title { font:700 34px sans-serif; fill:#111; }
|
||||
.h2 { font:700 24px sans-serif; fill:#111; }
|
||||
.txt { font:20px sans-serif; fill:#111; }
|
||||
.small { font:17px sans-serif; fill:#111; }
|
||||
.label { font:700 19px sans-serif; fill:#111; }
|
||||
.note { font:18px sans-serif; fill:#7a0012; }
|
||||
.node { fill:#111; }
|
||||
.res { fill:#fff; stroke:#111; stroke-width:3; }
|
||||
</style>
|
||||
</defs>
|
||||
|
||||
<rect width="1600" height="1000" fill="#fafafa"/>
|
||||
<text x="800" y="55" text-anchor="middle" class="title">OpenParcelBox — commande temporaire de SIG avec S8550 + S8050</text>
|
||||
<text x="800" y="88" text-anchor="middle" class="txt">Seeed Studio XIAO BLE nRF52840 • serrure KR-S79 • stack 8×AAA ≈ 13 V</text>
|
||||
|
||||
<!-- XIAO -->
|
||||
<rect x="70" y="170" width="330" height="390" class="box"/>
|
||||
<text x="235" y="215" text-anchor="middle" class="h2">XIAO BLE nRF52840</text>
|
||||
<text x="95" y="300" class="label">D7 — commande serrure</text>
|
||||
<circle cx="400" cy="292" r="7" class="node"/>
|
||||
<text x="95" y="505" class="label">GND</text>
|
||||
<circle cx="400" cy="500" r="7" class="node"/>
|
||||
<text x="95" y="545" class="small">Alimentation USB-C pendant le développement</text>
|
||||
|
||||
<!-- NPN base resistor -->
|
||||
<path d="M400 292 H500" class="logic"/>
|
||||
<rect x="500" y="267" width="120" height="50" class="res"/>
|
||||
<text x="560" y="300" text-anchor="middle" class="label">R1 4,7 kΩ</text>
|
||||
<path d="M620 292 H735" class="logic"/>
|
||||
|
||||
<!-- NPN transistor symbol -->
|
||||
<circle cx="810" cy="400" r="75" class="thin"/>
|
||||
<path d="M735 292 L775 332" class="thin"/>
|
||||
<path d="M775 332 V468" class="thin"/>
|
||||
<path d="M775 365 L845 325" class="thin"/>
|
||||
<path d="M775 435 L845 475" class="thin"/>
|
||||
<path d="M828 464 L845 475 L837 455" fill="#111"/>
|
||||
<path d="M845 325 V235" class="wire"/>
|
||||
<path d="M845 475 V560" class="wire"/>
|
||||
<text x="900" y="370" class="h2">Q2 — S8050</text>
|
||||
<text x="900" y="402" class="txt">NPN</text>
|
||||
<text x="710" y="278" class="label">B</text>
|
||||
<text x="855" y="225" class="label">C</text>
|
||||
<text x="855" y="590" class="label">E</text>
|
||||
|
||||
<!-- NPN base pulldown -->
|
||||
<path d="M700 292 V365" class="wire"/>
|
||||
<rect x="675" y="365" width="50" height="110" class="res"/>
|
||||
<text x="660" y="425" text-anchor="end" class="label">R4 100 kΩ</text>
|
||||
<path d="M700 475 V560" class="wire"/>
|
||||
|
||||
<!-- GND rail -->
|
||||
<path d="M400 500 H400 V560 H1290" class="wire"/>
|
||||
<circle cx="700" cy="560" r="7" class="node"/>
|
||||
<circle cx="845" cy="560" r="7" class="node"/>
|
||||
<circle cx="1290" cy="560" r="7" class="node"/>
|
||||
<path d="M1230 560 V640" class="wire"/>
|
||||
<path d="M1190 640 H1270" class="wire"/>
|
||||
<path d="M1205 655 H1255" class="wire"/>
|
||||
<path d="M1220 670 H1240" class="wire"/>
|
||||
<text x="1285" y="595" class="label">GND commun</text>
|
||||
|
||||
<!-- PNP drive resistor -->
|
||||
<path d="M845 235 H930" class="wire"/>
|
||||
<rect x="930" y="210" width="120" height="50" class="res"/>
|
||||
<text x="990" y="243" text-anchor="middle" class="label">R2 2,2 kΩ</text>
|
||||
<path d="M1050 235 H1120" class="wire"/>
|
||||
|
||||
<!-- PNP transistor -->
|
||||
<circle cx="1195" cy="235" r="75" class="thin"/>
|
||||
<path d="M1120 235 H1155" class="thin"/>
|
||||
<path d="M1155 167 V303" class="thin"/>
|
||||
<path d="M1155 195 L1225 155" class="thin"/>
|
||||
<path d="M1155 275 L1225 315" class="thin"/>
|
||||
<path d="M1170 188 L1155 195 L1176 202" fill="#111"/>
|
||||
<path d="M1225 155 V125" class="power"/>
|
||||
<path d="M1225 315 V390" class="signal"/>
|
||||
<text x="1285" y="210" class="h2">Q1 — S8550</text>
|
||||
<text x="1285" y="242" class="txt">PNP</text>
|
||||
<text x="1100" y="220" class="label">B</text>
|
||||
<text x="1238" y="115" class="label">E</text>
|
||||
<text x="1238" y="420" class="label">C</text>
|
||||
|
||||
<!-- +13V rail -->
|
||||
<path d="M1225 125 H1430" class="power"/>
|
||||
<path d="M1070 125 H1225" class="power"/>
|
||||
<text x="1435" y="132" class="label" fill="#b00020">+13 V stack</text>
|
||||
|
||||
<!-- PNP base pullup -->
|
||||
<path d="M1120 235 V150" class="wire"/>
|
||||
<rect x="1095" y="125" width="50" height="90" class="res"/>
|
||||
<text x="1080" y="170" text-anchor="end" class="label">R3 10 kΩ</text>
|
||||
<path d="M1120 125 H1225" class="power"/>
|
||||
<circle cx="1120" cy="235" r="7" class="node"/>
|
||||
<circle cx="1225" cy="125" r="7" class="node"/>
|
||||
|
||||
<!-- Lock box -->
|
||||
<rect x="1290" y="250" width="250" height="300" class="box"/>
|
||||
<text x="1415" y="292" text-anchor="middle" class="h2">SERRURE</text>
|
||||
<text x="1415" y="322" text-anchor="middle" class="small">KERONG KR-S79_20-JST</text>
|
||||
<text x="1315" y="385" class="label">SIG jaune</text>
|
||||
<circle cx="1290" cy="390" r="7" class="node"/>
|
||||
<path d="M1225 390 H1290" class="signal"/>
|
||||
<text x="1315" y="445" class="label">+ rouge → +13 V</text>
|
||||
<path d="M1430 125 V425 H1290" class="power"/>
|
||||
<circle cx="1290" cy="425" r="7" class="node"/>
|
||||
<text x="1315" y="505" class="label">− orange → GND</text>
|
||||
<path d="M1290 500 V560" class="wire"/>
|
||||
<circle cx="1290" cy="500" r="7" class="node"/>
|
||||
|
||||
<!-- Functional notes -->
|
||||
<rect x="70" y="630" width="700" height="300" class="box"/>
|
||||
<text x="95" y="675" class="h2">Fonctionnement</text>
|
||||
<text x="100" y="720" class="txt">D7 = LOW : Q2 bloqué → R3 remonte la base de Q1 à +13 V → Q1 bloqué → SIG non alimenté.</text>
|
||||
<text x="100" y="770" class="txt">D7 = HIGH : Q2 conduit → courant limité par R2 → base de Q1 abaissée d’environ 0,7 V</text>
|
||||
<text x="100" y="805" class="txt">sous son émetteur → Q1 conduit → environ +13 V appliqué sur SIG.</text>
|
||||
<text x="100" y="855" class="txt">R4 maintient Q2 bloqué pendant le démarrage ou si D7 est flottante.</text>
|
||||
<text x="100" y="900" class="note">Important : ne jamais relier directement D7 à la base du S8550.</text>
|
||||
|
||||
<!-- Verification notes -->
|
||||
<rect x="810" y="630" width="730" height="300" class="box"/>
|
||||
<text x="835" y="675" class="h2">Vérifications avant câblage</text>
|
||||
<text x="840" y="720" class="txt">• Identifier physiquement E, B et C de chaque transistor avec la fiche du fabricant ou un multimètre.</text>
|
||||
<text x="840" y="760" class="txt">• Les boîtiers S8050/S8550 ne garantissent pas tous le même ordre de broches.</text>
|
||||
<text x="840" y="800" class="txt">• Vérifier la polarité du stack : rouge = +13 V, noir = GND.</text>
|
||||
<text x="840" y="840" class="txt">• Relier le GND du XIAO au GND du stack avant d’activer D7.</text>
|
||||
<text x="840" y="880" class="txt">• Tester d’abord avec une impulsion courte (≈100 ms) et mesurer SIG au multimètre.</text>
|
||||
<text x="840" y="915" class="note">Ce schéma ne suppose aucun brochage physique gauche/centre/droite.</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.9 KiB |
+18
-2
@@ -83,6 +83,18 @@ Development keypad matrix scanning through the GPIO expander.
|
||||
|
||||
Active buzzer control through a GPIO-driven transistor.
|
||||
|
||||
### Lock Control
|
||||
|
||||
KR-S79 unlock command through a 100 ms active-high pulse on XIAO pin `D9`.
|
||||
|
||||
### Lock State
|
||||
|
||||
KR-S79 `COM/NC` feedback contact detection using `NC` on XIAO `D7` and `COM` on XIAO `D8`.
|
||||
|
||||
### Access Codes
|
||||
|
||||
Persistent storage for up to 10 six-digit unlock codes.
|
||||
|
||||
---
|
||||
|
||||
## Development Philosophy
|
||||
@@ -107,13 +119,17 @@ Implemented:
|
||||
- PCF8574 GPIO expander access.
|
||||
- 4x4 development keypad scanning.
|
||||
- Active buzzer feedback on accepted key presses.
|
||||
- Long success beep on valid access code.
|
||||
- Three short beeps on invalid access code.
|
||||
- Persistent six-digit access code storage.
|
||||
- Lock command pulse on XIAO pin `D9`.
|
||||
- Lock state feedback through the KR-S79 `COM/NC` contact.
|
||||
- Door opened / closed debug output.
|
||||
- Serial debug output.
|
||||
|
||||
In progress or planned:
|
||||
|
||||
- Production keypad mapping.
|
||||
- Access control.
|
||||
- Lock driver.
|
||||
- Battery monitoring.
|
||||
- NFC.
|
||||
- Zigbee.
|
||||
|
||||
@@ -12,6 +12,8 @@ target_sources(app PRIVATE
|
||||
src/main.c
|
||||
src/led.c
|
||||
src/buzzer.c
|
||||
src/lock_control.c
|
||||
src/access_codes.c
|
||||
src/lock_state.c
|
||||
src/gpio_expander.c
|
||||
src/keypad.c
|
||||
|
||||
@@ -6,6 +6,15 @@
|
||||
* Development board configuration.
|
||||
*/
|
||||
|
||||
/ {
|
||||
openparcelbox {
|
||||
lock_control: lock-control {
|
||||
compatible = "openparcelbox,lock-control";
|
||||
gpios = <&gpio1 14 GPIO_ACTIVE_HIGH>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
&i2c1 {
|
||||
status = "okay";
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
description: OpenParcelBox lock control GPIO
|
||||
|
||||
compatible: "openparcelbox,lock-control"
|
||||
|
||||
properties:
|
||||
gpios:
|
||||
type: phandle-array
|
||||
required: true
|
||||
description: Active-high lock command GPIO.
|
||||
@@ -0,0 +1 @@
|
||||
openparcelbox OpenParcelBox
|
||||
@@ -30,6 +30,10 @@
|
||||
#define BUZZER_GPIO_PIN 2
|
||||
#define BUZZER_GPIO_FLAGS GPIO_ACTIVE_HIGH
|
||||
|
||||
/* Lock control */
|
||||
|
||||
#define LOCK_CONTROL_GPIO_NODE DT_NODELABEL(lock_control)
|
||||
|
||||
/* Lock state feedback */
|
||||
|
||||
#define LOCK_STATE_NC_GPIO_NODE DT_NODELABEL(gpio1)
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
CONFIG_GPIO=y
|
||||
CONFIG_I2C=y
|
||||
CONFIG_FLASH=y
|
||||
CONFIG_FLASH_MAP=y
|
||||
CONFIG_NVS=y
|
||||
CONFIG_SETTINGS=y
|
||||
CONFIG_SETTINGS_NVS=y
|
||||
CONFIG_I2C_SHELL=y
|
||||
CONFIG_SHELL=y
|
||||
CONFIG_CONSOLE=y
|
||||
CONFIG_SERIAL=y
|
||||
CONFIG_UART_CONSOLE=y
|
||||
CONFIG_DEBUG_THREAD_INFO=y
|
||||
CONFIG_DEBUG_THREAD_INFO=y
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* OpenParcelBox
|
||||
* Copyright (c) 2026
|
||||
*
|
||||
* Persistent access code storage.
|
||||
*/
|
||||
|
||||
#include "access_codes.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <zephyr/settings/settings.h>
|
||||
|
||||
#define ACCESS_CODES_SETTINGS_ROOT "codes"
|
||||
#define ACCESS_CODES_SETTINGS_TABLE "table"
|
||||
#define ACCESS_CODES_SETTINGS_TABLE_PATH "codes/table"
|
||||
|
||||
#define ACCESS_CODES_MAGIC 0x4f504243U
|
||||
#define ACCESS_CODES_VERSION 1U
|
||||
|
||||
struct access_code_slot {
|
||||
bool enabled;
|
||||
char code[ACCESS_CODE_LENGTH + 1];
|
||||
};
|
||||
|
||||
struct access_code_table {
|
||||
uint32_t magic;
|
||||
uint16_t version;
|
||||
uint16_t count;
|
||||
struct access_code_slot slots[ACCESS_CODE_MAX_COUNT];
|
||||
};
|
||||
|
||||
static struct access_code_table code_table;
|
||||
static bool code_table_loaded;
|
||||
|
||||
static bool access_code_is_digit_string(const char *code, size_t length) {
|
||||
if (length != ACCESS_CODE_LENGTH) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < ACCESS_CODE_LENGTH; i++) {
|
||||
if (code[i] < '0' || code[i] > '9') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool access_code_slot_is_valid(const struct access_code_slot *slot) {
|
||||
if (!slot->enabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (slot->code[ACCESS_CODE_LENGTH] != '\0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return access_code_is_digit_string(slot->code, ACCESS_CODE_LENGTH);
|
||||
}
|
||||
|
||||
static bool access_code_table_is_valid(const struct access_code_table *table) {
|
||||
size_t enabled_count = 0;
|
||||
|
||||
if (table->magic != ACCESS_CODES_MAGIC ||
|
||||
table->version != ACCESS_CODES_VERSION ||
|
||||
table->count > ACCESS_CODE_MAX_COUNT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < ACCESS_CODE_MAX_COUNT; i++) {
|
||||
if (!access_code_slot_is_valid(&table->slots[i])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (table->slots[i].enabled) {
|
||||
enabled_count++;
|
||||
}
|
||||
}
|
||||
|
||||
return enabled_count == table->count;
|
||||
}
|
||||
|
||||
static void access_codes_set_default_table(void) {
|
||||
memset(&code_table, 0, sizeof(code_table));
|
||||
|
||||
code_table.magic = ACCESS_CODES_MAGIC;
|
||||
code_table.version = ACCESS_CODES_VERSION;
|
||||
code_table.count = 1;
|
||||
code_table.slots[0].enabled = true;
|
||||
memcpy(code_table.slots[0].code, "784512", ACCESS_CODE_LENGTH + 1);
|
||||
}
|
||||
|
||||
static int access_codes_save(void) {
|
||||
return settings_save_one(ACCESS_CODES_SETTINGS_TABLE_PATH, &code_table,
|
||||
sizeof(code_table));
|
||||
}
|
||||
|
||||
static int access_codes_settings_set(const char *key, size_t len,
|
||||
settings_read_cb read_cb, void *cb_arg) {
|
||||
struct access_code_table loaded_table;
|
||||
ssize_t bytes_read;
|
||||
|
||||
if (strcmp(key, ACCESS_CODES_SETTINGS_TABLE) != 0) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
if (len != sizeof(loaded_table)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
bytes_read = read_cb(cb_arg, &loaded_table, sizeof(loaded_table));
|
||||
if (bytes_read != sizeof(loaded_table)) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (!access_code_table_is_valid(&loaded_table)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
code_table = loaded_table;
|
||||
code_table_loaded = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct settings_handler access_codes_settings = {
|
||||
.name = ACCESS_CODES_SETTINGS_ROOT,
|
||||
.h_set = access_codes_settings_set,
|
||||
};
|
||||
|
||||
int access_codes_init(void) {
|
||||
int ret;
|
||||
|
||||
access_codes_set_default_table();
|
||||
code_table_loaded = false;
|
||||
|
||||
ret = settings_subsys_init();
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = settings_register(&access_codes_settings);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = settings_load_subtree(ACCESS_CODES_SETTINGS_ROOT);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!code_table_loaded || !access_code_table_is_valid(&code_table)) {
|
||||
access_codes_set_default_table();
|
||||
return access_codes_save();
|
||||
}
|
||||
|
||||
if (code_table.count == 0) {
|
||||
access_codes_set_default_table();
|
||||
return access_codes_save();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool access_codes_is_valid(const char *code, size_t length) {
|
||||
if (!access_code_is_digit_string(code, length)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < ACCESS_CODE_MAX_COUNT; i++) {
|
||||
if (!code_table.slots[i].enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (memcmp(code_table.slots[i].code, code, ACCESS_CODE_LENGTH) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int access_codes_set(size_t slot, const char *code) {
|
||||
if (slot >= ACCESS_CODE_MAX_COUNT) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!access_code_is_digit_string(code, ACCESS_CODE_LENGTH)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!code_table.slots[slot].enabled) {
|
||||
code_table.count++;
|
||||
}
|
||||
|
||||
code_table.slots[slot].enabled = true;
|
||||
memcpy(code_table.slots[slot].code, code, ACCESS_CODE_LENGTH);
|
||||
code_table.slots[slot].code[ACCESS_CODE_LENGTH] = '\0';
|
||||
|
||||
return access_codes_save();
|
||||
}
|
||||
|
||||
int access_codes_clear(size_t slot) {
|
||||
if (slot >= ACCESS_CODE_MAX_COUNT) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!code_table.slots[slot].enabled) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
code_table.slots[slot].enabled = false;
|
||||
memset(code_table.slots[slot].code, 0, sizeof(code_table.slots[slot].code));
|
||||
code_table.count--;
|
||||
|
||||
return access_codes_save();
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* OpenParcelBox
|
||||
* Copyright (c) 2026
|
||||
*
|
||||
* Persistent access code storage.
|
||||
*/
|
||||
|
||||
#ifndef ACCESS_CODES_H
|
||||
#define ACCESS_CODES_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#define ACCESS_CODE_LENGTH 6
|
||||
#define ACCESS_CODE_MAX_COUNT 10
|
||||
|
||||
/**
|
||||
* @brief Initialize persistent access code storage.
|
||||
*
|
||||
* Loads stored codes from non-volatile settings. If no valid table exists yet,
|
||||
* the development code 784512 is stored in slot 0.
|
||||
*
|
||||
* @return 0 on success, negative value on error.
|
||||
*/
|
||||
int access_codes_init(void);
|
||||
|
||||
/**
|
||||
* @brief Validate a six-digit access code.
|
||||
*
|
||||
* @param code Six ASCII digits, not necessarily null-terminated.
|
||||
* @param length Number of characters available in code.
|
||||
*
|
||||
* @return true when the code matches an enabled slot.
|
||||
*/
|
||||
bool access_codes_is_valid(const char *code, size_t length);
|
||||
|
||||
/**
|
||||
* @brief Store or replace a six-digit code in a slot.
|
||||
*
|
||||
* @param slot Slot index from 0 to ACCESS_CODE_MAX_COUNT - 1.
|
||||
* @param code Six ASCII digits.
|
||||
*
|
||||
* @return 0 on success, negative value on error.
|
||||
*/
|
||||
int access_codes_set(size_t slot, const char *code);
|
||||
|
||||
/**
|
||||
* @brief Disable one stored code slot.
|
||||
*
|
||||
* @param slot Slot index from 0 to ACCESS_CODE_MAX_COUNT - 1.
|
||||
*
|
||||
* @return 0 on success, negative value on error.
|
||||
*/
|
||||
int access_codes_clear(size_t slot);
|
||||
|
||||
#endif /* ACCESS_CODES_H */
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <zephyr/kernel.h>
|
||||
|
||||
#define BUZZER_KEY_BEEP_MS 50
|
||||
#define BUZZER_SUCCESS_BEEP_MS 400
|
||||
|
||||
/* D0 on the Seeed Studio XIAO nRF52840 is mapped to GPIO0 pin 2. */
|
||||
static const struct gpio_dt_spec buzzer_gpio = {
|
||||
@@ -52,4 +53,11 @@ void buzzer_beep_key(void) {
|
||||
buzzer_off();
|
||||
}
|
||||
|
||||
void buzzer_beep_success(void) {
|
||||
/* Longer active pulse used when an unlock code is accepted. */
|
||||
buzzer_on();
|
||||
k_msleep(BUZZER_SUCCESS_BEEP_MS);
|
||||
buzzer_off();
|
||||
}
|
||||
|
||||
void buzzer_off(void) { gpio_pin_set_raw(buzzer_gpio.port, buzzer_gpio.pin, 0); }
|
||||
|
||||
@@ -40,6 +40,14 @@ void buzzer_on(void);
|
||||
*/
|
||||
void buzzer_beep_key(void);
|
||||
|
||||
/**
|
||||
* @brief Emit a long success beep.
|
||||
*
|
||||
* This feedback is used when a valid access code is accepted before opening
|
||||
* the lock.
|
||||
*/
|
||||
void buzzer_beep_success(void);
|
||||
|
||||
/**
|
||||
* @brief Turn off the buzzer output.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* OpenParcelBox
|
||||
* Copyright (c) 2026
|
||||
*
|
||||
* Lock control driver.
|
||||
*/
|
||||
|
||||
#include "lock_control.h"
|
||||
|
||||
#include "board_config.h"
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/sys/printk.h>
|
||||
|
||||
#define LOCK_CONTROL_OPEN_PULSE_MS 100
|
||||
|
||||
/* D9 is declared in the application devicetree overlay as GPIO_ACTIVE_HIGH. */
|
||||
static const struct gpio_dt_spec lock_control_gpio =
|
||||
GPIO_DT_SPEC_GET(LOCK_CONTROL_GPIO_NODE, gpios);
|
||||
|
||||
int lock_control_init(void) {
|
||||
int ret;
|
||||
|
||||
if (!gpio_is_ready_dt(&lock_control_gpio)) {
|
||||
printk("Lock control GPIO is not ready\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
printk("Lock control GPIO ready\n");
|
||||
|
||||
ret = gpio_pin_configure_dt(&lock_control_gpio, GPIO_OUTPUT_INACTIVE);
|
||||
if (ret < 0) {
|
||||
printk("Lock control GPIO configure inactive failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
printk("Lock control GPIO configured inactive: %d\n", ret);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lock_control_open(void) {
|
||||
int ret;
|
||||
int inactive_ret;
|
||||
|
||||
ret = gpio_pin_set_dt(&lock_control_gpio, 1);
|
||||
if (ret < 0) {
|
||||
printk("Lock control GPIO active set failed: %d\n", ret);
|
||||
|
||||
inactive_ret = gpio_pin_set_dt(&lock_control_gpio, 0);
|
||||
if (inactive_ret < 0) {
|
||||
printk("Lock control GPIO inactive recovery failed: %d\n", inactive_ret);
|
||||
} else {
|
||||
printk("Lock control GPIO inactive recovery: %d\n", inactive_ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
printk("Lock control GPIO active set: %d\n", ret);
|
||||
|
||||
k_msleep(LOCK_CONTROL_OPEN_PULSE_MS);
|
||||
|
||||
ret = gpio_pin_set_dt(&lock_control_gpio, 0);
|
||||
if (ret < 0) {
|
||||
printk("Lock control GPIO inactive set failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
printk("Lock control GPIO inactive set: %d\n", ret);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* OpenParcelBox
|
||||
* Copyright (c) 2026
|
||||
*
|
||||
* Lock control driver.
|
||||
*/
|
||||
|
||||
#ifndef LOCK_CONTROL_H
|
||||
#define LOCK_CONTROL_H
|
||||
|
||||
/**
|
||||
* @brief Initialize the lock control output.
|
||||
*
|
||||
* Configures D9 as a push-pull GPIO output and leaves it low.
|
||||
*
|
||||
* @return 0 on success, negative value on error.
|
||||
*/
|
||||
int lock_control_init(void);
|
||||
|
||||
/**
|
||||
* @brief Send an unlock pulse to the lock.
|
||||
*
|
||||
* Drives D9 high for 100 ms, then returns it low.
|
||||
*
|
||||
* @return 0 on success, negative value on error.
|
||||
*/
|
||||
int lock_control_open(void);
|
||||
|
||||
#endif /* LOCK_CONTROL_H */
|
||||
+84
-14
@@ -9,14 +9,16 @@
|
||||
* Current test:
|
||||
* - RGB LED
|
||||
* - Buzzer
|
||||
* - Lock state feedback
|
||||
* - Lock control and state feedback
|
||||
* - GPIO Expander
|
||||
* - 4x4 Keypad
|
||||
*/
|
||||
|
||||
#include "access_codes.h"
|
||||
#include "buzzer.h"
|
||||
#include "keypad.h"
|
||||
#include "led.h"
|
||||
#include "lock_control.h"
|
||||
#include "lock_state.h"
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
@@ -30,19 +32,44 @@
|
||||
#define MAIN_LOOP_DELAY_MS 20
|
||||
#define KEY_PRESS_LED_MS 100
|
||||
#define LOCK_OPEN_BEEP_INTERVAL_MS 2000
|
||||
#define INVALID_CODE_LED_MS 1000
|
||||
#define INVALID_CODE_BEEP_COUNT 3
|
||||
#define INVALID_CODE_BEEP_MS 50
|
||||
#define INVALID_CODE_BEEP_PAUSE_MS 80
|
||||
#define INVALID_CODE_BEEP_SEQUENCE_MS \
|
||||
((INVALID_CODE_BEEP_COUNT * INVALID_CODE_BEEP_MS) + \
|
||||
((INVALID_CODE_BEEP_COUNT - 1) * INVALID_CODE_BEEP_PAUSE_MS))
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Private helpers
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
static void set_idle_led(bool lock_open) {
|
||||
static void set_lock_led(bool lock_open) {
|
||||
if (lock_open) {
|
||||
led_set_red();
|
||||
} else {
|
||||
led_set_green();
|
||||
} else {
|
||||
led_off();
|
||||
}
|
||||
}
|
||||
|
||||
static bool key_is_digit(char key) { return key >= '0' && key <= '9'; }
|
||||
|
||||
static void signal_invalid_code(void) {
|
||||
led_set_red();
|
||||
|
||||
for (int i = 0; i < INVALID_CODE_BEEP_COUNT; i++) {
|
||||
buzzer_on();
|
||||
k_msleep(INVALID_CODE_BEEP_MS);
|
||||
buzzer_off();
|
||||
|
||||
if (i < (INVALID_CODE_BEEP_COUNT - 1)) {
|
||||
k_msleep(INVALID_CODE_BEEP_PAUSE_MS);
|
||||
}
|
||||
}
|
||||
|
||||
k_msleep(INVALID_CODE_LED_MS - INVALID_CODE_BEEP_SEQUENCE_MS);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Public API
|
||||
* -------------------------------------------------------------------------- */
|
||||
@@ -52,13 +79,22 @@ int main(void) {
|
||||
bool lock_open;
|
||||
bool previous_lock_open;
|
||||
int64_t next_lock_open_beep_ms;
|
||||
char entered_code[ACCESS_CODE_LENGTH];
|
||||
size_t entered_code_length;
|
||||
|
||||
if (led_init() < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
led_set_red();
|
||||
k_msleep(1000);
|
||||
led_off();
|
||||
|
||||
if (lock_control_init() < 0) {
|
||||
led_set_magenta();
|
||||
|
||||
while (1) {
|
||||
k_msleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
if (buzzer_init() < 0) {
|
||||
led_set_magenta();
|
||||
@@ -76,6 +112,14 @@ int main(void) {
|
||||
}
|
||||
}
|
||||
|
||||
if (access_codes_init() < 0) {
|
||||
led_set_magenta();
|
||||
|
||||
while (1) {
|
||||
k_msleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
if (keypad_init() < 0) {
|
||||
led_set_magenta();
|
||||
|
||||
@@ -87,22 +131,23 @@ int main(void) {
|
||||
lock_open = lock_state_is_open();
|
||||
previous_lock_open = lock_open;
|
||||
next_lock_open_beep_ms = k_uptime_get();
|
||||
entered_code_length = 0;
|
||||
|
||||
set_idle_led(lock_open);
|
||||
set_lock_led(lock_open);
|
||||
|
||||
printf("\n");
|
||||
printf("========================================\n");
|
||||
printf("OpenParcelBox Firmware\n");
|
||||
printf("Hardware Test: RGB LED + Buzzer + Lock State + Keypad\n");
|
||||
printf("Hardware Test: RGB LED + Buzzer + Lock + Keypad\n");
|
||||
printf("========================================\n");
|
||||
|
||||
set_idle_led(lock_open);
|
||||
set_lock_led(lock_open);
|
||||
|
||||
while (1) {
|
||||
lock_open = lock_state_is_open();
|
||||
|
||||
if (lock_open != previous_lock_open) {
|
||||
set_idle_led(lock_open);
|
||||
set_lock_led(lock_open);
|
||||
previous_lock_open = lock_open;
|
||||
|
||||
if (lock_open) {
|
||||
@@ -125,14 +170,39 @@ int main(void) {
|
||||
printf("Key pressed: %c\n", key);
|
||||
|
||||
if (!lock_open) {
|
||||
/* keypad_get_key() returns once per press, so holding a key will not beep continuously. */
|
||||
buzzer_beep_key();
|
||||
if (key == '*' || key == '#' || key == 'B') {
|
||||
entered_code_length = 0;
|
||||
buzzer_beep_key();
|
||||
} else if (key_is_digit(key)) {
|
||||
entered_code[entered_code_length] = key;
|
||||
entered_code_length++;
|
||||
|
||||
led_set_blue();
|
||||
if (entered_code_length == ACCESS_CODE_LENGTH) {
|
||||
if (access_codes_is_valid(entered_code, entered_code_length)) {
|
||||
printf("Received valid access code\n");
|
||||
buzzer_beep_success();
|
||||
|
||||
if (lock_control_open() < 0) {
|
||||
printf("Lock control open failed\n");
|
||||
}
|
||||
} else {
|
||||
printf("Invalid access code\n");
|
||||
signal_invalid_code();
|
||||
}
|
||||
|
||||
entered_code_length = 0;
|
||||
} else {
|
||||
/*
|
||||
* keypad_get_key() returns once per press, so holding a key will
|
||||
* not beep continuously.
|
||||
*/
|
||||
buzzer_beep_key();
|
||||
}
|
||||
}
|
||||
|
||||
k_msleep(KEY_PRESS_LED_MS);
|
||||
|
||||
led_set_green();
|
||||
set_lock_led(lock_state_is_open());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -23,6 +23,7 @@ Seeed Studio XIAO BLE nRF52840
|
||||
| Active buzzer control | Output | D0 | GPIO signal to transistor driver |
|
||||
| Lock NC feedback | Input | D7 | KR-S79 NC contact, internal pull-up enabled |
|
||||
| Lock COM feedback reference | Output | D8 | KR-S79 COM contact, driven low for NC continuity detection |
|
||||
| Lock SIG command | Output | D9 | Active-high GPIO pulse to S8050/S8550 driver, no PWM |
|
||||
| PCF8574 I2C SDA | I/O | SDA | Development keypad GPIO expander |
|
||||
| PCF8574 I2C SCL | Output | SCL | Development keypad GPIO expander |
|
||||
| RGB LED red | Output | Board LED alias `led0` | On-board validation LED |
|
||||
@@ -35,7 +36,6 @@ Seeed Studio XIAO BLE nRF52840
|
||||
|
||||
These signals still need to be assigned after hardware validation:
|
||||
|
||||
- Lock `SIG` command.
|
||||
- Production keypad matrix.
|
||||
- NFC antenna and matching network.
|
||||
- Battery voltage measurement.
|
||||
|
||||
Reference in New Issue
Block a user