Refactor visitor creation and improve error handling
- Simplify create_visitor method to match successful API calls - Enhance error logging for better debugging - Update README with new usage instructions - Add CHANGELOG to track project changesmain
parent
cc7ba33e48
commit
a17df9c330
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.2.0] - 2024-09-11
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Automatic setting of default door group when only one is available
|
||||||
|
- Improved error logging for API interactions
|
||||||
|
- CHANGELOG file to track project changes
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Refactored `create_visitor` method to simplify API calls
|
||||||
|
- Updated `process_reservations` method to use new `create_visitor` implementation
|
||||||
|
- Improved error handling in API request methods
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Issue with visitor creation failing due to invalid parameters
|
||||||
|
|
||||||
|
### Updated
|
||||||
|
- README with more detailed usage instructions and configuration details
|
||||||
56
README.md
56
README.md
|
|
@ -19,71 +19,41 @@ This project integrates UniFi Access with Airbnb reservations, automating the pr
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Clone the repository:
|
1. Clone the repository:
|
||||||
|
git clone https://github.com/yourusername/unifi-access-airbnb.git
|
||||||
git clone https://github.com/keithah/unifi-access-airbnb.git
|
|
||||||
|
|
||||||
cd unifi-access-airbnb
|
cd unifi-access-airbnb
|
||||||
|
|
||||||
3. Install the required packages:
|
2. Install the required packages:
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
|
|
||||||
4. Copy the example configuration file and edit it with your settings:
|
3. Copy the example configuration file and edit it with your settings:
|
||||||
cp unifi.conf.example unifi.conf
|
cp unifi.conf.example unifi.conf
|
||||||
|
|
||||||
nano unifi.conf
|
nano unifi.conf
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
Edit the `unifi.conf` file with your specific settings. Here's an explanation of each section:
|
|
||||||
|
|
||||||
- `[UniFi]`: UniFi Access API settings
|
|
||||||
- `[Hostex]`: Hostex API settings (if used)
|
|
||||||
- `[Airbnb]`: Airbnb ICS feed URL (if used)
|
|
||||||
- `[Simplepush]`: Simplepush notification settings
|
|
||||||
- `[Door]`: Default door group ID for visitor access
|
|
||||||
- `[Visitor]`: Check-in and check-out times
|
|
||||||
- `[General]`: General settings like log file name and PIN code length
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Run the script using:
|
Run the script using:
|
||||||
python main.py
|
python3 main.py
|
||||||
|
|
||||||
Optional arguments:
|
Optional arguments:
|
||||||
- `-v` or `--verbose`: Increase output verbosity
|
- `-v` or `--verbose`: Increase output verbosity
|
||||||
- `-l [LOG_FILE]` or `--log [LOG_FILE]`: Specify a log file (default is set in the config file)
|
- `-l [LOG_FILE]` or `--log [LOG_FILE]`: Specify a log file
|
||||||
|
- `--list-door-groups`: List available door groups
|
||||||
|
|
||||||
## Scheduling
|
## Configuration
|
||||||
|
|
||||||
To run the script automatically, you can set up a cron job. For example, to run it every hour:
|
Edit the `unifi.conf` file with your specific settings. Key sections include:
|
||||||
|
- `[UniFi]`: UniFi Access API settings
|
||||||
1. Open your crontab file:
|
- `[Hostex]`: Hostex API settings (if used)
|
||||||
crontab -e
|
- `[Airbnb]`: Airbnb ICS feed URL (if used)
|
||||||
2. Add the following line (adjust the path as needed):
|
- `[Door]`: Default door group ID for visitor access
|
||||||
0 * * * * /usr/bin/python3 /path/to/unifi-access-airbnb/main.py
|
- `[Visitor]`: Check-in and check-out times
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Contributions are welcome! Please feel free to submit a Pull Request.
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
||||||
|
|
||||||
1. Fork the project
|
|
||||||
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
|
|
||||||
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
|
|
||||||
4. Push to the branch (`git push origin feature/AmazingFeature`)
|
|
||||||
5. Open a Pull Request
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
## Acknowledgments
|
|
||||||
|
|
||||||
- UniFi Access for their API
|
|
||||||
- Hostex for their reservation management API
|
|
||||||
- Airbnb for providing ICS feed functionality
|
|
||||||
- Simplepush for their notification service
|
|
||||||
|
|
||||||
## Support
|
|
||||||
|
|
||||||
If you encounter any problems or have any questions, please open an issue on the GitHub repository.
|
|
||||||
|
|
||||||
|
|
|
||||||
17
config.py
17
config.py
|
|
@ -1,9 +1,21 @@
|
||||||
import configparser
|
import configparser
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def load_config():
|
def load_config():
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
config.read('unifi.conf')
|
config.read('unifi.conf')
|
||||||
|
|
||||||
|
logger.debug("Loaded sections from unifi.conf: %s", config.sections())
|
||||||
|
|
||||||
|
if 'Door' not in config:
|
||||||
|
logger.error("'Door' section not found in unifi.conf")
|
||||||
|
elif 'default_group_id' not in config['Door']:
|
||||||
|
logger.error("'default_group_id' not found in 'Door' section of unifi.conf")
|
||||||
|
else:
|
||||||
|
logger.debug("Found default_group_id in config: %s", config['Door']['default_group_id'])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'api_host': config['UniFi']['api_host'],
|
'api_host': config['UniFi']['api_host'],
|
||||||
'api_token': config['UniFi']['api_token'],
|
'api_token': config['UniFi']['api_token'],
|
||||||
|
|
@ -13,7 +25,7 @@ def load_config():
|
||||||
'simplepush_enabled': config['Simplepush'].getboolean('enabled', fallback=False),
|
'simplepush_enabled': config['Simplepush'].getboolean('enabled', fallback=False),
|
||||||
'simplepush_key': config['Simplepush'].get('key', fallback=None),
|
'simplepush_key': config['Simplepush'].get('key', fallback=None),
|
||||||
'simplepush_url': config['Simplepush'].get('url', fallback=None),
|
'simplepush_url': config['Simplepush'].get('url', fallback=None),
|
||||||
'default_door_group_id': config['Door']['default_group_id'],
|
'default_door_group_id': config['Door'].get('default_group_id', ''),
|
||||||
'check_in_time': config['Visitor']['check_in_time'],
|
'check_in_time': config['Visitor']['check_in_time'],
|
||||||
'check_out_time': config['Visitor']['check_out_time'],
|
'check_out_time': config['Visitor']['check_out_time'],
|
||||||
'use_hostex': 'Hostex' in config and config['Hostex']['api_key'],
|
'use_hostex': 'Hostex' in config and config['Hostex']['api_key'],
|
||||||
|
|
@ -21,3 +33,6 @@ def load_config():
|
||||||
'log_file': config['General']['log_file'],
|
'log_file': config['General']['log_file'],
|
||||||
'pin_code_digits': int(config['General']['pin_code_digits'])
|
'pin_code_digits': int(config['General']['pin_code_digits'])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.debug("Loaded configuration: %s", {k: v for k, v in config.items() if k != 'api_token'})
|
||||||
|
return config
|
||||||
|
|
|
||||||
37
main.py
37
main.py
|
|
@ -15,28 +15,49 @@ def main():
|
||||||
parser = argparse.ArgumentParser(description="UniFi Access Visitor Management")
|
parser = argparse.ArgumentParser(description="UniFi Access Visitor Management")
|
||||||
parser.add_argument('-v', '--verbose', action='store_true', help="Increase output verbosity")
|
parser.add_argument('-v', '--verbose', action='store_true', help="Increase output verbosity")
|
||||||
parser.add_argument('-l', '--log', help="Log output to file")
|
parser.add_argument('-l', '--log', help="Log output to file")
|
||||||
|
parser.add_argument('--list-door-groups', action='store_true', help="List available door groups")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
config = load_config()
|
# Initialize logging first
|
||||||
log_file = args.log or config['log_file']
|
logger = logging.getLogger(__name__)
|
||||||
|
log_file = args.log or 'unifi_access.log' # Default log file if not specified
|
||||||
logger = setup_logging(args.verbose, log_file)
|
logger = setup_logging(args.verbose, log_file)
|
||||||
|
|
||||||
unifi_manager = UnifiAccessManager(config)
|
try:
|
||||||
|
config = load_config()
|
||||||
|
logger.debug(f"Loaded config: {config}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error loading configuration: {str(e)}")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
unifi_manager = UnifiAccessManager(config)
|
||||||
|
except ValueError as e:
|
||||||
|
logger.error(f"Error initializing UnifiAccessManager: {str(e)}")
|
||||||
|
return
|
||||||
|
|
||||||
|
if args.list_door_groups:
|
||||||
|
unifi_manager.print_door_groups()
|
||||||
|
return
|
||||||
|
|
||||||
hostex_manager = HostexManager(config)
|
hostex_manager = HostexManager(config)
|
||||||
ics_parser = ICSParser(config)
|
ics_parser = ICSParser(config)
|
||||||
notification_manager = NotificationManager(config)
|
notification_manager = NotificationManager(config)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logger.debug("Script started")
|
logger.info("Script started")
|
||||||
|
|
||||||
if config['use_hostex']:
|
if config['use_hostex']:
|
||||||
|
logger.info("Fetching reservations from Hostex")
|
||||||
reservations = hostex_manager.fetch_reservations()
|
reservations = hostex_manager.fetch_reservations()
|
||||||
elif config['use_ics']:
|
elif config['use_ics']:
|
||||||
|
logger.info("Parsing ICS file")
|
||||||
reservations = ics_parser.parse_ics()
|
reservations = ics_parser.parse_ics()
|
||||||
else:
|
else:
|
||||||
logger.error("No valid reservation source configured")
|
logger.error("No valid reservation source configured")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
logger.info(f"Processing {len(reservations)} reservations")
|
||||||
unifi_manager.process_reservations(reservations)
|
unifi_manager.process_reservations(reservations)
|
||||||
|
|
||||||
summary = unifi_manager.generate_summary()
|
summary = unifi_manager.generate_summary()
|
||||||
|
|
@ -47,15 +68,15 @@ def main():
|
||||||
|
|
||||||
if config['simplepush_enabled'] and unifi_manager.has_changes():
|
if config['simplepush_enabled'] and unifi_manager.has_changes():
|
||||||
notification_manager.send_notification("UniFi Access Update", summary)
|
notification_manager.send_notification("UniFi Access Update", summary)
|
||||||
logger.debug("Simplepush notification sent")
|
logger.info("Simplepush notification sent")
|
||||||
else:
|
else:
|
||||||
logger.debug("No Simplepush notification sent (no changes or Simplepush not enabled)")
|
logger.info("No Simplepush notification sent (no changes or Simplepush not enabled)")
|
||||||
|
|
||||||
logger.debug("Script completed successfully")
|
logger.info("Script completed successfully")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"An error occurred: {str(e)}", exc_info=True)
|
logger.error(f"An error occurred: {str(e)}", exc_info=True)
|
||||||
finally:
|
finally:
|
||||||
logger.debug("Script execution finished")
|
logger.info("Script execution finished")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
|
||||||
135
unifi_access.py
135
unifi_access.py
|
|
@ -7,57 +7,76 @@ class UnifiAccessManager:
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self.api_host = config['api_host']
|
self.api_host = config['api_host']
|
||||||
self.api_token = config['api_token']
|
self.api_token = config['api_token']
|
||||||
self.default_door_group_id = config['default_door_group_id']
|
self.default_door_group_id = config.get('default_door_group_id', '')
|
||||||
self.check_in_time = datetime.time.fromisoformat(config['check_in_time'])
|
self.check_in_time = datetime.time.fromisoformat(config['check_in_time'])
|
||||||
self.check_out_time = datetime.time.fromisoformat(config['check_out_time'])
|
self.check_out_time = datetime.time.fromisoformat(config['check_out_time'])
|
||||||
self.pin_code_digits = config['pin_code_digits']
|
self.pin_code_digits = config['pin_code_digits']
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
self.changes = {'added': [], 'deleted': [], 'unchanged': []}
|
self.changes = {'added': [], 'deleted': [], 'unchanged': []}
|
||||||
|
|
||||||
|
self.logger.debug(f"Loaded default_door_group_id from config: {self.default_door_group_id}")
|
||||||
|
if not self.default_door_group_id:
|
||||||
|
self.logger.warning("default_door_group_id is not set in config. Attempting to fetch available door groups.")
|
||||||
|
self.set_default_door_group()
|
||||||
|
else:
|
||||||
|
self.logger.debug(f"Initialized with default_door_group_id: {self.default_door_group_id}")
|
||||||
|
|
||||||
def create_visitor(self, first_name, last_name, remarks, phone_number, start_time, end_time):
|
def set_default_door_group(self):
|
||||||
|
door_groups = self.fetch_door_groups()
|
||||||
|
if len(door_groups) == 1:
|
||||||
|
self.default_door_group_id = door_groups[0]['id']
|
||||||
|
self.logger.info(f"Automatically set default_door_group_id to the only available group: {self.default_door_group_id}")
|
||||||
|
elif len(door_groups) > 1:
|
||||||
|
self.logger.error("Multiple door groups available. Please specify default_group_id in unifi.conf")
|
||||||
|
raise ValueError("Multiple door groups available. Please specify default_group_id in unifi.conf")
|
||||||
|
else:
|
||||||
|
self.logger.error("No door groups available")
|
||||||
|
raise ValueError("No door groups available")
|
||||||
|
|
||||||
|
def create_visitor(self, first_name, last_name, start_time, end_time):
|
||||||
url = f"{self.api_host}/api/v1/developer/visitors"
|
url = f"{self.api_host}/api/v1/developer/visitors"
|
||||||
headers = {
|
headers = {
|
||||||
"Authorization": f"Bearer {self.api_token}",
|
"Authorization": f"Bearer {self.api_token}",
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
}
|
}
|
||||||
pin_code = phone_number[-self.pin_code_digits:] if phone_number and len(phone_number) >= self.pin_code_digits else ""
|
|
||||||
data = {
|
data = {
|
||||||
"first_name": first_name,
|
"first_name": first_name,
|
||||||
"last_name": last_name,
|
"last_name": last_name,
|
||||||
"remarks": remarks,
|
|
||||||
"mobile_phone": phone_number,
|
|
||||||
"email": "",
|
|
||||||
"visitor_company": "",
|
|
||||||
"start_time": start_time,
|
"start_time": start_time,
|
||||||
"end_time": end_time,
|
"end_time": end_time,
|
||||||
"visit_reason": "Other",
|
"visit_reason": "Other",
|
||||||
"resources": [
|
"resources": [
|
||||||
{"id": self.default_door_group_id, "type": "door_group"}
|
{
|
||||||
|
"id": self.default_door_group_id,
|
||||||
|
"type": "door_group"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
response = requests.post(url, json=data, headers=headers, verify=False)
|
|
||||||
if response.status_code != 200:
|
self.logger.debug(f"Creating visitor with data: {json.dumps(data, indent=2)}")
|
||||||
self.logger.error(f"Failed to create visitor account for {first_name} {last_name}")
|
|
||||||
|
try:
|
||||||
|
response = requests.post(url, json=data, headers=headers, verify=False)
|
||||||
|
self.logger.debug(f"API response status code: {response.status_code}")
|
||||||
|
self.logger.debug(f"API response content: {response.text}")
|
||||||
|
|
||||||
|
response.raise_for_status() # Raise an exception for bad status codes
|
||||||
|
|
||||||
|
response_data = response.json()
|
||||||
|
if response_data.get('code') == 'SUCCESS':
|
||||||
|
visitor_id = response_data.get('data', {}).get('id')
|
||||||
|
if visitor_id:
|
||||||
|
self.logger.debug(f"Created visitor with ID: {visitor_id}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
self.logger.error("Visitor ID not found in the response")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
self.logger.error(f"API returned an error: {response_data.get('msg')}")
|
||||||
|
return False
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
self.logger.error(f"Request failed: {str(e)}")
|
||||||
return False
|
return False
|
||||||
else:
|
|
||||||
visitor_id = response.json()["data"]["id"]
|
|
||||||
return self.assign_pin_to_visitor(visitor_id, pin_code)
|
|
||||||
|
|
||||||
def assign_pin_to_visitor(self, visitor_id, pin_code):
|
|
||||||
url = f"{self.api_host}/api/v1/developer/visitors/{visitor_id}/pin_codes"
|
|
||||||
headers = {
|
|
||||||
"Authorization": f"Bearer {self.api_token}",
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
}
|
|
||||||
data = {
|
|
||||||
"pin_code": pin_code
|
|
||||||
}
|
|
||||||
response = requests.put(url, json=data, headers=headers, verify=False)
|
|
||||||
if response.status_code != 200:
|
|
||||||
self.logger.error(f"Failed to assign PIN code to visitor: {visitor_id}")
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
def fetch_visitors(self):
|
def fetch_visitors(self):
|
||||||
url = f"{self.api_host}/api/v1/developer/visitors"
|
url = f"{self.api_host}/api/v1/developer/visitors"
|
||||||
|
|
@ -66,8 +85,15 @@ class UnifiAccessManager:
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
}
|
}
|
||||||
response = requests.get(url, headers=headers, verify=False)
|
response = requests.get(url, headers=headers, verify=False)
|
||||||
|
self.logger.debug(f"Fetch visitors API response status code: {response.status_code}")
|
||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
return response.json()["data"]
|
data = response.json()
|
||||||
|
if 'data' in data:
|
||||||
|
return data['data']
|
||||||
|
else:
|
||||||
|
self.logger.error(f"Unexpected response format: {data}")
|
||||||
|
return []
|
||||||
else:
|
else:
|
||||||
self.logger.error("Failed to fetch existing visitors")
|
self.logger.error("Failed to fetch existing visitors")
|
||||||
return []
|
return []
|
||||||
|
|
@ -81,30 +107,32 @@ class UnifiAccessManager:
|
||||||
params = {"is_force": "true"} if is_completed else {}
|
params = {"is_force": "true"} if is_completed else {}
|
||||||
|
|
||||||
response = requests.delete(url, headers=headers, params=params, verify=False)
|
response = requests.delete(url, headers=headers, params=params, verify=False)
|
||||||
|
self.logger.debug(f"Delete visitor API response status code: {response.status_code}")
|
||||||
|
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
self.logger.error(f"Failed to delete visitor account: {visitor_id}")
|
self.logger.error(f"Failed to delete visitor account: {visitor_id}")
|
||||||
return False
|
return False
|
||||||
else:
|
return True
|
||||||
return True
|
|
||||||
|
|
||||||
def process_reservations(self, reservations):
|
def process_reservations(self, reservations):
|
||||||
today = datetime.date.today()
|
today = datetime.date.today()
|
||||||
next_month = today + datetime.timedelta(days=30)
|
next_month = today + datetime.timedelta(days=30)
|
||||||
existing_visitors = self.fetch_visitors()
|
existing_visitors = self.fetch_visitors()
|
||||||
|
|
||||||
|
self.logger.debug(f"Processing {len(reservations)} reservations")
|
||||||
|
|
||||||
for reservation in reservations:
|
for reservation in reservations:
|
||||||
check_in_date = datetime.datetime.strptime(reservation["check_in_date"], "%Y-%m-%d").date()
|
check_in_date = datetime.datetime.strptime(reservation["check_in_date"], "%Y-%m-%d").date()
|
||||||
check_out_date = datetime.datetime.strptime(reservation["check_out_date"], "%Y-%m-%d").date()
|
check_out_date = datetime.datetime.strptime(reservation["check_out_date"], "%Y-%m-%d").date()
|
||||||
|
|
||||||
if today <= check_in_date <= next_month and reservation["status"] == "accepted":
|
if today <= check_in_date <= next_month and reservation["status"] == "accepted":
|
||||||
guest_name = reservation["guests"][0]["name"] if reservation["guests"] else "Guest"
|
guest_name = reservation["guests"][0]["name"] if reservation["guests"] else "Guest"
|
||||||
remarks = json.dumps(reservation)
|
first_name, last_name = guest_name.split(" ", 1) if " " in guest_name else (guest_name, "")
|
||||||
phone_number = reservation["guests"][0].get("phone", "") if reservation["guests"] else ""
|
|
||||||
|
|
||||||
existing_visitor = next(
|
existing_visitor = next(
|
||||||
(v for v in existing_visitors if
|
(v for v in existing_visitors if
|
||||||
datetime.datetime.fromtimestamp(v["start_time"]).date() == check_in_date and
|
datetime.datetime.fromtimestamp(int(v["start_time"])).date() == check_in_date and
|
||||||
datetime.datetime.fromtimestamp(v["end_time"]).date() == check_out_date),
|
datetime.datetime.fromtimestamp(int(v["end_time"])).date() == check_out_date),
|
||||||
None
|
None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -116,14 +144,14 @@ class UnifiAccessManager:
|
||||||
start_timestamp = int(start_datetime.timestamp())
|
start_timestamp = int(start_datetime.timestamp())
|
||||||
end_timestamp = int(end_datetime.timestamp())
|
end_timestamp = int(end_datetime.timestamp())
|
||||||
|
|
||||||
success = self.create_visitor(guest_name, "", remarks, phone_number, start_timestamp, end_timestamp)
|
success = self.create_visitor(first_name, last_name, start_timestamp, end_timestamp)
|
||||||
if success:
|
if success:
|
||||||
self.changes['added'].append(guest_name)
|
self.changes['added'].append(guest_name)
|
||||||
else:
|
else:
|
||||||
self.logger.error(f"Failed to create visitor: {guest_name}")
|
self.logger.error(f"Failed to create visitor: {guest_name}")
|
||||||
|
|
||||||
for visitor in existing_visitors:
|
for visitor in existing_visitors:
|
||||||
visitor_end = datetime.datetime.fromtimestamp(visitor["end_time"]).date()
|
visitor_end = datetime.datetime.fromtimestamp(int(visitor["end_time"])).date()
|
||||||
is_completed = visitor.get("status") == "VISITED"
|
is_completed = visitor.get("status") == "VISITED"
|
||||||
if visitor_end < today or is_completed:
|
if visitor_end < today or is_completed:
|
||||||
success = self.delete_visitor(visitor["id"], is_completed)
|
success = self.delete_visitor(visitor["id"], is_completed)
|
||||||
|
|
@ -146,3 +174,32 @@ class UnifiAccessManager:
|
||||||
|
|
||||||
def has_changes(self):
|
def has_changes(self):
|
||||||
return bool(self.changes['added'] or self.changes['deleted'])
|
return bool(self.changes['added'] or self.changes['deleted'])
|
||||||
|
|
||||||
|
def fetch_door_groups(self):
|
||||||
|
url = f"{self.api_host}/api/v1/developer/door_groups"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {self.api_token}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
response = requests.get(url, headers=headers, verify=False)
|
||||||
|
self.logger.debug(f"Fetch door groups API response status code: {response.status_code}")
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
if 'data' in data:
|
||||||
|
return data['data']
|
||||||
|
else:
|
||||||
|
self.logger.error(f"Unexpected response format: {data}")
|
||||||
|
return []
|
||||||
|
else:
|
||||||
|
self.logger.error("Failed to fetch door groups")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def print_door_groups(self):
|
||||||
|
door_groups = self.fetch_door_groups()
|
||||||
|
if door_groups:
|
||||||
|
print("Available Door Groups:")
|
||||||
|
for group in door_groups:
|
||||||
|
print(f"ID: {group['id']}, Name: {group['name']}")
|
||||||
|
else:
|
||||||
|
print("No door groups found or failed to fetch door groups.")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue