diff --git a/hid/DoorController.py b/hid/DoorController.py index fb34591..73b63dd 100644 --- a/hid/DoorController.py +++ b/hid/DoorController.py @@ -26,7 +26,7 @@ class DoorController(): self.name = name self.access = access - def doImportRequest(self, params=None, files=None): + def doImport(self, params=None, files=None): """Send a request to the door control import script""" r = requests.post( 'https://' + self.ip + '/cgi-bin/import.cgi', @@ -42,10 +42,10 @@ class DoorController(): def doCSVImport(self, csv): """Do the CSV import procedure on a door control""" - self.doImportRequest({"task": "importInit"}) - self.doImportRequest({"task": "importCardsPeople", "name": "cardspeopleschedule.csv"}, + self.doImport({"task": "importInit"}) + self.doImport({"task": "importCardsPeople", "name": "cardspeopleschedule.csv"}, {"importCardsPeopleButton": ("cardspeopleschedule.csv", csv, 'text/csv')}) - self.doImportRequest({"task": "importDone"}) + self.doImport({"task": "importDone"}) def doXMLRequest(self, xml, prefix=b''): if not isinstance(xml, bytes): @@ -62,7 +62,28 @@ class DoorController(): raise RemoteError(r) return resp_xml - def sendSchedules(self, schedules): + def get_scheduleMap(self): + schedules = self.doXMLRequest( + ROOT(E.Schedules({"action": "LR", + "recordOffset": "0", + "recordCount": "8"}))) + return {fmt.attrib["scheduleName"]: fmt.attrib["scheduleID"] + for fmt in schedules[0]} + + def get_schedules(self): + # TODO: might be able to do in one request + schedules = self.doXMLRequest(ROOT( + E.Schedules({"action": "LR"}))) + etree.dump(schedules) + + data = self.doXMLRequest(ROOT( + *[E.Schedules({"action": "LR", + "scheduleID": schedule.attrib["scheduleID"]}) + for schedule in schedules[0]])) + return ROOT(E_corp.Schedules({"action": "AD"}, + *[s[0] for s in data])) + + def set_schedules(self, schedules): # clear all people outString = StringIO() writer = csv.DictWriter(outString, fieldnames) @@ -84,20 +105,15 @@ class DoorController(): # load new schedules self.doXMLRequest(schedules) - def getSchedules(self): - # TODO: might be able to do in one request - schedules = self.doXMLRequest(ROOT( - E.Schedules({"action": "LR"}))) - etree.dump(schedules) + def get_cardFormats(self): + cardFormats = self.doXMLRequest( + ROOT(E.CardFormats({"action": "LR", + "responseFormat": "expanded"}))) - data = self.doXMLRequest(ROOT( - *[E.Schedules({"action": "LR", - "scheduleID": schedule.attrib["scheduleID"]}) - for schedule in schedules[0]])) - return ROOT(E_corp.Schedules({"action": "AD"}, - *[s[0] for s in data])) + return {fmt[0].attrib["value"]: fmt.attrib["formatID"] + for fmt in cardFormats[0].findall('{*}CardFormat[{*}FixedField]')} - def sendCardFormat(self, formatName, templateID, facilityCode): + def set_cardFormat(self, formatName, templateID, facilityCode): # TODO: add ability to delete formats # delete example: @@ -108,15 +124,22 @@ class DoorController(): E.FixedField({"value": str(facilityCode)})))) return self.doXMLRequest(el) - def lockOrUnlockDoor(self, lock=True): - el = ROOT( - E.Doors({"action": "CM", - "command": "lockDoor" if lock else "unlockDoor"})) - return self.doXMLRequest(el) + def get_cardholders(self): + return self.doXMLRequest( + ROOT(E.Cardholders({"action": "LR", + "responseFormat": "expanded", + "recordOffset": "0", + "recordCount": "1000"})))[0] - def getStatus(self): + def get_lock(self): el = ROOT( E.Doors({"action": "LR", "responseFormat": "status"})) xml = self.doXMLRequest(el) relayState = xml.find('./{*}Doors/{*}Door').attrib['relayState'] return "unlocked" if relayState == "set" else "locked" + + def set_lock(self, lock=True): + el = ROOT( + E.Doors({"action": "CM", + "command": "lockDoor" if lock else "unlockDoor"})) + return self.doXMLRequest(el) diff --git a/new_xml_based.py b/new_xml_based.py index 068f917..66abfba 100644 --- a/new_xml_based.py +++ b/new_xml_based.py @@ -83,78 +83,56 @@ Schedules: {self.schedules} schedules=list(levels.values())) def attribs(self): - out = {"forename": self.forename, - "surname": self.surname, - "middleName": self.middleName, - "email": self.email, - "phone": self.phone, - "custom1": "|".join(self.levels).replace("&", "and"), - "custom2": self.membershipWorksID} - return out + return {"forename": self.forename, + "surname": self.surname, + "middleName": self.middleName, + "email": self.email, + "phone": self.phone, + "custom1": "|".join(self.levels).replace("&", "and"), + "custom2": self.membershipWorksID} - def make_roles(self, door, schedulesMap): + def schedulesForDoor(self, door): if door.name not in self.doorAccess or self.onHold: return [] + else: + return self.schedules - for schedule in self.schedules: - yield E.Role({"roleID": self.cardholderID, - "scheduleID": schedulesMap[schedule], - "resourceID": "0"}) + def make_schedules(self, door, schedulesMap): + roles = [ + E.Role({"roleID": self.cardholderID, + "scheduleID": schedulesMap[schedule], + "resourceID": "0"}) + for schedule in self.schedulesForDoor(door)] + + return E.RoleSet({"action": "UD", "roleSetID": self.cardholderID}, + E.Roles(*roles)) def make_credentials(self, cardFormats): - for credential in self.credentials: - yield E.Credential( + credentials = [ + E.Credential( {"formatName": credential[0], "cardNumber": credential[1], "formatID": cardFormats[credential[0]], "isCard": "true", "cardholderID": self.cardholderID}) + for credential in self.credentials] -def get_cardholders(door, cardFormats): - cardholders = door.doXMLRequest( - ROOT(E.Cardholders({"action": "LR", - "responseFormat": "expanded", - "recordOffset": "0", - "recordCount": "1000"}))) + return E.Credentials({"action": "AD"}, *credentials) - for cardholder in cardholders[0]: - yield Member.from_cardholder(cardholder, cardFormats) - -def get_cardFormats(door): - cardFormats = door.doXMLRequest( - ROOT(E.CardFormats({"action": "LR", - "responseFormat": "expanded"}))) - - return {fmt[0].attrib["value"]: fmt.attrib["formatID"] - for fmt in cardFormats[0].findall('{*}CardFormat[{*}FixedField]')} - -def get_schedules(door): - schedules = door.doXMLRequest( - ROOT(E.Schedules({"action": "LR", - "recordOffset": "0", - "recordCount": "8"}))) - return {fmt.attrib["scheduleName"]: fmt.attrib["scheduleID"] - for fmt in schedules[0]} - -def update_credentials(member, cardFormats): - return E.Credentials({"action": "AD"}, *member.make_credentials(cardFormats)) - -def update_schedules(member, door, schedulesMap): - return E.RoleSet({"action": "UD", "roleSetID": member.cardholderID}, - E.Roles(*member.make_roles(door, schedulesMap))) def update_door(door, members): - cardFormats = get_cardFormats(door) - cardholders = {ch.membershipWorksID: ch - for ch in get_cardholders(door, cardFormats)} - schedulesMap = get_schedules(door) + cardFormats = door.get_cardFormats() + cardholders = {member.membershipWorksID: member + for member in [Member.from_cardholder(ch, cardFormats) + for ch in door.get_cardholders()]} + schedulesMap = door.get_scheduleMap() for member in members: # TODO: can I combine requests? if member.membershipWorksID in cardholders: # cardholder exists, compare contents ch = cardholders.pop(member.membershipWorksID) member.cardholderID = ch.cardholderID - if member.attribs() != ch.attribs(): # update cardholder + if member.attribs() != ch.attribs(): # update cardholder attributes print(f"- Updating profile for {member.forename} {member.surname}") print(f" - Old: {ch.attribs()}") print(f" - New: {member.attribs()}") @@ -162,19 +140,18 @@ def update_door(door, members): E.Cardholders( {"action": "UD", "cardholderID": member.cardholderID}, E.CardHolder(member.attribs())))) + if member.credentials != ch.credentials: print(f"- Updating card for {member.forename} {member.surname}") print(f" - {ch.credentials} -> {member.credentials}") - door.doXMLRequest(ROOT(update_credentials(member, cardFormats))) + door.doXMLRequest(ROOT(member.make_credentials(cardFormats))) - # TODO: might not handle people on hold correctly? - schedulesForDoor = member.schedules if door.name in member.doorAccess else [] - if schedulesForDoor != ch.schedules: + if member.schedulesForDoor(door) != ch.schedules: print("- Updating schedule for" + f" {member.forename} {member.surname}:" + - f" {ch.schedules} -> {member.schedules}") - door.doXMLRequest(ROOT(update_schedules(member, door, schedulesMap))) - else: # do add + f" {ch.schedules} -> {member.schedulesForDoor(door)}") + door.doXMLRequest(ROOT(member.make_schedules(door, schedulesMap))) + else: # cardholder did not exist, so add them print(f"- Adding Member:") print(member) resp = door.doXMLRequest(ROOT( @@ -182,8 +159,8 @@ def update_door(door, members): E.Cardholder(member.attribs())))) member.cardholderID = resp.find('{*}Cardholders/{*}Cardholder') \ .attrib["cardholderID"] - door.doXMLRequest(ROOT(update_credentials(member, cardFormats), - update_schedules(member, door, schedulesMap))) + door.doXMLRequest(ROOT(member.make_credentials(cardFormats), + member.update_schedules(door, schedulesMap))) # TODO: delete cardholders that are no longer members? @@ -198,9 +175,4 @@ def main(): print(door.name, door.ip) update_door(door, members) -# m = Member("test", "test", membershipWorksID="5af07954afd691b84c15a24d", -# credentials=[("A901146A-241", "20178")], -# schedules=["15:00-24:00"], -# doorAccess=["Front Door"]) -# update_door(doors["Front Door"], [m]) main()