Add doorcontrol app for managing HID door controllers

Just read-only access to the event log dump for now
This commit is contained in:
Adam Goldsmith 2023-01-24 21:21:26 -05:00
parent 12d5c9cba5
commit a6c531c22f
10 changed files with 281 additions and 0 deletions

View File

@ -39,6 +39,7 @@ INSTALLED_APPS = [
"rentals.apps.RentalsConfig",
"membershipworks.apps.MembershipworksConfig",
"paperwork.apps.PaperworkConfig",
"doorcontrol.apps.DoorControlConfig",
]
MIDDLEWARE = [
@ -75,6 +76,7 @@ WSGI_APPLICATION = "cmsmanage.wsgi.application"
DATABASE_ROUTERS = [
"membershipworks.routers.MembershipWorksRouter",
"doorcontrol.routers.DoorControlRouter",
]
# Default URL to redirect to after authentication

0
doorcontrol/__init__.py Normal file
View File

19
doorcontrol/admin.py Normal file
View File

@ -0,0 +1,19 @@
from django.contrib import admin
from .models import HIDEvent
@admin.register(HIDEvent)
class HIDEventAdmin(admin.ModelAdmin):
search_fields = ["description", "forename", "surname", "cardholder_id"]
list_display = ["door_name", "timestamp", "event_type", "description", "is_red"]
list_filter = ["door_name", "event_type"]
def has_add_permission(self, request, obj=None):
return False
def has_change_permission(self, request, obj=None):
return False
def has_delete_permission(self, request, obj=None):
return False

7
doorcontrol/apps.py Normal file
View File

@ -0,0 +1,7 @@
from django.apps import AppConfig
class DoorControlConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "doorcontrol"
verbose_name = "Door Control"

View File

@ -0,0 +1,99 @@
# Generated by Django 4.1.3 on 2023-01-25 02:18
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="HIDEvent",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("door_name", models.CharField(db_column="doorName", max_length=64)),
("timestamp", models.DateTimeField()),
(
"event_type",
models.IntegerField(
choices=[
(1022, "Denied Access: Card Not Found"),
(1023, "Denied Access Access: PIN Not Found"),
(2020, "Granted Access"),
(2021, "Granted Access: Extended Time"),
(2024, "Denied Access: Schedule"),
(2029, "Denied Access: Wrong PIN"),
(2036, "Denied Access: Card Expired"),
(2042, "Denied Access: PIN Lockout"),
(2043, "Denied Access: Unassigned Card"),
(2044, "Denied Access: Unassigned Access PIN"),
(2046, "Denied Access: PIN Expired"),
(4034, "Alarm Acknowledged"),
(4035, "Door Locked: Scheduled"),
(4036, "Door Unlocked: Scheduled"),
(4041, "Door Forced Alarm"),
(4042, "Door Held Alarm"),
(4043, "Tamper Switch Alarm"),
(4044, "AC Failure"),
(4045, "Battery Failure"),
(4051, "REX Switch Alarm"),
(7020, "Time Set To"),
(12031, "Granted Access: Manual"),
(12032, "Door Unlocked"),
(12033, "Door Locked"),
],
db_column="eventType",
),
),
("reader_address", models.IntegerField(db_column="readerAddress")),
(
"cardholder_id",
models.IntegerField(
blank=True, db_column="cardholderID", null=True
),
),
(
"command_status",
models.BooleanField(
blank=True, db_column="commandStatus", null=True
),
),
("forename", models.TextField(blank=True, null=True)),
("surname", models.TextField(blank=True, null=True)),
(
"io_state",
models.BooleanField(blank=True, db_column="ioState", null=True),
),
(
"new_time",
models.DateTimeField(blank=True, db_column="newTime", null=True),
),
(
"old_time",
models.DateTimeField(blank=True, db_column="oldTime", null=True),
),
(
"raw_card_number",
models.CharField(
blank=True, db_column="rawCardNumber", max_length=8, null=True
),
),
],
options={
"db_table": "hidevent",
"ordering": ("-timestamp",),
"managed": False,
},
),
]

View File

122
doorcontrol/models.py Normal file
View File

