init repo

This commit is contained in:
2026-07-01 10:34:38 +08:00
commit 9b56774d43
2 changed files with 158 additions and 0 deletions

88
README.md Normal file
View File

@@ -0,0 +1,88 @@
# MicroPython Wiegand Driver
A lightweight, event-driven Wiegand protocol driver for MicroPython.
Supports 26-bit Wiegand, 34-bit Wiegand, and 8-bit keypad input.
No polling, no debouncing just pure interrupt-driven collection with a one-shot timeout.
## Features
- **Fully interrupt-driven** Records bits on both DATA0/DATA1 falling edges.
- **Oneshot timer** 80ms timeout started only on the first bit; zero CPU overhead when idle.
- **No software debouncing** Relies on frame length and parity checks to reject noise.
- **Multiformat support** Automatically detects 26bit, 34bit, and 8bit keypad frames.
- **Raw payload output** Returns the data bits without parity bits (24 bits for WG26, 32 bits for WG34), giving the application full control over facility code / card number extraction.
- **Automatic parity validation** Standard Wiegand even/odd parity check.
- **Keypad verification** For 8bit keypad frames, validates that the upper nibble is the bitwise complement of the lower nibble, then returns the key number (015).
- **Safe for MicroPython** Uses a preallocated `bytearray` buffer; no memory allocation inside interrupts.
## Installation
Copy the file `wiegand.py` to your MicroPython device (e.g., into `/lib/` or the root directory).
## Usage
```python
from machine import Pin
from wiegand import Wiegand
def on_wiegand(data_type, value):
if data_type == 'wg26':
facility = (value >> 16) & 0xFF
card = value & 0xFFFF
print("WG26 - Facility: {}, Card: {}".format(facility, card))
elif data_type == 'wg34':
print("WG34 raw payload: 0x{:08X}".format(value))
elif data_type == 'keypad':
print("Key pressed:", value)
# DATA0 on GPIO16, DATA1 on GPIO17
reader = Wiegand(pin0=16, pin1=17, callback=on_wiegand)
```
## API
### `Wiegand(pin0, pin1, callback, buf_len=34)`
- **`pin0`** GPIO number for the DATA0 (zerobit) line.
- **`pin1`** GPIO number for the DATA1 (onebit) line.
- **`callback`** Function called when a valid frame is received.
Signature: `callback(data_type, value)`
- `data_type` (str): one of `'wg26'`, `'wg34'`, `'keypad'`.
- `value` (int):
- `'wg26'`: 24bit payload (bits 124 of the 26bit frame, parity bits removed).
- `'wg34'`: 32bit payload (bits 132 of the 34bit frame).
- `'keypad'`: integer 015, the key number.
- **`buf_len`** (optional) maximum buffer size, default `34`. Must be at least as large as the longest expected frame.
## Supported Formats
| Format | Total bits | Payload bits returned | Parity check |
|---------|------------|------------------------|----------------------------------------|
| WG26 | 26 | 24 | Even (first half) / Odd (second half) |
| WG34 | 34 | 32 | Even (first half) / Odd (second half) |
| Keypad | 8 | lower nibble (015) | Upper nibble must be ~lower nibble |
*For WG26 and WG34 the leading and trailing parity bits are stripped. The user is responsible for extracting facility code and card number from the raw payload.*
## How It Works
1. **Pin setup** Both DATA0 and DATA1 pins are configured as inputs with internal pullups and fallingedge interrupts.
2. **Interrupt handler** On each falling edge, the handler reads the state of **both** pins and encodes it as `0b01` (bit 0) or `0b10` (bit 1). Any other combination (e.g., both low) is ignored.
3. **Oneshot timer** When the **first** valid bit is recorded, a hardware timer is started in `ONE_SHOT` mode with an 80ms period.
4. **Timeout** When the timer fires, the collected bit array is copied, parsed, and the buffer is reset.
- If the frame length is 26 or 34 and parity checks pass, the callback is called with the raw payload.
- If the frame length is 8 and the keypad nibble inversion check passes, the key number is reported.
- Otherwise the frame is silently discarded.
5. **Ready for next read** The buffer index is set to zero immediately after the timeout, so a new card can be processed right away.
## Notes
- The 80ms timeout works for virtually all standard Wiegand readers. If you have an exceptionally slow reader, adjust the `period` value in `self.timer.init(period=...)` inside the source.
- No debouncing is applied. If your hardware produces spurious edges, the parity or length checks will reject the frame, and no callback will fire. This keeps the interrupt handler extremely short.
- The driver uses a preallocated `bytearray` it never allocates memory in the IRQ, making it safe for all MicroPython ports.
- If your platform does not support `Timer.ONE_SHOT`, you can replace it with a periodic timer that calls `self.timer.deinit()` on the first callback.
- This library is **not threadsafe** it is designed for the singlethreaded MicroPython environment.
## License
MIT License.

70
wiegand.py Normal file
View File

@@ -0,0 +1,70 @@
from machine import Pin, Timer
class Wiegand:
def __init__(self, pin0, pin1, callback, buf_len=34):
self.pin0 = Pin(pin0, Pin.IN, Pin.PULL_UP)
self.pin1 = Pin(pin1, Pin.IN, Pin.PULL_UP)
self.callback = callback
self.buf = bytearray(buf_len)
self.idx = 0
self.timer = Timer(-1)
self.pin0.irq(trigger=Pin.IRQ_FALLING, handler=self._irq)
self.pin1.irq(trigger=Pin.IRQ_FALLING, handler=self._irq)
def _irq(self, p):
# 同时读取两根线,组合成 0b01 (bit=0) 或 0b10 (bit=1)
state = (self.pin1.value() << 1) | self.pin0.value()
if state not in (0b01, 0b10):
return
if self.idx < len(self.buf):
self.buf[self.idx] = state
self.idx += 1
if self.idx == 1:
# 只启动一次80ms 后触发解析
self.timer.init(period=80, mode=Timer.ONE_SHOT, callback=self._timeout)
def _timeout(self, t):
n = self.idx
self.idx = 0
data = bytes(self.buf[:n]) # 拷贝,防止中断覆盖
self._parse(n, data)
def _parse(self, n, data):
if n == 26 and self._check_parity(data, 26):
# 剔除首尾校验位,保留中间 24 位有效载荷
payload = self._extract_bits(data, 1, 24)
self.callback('wg26', payload)
elif n == 34 and self._check_parity(data, 34):
# 剔除首尾校验位,保留中间 32 位有效载荷
payload = self._extract_bits(data, 1, 32)
self.callback('wg34', payload)
elif n == 8:
# 键盘低4位键值高4位为低4位取反
val = self._extract_bits(data, 0, 8)
if ((val >> 4) ^ (val & 0x0F)) == 0x0F:
self.callback('keypad', val & 0x0F)
# 其余长度或校验失败直接丢弃
def _check_parity(self, data, bits):
"""标准 Wiegand 奇偶校验前一半偶数个1后一半奇数个1"""
half = bits // 2
cnt_even = 0
cnt_odd = 0
for i in range(bits):
if data[i] == 0b10: # 该比特为 1
if i < half:
cnt_even += 1
else:
cnt_odd += 1
return (cnt_even % 2 == 0) and (cnt_odd % 2 == 1)
def _extract_bits(self, data, start, count):
"""从 data 中提取连续比特MSB first返回整数"""
val = 0
for i in range(start, start + count):
val <<= 1
if data[i] == 0b10:
val |= 1
return val