forked from CMS/memberPlumbing
110 lines
3.9 KiB
Python
Executable File
110 lines
3.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
from collections import defaultdict
|
|
from xml.etree import ElementTree as ET
|
|
import os
|
|
import re
|
|
import requests
|
|
|
|
from common import *
|
|
|
|
def getStrings(targetIP):
|
|
"""Parses out the message strings from source."""
|
|
r = requests.get('https://' + targetIP + '/html/en_EN/en_EN.js',
|
|
auth=requests.auth.HTTPDigestAuth(DOOR_USERNAME, DOOR_PASSWORD),
|
|
verify=False)
|
|
regex = re.compile(r'([0-9]+)="([^"]*)')
|
|
strings = [regex.search(s) for s in r.text.split(';')
|
|
if s.startswith('localeStrings.eventDetails')]
|
|
print({int(g.group(1)): g.group(2) for g in strings})
|
|
|
|
# hardcoded for less bandwidth usage
|
|
eventStrings = {
|
|
1022: 'Denied Access{6} Card Not Found {3}',
|
|
1023: 'Denied Access{6} Access PIN Not Found {3}',
|
|
2020: 'Granted Access{6} {2}',
|
|
2021: 'Granted Access{6} Extended Time {2}',
|
|
2024: 'Denied Access{6} Schedule {2}',
|
|
2029: 'Denied Access{6} Wrong PIN {2}',
|
|
2036: 'Denied Access{6} Card Expired {2}',
|
|
2042: 'Denied Access{6} PIN Lockout {2}',
|
|
2043: 'Denied Access{6} Unassigned Card {3}',
|
|
2044: 'Denied Access{6} Unassigned Access PIN {3}',
|
|
2046: 'Denied Access - PIN Expired {2}',
|
|
4051: 'REX Switch Alarm',
|
|
7020: 'Time Set to: {5}',
|
|
12031: 'Granted Access{6} Manual',
|
|
12032: 'Door Unlocked',
|
|
12033: 'Door Locked',
|
|
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',
|
|
}
|
|
|
|
def formatMessage(event):
|
|
att = defaultdict(str, event.attrib)
|
|
eventType = int(att["eventType"])
|
|
return att["timestamp"], eventStrings[eventType].format(
|
|
'ios-' + att['ioState'],
|
|
'status-' + att['commandStatus'],
|
|
att['forename'] + " " + att['surname'],
|
|
att['rawCardNumber'],
|
|
att['oldTime'],
|
|
att['newTime'],
|
|
" IN" if att['readerAddress'] == '0' else " OUT")
|
|
|
|
def getMessages(doorName, doorIP):
|
|
# get parameters for messages to get?
|
|
# honestly not really sure why this is required, their API is confusing
|
|
parXMLIn = ET.Element("VertXMessage")
|
|
ET.SubElement(parXMLIn, "hid:EventMessages", attrib={"action": "LR"})
|
|
parXMLOut = doXMLRequest(doorIP, ET.tostring(parXMLIn))
|
|
ET.dump(parXMLOut)
|
|
|
|
if os.path.exists("logs/" + doorName + ".xml"):
|
|
# read last log
|
|
tree = ET.ElementTree(None, "logs/" + doorName + ".xml")
|
|
root = tree.getroot()
|
|
recordCount = int(parXMLOut[0].attrib["historyRecordMarker"]) - \
|
|
int(root[0][0].attrib["recordMarker"])
|
|
else:
|
|
# first run for this door
|
|
root = None
|
|
recordCount = 1000
|
|
|
|
if recordCount == 0:
|
|
print("No records to get!")
|
|
return
|
|
print("Getting", recordCount, "records")
|
|
# get the actual messages
|
|
eventsXMLIn = ET.Element("VertXMessage")
|
|
ET.SubElement(eventsXMLIn, "hid:EventMessages",
|
|
attrib={"action": "LR",
|
|
"recordCount": str(recordCount),
|
|
"historyRecordMarker": parXMLOut[0].attrib["historyRecordMarker"],
|
|
"historyTimestamp": parXMLOut[0].attrib["historyTimestamp"]})
|
|
eventsXMLOut = doXMLRequest(doorIP, ET.tostring(eventsXMLIn))
|
|
#TODO: handle modeRecords=true
|
|
|
|
for index, event in enumerate(eventsXMLOut[0]):
|
|
event.attrib["recordMarker"] = str(int(parXMLOut[0].attrib["historyRecordMarker"]) - index)
|
|
# print(formatMessage(event))
|
|
|
|
if root is None:
|
|
tree = ET.ElementTree(eventsXMLOut)
|
|
else:
|
|
for event in reversed(eventsXMLOut[0]):
|
|
root[0].insert(0, event)
|
|
tree.write("logs/" + doorName + ".xml")
|
|
|
|
def main():
|
|
for doorName, doorData in config["doors"].items():
|
|
getMessages(doorName, doorData["ip"])
|
|
|
|
if __name__ == '__main__':
|
|
main()
|