@ -0,0 +1,122 @@
from django.contrib import admin
from django.db import models
class HIDEvent(models.Model):
class EventType(models.IntegerChoices):
DENIED_ACCESS_CARD_NOT_FOUND = 1022, "Denied Access: Card Not Found"
DENIED_ACCESS_ACCESS_PIN_NOT_FOUND = 1023, "Denied Access Access: PIN Not Found"
GRANTED_ACCESS = 2020, "Granted Access"
GRANTED_ACCESS_EXTENDED_TIME = 2021, "Granted Access: Extended Time"
DENIED_ACCESS_SCHEDULE = 2024, "Denied Access: Schedule"
DENIED_ACCESS_WRONG_PIN = 2029, "Denied Access: Wrong PIN"
DENIED_ACCESS_CARD_EXPIRED = 2036, "Denied Access: Card Expired"
DENIED_ACCESS_PIN_LOCKOUT = 2042, "Denied Access: PIN Lockout"
DENIED_ACCESS_UNASSIGNED_CARD = 2043, "Denied Access: Unassigned Card"
DENIED_ACCESS_UNASSIGNED_ACCESS_PIN = (
2044,
"Denied Access: Unassigned Access PIN",
)
DENIED_ACCESS_PIN_EXPIRED = 2046, "Denied Access: PIN Expired"
ALARM_ACKNOWLEDGED = 4034, "Alarm Acknowledged"
DOOR_LOCKED_SCHEDULED = 4035, "Door Locked: Scheduled"
DOOR_UNLOCKED_SCHEDULED = 4036, "Door Unlocked: Scheduled"
DOOR_FORCED_ALARM = 4041, "Door Forced Alarm"
DOOR_HELD_ALARM = 4042, "Door Held Alarm"
TAMPER_SWITCH_ALARM = 4043, "Tamper Switch Alarm"
AC_FAILURE = 4044, "AC Failure"
BATTERY_FAILURE = 4045, "Battery Failure"
REX_SWITCH_ALARM = 4051, "REX Switch Alarm"
TIME_SET_TO = 7020, "Time Set To"
GRANTED_ACCESS_MANUAL = 12031, "Granted Access: Manual"
DOOR_UNLOCKED = 12032, "Door Unlocked"
DOOR_LOCKED = 12033, "Door Locked"
door_name = models.CharField(max_length=64, db_column="doorName")
timestamp = models.DateTimeField()
event_type = models.IntegerField(db_column="eventType", choices=EventType.choices)
reader_address = models.IntegerField(db_column="readerAddress")
cardholder_id = models.IntegerField(blank=True, null=True, db_column="cardholderID")
command_status = models.BooleanField(
blank=True, null=True, db_column="commandStatus"
)
forename = models.TextField(blank=True, null=True)
surname = models.TextField(blank=True, null=True)
io_state = models.BooleanField(blank=True, null=True, db_column="ioState")
new_time = models.DateTimeField(blank=True, null=True, db_column="newTime")
old_time = models.DateTimeField(blank=True, null=True, db_column="oldTime")
raw_card_number = models.CharField(
max_length=8, blank=True, null=True, db_column="rawCardNumber"
)
@property
def description(self):
"""
Based on `Global.localeStrings` from /html/en_EN/en_EN.js
and `function eventDataHandler` from /html/modules/hid-dashboard.js
on a HID EDGE EVO Solo
"""
name = f"{self.forename} {self.surname}"
direction = "IN" if self.reader_address == 0 else "OUT"
event_types = {
1022: f"Denied Access, {direction} Card Not Found {self.raw_card_number}",
1023: f"Denied Access, {direction} Access PIN Not Found {self.raw_card_number}",
2020: f"Granted Access, {direction} {name}",
2021: f"Granted Access, {direction} Extended Time {name}",
2024: f"Denied Access, {direction} Schedule {name}",
2029: f"Denied Access, {direction} Wrong PIN {name}",
2036: f"Denied Access, {direction} Card Expired {name}",
2042: f"Denied Access, {direction} PIN Lockout {name}",
2043: f"Denied Access, {direction} Unassigned Card {self.raw_card_number}",
2044: f"Denied Access, {direction} Unassigned Access PIN {self.raw_card_number}",
2046: f"Denied Access - PIN Expired {name}",
4034: "Alarm Acknowledged",
4035: "Door Locked-Scheduled",
4036: "Door Unlocked-Scheduled",
4041: "Door Forced Alarm",
4042: "Door Held Alarm",
4043: "Tamper Switch Alarm",
4044: "AC Failure",
4045: "Battery Failure",
4051: "REX Switch Alarm",
7020: f"Time Set to: {self.new_time}",
12031: f"Granted Access, {direction} Manual",
12032: "Door Unlocked",
12033: "Door Locked",
}
return event_types.get(self.event_type, f"Unknown Event Type {self.event_type}")
@admin.display(boolean=True)
def is_red(self):
"""Based on `function isRedEvent` from /html/hid-global.js on a HID EDGE EVO Solo"""
return self.event_type in [
1022,
1023,
2024,
2029,
2036,
2042,
2043,
2046,
4041,
4042,
4043,
4044,
4045,
]
def __str__(self):
return f"{self.door_name} {self.timestamp} - {self.description}"
class Meta:
constraints = [
models.UniqueConstraint(
fields=["door_name", "timestamp", "event_type"], name="unique_hidevent"
)
]
managed = False
db_table = "hidevent"
ordering = ("-timestamp",)

26
doorcontrol/routers.py Normal file
View File

@ -0,0 +1,26 @@
class DoorControlRouter:
app_label = "doorcontrol"
db = "doors"
def db_for_read(self, model, **hints):
if model._meta.app_label == self.app_label:
return self.db
return None
def db_for_write(self, model, **hints):
if model._meta.app_label == self.app_label:
return self.db
return None
def allow_migrate(self, db, app_label, model_name=None, **hints):
if db == self.db:
return False
return None
def allow_relation(self, obj1, obj2, **hints):
if (
obj1._meta.app_label == self.app_label
or obj2._meta.app_label == self.app_label
):
return True
return None

3
doorcontrol/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

3
doorcontrol/views.py Normal file
View File

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.