From 30fde0ac5c41244d8992b4c03d51fb57cfd31493 Mon Sep 17 00:00:00 2001 From: gcobb321 Date: Thu, 7 Nov 2024 11:47:51 -0500 Subject: [PATCH] iCloud3 v3.1.1 --- README.md | 2 +- custom_components/icloud3/ChangeLog.txt | 15 + custom_components/icloud3/__init__.py | 56 +- custom_components/icloud3/config_flow.py | 246 ++-- .../icloud3/config_flow_forms.py | 15 +- custom_components/icloud3/const.py | 41 +- .../icloud3/const_config_flow.py | 12 +- custom_components/icloud3/device.py | 10 +- custom_components/icloud3/device_tracker.py | 62 +- custom_components/icloud3/global_variables.py | 1 + .../icloud3/helpers/messaging-filter-redo.py | 1050 +++++++++++++++++ .../icloud3/helpers/messaging.py | 35 +- custom_components/icloud3/icloud3_main.py | 7 - custom_components/icloud3/strings.json | 8 +- .../icloud3/support/config_file.py | 33 +- .../icloud3/support/icloud_data_handler.py | 9 +- .../icloud3/support/pyicloud_ic3.py | 314 ++--- .../icloud3/support/pyicloud_ic3_interface.py | 93 +- .../icloud3/support/start_ic3.py | 26 +- .../icloud3/support/start_ic3_control.py | 16 +- .../icloud3/support/zone_handler.py | 6 +- .../icloud3/translations/en.json | 8 +- 22 files changed, 1686 insertions(+), 379 deletions(-) create mode 100644 custom_components/icloud3/helpers/messaging-filter-redo.py diff --git a/README.md b/README.md index 54932b0..fc046fc 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ------ -[![CurrentVersion](https://img.shields.io/badge/Current_Version-v3.1-blue.svg)](https://github.com/gcobb321/icloud3_v3) [![Type](https://img.shields.io/badge/Type-Custom_Component-orange.svg)](https://github.com/gcobb321/icloud3_v3) [![HACS](https://img.shields.io/badge/HACS-Custom_Repository-orange.svg)](https://github.com/gcobb321/icloud3_v3) +[![CurrentVersion](https://img.shields.io/badge/Current_Version-v3.1.1-blue.svg)](https://github.com/gcobb321/icloud3_v3) [![Type](https://img.shields.io/badge/Type-Custom_Component-orange.svg)](https://github.com/gcobb321/icloud3_v3) [![HACS](https://img.shields.io/badge/HACS-Custom_Repository-orange.svg)](https://github.com/gcobb321/icloud3_v3) [![ProjectStage](https://img.shields.io/badge/Project_Stage-Development_Version-forestgreen.svg)](https://github/gcobb321/icloud3_v3) [![Released](https://img.shields.io/badge/Released-November,_2024-forestgreen.svg)](https://github.com/gcobb321/icloud3_v3) diff --git a/custom_components/icloud3/ChangeLog.txt b/custom_components/icloud3/ChangeLog.txt index 7a90fc3..00604f0 100644 --- a/custom_components/icloud3/ChangeLog.txt +++ b/custom_components/icloud3/ChangeLog.txt @@ -3,6 +3,21 @@ **Installing for the first time_** - See [here](https://gcobb321.github.io/icloud3_v3_docs/#/chapters/3.2-installing-and-configuring) for instructions on installing as a New Installation **iCloud3 v3 Documentation** - iCloud3 User Guide can be found [here](https://gcobb321.github.io/icloud3_v3_docs/#/) + + +3.1.1 +....................... +1. LOCATING DEVICES: + - Added additional checks to insure the Apple Account location data was refreshed during startup and while configuing iCloud3 settings. Fixed a problem where the location information data from Apple was not being initialized properly. + - Fixed the location refresh not being causied the '0 of 0' to be displayed in the Configure Setting screens, leading to the Apple Account selection lists to not be populated. + - Fixed some problems where 'Locate All Devices = False' would still locate all the devices in the Apple account + - Added error checking to make sure the Locate All Devices can not be disabled if there were Family devices that + were asigned to that Apple account. If it was disabled, they would never be locaed. +2. UPDATE DEVICE SCREEN - Added (and fixed) the TOOLS - RESET DATA SOURCE(S), DELETE DEVICE(S) option where you can reset the device's Apple Account and Mobile App to default values (None) and fixed a problem deleting devices. Reworked the Apple Acccount selection list to provide more information and identify setup errors. +3. UPDATE APPLE ACCOUNT USERNAME/PASSWORD SCREEN - Added checks to insure Locating All Devices can not be disabled if there are tracked devices assigned to this account that are in the Family list. +4. OTHER THINGS - Changed several things under the covers. + + 3.1 ....................... ### Change Log - v3.1 diff --git a/custom_components/icloud3/__init__.py b/custom_components/icloud3/__init__.py index d6c51fb..eea5038 100644 --- a/custom_components/icloud3/__init__.py +++ b/custom_components/icloud3/__init__.py @@ -29,12 +29,14 @@ log_exception_HA, log_exception) from .helpers.time_util import (time_now_secs, ) from .helpers.file_io import (async_make_directory, async_directory_exists, async_copy_file, + read_json_file, save_json_file, async_rename_file, async_delete_directory, make_directory, directory_exists, copy_file, file_exists, rename_file, move_files, ) from .support.v2v3_config_migration import iCloud3_v2v3ConfigMigration from .support import start_ic3 from .support import config_file +from . import device_tracker as ic3_device_tracker from .support import restore_state from .support.service_handler import register_icloud3_services from .support import pyicloud_ic3_interface @@ -135,6 +137,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): Gb.entry_id = entry.entry_id Gb.operating_mode = MODE_INTEGRATION Gb.PyiCloud = None + ic3_device_tracker.get_ha_device_ids_from_device_registry(Gb.hass) start_ic3.initialize_directory_filenames() @@ -181,9 +184,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): Gb.EvLog.display_user_message(f"Starting {ICLOUD3_VERSION_MSG}") if Gb.use_data_source_ICLOUD: - await _rename_icloud_v30_cookie_directory() - Gb.hass.async_add_executor_job( - pyicloud_ic3_interface.verify_all_apple_accounts) + await Gb.hass.async_add_executor_job( + move_icloud_cookies_to_icloud3_apple_acct) + await Gb.hass.async_add_executor_job( + pyicloud_ic3_interface.verify_all_apple_accounts) # pyicloud_ic3_interface.create_all_PyiCloudServices) # set_up_default_area_id() @@ -298,7 +302,7 @@ async def async_get_ha_location_info(hass): pass #------------------------------------------------------------------------------------------- -async def _rename_icloud_v30_cookie_directory(): +def move_icloud_cookies_to_icloud3_apple_acct(): ''' iCloud3 v3.1 uses the '.../apple_acct.ic3' cookie directory instead of the '.../icloud' directory to avoid conflicts with Apple Account integrations (iCloud, HomeKit, etc) @@ -309,20 +313,15 @@ async def _rename_icloud_v30_cookie_directory(): if the '.../icloapple_acct.ic3/' directory does not exist. create it. ''' try: - # v30_icloud_config_dir = Gb.ha_storage_icloud3.replace('.config', '') - # v31_ic3_config_dir_exists = await async_directory_exists(Gb.ha_storage_icloud3) - # if v31_ic3_config_dir_exists is False: - # Gb.HALogger.info(f"{v30_icloud_config_dir} --> {Gb.ha_storage_icloud3}") - # move_files(v30_icloud_config_dir, Gb.ha_storage_icloud3) - - v31_cookie_dir = Gb.icloud_cookie_directory - v31_cookie_dir_exists = await async_directory_exists(v31_cookie_dir) + + v31_cookie_dir = f"{Gb.icloud_cookie_directory}" + v31_cookie_dir_exists = directory_exists(v31_cookie_dir) if v31_cookie_dir_exists: return - await async_make_directory(v31_cookie_dir) + make_directory(v31_cookie_dir) v30_cookie_dir = Gb.hass.config.path(Gb.ha_storage_directory, 'icloud') - v30_cookie_dir_exists = await async_directory_exists(v30_cookie_dir) + v30_cookie_dir_exists = directory_exists(v30_cookie_dir) Gb.HALogger.info(f"{v30_cookie_dir=} {v30_cookie_dir_exists=}") if v30_cookie_dir_exists is False: @@ -335,10 +334,13 @@ async def _rename_icloud_v30_cookie_directory(): cookie_filename = "".join([c for c in username if match(r"\w", c)]) Gb.HALogger.info(f"{cookie_filename=}") - v30_cookie_filename = f"{v30_cookie_dir}/{cookie_filename}" + v30_cookie_filename = f"{v30_cookie_dir}/{cookie_filename}" v30_cookie_tpw_filename = f"{v30_cookie_dir}/session/{cookie_filename}.tpw" - v30_session_filename = f"{v30_cookie_dir}/session/{cookie_filename}" - v30_tpw_file_exists = await async_directory_exists(v30_cookie_tpw_filename) + v30_session_filename = f"{v30_cookie_dir}/session/{cookie_filename}" + v30_tpw_file_exists = directory_exists(v30_cookie_tpw_filename) + v31_cookie_filename = f"{v31_cookie_dir}/{cookie_filename}" + v31_cookie_tpw_filename = f"{v31_cookie_dir}/{cookie_filename}.tpw" + v31_session_filename = f"{v31_cookie_dir}/{cookie_filename}.session" Gb.HALogger.info(f"{v30_cookie_filename =}") Gb.HALogger.info(f"{v30_cookie_tpw_filename =}") @@ -347,16 +349,20 @@ async def _rename_icloud_v30_cookie_directory(): if v30_tpw_file_exists is False: return - Gb.HALogger.info(f"{v30_cookie_filename} --> {v31_cookie_dir}/{cookie_filename}.session") - await async_copy_file(v30_cookie_filename, f"{v31_cookie_dir}/{cookie_filename}") + Gb.HALogger.info(f"{v30_cookie_filename} --> {v31_session_filename}") + data = read_json_file(v30_session_filename) + save_json_file(v31_session_filename, data) + + Gb.HALogger.info(f"{v30_cookie_tpw_filename} --> {v31_cookie_tpw_filename}") + data = read_json_file(v30_cookie_tpw_filename) + save_json_file(v31_cookie_tpw_filename, data) - Gb.HALogger.info(f"{v30_session_filename} --> {v31_cookie_dir}/{cookie_filename}.session") - await async_copy_file(v30_session_filename, f"{v31_cookie_dir}/{cookie_filename}.session") - # await async_rename_file(v30_session_filename, f"{v30_cookie_dir}/{cookie_filename}.session") + Gb.HALogger.info(f"{v30_cookie_filename} --> {v31_cookie_filename}") + with open(v30_cookie_filename, 'r') as v30_file: + data = v30_file.read() - Gb.HALogger.info(f"{v30_session_filename}.tpw --> {v31_cookie_dir}/{cookie_filename}.tpw") - await async_copy_file(f"{v30_session_filename}.tpw", f"{v31_cookie_dir}/{cookie_filename}.tpw") - # await async_rename_file(f"{v30_session_filename}.tpw", f"{v30_cookie_dir}/{cookie_filename}.tpw") + with open(v31_cookie_filename, 'w') as v31_file: + v31_file.write(data) post_monitor_msg(f"Cookie Directory > Directory and files were copied " f"from `{v30_cookie_dir}` to `{v31_cookie_dir}`") diff --git a/custom_components/icloud3/config_flow.py b/custom_components/icloud3/config_flow.py index 9aa4e80..f71ee2b 100644 --- a/custom_components/icloud3/config_flow.py +++ b/custom_components/icloud3/config_flow.py @@ -20,7 +20,7 @@ NBSP, RARROW, PHDOT, CRLF_DOT, DOT, HDOT, PHDOT, CIRCLE_STAR, RED_X, YELLOW_ALERT, RED_ALERT, EVLOG_NOTICE, EVLOG_ALERT, EVLOG_ERROR, LINK, LLINK, RLINK, IPHONE_FNAME, IPHONE, IPAD, WATCH, AIRPODS, ICLOUD, OTHER, HOME, FAMSHR, - DEVICE_TYPES, DEVICE_TYPE_FNAME, DEVICE_TRACKER_DOT, + DEVICE_TYPES, DEVICE_TYPE_FNAME, DEVICE_TYPE_FNAMES, DEVICE_TRACKER_DOT, MOBAPP, NO_MOBAPP, TRACK_DEVICE, MONITOR_DEVICE, INACTIVE_DEVICE, NAME, FRIENDLY_NAME, FNAME, TITLE, BATTERY, @@ -44,7 +44,7 @@ CONF_WAZE_REALTIME, CONF_WAZE_HISTORY_DATABASE_USED, CONF_WAZE_HISTORY_TRACK_DIRECTION, CONF_STAT_ZONE_FNAME, CONF_STAT_ZONE_STILL_TIME, CONF_DISPLAY_TEXT_AS, - CONF_IC3_DEVICENAME, CONF_FNAME, CONF_FAMSHR_DEVICENAME, CONF_MOBILE_APP_DEVICE, CONF_FMF_EMAIL, + CONF_IC3_DEVICENAME, CONF_FNAME, CONF_FAMSHR_DEVICENAME, CONF_MOBILE_APP_DEVICE, CONF_FMF_EMAIL,CONF_FMF_DEVICE_ID, CONF_TRACKING_MODE, CONF_INZONE_INTERVAL, CONF_FIXED_INTERVAL, CONF_AWAY_TIME_ZONE_1_OFFSET, CONF_AWAY_TIME_ZONE_1_DEVICES, CONF_AWAY_TIME_ZONE_2_OFFSET, CONF_AWAY_TIME_ZONE_2_DEVICES, @@ -54,7 +54,7 @@ CONF_PARAMETER_TIME_STR, CONF_PARAMETER_FLOAT, CF_PROFILE, CF_TRACKING, CF_GENERAL, DEFAULT_DEVICE_CONF, DEFAULT_GENERAL_CONF, DEFAULT_APPLE_ACCOUNTS_CONF, - DEFAULT_DEVICE_REINITIALIZE_CONF, + DEFAULT_DEVICE_DATA_SOURCE, ) from .const_sensor import (SENSOR_GROUPS ) from .const_config_flow import * @@ -838,7 +838,7 @@ async def async_step_confirm_action(self, user_input=None, Notes: Before calling this function, set the self.multi_form_user_input to the user_input. - This will preserve all parameter changes in the calling screen. They are + This will have all parameter changes in the calling screen. They are returned to the called from step on exit. Action item - The action_item selected on this screen is added to the self.multi_form_user_input variable returned. It is resolved in the calling @@ -1866,8 +1866,17 @@ async def async_step_update_apple_acct(self, user_input=None, errors=None): if user_input.get('url_suffix_china') is True else 'None' user_input = self._strip_spaces(user_input, [CONF_USERNAME, CONF_PASSWORD, CONF_TOTP_KEY]) + if (user_input[CONF_LOCATE_ALL] is False + and self._can_disable_locate_all(user_input) is False): + self.errors[CONF_LOCATE_ALL] = 'icloud_acct_locate_all_reqd' + user_input[CONF_LOCATE_ALL] = True + action_item = '' + return await self.async_step_update_apple_acct( + user_input=user_input, + errors=self.errors) + user_input[CONF_USERNAME] = user_input[CONF_USERNAME].lower() - user_input[CONF_TOTP_KEY] = user_input[CONF_TOTP_KEY].upper() + user_input[CONF_TOTP_KEY] = '' #user_input[CONF_TOTP_KEY].upper() username = user_input[CONF_USERNAME] password = user_input[CONF_PASSWORD] ui_apple_acct ={CONF_USERNAME: user_input[CONF_USERNAME], @@ -1914,6 +1923,7 @@ async def async_step_update_apple_acct(self, user_input=None, errors=None): and ui_apple_acct == self.conf_apple_acct and user_input[CONF_ICLOUD_SERVER_ENDPOINT_SUFFIX] ==\ Gb.conf_tracking[CONF_ICLOUD_SERVER_ENDPOINT_SUFFIX] + and user_input[CONF_LOCATE_ALL] != conf_locate_all and Gb.PyiCloud_by_username.get(username) is not None): self.errors['base'] = 'icloud_acct_logged_into' action_item = '' @@ -1932,7 +1942,8 @@ async def async_step_update_apple_acct(self, user_input=None, errors=None): username_password_valid = True aa_login_info_changed = False - self.errors = {} + other_flds_changed = False + # self.errors = {} if action_item == 'log_into_apple_acct': # Apple acct login info changed, validate it without logging in if (conf_username != user_input[CONF_USERNAME] @@ -1943,6 +1954,10 @@ async def async_step_update_apple_acct(self, user_input=None, errors=None): or Gb.PyiCloud_by_username.get(username) is None): aa_login_info_changed = True + if (user_input[CONF_LOCATE_ALL] != self.conf_apple_acct[CONF_LOCATE_ALL] + or user_input[CONF_TOTP_KEY] != self.conf_apple_acct[CONF_TOTP_KEY]): + other_flds_changed = True + if aa_login_info_changed: username_password_valid = \ await self._async_validate_username_password(username, password) @@ -1955,7 +1970,7 @@ async def async_step_update_apple_acct(self, user_input=None, errors=None): user_input=user_input, errors=self.errors) - if aa_login_info_changed: + if aa_login_info_changed or other_flds_changed: self._update_conf_apple_accounts(self.aa_idx, user_input) await self._async_write_storage_icloud3_configuration_file() @@ -2111,6 +2126,13 @@ def get_conf_device_names_by_username(self, username): return devicenames_by_username, icloud_dnames_by_username +#------------------------------------------------------------------------------------------- + def _can_disable_locate_all(self, user_input): + famshr_Devices = [Device for Device in Gb.Devices + if Device.family_share_device + and Device.conf_apple_acct_username == user_input[CONF_USERNAME]] + + return is_empty(famshr_Devices) #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> # DELETE APPLE ACCOUNT @@ -2329,8 +2351,8 @@ async def reauth_send_verification_code_handler(caller_self, user_input): # iCloud setup screen. If it was changed, another account is being logged into # and it will be restarted when exiting the configurator. if valid_code: - post_event(f"{EVLOG_NOTICE}Apple Acct > {caller_self.PyiCloud.account_owner}, " - f"Code accepted, Verification completed") + post_event( f"{EVLOG_NOTICE}Apple Acct > {caller_self.PyiCloud.account_owner}, " + f"Code accepted, Verification completed") await caller_self._build_icloud_device_selection_list() @@ -2439,9 +2461,9 @@ async def log_into_icloud_account(self, user_input, called_from_step_id=None): self.endpoint_suffix = endpoint_suffix Gb.username_valid_by_username[username] = True - if PyiCloud.DeviceSvc: - PyiCloud.DeviceSvc.refresh_client() - + #if PyiCloud.DeviceSvc: + #PyiCloud.DeviceSvc.refresh_client() + PyiCloud.refresh_icloud_data() start_ic3.dump_startup_lists_to_log() if PyiCloud.requires_2fa or called_from_step_id is None: @@ -2533,9 +2555,10 @@ def create_PyiCloudService_config_flow(username, password, endpoint_suffix): @staticmethod def create_DeviceSvc_config_flow(PyiCloud): - iCloud = PyiCloud.create_DeviceSvc_object(config_flow_login=True) + _DeviceSvc = PyiCloud.create_DeviceSvc_object(config_flow_login=True) + start_ic3.dump_startup_lists_to_log() - return iCloud + return _DeviceSvc #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> # RESET PYICLOUD SESSION, GENERATE VERIFICATION CODE @@ -2848,8 +2871,11 @@ async def async_step_delete_device(self, user_input=None, errors=None): elif action_item == 'delete_all_devices': self._delete_all_devices() - elif action_item == 'delete_icloud_mobapp_info': - self._clear_icloud_mobapp_selection_parms() + elif action_item == 'reset_this_device_data_source': + self._reset_this_device_data_source_fields() + + elif action_item == 'reset_all_devices_data_source': + self._reset_all_devices_data_source_fields() if action_item != 'delete_device_cancel': list_add(self.config_parms_update_control, ['tracking', 'restart']) @@ -2861,29 +2887,38 @@ async def async_step_delete_device(self, user_input=None, errors=None): def _delete_this_device(self, conf_device=None): """ Delete the device_tracker entity and associated ic3 configuration """ - if conf_device: - devicename = conf_device[CONF_IC3_DEVICENAME] - self.conf_device = conf_device - self.conf_device_idx = Gb.conf_devices_idx_by_devicename[devicename] - else: - devicename = self.conf_device[CONF_IC3_DEVICENAME] + try: + if conf_device: + devicename = conf_device[CONF_IC3_DEVICENAME] + self.conf_device = conf_device + self.conf_device_idx = Gb.conf_devices_idx_by_devicename[devicename] + else: + devicename = self.conf_device[CONF_IC3_DEVICENAME] - event_msg = (f"Configuration Changed > DeleteDevice-{devicename}, " - f"{self.conf_device[CONF_FNAME]}/" - f"{DEVICE_TYPE_FNAME[self.conf_device[CONF_DEVICE_TYPE]]}") - post_event(event_msg) + event_msg = (f"Configuration Changed > DeleteDevice-{devicename}, " + f"{self.conf_device[CONF_FNAME]}/" + f"{DEVICE_TYPE_FNAME(self.conf_device[CONF_DEVICE_TYPE])}") + post_event(event_msg) - self._remove_device_tracker_entity(devicename) + # if deleting last device, use _delete all to simplifying table resetting + if len(Gb.conf_devices) <= 1: + return self._delete_all_devices() - self.dev_page_item[self.dev_page_no] = '' - Gb.conf_devices.pop(self.conf_device_idx) - self._update_config_file_tracking(update_config_flag=True) + self._remove_device_tracker_entity(devicename) - # The lists may have not been built if deleting a device when deleting an Apple acct - if self.device_items_by_devicename == {}: - return + self.dev_page_item[self.dev_page_no] = '' + Gb.conf_devices.pop(self.conf_device_idx) + self._update_config_file_tracking(update_config_flag=True) - del self.device_items_by_devicename[devicename] + # The lists may have not been built if deleting a device when deleting an Apple acct + if devicename in self.device_items_by_devicename: + del self.device_items_by_devicename[devicename] + # if self.device_items_by_devicename == {}: + # return + + # del self.device_items_by_devicename[devicename] + except Exception as err: + log_exception(err) #------------------------------------------------------------------------------------------- @@ -2893,26 +2928,41 @@ def _delete_all_devices(self): Delete the device_tracker entity and associated ic3 configuration """ - for conf_device in Gb.conf_devices: - devicename = conf_device[CONF_IC3_DEVICENAME] - self._remove_device_tracker_entity(devicename) + try: + for conf_device in Gb.conf_devices: + devicename = conf_device[CONF_IC3_DEVICENAME] + self._remove_device_tracker_entity(devicename) + + Gb.conf_devices = [] + self.device_items_by_devicename = {} # List of the apple_accts in the Gb.conf_tracking[apple_accts] parameter + self.device_items_displayed = [] # List of the apple_accts displayed on the device_list form + self.dev_page_item = ['', '', '', '', ''] # Device's devicename last displayed on each page + self.dev_page_no = 0 # apple_accts List form page number, starting with 0 - Gb.conf_devices = [] - self.devicename = {} - self.conf_device_idx = 0 - self.dev_page_item['', '', '', '', ''] + self._update_config_file_tracking(update_config_flag=True) + except Exception as err: + log_exception(err) + +#------------------------------------------------------------------------------------------- + def _reset_this_device_data_source_fields(self): + """ + Reset the iCloud & Mobile App to their initiial values. + Keep the devicename, friendly name, picture and other fields + """ + + self.conf_device.update(DEFAULT_DEVICE_DATA_SOURCE) self._update_config_file_tracking(update_config_flag=True) #------------------------------------------------------------------------------------------- - def _clear_icloud_mobapp_selection_parms(self): + def _reset_all_devices_data_source_fields(self): """ - Reset the iCloud & Mobile App, track_from_zone fields to their initiial values. + Reset the iCloud & Mobile App fields to their initiial values. Keep the devicename, friendly name, picture and other fields """ for conf_device in Gb.conf_devices: - conf_device.update(DEFAULT_DEVICE_REINITIALIZE_CONF) + conf_device.update(DEFAULT_DEVICE_DATA_SOURCE) self._update_config_file_tracking(update_config_flag=True) @@ -2939,7 +2989,7 @@ async def async_step_add_device(self, user_input=None, errors=None): user_input = self._strip_special_text_from_user_input(user_input, CONF_FNAME) user_input = self._strip_special_text_from_user_input(user_input, CONF_MOBILE_APP_DEVICE) user_input = self._option_text_to_parm(user_input, CONF_TRACKING_MODE, TRACKING_MODE_OPTIONS) - user_input = self._option_text_to_parm(user_input, CONF_DEVICE_TYPE, DEVICE_TYPE_FNAME) + user_input = self._option_text_to_parm(user_input, CONF_DEVICE_TYPE, DEVICE_TYPE_FNAMES) self.log_step_info(user_input, action_item) if (action_item == 'cancel' @@ -3045,11 +3095,13 @@ async def async_step_update_device(self, user_input=None, errors=None): else: user_input[CONF_APPLE_ACCOUNT] = self.conf_device[CONF_APPLE_ACCOUNT] user_input[CONF_FAMSHR_DEVICENAME] = self.conf_device[CONF_FAMSHR_DEVICENAME] - user_input[CONF_FMF_EMAIL] = 'None' + if CONF_FMF_EMAIL in user_input: + user_input[CONF_FMF_EMAIL] = 'None' + user_input[CONF_FMF_DEVICE_ID] = '' user_input = self._option_text_to_parm(user_input, CONF_MOBILE_APP_DEVICE, self.mobapp_list_text_by_entity_id) user_input = self._option_text_to_parm(user_input, CONF_PICTURE, self.picture_by_filename) - user_input = self._option_text_to_parm(user_input, CONF_DEVICE_TYPE, DEVICE_TYPE_FNAME) + user_input = self._option_text_to_parm(user_input, CONF_DEVICE_TYPE, DEVICE_TYPE_FNAMES) user_input = self._option_text_to_parm(user_input, CONF_TRACK_FROM_BASE_ZONE, self.zone_name_key_text) user_input = self._strip_special_text_from_user_input(user_input, CONF_IC3_DEVICENAME) self.log_step_info(user_input, action_item) @@ -3090,13 +3142,13 @@ async def async_step_update_device(self, user_input=None, errors=None): post_event( f"Configuration Changed > AddDevice-{ui_devicename}, " f"{self.conf_device[CONF_FNAME]}/" - f"{DEVICE_TYPE_FNAME[self.conf_device[CONF_DEVICE_TYPE]]}") + f"{DEVICE_TYPE_FNAME(self.conf_device[CONF_DEVICE_TYPE])}") else: Gb.conf_devices[self.conf_device_idx] = self.conf_device post_event (f"Configuration Changed > ChangeDevice-{ui_devicename}, " f"{self.conf_device[CONF_FNAME]}/" - f"{DEVICE_TYPE_FNAME[self.conf_device[CONF_DEVICE_TYPE]]}") + f"{DEVICE_TYPE_FNAME(self.conf_device[CONF_DEVICE_TYPE])}") self.dev_page_item[self.dev_page_no] = ui_devicename self._update_config_file_tracking(update_config_flag=True) @@ -3120,9 +3172,18 @@ async def async_step_update_device(self, user_input=None, errors=None): # Update the device_tracker & sensor entities now that the configuration has been updated if 'add_device' in self.conf_device_update_control: + # This is the first device being added, + # we need to set up the device_tracker platform, which will add it + ic3_device_tracker.get_ha_device_ids_from_device_registry(Gb.hass) if Gb.async_add_entities_device_tracker is None: await Gb.hass.config_entries.async_forward_entry_setups(Gb.config_entry, ['device_tracker']) - self._create_device_tracker_and_sensor_entities(ui_devicename, self.conf_device) + + sensors_list = self._build_all_sensors_list() + self._create_sensor_entity(devicename, conf_device, sensors_list) + + else: + self._create_device_tracker_and_sensor_entities(ui_devicename, self.conf_device) + ic3_device_tracker.get_ha_device_ids_from_device_registry(Gb.hass) else: self._update_changed_sensor_entities() @@ -3274,7 +3335,9 @@ def _validate_update_device(self, user_input): if user_input[CONF_FAMSHR_DEVICENAME].strip() == '': user_input[CONF_FAMSHR_DEVICENAME] = 'None' - user_input[CONF_FMF_EMAIL] = 'None' + if CONF_FMF_EMAIL in user_input: + user_input[CONF_FMF_EMAIL] = 'None' + user_input[CONF_FMF_DEVICE_ID] = '' if (user_input[CONF_MOBILE_APP_DEVICE].strip() == '' or user_input[CONF_MOBILE_APP_DEVICE] == 'scan_hdr'): @@ -3729,8 +3792,8 @@ async def _build_icloud_device_selection_list(self, selected_devicename=None): # Get the list of devices with valid apple accounts aa_idx = 0 - for apple_account in Gb.conf_apple_accounts: - username = apple_account[CONF_USERNAME] + for apple_acct in Gb.conf_apple_accounts: + username = apple_acct[CONF_USERNAME] aa_idx += 1 if Gb.username_valid_by_username.get(username, False) is False: @@ -3753,8 +3816,9 @@ async def _build_icloud_device_selection_list(self, selected_devicename=None): PyiCloud, PyiCloud.account_owner, selected_devicename) # Available devices - devices_cnt = len(devices_used) + len(devices_available) + len(this_device) + devices_cnt = len(devices_used) + len(devices_available) + len(this_device) assigned_cnt = len(devices_used) + len(this_device) + aa_idx_dots = '.'*aa_idx username_hdr_available = {f"{aa_idx_dots}hdr": f"🍎 ~~~~ {PyiCloud.account_owner}, " @@ -3804,57 +3868,64 @@ def _get_icloud_devices_list_avail_used_this( self, aa_idx, PyiCloud, apple_acct devices_assigned = {} selected_device_icloud_dname = '' for _conf_device in Gb.conf_devices: - if _conf_device[CONF_APPLE_ACCOUNT] != PyiCloud.username: + icloud_dname = _conf_device[CONF_FAMSHR_DEVICENAME] + username = _conf_device[CONF_APPLE_ACCOUNT] + if (icloud_dname == 'None' + or PyiCloud.username != username): continue - if _conf_device[CONF_FAMSHR_DEVICENAME] != 'None': - devices_assigned[_conf_device[CONF_FAMSHR_DEVICENAME]] = _conf_device[CONF_IC3_DEVICENAME] - devices_assigned[_conf_device[CONF_IC3_DEVICENAME]] = _conf_device[CONF_FAMSHR_DEVICENAME] + devices_assigned[icloud_dname] = _conf_device[CONF_IC3_DEVICENAME] + devices_assigned[_conf_device[CONF_IC3_DEVICENAME]] = icloud_dname - if _conf_device[CONF_FAMSHR_DEVICENAME] not in PyiCloud.device_id_by_icloud_dname: - icloud_dname_username = f"{_conf_device[CONF_FAMSHR_DEVICENAME]}{LINK}{PyiCloud.username}" - icloud_dname_owner = f"{_conf_device[CONF_FAMSHR_DEVICENAME]}{LINK}{PyiCloud.account_owner}{RLINK}" + if icloud_dname not in PyiCloud.device_id_by_icloud_dname: + icloud_dname_username = f"{icloud_dname}{LINK}{username}" + icloud_dname_owner = f"{icloud_dname}{LINK}{username}{RLINK}" unknown_devices[icloud_dname_username] = ( f"{RED_X}{icloud_dname_owner} >" f"{RARROW}UNKNOWN DEVICE") try: - for icloud_dname, device_display_name in PyiCloud.device_model_name_by_icloud_dname.items(): + for icloud_dname, device_model in PyiCloud.device_model_name_by_icloud_dname.items(): + device_id = PyiCloud.device_id_by_icloud_dname[icloud_dname] + _RawData = PyiCloud.RawData_by_device_id[device_id] + conf_apple_acct, conf_aa_idx = config_file.conf_apple_acct(PyiCloud.username) + locate_all_sym = '' if conf_apple_acct[CONF_LOCATE_ALL] else 'ⓧ ' + famshr_device = ' (FamShr)' if _RawData.family_share_device else '' + if famshr_device and locate_all_sym: + famshr_device = ' (FamShr - NOT LOCATING DEVICE)' + icloud_dname_username = f"{icloud_dname}{LINK}{PyiCloud.username}" icloud_dname_owner = f"{icloud_dname}{LINK}{PyiCloud.account_owner}{RLINK}" + icloud_dname_owner_model = f"{icloud_dname} > {device_model}{famshr_device}" # If not assigned to an ic3 device if icloud_dname not in devices_assigned: - device_id = PyiCloud.device_id_by_icloud_dname[icloud_dname] - _RawData = PyiCloud.RawData_by_device_id[device_id] - if _RawData.family_share_device: + if famshr_device: famshr_available[icloud_dname_username] = ( - f"{icloud_dname_owner} > " - f"{device_display_name}, " - f"Family Share Device" + f"{locate_all_sym}" + f"{icloud_dname_owner_model}" f"{aa_idx_dots}") else: owner_available[icloud_dname_username] = ( - f"{icloud_dname_owner} > " - f"{device_display_name}" + f"{icloud_dname_owner_model}" f"{aa_idx_dots}") continue # Is the icloud device name assigned to the current device being updated devicename = devices_assigned[icloud_dname] if devicename == selected_devicename: + err = RED_X if instr(icloud_dname_owner_model, 'NOT LOCATING') else '' this_device[icloud_dname_username] = ( - f"{icloud_dname_owner} > " - f"{device_display_name}") + f"{err}{icloud_dname_owner} > " + f"{device_model}{famshr_device}") continue # Assigned to another device - _assigned_to_fname = self._icloud_device_assigned_to(PyiCloud.username, icloud_dname) - if _assigned_to_fname: - devices_used[icloud_dname_username] = ( - f"{icloud_dname_owner}{RARROW}" - f"{_assigned_to_fname}, " - f"{device_display_name}") + _assigned_to_fname = self._icloud_device_assigned_to(PyiCloud, icloud_dname) + err = RED_X if instr(icloud_dname_owner_model, 'NOT LOCATING') else '' + devices_used[icloud_dname_username] = ( + f"{err}{icloud_dname_owner_model}{RARROW}" + f"{_assigned_to_fname}") except Exception as err: log_exception(err) @@ -3866,10 +3937,10 @@ def _get_icloud_devices_list_avail_used_this( self, aa_idx, PyiCloud, apple_acct return devices_available, devices_used, this_device #---------------------------------------------------------------------- - def _icloud_device_assigned_to(self, username, icloud_dname): - _assigned_to_fname = [f"{conf_device[CONF_FNAME]} ({conf_device[CONF_IC3_DEVICENAME]})" + def _icloud_device_assigned_to(self, PyiCloud, icloud_dname): + _assigned_to_fname = [f"{PyiCloud.account_owner_username})" for conf_device in Gb.conf_devices - if (username == conf_device[CONF_APPLE_ACCOUNT] + if (PyiCloud.username == conf_device[CONF_APPLE_ACCOUNT] and icloud_dname == conf_device[CONF_FAMSHR_DEVICENAME])] if _assigned_to_fname: @@ -4150,6 +4221,7 @@ def _create_device_tracker_and_sensor_entities(self, devicename, conf_device): NewDeviceTrackers = [] DeviceTracker = None + ic3_device_tracker.get_ha_device_ids_from_device_registry(Gb.hass) if devicename in Gb.DeviceTrackers_by_devicename: DeviceTracker = Gb.DeviceTrackers_by_devicename[devicename] else: @@ -4159,9 +4231,15 @@ def _create_device_tracker_and_sensor_entities(self, devicename, conf_device): if DeviceTracker is None: return + ic3_device_tracker.get_ha_device_ids_from_device_registry(Gb.hass) Gb.DeviceTrackers_by_devicename[devicename] = DeviceTracker NewDeviceTrackers.append(DeviceTracker) + # if devicename not in Gb.ha_device_id_by_devicename: + # NewDeviceTrackers.append(DeviceTracker) + # else: + # NewDeviceTrackers.append(DeviceTracker) + Gb.async_add_entities_device_tracker(NewDeviceTrackers, True) sensors_list = self._build_all_sensors_list() @@ -4178,6 +4256,7 @@ def _remove_device_tracker_entity(self, devicename): devicename to be removed """ # Inactive devices were not created so they are not in Gb.DeviceTrackers_by_devicename + ic3_device_tracker.get_ha_device_ids_from_device_registry(Gb.hass) if devicename not in Gb.DeviceTrackers_by_devicename: return @@ -4198,6 +4277,7 @@ def _remove_device_tracker_entity(self, devicename): DeviceTracker.remove_device_tracker() except: pass + ic3_device_tracker.get_ha_device_ids_from_device_registry(Gb.hass) #------------------------------------------------------------------------------------------- def _devices_form_identify_new_and_removed_tfz_zones(self, user_input): @@ -4554,7 +4634,7 @@ def _parm_or_device(self, pname, suggested_value=''): or suggested_value if pname == 'device_type': - parm_displayed = DEVICE_TYPE_FNAME.get(parm_displayed, IPHONE_FNAME) + parm_displayed = DEVICE_TYPE_FNAME(parm_displayed) parm_displayed = ' ' if parm_displayed == '' else parm_displayed except Exception as err: @@ -4772,7 +4852,7 @@ def _extract_name_device_type(self, devicename): elif instr(devicename, ic3dev_type): fnamew = devicename.replace(ic3dev_type, "") fname = fnamew.replace("_", "").replace("-", "").title().strip() - device_type = DEVICE_TYPE_FNAME.get(ic3dev_type, ic3dev_type) + device_type = DEVICE_TYPE_FNAME(ic3dev_type) break if device_type == "": diff --git a/custom_components/icloud3/config_flow_forms.py b/custom_components/icloud3/config_flow_forms.py index bada6c1..3e10178 100644 --- a/custom_components/icloud3/config_flow_forms.py +++ b/custom_components/icloud3/config_flow_forms.py @@ -10,7 +10,7 @@ from .global_variables import GlobalVariables as Gb from .const import (RED_ALERT, LINK, RLINK, RARROW, IPHONE, IPAD, WATCH, AIRPODS, ICLOUD, OTHER, - DEVICE_TYPE_FNAME, MOBAPP, NO_MOBAPP, + DEVICE_TYPE_FNAME, DEVICE_TYPE_FNAMES, MOBAPP, NO_MOBAPP, INACTIVE_DEVICE, HOME_DISTANCE, PICTURE_WWW_STANDARD_DIRS, CONF_PICTURE_WWW_DIRS, DEFAULT_DEVICE_CONF, @@ -313,7 +313,7 @@ def form_update_apple_acct(self): default=password): password_selector, vol.Optional(CONF_TOTP_KEY, - default=totp_key): + default='For future use in supporting hardware keys (YubiKey)'): selector.TextSelector(), vol.Optional('locate_all', default=locate_all): @@ -498,6 +498,7 @@ def _build_device_items_displayed_over_5(self): #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> def form_add_device(self): self.actions_list = ACTION_LIST_ITEMS_BASE.copy() + device_type_fname = DEVICE_TYPE_FNAME(self._parm_or_device(CONF_DEVICE_TYPE)) return vol.Schema({ vol.Required(CONF_IC3_DEVICENAME, @@ -509,7 +510,7 @@ def form_add_device(self): vol.Required(CONF_DEVICE_TYPE, default=self._parm_or_device(CONF_DEVICE_TYPE, suggested_value=IPHONE)): selector.SelectSelector(selector.SelectSelectorConfig( - options=dict_value_to_list(DEVICE_TYPE_FNAME), mode='dropdown')), + options=dict_value_to_list(DEVICE_TYPE_FNAMES), mode='dropdown')), vol.Required(CONF_TRACKING_MODE, default=self._option_parm_to_text(CONF_TRACKING_MODE, TRACKING_MODE_OPTIONS)): selector.SelectSelector(selector.SelectSelectorConfig( @@ -536,7 +537,8 @@ def form_update_device(self): # Display Advanced Tracking Parameters log_zones_fnames = [zone_dname(zone) for zone in self.conf_device[CONF_LOG_ZONES] if zone.startswith('Name') is False] tfz_fnames = [zone_dname(zone) for zone in self.conf_device[CONF_TRACK_FROM_ZONES]] - RARELY_UPDATED_PARMS_HEADER = ( f"DeviceType ({self._option_parm_to_text(CONF_DEVICE_TYPE, DEVICE_TYPE_FNAME)}), " + device_type_fname = DEVICE_TYPE_FNAME(self._parm_or_device(CONF_DEVICE_TYPE)) + RARELY_UPDATED_PARMS_HEADER = ( f"DeviceType ({device_type_fname}), " f"inZoneInterval ({format_timer(self.conf_device[CONF_INZONE_INTERVAL]*60)}), " f"FixedInterval ({format_timer(self.conf_device[CONF_FIXED_INTERVAL]*60)}), " f"LogFromZones ({list_to_str(log_zones_fnames)}), " @@ -628,11 +630,12 @@ def form_update_device(self): }) if self.display_rarely_updated_parms: + device_type_fname = DEVICE_TYPE_FNAME(self._parm_or_device(CONF_DEVICE_TYPE)) schema.update({ vol.Required(CONF_DEVICE_TYPE, - default=self._option_parm_to_text(CONF_DEVICE_TYPE, DEVICE_TYPE_FNAME)): + default=self._option_parm_to_text(CONF_DEVICE_TYPE, DEVICE_TYPE_FNAMES)): selector.SelectSelector(selector.SelectSelectorConfig( - options=dict_value_to_list(DEVICE_TYPE_FNAME), mode='dropdown')), + options=dict_value_to_list(DEVICE_TYPE_FNAMES), mode='dropdown')), vol.Required(CONF_INZONE_INTERVAL, default=self.conf_device[CONF_INZONE_INTERVAL]): # default=self._parm_or_device(CONF_INZONE_INTERVAL)): diff --git a/custom_components/icloud3/const.py b/custom_components/icloud3/const.py index 15127e3..c6a7166 100644 --- a/custom_components/icloud3/const.py +++ b/custom_components/icloud3/const.py @@ -10,7 +10,7 @@ # #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -VERSION = '3.1' +VERSION = '3.1.1' VERSION_BETA = '' #----------------------------------------- ICLOUD3 = 'iCloud3' @@ -121,7 +121,7 @@ IPHONE_FNAME, IPAD_FNAME, WATCH_FNAME, AIRPODS_FNAME, IMAC_FNAME, IPOD_FNAME, ICLOUD, ] -DEVICE_TYPE_FNAME = { +DEVICE_TYPE_FNAMES = { IPHONE: IPHONE_FNAME, IPAD: IPAD_FNAME, WATCH: WATCH_FNAME, @@ -130,6 +130,9 @@ IPOD: IPOD_FNAME, OTHER: OTHER_FNAME, } +def DEVICE_TYPE_FNAME(device_type): + return DEVICE_TYPE_FNAMES.get(device_type, device_type) + DEVICE_TYPE_ICONS = { IPHONE: "mdi:cellphone", IPAD: "mdi:tablet", @@ -315,12 +318,12 @@ NL_DOT = f'{NL} • ' CRLF_XD = f'{CRLF}{NBSP2}×{NBSP2}' CRLF_X = f'{CRLF}{NBSP3}×{NBSP2}' -CRLF_HDOT = f'{CRLF}{NBSP6}◦{NBSP2}' -CRLF_CHK = f'{CRLF}{NBSP3}✓{NBSP}' -CRLF_STAR = f'{CRLF}{NBSP3}✪{NBSP}' +CRLF_CIRCLE_X = f'{CRLF}{NBSP2}⮾{NBSP}' CRLF_RED_X = f'{CRLF}❌' +CRLF_HDOT = f'{CRLF}{NBSP4}{NBSP3}◦{NBSP2}' +CRLF_CHK = f'{CRLF}{NBSP3}✓{NBSP}' +CRLF_STAR = f'{CRLF}{NBSP2}✪{NBSP}' CRLF_YELLOW_ALERT = f'{CRLF}⚠️{NBSP}' -CRLF_CIRCLE_X = f'{CRLF}{NBSP2}ⓧ{NBSP}' CRLF_SP3_DOT = f'{CRLF}{NBSP3}•{NBSP}' CRLF_SP5_DOT = f'{CRLF}{NBSP5}•{NBSP}' CRLF_SP8_DOT = f'{CRLF}{NBSP4}{NBSP4}•{NBSP}' @@ -861,7 +864,7 @@ CONF_IC3_DEVICENAME: ' ', CONF_FNAME: '', CONF_PICTURE: 'None', - CONF_EVLOG_DISPLAY_ORDER: 0, + #CONF_EVLOG_DISPLAY_ORDER: 0, CONF_UNIQUE_ID: '', CONF_DEVICE_TYPE: 'iPhone', CONF_INZONE_INTERVAL: 120, @@ -873,29 +876,29 @@ CONF_RAW_MODEL : '', CONF_MODEL: '', CONF_MODEL_DISPLAY_NAME: '', - CONF_FMF_EMAIL: 'None', - CONF_FMF_DEVICE_ID: '', + #CONF_FMF_EMAIL: 'None', + #CONF_FMF_DEVICE_ID: '', CONF_MOBILE_APP_DEVICE: 'None', CONF_TRACK_FROM_BASE_ZONE: HOME, CONF_TRACK_FROM_ZONES: [HOME], CONF_LOG_ZONES: ['none'], } +# Used in conf_flow to reinialize the Configuration Devices +DEFAULT_DEVICE_DATA_SOURCE = { + CONF_APPLE_ACCOUNT: '', + CONF_FAMSHR_DEVICENAME: 'None', + CONF_FAMSHR_DEVICE_ID: '', + CONF_RAW_MODEL : '', + CONF_MODEL: '', + CONF_MODEL_DISPLAY_NAME: '', + CONF_MOBILE_APP_DEVICE: 'None', +} RANGE_DEVICE_CONF = { CONF_INZONE_INTERVAL: [5, 480], CONF_FIXED_INTERVAL: [0, 480], } -# Used in conf_flow to reinialize the Configuration Devices -# Reset the FamShe FmF Mobile App track_from_zone fields -DEFAULT_DEVICE_REINITIALIZE_CONF = DEFAULT_DEVICE_CONF.copy() -DEFAULT_DEVICE_REINITIALIZE_CONF.pop(CONF_IC3_DEVICENAME, None) -DEFAULT_DEVICE_REINITIALIZE_CONF.pop(CONF_FNAME, None) -DEFAULT_DEVICE_REINITIALIZE_CONF.pop(CONF_PICTURE, None) -DEFAULT_DEVICE_REINITIALIZE_CONF.pop(CONF_EVLOG_DISPLAY_ORDER, None) -DEFAULT_DEVICE_REINITIALIZE_CONF.pop(CONF_DEVICE_TYPE, None) -DEFAULT_DEVICE_REINITIALIZE_CONF.pop(CONF_UNIQUE_ID, None) - DEFAULT_GENERAL_CONF = { CONF_LOG_LEVEL: 'debug-auto-reset', CONF_LOG_LEVEL_DEVICES: ['all'], diff --git a/custom_components/icloud3/const_config_flow.py b/custom_components/icloud3/const_config_flow.py index 2fc0b8a..0c8c1a0 100644 --- a/custom_components/icloud3/const_config_flow.py +++ b/custom_components/icloud3/const_config_flow.py @@ -73,12 +73,13 @@ 'update_device': 'SELECT THE DEVICE ᐳ Update the selected device, Add a new device to be tracked by iCloud3, Display more Devices on the next page', 'add_device': 'ADD DEVICE ᐳ Add a device to be tracked by iCloud3', - 'delete_device': 'DELETE DEVICE(S), OTHER DEVICE MAINTENANCE ᐳ Delete the device(s) from the tracked device list, clear the iCloud/Mobile App selection fields', + 'delete_device': 'TOOLS - RESET DATA SOURCE(S), DELETE DEVICE(S) ᐳ Reset Apple Acct & Mobile App to `None`, Delete the device(s)', 'change_device_order': 'CHANGE DEVICE ORDER ᐳ Change the tracking order of the Devices and their display sequence on the Event Log', - 'delete_this_device': 'DELETE THIS DEVICE ᐳ Delete this device', - 'delete_all_devices': 'DELETE ALL DEVICES ᐳ Delete all devices from the iCloud3 tracked devices list', - 'delete_icloud_mobapp_info':'CLEAR ICLOUDR/MOBAPP INFO ᐳ Reset the iCloud/Mobile App seletion fields on all devices', + 'reset_this_device_data_source': 'RESET THIS DEVICE`S DATA SOURCE ᐳ Set Apple Acct & Mobile App to `None`', + 'delete_this_device': 'DELETE THIS DEVICE ᐳ Delete this device from the iCloud3 tracked devices list', + 'reset_all_devices_data_source': '⚠️ RESET ALL DEVICE`S DATA SOURCE ᐳ Set Apple Acct & Mobile App to `None`', + 'delete_all_devices': '⚠️ DELETE ALL DEVICES ᐳ Delete all devices from the iCloud3 tracked devices list', 'delete_device_cancel': 'CANCEL ᐳ Return to the Device List screen', 'inactive_to_track': 'TRACK ALL OR SELECTED ᐳ Change the `Tracking Mode‘ of all of the devices (or the selected devices) from `Inactive‘ to `Tracked‘', @@ -171,9 +172,10 @@ ACTION_LIST_OPTIONS['change_device_order'], ACTION_LIST_OPTIONS['return']] DELETE_DEVICE_ACTIONS = [ + ACTION_LIST_OPTIONS['reset_this_device_data_source'], ACTION_LIST_OPTIONS['delete_this_device'], + ACTION_LIST_OPTIONS['reset_all_devices_data_source'], ACTION_LIST_OPTIONS['delete_all_devices'], - ACTION_LIST_OPTIONS['delete_icloud_mobapp_info'], ACTION_LIST_OPTIONS['delete_device_cancel']] REVIEW_INACTIVE_DEVICES = [ ACTION_LIST_OPTIONS['inactive_to_track'], diff --git a/custom_components/icloud3/device.py b/custom_components/icloud3/device.py index 99bf8fc..fb72423 100644 --- a/custom_components/icloud3/device.py +++ b/custom_components/icloud3/device.py @@ -124,8 +124,8 @@ def initialize(self): # Operational variables self.device_type = 'iPhone' - self.raw_model = DEVICE_TYPE_FNAME.get(self.device_type, self.device_type) # iPhone15,2 - self.model = DEVICE_TYPE_FNAME.get(self.device_type, self.device_type) # iPhone + self.raw_model = DEVICE_TYPE_FNAME(self.device_type) # iPhone15,2 + self.model = DEVICE_TYPE_FNAME(self.device_type) # iPhone #self.model_display_name = DEVICE_TYPE_FNAME.get(self.device_type, self.device_type) # iPhone 14 Pro self.model_display_name = Gb.model_display_name_by_raw_model.get(self.raw_model, self.raw_model) # iPhone 14 Pro self.data_source = None @@ -815,15 +815,15 @@ def devicename_fname(self): @property def devtype_fname(self): - return DEVICE_TYPE_FNAME.get(self.device_type, self.device_type) + return DEVICE_TYPE_FNAME(self.device_type) @property def fname_devtype(self): - if instr(self.fname, DEVICE_TYPE_FNAME.get(self.device_type, self.device_type)): + if instr(self.fname, DEVICE_TYPE_FNAME(self.device_type)): return self.fname return (f"{self.fname} " - f"({DEVICE_TYPE_FNAME.get(self.device_type, self.device_type)})") + f"({DEVICE_TYPE_FNAME(self.device_type)})") # return (f"{self.fname}{INFO_SEPARATOR}" # f"{DEVICE_TYPE_FNAME.get(self.device_type, self.device_type)}") diff --git a/custom_components/icloud3/device_tracker.py b/custom_components/icloud3/device_tracker.py index 6e33d6f..ea6a0f3 100644 --- a/custom_components/icloud3/device_tracker.py +++ b/custom_components/icloud3/device_tracker.py @@ -32,7 +32,7 @@ log_info_msg, log_debug_msg, log_error_msg, log_exception, log_exception_HA, log_info_msg_HA, _evlog, _log, ) -from .helpers.time_util import (adjust_time_hour_values, secs_to_datetime) +from .helpers.time_util import (adjust_time_hour_values, datetime_now, ) from .support import start_ic3 from .support import config_file @@ -73,7 +73,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_e for conf_device in Gb.conf_devices if conf_device[CONF_IC3_DEVICENAME] != ''] - _get_ha_device_ids_from_device_registry(hass) + get_ha_device_ids_from_device_registry(hass) except Exception as err: log_exception(err) @@ -93,8 +93,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_e if DeviceTracker: Gb.DeviceTrackers_by_devicename[devicename] = DeviceTracker + # if devicename not in Gb.ha_device_id_by_devicename: + # NewDeviceTrackers.append(DeviceTracker) + # else: + # NewDeviceTrackers.append(DeviceTracker) NewDeviceTrackers.append(DeviceTracker) + # Set the total count of the device_trackers that will be created if Gb.device_trackers_cnt == 0: Gb.device_trackers_cnt = len(NewDeviceTrackers) @@ -103,7 +108,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_e if NewDeviceTrackers != []: async_add_entities(NewDeviceTrackers, True) - _get_ha_device_ids_from_device_registry(hass) + get_ha_device_ids_from_device_registry(hass) log_info_msg_HA(f"{ICLOUD3} Device Tracker entities: {Gb.device_trackers_cnt}") Devices_no_area = [Device for Device in Gb.DeviceTrackers_by_devicename.values() \ @@ -123,7 +128,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_e log_error_msg(log_msg) #------------------------------------------------------------------------------------------- -def _get_ha_device_ids_from_device_registry(hass): +def get_ha_device_ids_from_device_registry(hass): ''' Cycle thru the ha device registry, extract the iCloud3 entries and associate the ha device_id with the ic3_devicename parameters @@ -196,6 +201,12 @@ def _get_ha_device_id_from_device_entry(hass, device, device_entry): if item in Gb.conf_devicenames: Gb.ha_device_id_by_devicename[item] = device_entry.id Gb.ha_area_id_by_devicename[item] = device_entry.area_id + # _log(f"{device_entry.id=}") + # _log(f"{device_entry.name=}") + # _log(f"{device_entry.name_by_user=}") + # _log(f"{device_entry.identifiers=}") + # _log(f"{device_entry.is_new=}") + # _log(f"{device_entry.disabled_by=}") except Exception as err: @@ -215,10 +226,8 @@ def __init__(self, devicename, conf_device, data=None): self.devicename = devicename self.Device = None # Filled in after Device object has been created in start_ic3 self.entity_id = f"device_tracker.{devicename}" - # If the DOMAIN is not 'icloud3' ('iCloud3_dev'), add it to the device_tracker - # entity name to make it unique - if DOMAIN != 'icloud3': self.entity_id += f"_{DOMAIN}" self.ha_device_id = Gb.ha_device_id_by_devicename.get(self.devicename) + self.ha_area_id = Gb.ha_area_id_by_devicename.get(self.devicename) self.device_fname = conf_device[FNAME] @@ -241,6 +250,7 @@ def __init__(self, devicename, conf_device, data=None): self._attr_force_update = True self._unsub_dispatcher = None self._on_remove = [self.after_removal_cleanup] + self.remove_device_flag = False self.entity_removed_flag = False self.extra_attrs_track_from_zones = 'home' @@ -483,30 +493,32 @@ def _get_attribute_value(self, attribute): return attr_value #------------------------------------------------------------------------------------------- - def update_entity_attribute(self, new_fname=None, area_id=None): + def update_entity_attribute(self, new_fname=None, area_id=None, removing_device=False): """ Update entity definition attributes """ ha_device_id = Gb.ha_device_id_by_devicename.get(self.devicename) if ha_device_id is None: return - if new_fname is None and area_id is None: + if removing_device: + pass + elif new_fname is None and area_id is None: return - try: - area_id = area_id or self.ha_area_id or Gb.area_id_personal_device - area_reg = ar.async_get(Gb.hass) - area_name = area_reg.async_get_area(area_id).name - except: - area_id = area_name = None + # try: + # area_id = area_id or self.ha_area_id or Gb.area_id_personal_device + # area_reg = ar.async_get(Gb.hass) + # area_name = area_reg.async_get_area(area_id).name + # except: + # area_id = area_name = None self.device_fname = new_fname or self.device_fname - self.ha_area_id = Gb.ha_area_id_by_devicename[self.devicename] = \ - area_id + # self.ha_area_id = Gb.ha_area_id_by_devicename[self.devicename] = \ + # area_id log_debug_msg(f"Device Tracker entity changed: device_tracker.{self.devicename}, " - f"{self.device_fname} " - f"({area_name})") + f"{self.device_fname}") + # f"({area_name})") kwargs = {} kwargs['original_name'] = self.device_fname @@ -540,7 +552,7 @@ def update_entity_attribute(self, new_fname=None, area_id=None): kwargs = {} kwargs['name'] = f"{self.device_fname} ({self.devicename})" kwargs['name_by_user'] = "" - kwargs['area_id'] = self.ha_area_id + # kwargs['area_id'] = self.ha_area_id device_registry = dr.async_get(Gb.hass) dr_entry = device_registry.async_update_device(self.ha_device_id, **kwargs) @@ -582,6 +594,16 @@ def _remove_from_registries(self) -> None: if not self.registry_entry: return + device_registry = dr.async_get(Gb.hass) + + self.remove_device_flag = True + + # deleted = f"deleted-{datetime_now()}" + # kwargs = {'merge_identifiers': (deleted)} + # # kwargs = {'merge_identifiers': (DOMAIN, self.devicename, deleted)} + # dr_entry = device_registry.async_update_device(self.ha_device_id, **kwargs) + # _log(f"{dr_entry=}") + # Remove from device registry. if device_id := self.registry_entry.device_id: device_registry = dr.async_get(self.hass) diff --git a/custom_components/icloud3/global_variables.py b/custom_components/icloud3/global_variables.py index b9ebeae..cbe14fb 100644 --- a/custom_components/icloud3/global_variables.py +++ b/custom_components/icloud3/global_variables.py @@ -94,6 +94,7 @@ class GlobalVariables(object): HALogger = None iC3Logger = None iC3Logger_last_check_exist_secs = 0 + prestartup_log = '' iC3EntityPlatform = None # iCloud3 Entity Platform (homeassistant.helpers.entity_component) PyiCloud = None # iCloud Account service diff --git a/custom_components/icloud3/helpers/messaging-filter-redo.py b/custom_components/icloud3/helpers/messaging-filter-redo.py new file mode 100644 index 0000000..e35c31d --- /dev/null +++ b/custom_components/icloud3/helpers/messaging-filter-redo.py @@ -0,0 +1,1050 @@ + + +from ..global_variables import GlobalVariables as Gb +from ..const import (VERSION, VERSION_BETA, ICLOUD3, ICLOUD3_VERSION, DOMAIN, ICLOUD3_VERSION_MSG, + DOT, ICLOUD3_ERROR_MSG, EVLOG_DEBUG, EVLOG_ERROR, EVLOG_INIT_HDR, EVLOG_MONITOR, + EVLOG_TIME_RECD, EVLOG_UPDATE_HDR, EVLOG_UPDATE_START, EVLOG_UPDATE_END, + EVLOG_ALERT, EVLOG_WARNING, EVLOG_HIGHLIGHT, EVLOG_IC3_STARTING,EVLOG_IC3_STAGE_HDR, + IC3LOG_FILENAME, EVLOG_TIME_RECD, EVLOG_TRACE, + CRLF, CRLF_DOT, NBSP, NBSP2, NBSP3, NBSP4, NBSP5, NBSP6, CRLF_INDENT, LINK, + DASH_50, DASH_DOTTED_50, TAB_11, RED_ALERT, RED_STOP, RED_CIRCLE, YELLOW_ALERT, + DATETIME_FORMAT, DATETIME_ZERO, + NEXT_UPDATE_TIME, INTERVAL, + ICLOUD, MOBAPP, + CONF_IC3_DEVICENAME, CONF_FNAME, CONF_LOG_LEVEL, CONF_PASSWORD, CONF_USERNAME, + CONF_DEVICES, + LATITUDE, LONGITUDE, LOCATION_SOURCE, TRACKING_METHOD, + ZONE, ZONE_DATETIME, INTO_ZONE_DATETIME, LAST_ZONE, + TIMESTAMP, TIMESTAMP_SECS, TIMESTAMP_TIME, LOCATION_TIME, DATETIME, AGE, + TRIGGER, BATTERY, BATTERY_LEVEL, BATTERY_STATUS, + INTERVAL, ZONE_DISTANCE, CALC_DISTANCE, WAZE_DISTANCE, + TRAVEL_TIME, TRAVEL_TIME_MIN, DIR_OF_TRAVEL, MOVED_DISTANCE, + DEVICE_STATUS, LOW_POWER_MODE, ICLOUD_LOST_MODE_CAPABLE, + AUTHENTICATED, + LAST_UPDATE_TIME, LAST_UPDATE_DATETIME, NEXT_UPDATE_TIME, LAST_LOCATED_DATETIME, LAST_LOCATED_TIME, + INFO, GPS_ACCURACY, GPS, POLL_COUNT, VERT_ACCURACY, ALTITUDE, + BADGE, + ) +from ..const_more_info import more_info_text +from .common import (obscure_field, instr, is_empty, isnot_empty, ) + +import homeassistant.util.dt as dt_util +from homeassistant.components import persistent_notification + +import os +import time +from inspect import getframeinfo, stack +import traceback +import logging + +DO_NOT_SHRINK = ['url', 'accountName', ] +FILTER_DATA_DICTS = ['items', 'userInfo', 'dsid', 'dsInfo', 'webservices', 'locations','location', + 'params', 'headers', 'kwargs', ] +FILTER_DATA_LISTS = ['devices', 'content', 'followers', 'following', 'contactDetails',] +FILTER_FIELDS = [ + ICLOUD3_VERSION, AUTHENTICATED, + LATITUDE, LONGITUDE, LOCATION_SOURCE, TRACKING_METHOD, + ZONE, ZONE_DATETIME, INTO_ZONE_DATETIME, LAST_ZONE, + TIMESTAMP, TIMESTAMP_SECS, TIMESTAMP_TIME, LOCATION_TIME, DATETIME, AGE, + TRIGGER, BATTERY, BATTERY_LEVEL, BATTERY_STATUS, + INTERVAL, ZONE_DISTANCE, CALC_DISTANCE, WAZE_DISTANCE, + TRAVEL_TIME, TRAVEL_TIME_MIN, DIR_OF_TRAVEL, MOVED_DISTANCE, + DEVICE_STATUS, LOW_POWER_MODE, BADGE, + LAST_UPDATE_TIME, LAST_UPDATE_DATETIME, + NEXT_UPDATE_TIME, NEXT_UPDATE_TIME, + LAST_LOCATED_TIME, LAST_LOCATED_DATETIME, + INFO, GPS_ACCURACY, GPS, POLL_COUNT, VERT_ACCURACY, ALTITUDE, ICLOUD_LOST_MODE_CAPABLE, + 'ResponseCode', 'reason', + 'id', 'firstName', 'lastName', 'name', 'fullName', CONF_IC3_DEVICENAME, + 'appleId', 'emails', 'phones', 'locked', + 'deviceStatus', 'batteryStatus', 'batteryLevel', 'membersInfo', + 'deviceModel', 'rawDeviceModel', 'deviceDisplayName', 'modelDisplayName', 'deviceClass', + 'isOld', 'isInaccurate', 'timeStamp', 'altitude', 'location', 'latitude', 'longitude', + 'horizontalAccuracy', 'verticalAccuracy', + 'hsaVersion', 'hsaEnabled', 'hsaTrustedBrowser', 'hsaChallengeRequired', + 'locale', 'appleIdEntries', 'statusCode', + 'familyEligible', 'findme', 'requestInfo', + 'invitationSentToEmail', 'invitationAcceptedByEmail', 'invitationFromHandles', + 'invitationFromEmail', 'invitationAcceptedHandles', + 'items', 'userInfo', 'prsId', 'dsid', 'dsInfo', 'webservices', 'locations', + 'devices', 'content', 'followers', 'following', 'contactDetails', + 'dsWebAuthToken', 'accountCountryCode', 'extended_login', 'trustToken', + 'data', 'json', 'headers', 'params', 'url', 'retry_cnt', 'retried', 'retry', '#', + 'code', 'ok', 'method', 'securityCode', + 'accountName', 'salt', 'a', 'b', 'c', 'm1', 'm2', 'protocols', 'iteration', 'Authorization', ] + + +SP_str = ' '*50 +SP_dict = { + 4: SP_str[1:4], + 5: SP_str[1:5], + 6: SP_str[1:6], + 8: SP_str[1:8], + 9: SP_str[1:9], + 10: SP_str[1:10], + 11: SP_str[1:11], + 12: SP_str[1:12], + 13: SP_str[1:13], + 14: SP_str[1:14], + 16: SP_str[1:16], + 22: SP_str[1:22], + 28: SP_str[1:28], + 26: SP_str[1:26], + 44: SP_str[1:44], + 48: SP_str[1:48], + 50: SP_str, +} +def SP(space_cnt): + if space_cnt in SP_dict: return SP_dict[space_cnt] + if space_cnt < len(SP_str): return SP_str[1:space_cnt] + return ' '*space_cnt + +#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +# +# MISCELLANEOUS MESSAGING ROUTINES +# +#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +def broadcast_info_msg(info_msg): + ''' + Display a message in the info sensor for all devices + ''' + if INFO not in Gb.conf_sensors['device']: + return + + Gb.broadcast_info_msg = f"{info_msg}" + + try: + for conf_device in Gb.conf_devices: + devicename = conf_device[CONF_IC3_DEVICENAME] + InfoSensor = Gb.Sensors_by_devicename[devicename][INFO] + InfoSensor.write_ha_sensor_state() + + # Catch error if the Info sensor has not been set up yet during startup + # or if the info sensor has not been selected in config_vlow > sensors + except KeyError: + pass + + + return + +#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +# +# EVENT LOG POST ROUTINES +# +#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +def post_event(devicename_or_Device, event_msg='+'): + ''' + Add records to the Event Log table. This does not change + the info displayed on the Event Log screen. Use the + '_update_event_log_display' function to display the changes. + ''' + + devicename, event_msg = _resolve_devicename_log_msg(devicename_or_Device, event_msg) + + try: + if event_msg.endswith(', '): + event_msg = event_msg[:-2] + event_msg = event_msg.replace(', , ', ', ') + except: + # Gb.HALogger.info(event_msg) + pass + + Gb.EvLog.post_event(devicename, event_msg) + + if len(event_msg) <= 3: + return + + elif event_msg[0:2] in [EVLOG_ALERT, EVLOG_ERROR, EVLOG_WARNING]: + alert_type = event_msg[0:2] + event_msg = (f"{devicename} > {str(event_msg)}") + if alert_type == EVLOG_ALERT: + log_warning_msg(event_msg) + elif alert_type == EVLOG_ERROR: + log_error_msg(event_msg) + elif alert_type == EVLOG_WARNING: + log_warning_msg(event_msg) + + elif instr(event_msg, EVLOG_IC3_STAGE_HDR): + pass + + elif event_msg.startswith('^') is False: + event_msg = (f"{devicename} > {str(event_msg)}") + log_info_msg(event_msg) + + elif event_msg.startswith(EVLOG_TIME_RECD) is False: + event_msg = (f"{devicename} > {str(event_msg)}") + log_debug_msg(event_msg) + +#-------------------------------------------------------------------- +def post_error_msg(devicename_or_Device, event_msg="+"): + ''' + Always display log_msg in Event Log; always add to HA log + ''' + devicename, event_msg = _resolve_devicename_log_msg(devicename_or_Device, event_msg) + if event_msg.find("iCloud3 Error") >= 0: + for Device in Gb.Devices_by_devicename.values(): + Device.display_info_msg(ICLOUD3_ERROR_MSG) + + post_event(devicename, event_msg) + + if devicename == '*': + devicename = 'iCloud3 Error >' if event_msg.find("iCloud3 Error") < 0 else '' + log_msg = (f"{devicename} {event_msg}") + log_msg = str(log_msg).replace(CRLF, ". ") + + log_error_msg(log_msg) + +#-------------------------------------------------------------------- +def post_monitor_msg(devicename_or_Device, event_msg='+'): + ''' + Post the event message and display it in Event Log and HA log + when the config parameter "log_level: eventlog" is specified or + the Show Tracking Monitors was selected in Event Log > Actions + ''' + devicename, event_msg = _resolve_devicename_log_msg(devicename_or_Device, event_msg) + post_event(devicename, f"{EVLOG_MONITOR}{event_msg}") + +#------------------------------------------------------------------------------------------- +def refresh_event_log(devicename='', show_one_screen=False): + Gb.EvLog.update_event_log_display(devicename='', show_one_screen=False) + +#------------------------------------------------------------------------------------------- +def post_evlog_greenbar_msg(Device, evlog_greenbar_msg='+'): + ''' + Post an Alert Message on the first line of the event log items + + Input: + Device: + If specified, display msg on this Device's EvLog screen only + If not specified, display on all screens + evlog_greenbar_msg: + Message to display - + ''' + if evlog_greenbar_msg == '': + return Gb.EvLog.clear_evlog_greenbar_msg() + + # See if the message is really in the Device parameter + if evlog_greenbar_msg == '+': + evlog_greenbar_msg = Device + + # Device was specified, check to see if this is the screen displayed + else: + fname = Device.fname if Device.is_tracked else f"{Device.fname} 🅜" + if Gb.EvLog.evlog_attrs["fname"] != fname: + return + + if Gb.EvLog.greenbar_alert_msg == evlog_greenbar_msg: + return + + else: + Gb.EvLog.greenbar_alert_msg = evlog_greenbar_msg + Gb.EvLog.display_user_message(Gb.EvLog.user_message) + +#------------------------------------------------------------------------------------------- +def clear_evlog_greenbar_msg(): + Gb.EvLog.clear_evlog_greenbar_msg() + Gb.EvLog.display_user_message(Gb.EvLog.user_message) + +#-------------------------------------------------------------------- +def post_startup_alert(alert_msg): + + if alert_msg not in Gb.startup_alerts: + Gb.startup_alerts.append(alert_msg) + +#------------------------------------------------------------------------------------------- +def ha_notification(msg_line1, msg_line2=None) -> None: + + if msg_line2: + msg_line1 += f"
{msg_line2}" + + persistent_notification.create( + Gb.hass, + f"Notification: {msg_line1}", + title=f"iCloud3 Notification", + notification_id="icloud3", + ) + +#-------------------------------------------------------------------- +def format_filename(path): + if path.startswith('/config') or len(path) < 50: + return path + else: + return (f"{Gb.ha_config_directory}……{CRLF_INDENT}" + f"{path.replace(Gb.ha_config_directory, '')}") +#-------------------------------------------------------------------- +def more_info(key): + + if key in Gb.startup_stage_status_controls: + return f"{more_info_text['instructions_already_displayed']}" + + elif key in more_info_text: + Gb.startup_stage_status_controls.append(key) + return more_info_text[key] + + else: + return f"{more_info_text['invalid_msg_key']} `{key}`" + +#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +# +# ICLOUD3-DEBUG.LOG FILE ROUTINES +# +#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +def open_ic3log_file_init(): + ''' + Entry point for async_add_executor_job in __init__.py + ''' + open_ic3log_file(new_log_file=Gb.log_debug_flag) + +#------------------------------------------------------------------------------ +def open_ic3log_file(new_log_file=False): + + # Items will be logged to home-assistane.log until the configuration file has been read + if is_empty(Gb.conf_general): + return + + ic3logger_file = Gb.hass.config.path(IC3LOG_FILENAME) + filemode = 'w' if new_log_file else 'a' + + if Gb.iC3Logger is None or new_log_file: + Gb.iC3Logger = logging.getLogger(DOMAIN) + formatter = logging.Formatter('%(asctime)s %(message)s', datefmt='%m-%d %H:%M:%S') + fileHandler = logging.FileHandler(ic3logger_file, mode=filemode, encoding='utf-8') + fileHandler.setFormatter(formatter) + Gb.iC3Logger.addHandler(fileHandler) + Gb.iC3Logger.propagate = (Gb.conf_general[CONF_LOG_LEVEL] == 'debug-ha') + Gb.iC3Logger.setLevel(logging.INFO) + + write_config_file_to_ic3log() + + +#-------------------------------------------------------------------- +def write_ic3log_recd(log_msg): + ''' + Check to make sure the icloud3-0.log file exists if the last write to it + was moe than 2-secs ago. Recreate it if it does not. This catches deletes + and renames while iCloud3 is running + ''' + try: + if is_empty(Gb.conf_general): + Gb.prestartup_log += f"\n{dt_util.now().strftime(DATETIME_FORMAT)[5:19]} {log_msg}" + return + + time_now_msecs = time.time() + if time_now_msecs - Gb.iC3Logger_last_check_exist_secs > 2: + Gb.iC3Logger_last_check_exist_secs = time_now_msecs + + check_ic3log_file_exists(Gb.hass.config.path(IC3LOG_FILENAME)) + + if Gb.iC3Logger: + Gb.iC3Logger.info(log_msg) + else: + open_ic3log_file(new_log_file=True) + + Gb.iC3Logger.info(log_msg) + + except Exception as err: + return False + +#-------------------------------------------------------------------- +def check_ic3log_file_exists(ic3logger_file): + ''' + See if the icloud3-0.log file exists. Recreate it if it does not. This + catches deletes and renames while iCloud3 is running + ''' + try: + if Gb.iC3Logger is None: + open_ic3log_file(new_log_file=True) + + elif Gb.iC3Logger and os.path.isfile(ic3logger_file) is False: + Gb.iC3Logger.removeHandler(Gb.iC3Logger.handlers[0]) + open_ic3log_file(new_log_file=True) + + log_msg = f"{EVLOG_IC3_STARTING}Recreated iCloud3 Log File: {ic3logger_file}" + log_msg = f"{format_startup_header_box(log_msg, 20)}" + log_msg = log_msg.replace('⡇', '⛔') + Gb.iC3Logger.info(log_msg) + + return True + + except IndexError: + pass + except Exception as err: + log_exception_HA(err) + + return False + +#-------------------------------------------------------------------- +def archive_ic3log_file(): + ''' + At midnight, archive the log files and create a new one for the current day + Remove icloud-2.log, + rename icloud3-1.log to icloud3_2.log, + rename icloud3-0.log to icloud3-1.log + ''' + try: + log_file_0 = Gb.hass.config.path(IC3LOG_FILENAME) + log_file_1 = Gb.hass.config.path(IC3LOG_FILENAME).replace('-0.', '-1.') + log_file_2 = Gb.hass.config.path(IC3LOG_FILENAME).replace('-0.', '-2.') + + post_event(f"{ICLOUD3} Log File Archived") + + if os.path.isfile(log_file_2): os.remove(log_file_2) + if os.path.isfile(log_file_1): os.rename(log_file_1, log_file_2) + + if os.path.isfile(log_file_0): + Gb.iC3Logger.removeHandler(Gb.iC3Logger.handlers[0]) + os.rename(log_file_0, log_file_1) + + open_ic3log_file(new_log_file=True) + + except Exception as err: + post_event(f"{ICLOUD3} Log File Archive encountered an error > {err}") + +#------------------------------------------------------------------------------ +def write_config_file_to_ic3log(): + + if Gb.prestartup_log: + write_ic3log_recd(f"{Gb.prestartup_log}") + Gb.prestartup_log = '' + + conf_tracking_recd = Gb.conf_tracking.copy() + # conf_tracking_recd[CONF_USERNAME] = obscure_field(conf_tracking_recd[CONF_USERNAME]) + conf_tracking_recd[CONF_PASSWORD] = obscure_field(conf_tracking_recd[CONF_PASSWORD]) + conf_tracking_recd[CONF_DEVICES] = f"{len(Gb.conf_devices)}" + + Gb.trace_prefix = '_INIT_' + indent = SP(44) if Gb.log_debug_flag else SP(26) + log_msg = ( f"{ICLOUD3_VERSION_MSG}, " + f"{dt_util.now().strftime('%A')}, " + f"{dt_util.now().strftime(DATETIME_FORMAT)[:19]}") + log_msg = ( f" \n" + f"{indent}⛔{DASH_50}\n" + f"{indent}⛔ {log_msg}\n" + f"{indent}⛔{DASH_50}") + + Gb.iC3Logger.info(log_msg) + + # Write the ic3 configuration (general & devices) to the Log file + log_info_msg(f"Profile:\n" + f"{indent}{Gb.conf_profile}") + log_info_msg(f"Tracking:\n" + f"{indent}{conf_tracking_recd}") + log_info_msg(f"Apple Accounts:\n" + f"{indent}{Gb.conf_apple_accounts}") + log_info_msg(f"General Configuration:\n" + f"{indent}{Gb.conf_general}\n" + f"{indent}{Gb.ha_location_info}") + log_info_msg("") + + for conf_device in Gb.conf_devices: + log_info_msg( f"{conf_device[CONF_FNAME]}, {conf_device[CONF_IC3_DEVICENAME]}:\n" + f"{indent}{conf_device}") + log_info_msg("") + +#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +# +# LOG MESSAGE ROUTINES +# +#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + +def log_info_msg_HA(msg): + Gb.HALogger.info(msg) + +def log_warning_msg_HA(msg): + Gb.HALogger.warning(msg) + +def log_error_msg_HA(msg): + Gb.HALogger.error(msg) + +def log_exception_HA(err): + Gb.HALogger.exception(err) + +#-------------------------------------------------------------------- +def log_info_msg(module_name, log_msg='+'): + + if module_name and log_msg == '': return + log_msg = _resolve_module_name_log_msg(module_name, log_msg) + + log_msg = format_msg_line(log_msg) + write_ic3log_recd(log_msg) + + log_msg = log_msg.replace(' > +', f" > ……\n{SP(22)}+") + Gb.HALogger.debug(log_msg) + +#-------------------------------------------------------------------- +def log_warning_msg(module_name, log_msg='+'): + + log_msg = _resolve_module_name_log_msg(module_name, log_msg) + log_msg = filter_special_chars(log_msg) + Gb.HALogger.warning(log_msg) + + log_msg = format_msg_line(log_msg) + write_ic3log_recd(log_msg) + +#-------------------------------------------------------------------- +def log_error_msg(module_name, log_msg='+'): + + log_msg = _resolve_module_name_log_msg(module_name, log_msg) + log_msg = filter_special_chars(log_msg) + Gb.HALogger.error(log_msg) + + log_msg = format_msg_line(log_msg) + write_ic3log_recd(log_msg) + +#-------------------------------------------------------------------- +def log_exception(err): + + try: + write_ic3log_recd(f"{ICLOUD3_VERSION_MSG}\n{traceback.format_exc()}") + except: + write_ic3log_recd(err) + + Gb.HALogger.exception(err) + +#-------------------------------------------------------------------- +def log_debug_msg(devicename_or_Device, log_msg='+', msg_prefix=None): + + if Gb.log_debug_flag is False: return + if devicename_or_Device and log_msg == '': return + + devicename, log_msg = _resolve_devicename_log_msg(devicename_or_Device, log_msg) + + dn_str = '' if devicename == '*' else f"{devicename} > " + log_msg = f"{dn_str}{str(log_msg).replace(CRLF, ', ')}" + log_msg = format_msg_line(log_msg) + + write_ic3log_recd(log_msg) + + log_msg = log_msg.replace(' > +', f" > ……\n{SP(22)}+") + Gb.HALogger.debug(log_msg) + +#-------------------------------------------------------------------- +def log_start_finish_update_banner(start_finish, devicename, + method, update_reason): + ''' + Display a banner in the log file at the start and finish of a + device update cycle + ''' + # The devicename may be the 'appleacct~devicename' + # if instr(devicename, '~'): devicename = devicename.split('~')[1] + # Device = Gb.Devices_by_devicename[devicename] + text = (f"{devicename}, {method}, {update_reason} ") + log_msg = format_header_box(text, indent=43, start_finish=start_finish) + + log_info_msg(log_msg) + + + +#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +# +# LOG MESSAGE SUPPORT ROUTINES +# +#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +def format_msg_line(log_msg, area=None): + try: + if type(log_msg) is str is False: + return log_msg + + program_area = f"{RED_STOP} " if instr(log_msg, EVLOG_ALERT) else \ + f"{RED_ALERT} " if instr(log_msg, EVLOG_ERROR) else \ + area if area else \ + Gb.trace_prefix + source = f"{_called_from()}{program_area}" + log_msg = format_startup_header_box(log_msg) + msg_prefix= ' ' if log_msg.startswith('⡇') else \ + '\n🔺 ' if instr(log_msg, 'REQUEST') else \ + '\n🔻 ' if instr(log_msg, 'RESPONSE') else \ + '\n❗' if instr(log_msg, 'ICLOUD DATA') else \ + ' ⡇ ' if Gb.trace_group else \ + ' ' + + # if instr(msg_prefix, '🔺'): log_msg += " 🔺" + # if instr(msg_prefix, '🔻'): log_msg += " 🔻" + log_msg = filter_special_chars(log_msg) + log_msg = f"{source}{msg_prefix}{log_msg}" + + except: + pass + + return log_msg + +#-------------------------------------------------------------------- +def filter_special_chars(log_msg, evlog_export=False): + ''' + Filter out EVLOG_XXX control fields + ''' + + indent =SP(16) if evlog_export else \ + SP(48) if Gb.log_debug_flag else \ + SP(28) + if log_msg.startswith('^'): log_msg = log_msg[3:] + + log_msg = log_msg.replace(EVLOG_MONITOR, '') + log_msg = log_msg.replace(NBSP, ' ') + log_msg = log_msg.replace(NBSP2, ' ') + log_msg = log_msg.replace(NBSP3, ' ') + log_msg = log_msg.replace(NBSP4, ' ') + log_msg = log_msg.replace(NBSP5, ' ') + log_msg = log_msg.replace(NBSP6, ' ') + log_msg = log_msg.strip() + log_msg = log_msg.replace(CRLF, f"\n{indent}") + log_msg = log_msg.replace('◦', f" ◦") + log_msg = log_msg.replace('* >', '') + log_msg = log_msg.replace('<', '<') + + for log_file_filter in Gb.log_file_filter: + log_msg = log_msg.replace(log_file_filter, '…………') + + if log_msg.find('^') == -1: return log_msg.strip() + + log_msg = log_msg.replace(EVLOG_TIME_RECD , '') + log_msg = log_msg.replace(EVLOG_UPDATE_HDR, '') + log_msg = log_msg.replace(EVLOG_UPDATE_START, '') + log_msg = log_msg.replace(EVLOG_UPDATE_END , '') + log_msg = log_msg.replace(EVLOG_ERROR, '') + log_msg = log_msg.replace(EVLOG_ALERT, '') + log_msg = log_msg.replace(EVLOG_WARNING, '') + log_msg = log_msg.replace(EVLOG_INIT_HDR, '') + log_msg = log_msg.replace(EVLOG_HIGHLIGHT, '') + log_msg = log_msg.replace(EVLOG_IC3_STARTING, '') + log_msg = log_msg.replace(EVLOG_IC3_STAGE_HDR, '') + + log_msg = log_msg.replace('^1^', '').replace('^2^', '').replace('^3^', '') + log_msg = log_msg.replace('^4^', '').replace('^5^', '') + + + return log_msg.strip() + + +#-------------------------------------------------------------------- +def format_startup_header_box(log_msg): + ''' + Put a box around this item if it is an Event Log header item + ''' + p = log_msg.find('^') + if p == -1: + return log_msg + + hdr_code = log_msg[p:p+3] + log_msg = log_msg[:p] + log_msg[p+3:] + if hdr_code in [EVLOG_IC3_STARTING, EVLOG_IC3_STAGE_HDR]: + log_msg = format_header_box(log_msg) + + return log_msg + +#-------------------------------------------------------------------- +def format_header_box(log_msg, indent=None, start_finish=None, evlog_export=False): + ''' + Format a box around this item + ''' + start_pos = log_msg.find('^') + if start_pos == -1: start_pos = 0 + + # Default indent for icloud3-0.log file is 43 + if indent is None: indent = 43 + + top_char = bot_char = DASH_50 + if start_finish == 'start': + bot_char = f"{'⠂'*37}" + Gb.trace_group = True + elif start_finish == 'finish': + top_char = f"{'⠂'*37}" + Gb.trace_group = False + + return (f"⡇{top_char}\n" + f"{SP(indent)}⡇{SP(4)}{log_msg[start_pos:].upper()}\n" + f"{SP(indent)}⡇{bot_char}") + +#------------------------------------------------------------------------------------------- +def _resolve_devicename_log_msg(devicename_or_Device, event_msg): + if event_msg == '+': + return ("*", devicename_or_Device) + if devicename_or_Device in Gb.Devices: + return devicename_or_Device.devicename, event_msg + if devicename_or_Device in Gb.Devices_by_devicename: + return devicename_or_Device, event_msg + return ('*', event_msg) + +#-------------------------------------------------------------------- +def _resolve_module_name_log_msg(module_name, log_msg): + if log_msg == "+": + try: + return (module_name.replace(NBSP, '')) + except: + pass + + return (f"{module_name} {log_msg.replace(NBSP, '')}") + +#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +# +# RAWDATA LOGGING ROUTINES +# +#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +def log_rawdata_unfiltered(title, rawdata, data_source=None, filter_id=None): + try: + rawdata_copy = rawdata['raw'].copy() if 'raw' in rawdata else rawdata.copy() + + except: + log_info_msg(f"__ {title.upper()}\n{rawdata}") + return + + devices_data = {} + + if 'content' in rawdata_copy: + for device_data in rawdata_copy['content']: + devices_data[device_data['name']] = device_data + + rawdata_copy['content'] = 'DeviceData……' + + log_info_msg(f"__ {title.upper()}\n{rawdata_copy}") + + for device_data in devices_data: + log_msg = ( f"iCloud PyiCloud Data (unfiltered -- " + f"{device_data}") + log_info_msg(log_msg) + +#-------------------------------------------------------------------- +def log_rawdata(title, rawdata, log_rawdata_flag=False, data_source=None, filter_id=None): + ''' + Add raw data records to the HA log file for debugging purposes. + + This is used in Pyicloud_ic3 to log all data requests and responses, + and in other routines in iCloud3 when device_tracker or other entities + are read from or updated in HA. + + A filter is applied to the raw data and dictionaries and lists in the + data to eliminate displaying uninteresting fields. The fields, dictionaries, + and list are defined in the FILTER_FIELDS, FILTER_DATA_DICTS and + FILTER_DATA_LISTS. + ''' + + if rawdata is None: + return False + elif Gb.log_rawdata_flag is False and log_rawdata_flag is False: + return False + + if (Gb.start_icloud3_inprocess_flag + or 'all' in Gb.log_level_devices + or Gb.log_level_devices == []): + pass + + elif (Gb.log_level_devices + and (instr(title, ICLOUD) + or instr(title, MOBAPP) + or instr(title, 'iCloud') + or instr(title, 'Mobile'))): + + if instr(title,'iCloud Data'): + log_level_devices = [devicename for devicename in Gb.log_level_devices if instr(title, devicename)] + if log_level_devices == []: + return + + rawdata_data = {} + log_msg = '' + + try: + if type(rawdata) is not dict: + log_info_msg(f"__ {title.upper()}\n{rawdata}") + return + + rawdata_items = {k: _shrink_value(k, v) + for k, v in rawdata['filter'].items() + if type(v) not in [dict, list]} + + rawdata_data['filter'] = {k: _shrink_value(k, v) + for k, v in rawdata['filter'].items() + if k in FILTER_FIELDS or Gb.log_rawdata_flag_unfiltered} + except: + rawdata_items = {k: _shrink_value(k, v) + for k, v in rawdata.items() + if type(v) not in [dict, list]} + + rawdata_data['filter'] = {k: _shrink_value(k, v) + for k, v in rawdata.items() + if (k in FILTER_FIELDS or Gb.log_rawdata_flag_unfiltered)} + + + filter_dict, filter_list, filter_data_items, filter_data_dict = filter_raw_data(rawdata_items) + if filter_dict: + log_msg += f"\n❗ {data_dict}={filter_dict}" + if filter_list: + log_msg += f"\n❗ {data_list}={filter_list}" + if rawdata_data_items: + log_msg += f"\n❗ data.items={rawdata_data_items}" + if log_msg: + log_info_msg(f"{title.upper()}{log_msg}") + + return + + +def filter_rawdata_data(rawdata_items): + rawdata_data = {} + rawdata_data['filter']['items'] = rawdata_items + filter_dict = {} + log_msg = '' + + if rawdata_data['filter']: + for data_dict in FILTER_DATA_DICTS: + filter_dict_items = filter_data_dict(rawdata_data['filter'], data_dict) + if filter_dict_items: + filter_dict[data_dict] = filter_dict_items + log_msg += f"\n❗ {data_dict}={filter_dict}" + + for data_list in FILTER_DATA_LISTS: + if data_list in rawdata_data['filter']: + filter_list_items = _filter_data_list(rawdata_data['filter'][data_list]) + if filter_list_items: + log_msg += f"\n❗ {data_list}={filter_list}" + + + if 'data' in rawdata_data['filter']: + try: + filter_data_items = {k: _shrink_value(k, v) + for k, v in rawdata_data['filter']['data'].items() + if k in FILTER_FIELDS and type(v) not in [dict, list]} + if filter_data_items: + log_msg += f"\n❗ data.items={filter_data_items}" + except: + pass + + for data_dict in FILTER_DATA_DICTS: + filter_data_dict = filter_data_dict(rawdata_data['filter']['data'], data_dict) + if filter_data_dict: + log_msg += f"\n❗ data.{data_dict}={filter_data_dict}" + return filter_dict, filter_list, filter_data_items, filter_data_dict + + if log_msg: + log_info_msg(f"{title.upper()}{log_msg}") + + return + +#-------------------------------------------------------------------- +def filter_data_dict(rawdata_data, data_dict): + try: + if data_dict == 'webservices' and 'webservices' in rawdata_data: + webservices = { 'findme': rawdata_data['webservices']['findme'], + 'contacts': rawdata_data['webservices']['contacts']} + return webservices + # return rawdata_data.get('webservices') + + filter_results = {k: _shrink_value(k, v) + for k, v in rawdata_data[data_dict].items() + if (k in FILTER_FIELDS or Gb.log_rawdata_flag_unfiltered)} + + return filter_results + + except Exception as err: + # log_exception(err) + return '' + +#-------------------------------------------------------------------- +def _filter_data_list(rawdata_data_list): + + try: + filtered_list = '' + for list_item in rawdata_data_list: + + filter_results = {k: _shrink_value(k, v) + for k, v in list_item.items() + if (k in FILTER_FIELDS or Gb.log_rawdata_flag_unfiltered)} + + if 'location' in filter_results and filter_results['location']: + if Gb.log_rawdata_flag_unfiltered: + filter_results['location'] = {k: v for k, v in filter_results['location'].items()} + else: + filter_results['location'] = {k: v for k, v in filter_results['location'].items() + if k in FILTER_FIELDS} + filter_results['location'].pop('address', None) + + if filter_results: + filtered_list += f"\n❗ {filter_results['name']}={filter_results}" + + return filtered_list + + except: + return '' + +#-------------------------------------------------------------------- +def _shrink_value(k, v): + if (k in DO_NOT_SHRINK + or Gb.log_rawdata_flag_unfiltered): + return v + + if type(v) is str: + if v.startswith('http'): + return v + + if len(v) > 20: + return f"{v[:6]}……{v[-6:]}" + else: + return v + else: + return v + +#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +# +# ERROR MESSAGE ROUTINES +# +#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +def internal_error_msg(err_text, msg_text=''): + + caller = getframeinfo(stack()[1][0]) + filename = os.path.basename(caller.filename).split('.')[0][:12] + try: + parent = getframeinfo(stack()[2][0]) + parent_lineno = parent.lineno + except: + parent_lineno = '' + + if msg_text: + msg_text = (f", {msg_text}") + + log_msg = (f"INTERNAL ERROR-RETRYING ({parent_lineno}>{caller.lineno}{msg_text} -- " + f"{filename}»{caller.function[:20]} -- {err_text})") + post_error_msg(log_msg) + + attrs = {} + attrs[INTERVAL] = 0 + attrs[NEXT_UPDATE_TIME] = DATETIME_ZERO + + return attrs + +#-------------------------------------------------------------------- +def internal_error_msg2(err_text, traceback_format_exec_obj): + post_internal_error(err_text, traceback_format_exec_obj) + +def post_internal_error(err_text, traceback_format_exec_obj='+'): + + ''' + Display an internal error message in the Event Log and the in the HA log file. + + Parameters: + - traceback_format_exc = traceback.format_exec_obj object with the error information + + Example traceback_format_exec_obj(): + [ + 'Traceback (most recent call last):' + ' File "/config/custom_components/icloud3/support/start_ic3.py", line 1268, in setup_tracked_devices_for_icloud' + " a = 1 + 'a'" + ' ~~^……' + "TypeError: unsupported operand type(s) for +: 'int' and 'str'" + '' + ] + ''' + if traceback_format_exec_obj == '+': + traceback_format_exec_obj = err_text + err_text = '' + + tb_err_msg = traceback_format_exec_obj() + log_error_msg(tb_err_msg) + + # rc9 Reworked message extraction due to Python code change + err_lines = tb_err_msg.split('\n') + err_error_msg = err_code = err_file_line_module = "" + err_lines.reverse() + + + for err_line in err_lines: + err_line = err_line.strip() + if (err_line == "" or err_line.find('~') >= 0 or err_line.find('^^') >= 0 + or err_line.startswith('^')): + continue + + elif err_error_msg == "": + err_error_msg = err_line + + elif err_code == "": + err_code = err_line + + elif err_line.startswith('File') and err_file_line_module == '': + err_file_line_module = err_line.replace(Gb.icloud3_directory, '') + + + try: + err_msg = (f"{CRLF_DOT}File…… > {err_file_line_module})" + f"{CRLF_DOT}Code > {err_code}" + f"{CRLF_DOT}Error. > {err_error_msg}") + + except Exception as err: + err_msg = f"{CRLF_DOT}Unknown Error, Review HA Logs" + + post_event(f"{EVLOG_ERROR}INTERNAL ERROR > {err_text}{err_msg}") + + attrs = {} + attrs[INTERVAL] = '0 sec' + attrs[NEXT_UPDATE_TIME] = DATETIME_ZERO + + return attrs + +#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +# +# DEBUG TRACE ROUTINES +# +#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +def dummy_evlog(): + _evlog(None, None) +def dummy_log(): + _log(None, None) + +#-------------------------------------------------------------------- +def _evlog(devicename_or_Device, items='+'): + ''' + Display a message or variable in the Event Log + ''' + devicename, items = _resolve_devicename_log_msg(devicename_or_Device, items) + + if (type(items) is str) is False: + items = f"{items}" + items = items.replace('<', '<') + called_from = _called_from(trace=True) + + #rc9 Reworked post_event and write_config_file to call modules directly + if Gb.EvLog: + Gb.EvLog.post_event(devicename, f"{EVLOG_TRACE}{called_from} {items}") + write_ic3log_recd(f"{called_from}⛔.⛔ . . . {devicename} > {items}") + +#-------------------------------------------------------------------- +def _log(items, v1='+++', v2='', v3='', v4='', v5=''): + ''' + Display a message or variable in the HA log file + ''' + try: + called_from = _called_from(trace=True) + if v1 == '+++': + trace_msg = '' + else: + trace_msg = (f"|{v1}|-|{v2}|-|{v3}|-|{v4}|-|{v5}|") + + trace_msg = (f"{called_from}⛔.⛔ . . . {items} {trace_msg}") + + write_ic3log_recd(trace_msg) + + + except Exception as err: + Gb.HARootLogger.info(trace_msg) + # log_exception(err) + +#-------------------------------------------------------------------- +def _called_from(trace=False): + + + if Gb.log_debug_flag is False and trace == False: + return '' + + caller = None + level = 0 + while level < 5: + level += 1 + caller = getframeinfo(stack()[level][0]) + + if caller.filename.endswith('messaging.py') is False: + break + + if caller is None: + return ' ' + + caller_path = caller.filename.replace('.py','') + caller_filename = f"{caller_path.split('/')[-1]}………….." + caller_lineno = caller.lineno + + return f"[{caller_filename[:12]}:{caller_lineno:04}] " diff --git a/custom_components/icloud3/helpers/messaging.py b/custom_components/icloud3/helpers/messaging.py index 63bba89..f894d4f 100644 --- a/custom_components/icloud3/helpers/messaging.py +++ b/custom_components/icloud3/helpers/messaging.py @@ -25,21 +25,21 @@ INFO, GPS_ACCURACY, GPS, POLL_COUNT, VERT_ACCURACY, ALTITUDE, BADGE, ) -from ..const_more_info import more_info_text -from .common import (obscure_field, instr, is_empty, isnot_empty, ) +from ..const_more_info import more_info_text +from .common import (obscure_field, instr, is_empty, isnot_empty, ) import homeassistant.util.dt as dt_util from homeassistant.components import persistent_notification import os import time -from inspect import getframeinfo, stack +from inspect import getframeinfo, stack import traceback import logging DO_NOT_SHRINK = ['url', 'accountName', ] FILTER_DATA_DICTS = ['items', 'userInfo', 'dsid', 'dsInfo', 'webservices', 'locations','location', - 'params', 'headers', 'kwargs', ] + 'params', 'headers', 'kwargs', 'clientContext', ] FILTER_DATA_LISTS = ['devices', 'content', 'followers', 'following', 'contactDetails',] FILTER_FIELDS = [ ICLOUD3_VERSION, AUTHENTICATED, @@ -71,7 +71,15 @@ 'dsWebAuthToken', 'accountCountryCode', 'extended_login', 'trustToken', 'data', 'json', 'headers', 'params', 'url', 'retry_cnt', 'retried', 'retry', '#', 'code', 'ok', 'method', 'securityCode', + 'fmly', 'shouldLocate', 'selectedDevice', 'accountName', 'salt', 'a', 'b', 'c', 'm1', 'm2', 'protocols', 'iteration', 'Authorization', ] +FILTER_OUT = [ + 'features', 'BTR', 'LLC', 'CLK', 'TEU', 'SND', 'ALS', 'CLT', 'PRM', 'SVP', 'SPN', 'XRM', 'NWF', 'CWP', + 'MSG', 'LOC', 'LME', 'LMG', 'LYU', 'LKL', 'LST', 'LKM', 'WMG', 'SCA', 'PSS', 'EAL', 'LAE', 'PIN', + 'LCK', 'REM', 'MCS', 'REP', 'KEY', 'KPD', 'WIP', 'scd', + 'rm2State', 'pendingRemoveUntilTS', 'repairReadyExpireTS', 'repairReady', 'lostModeCapable', 'wipedTimestamp', + 'encodedDeviceId', 'scdPh', 'locationCapable', 'trackingInfo', 'nwd', 'remoteWipe', 'canWipeAfterLock', 'baUUID', + 'snd', 'continueButtonTitle', 'alertText', 'cancelButtonTitle', 'createTimestamp', 'alertTitle', ] SP_str = ' '*50 @@ -298,6 +306,10 @@ def open_ic3log_file_init(): #------------------------------------------------------------------------------ def open_ic3log_file(new_log_file=False): + # Items will be logged to home-assistane.log until the configuration file has been read + if is_empty(Gb.conf_general): + return + ic3logger_file = Gb.hass.config.path(IC3LOG_FILENAME) filemode = 'w' if new_log_file else 'a' @@ -312,6 +324,7 @@ def open_ic3log_file(new_log_file=False): write_config_file_to_ic3log() + #-------------------------------------------------------------------- def write_ic3log_recd(log_msg): ''' @@ -320,6 +333,10 @@ def write_ic3log_recd(log_msg): and renames while iCloud3 is running ''' try: + if is_empty(Gb.conf_general): + Gb.prestartup_log += f"\n{dt_util.now().strftime(DATETIME_FORMAT)[5:19]} {log_msg}" + return + time_now_msecs = time.time() if time_now_msecs - Gb.iC3Logger_last_check_exist_secs > 2: Gb.iC3Logger_last_check_exist_secs = time_now_msecs @@ -330,6 +347,7 @@ def write_ic3log_recd(log_msg): Gb.iC3Logger.info(log_msg) else: open_ic3log_file(new_log_file=True) + Gb.iC3Logger.info(log_msg) except Exception as err: @@ -393,6 +411,10 @@ def archive_ic3log_file(): #------------------------------------------------------------------------------ def write_config_file_to_ic3log(): + if Gb.prestartup_log: + write_ic3log_recd(f"{Gb.prestartup_log}") + Gb.prestartup_log = '' + conf_tracking_recd = Gb.conf_tracking.copy() # conf_tracking_recd[CONF_USERNAME] = obscure_field(conf_tracking_recd[CONF_USERNAME]) conf_tracking_recd[CONF_PASSWORD] = obscure_field(conf_tracking_recd[CONF_PASSWORD]) @@ -949,6 +971,8 @@ def post_internal_error(err_text, traceback_format_exec_obj='+'): #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> def dummy_evlog(): _evlog(None, None) +def dummy_log(): + _log(None, None) #-------------------------------------------------------------------- def _evlog(devicename_or_Device, items='+'): @@ -985,7 +1009,8 @@ def _log(items, v1='+++', v2='', v3='', v4='', v5=''): except Exception as err: - log_exception(err) + Gb.HARootLogger.info(trace_msg) + # log_exception(err) #-------------------------------------------------------------------- def _called_from(trace=False): diff --git a/custom_components/icloud3/icloud3_main.py b/custom_components/icloud3/icloud3_main.py index d4f0384..75203ab 100644 --- a/custom_components/icloud3/icloud3_main.py +++ b/custom_components/icloud3/icloud3_main.py @@ -143,13 +143,6 @@ def start_icloud3(self): Gb.start_icloud3_inprocess_flag = True Gb.restart_icloud3_request_flag = False Gb.all_tracking_paused_flag = False - # _evlog(f"fil exists {Gb.icloud3_config_filename} {file_exists(Gb.icloud3_config_filename)}") - # _evlog(f"dir exists {Gb.ha_storage_icloud3} {directory_exists(Gb.ha_storage_icloud3)}") - # _evlog(f"dir exists {Gb.ha_storage_directory} {directory_exists(Gb.ha_storage_directory)}") - # _evlog(f"fil exists {f'{Gb.icloud3_config_filename}.test2'} {file_exists(f'{Gb.icloud3_config_filename}.test2')}") - # _evlog(f"dir exists {f'{Gb.ha_storage_icloud3}/test2'} {directory_exists(f'{Gb.ha_storage_icloud3}/test2')}") - # _evlog(f"make dir {f'make-{Gb.ha_storage_icloud3}/test2'} {make_directory(f'{Gb.ha_storage_icloud3}/test2')}") - # _evlog(f"ext filename {Gb.icloud3_config_filename} {extract_filename(Gb.icloud3_config_filename)}") start_ic3_control.stage_1_setup_variables() start_ic3_control.stage_2_prepare_configuration() diff --git a/custom_components/icloud3/strings.json b/custom_components/icloud3/strings.json index a0b639b..60d718c 100644 --- a/custom_components/icloud3/strings.json +++ b/custom_components/icloud3/strings.json @@ -69,6 +69,7 @@ "icloud_acct_logging_into": "Logging into Apple Account", "icloud_acct_logged_into": "✅ Logged into the Apple Account", "icloud_acct_already_logged_into": "Already Logged into the Apple Account", + "icloud_acct_locate_all_reqd": "All devices must be located. Other Family devices are tracked with this Apple Account", "icloud_acct_login_error_user_pw": "❌ Login Error, Invalid Username or Password", "icloud_acct_login_error_other": "❌ Login Error, Other Error or iCloud is not Available", "icloud_acct_login_error_503": "🍎 Apple is delaying displaying a new Verification code to prevent Suspicious Activity, probably due to too many requests. It should be displayed in about 20-30 minutes. Restart HA if it is not displayed", @@ -97,7 +98,7 @@ "inactive_most_devices": "MOST Devices are INACTIVE and will not be located or tracked", "inactive_some_devices": "Some Devices are INACTIVE and will not be located or tracked", "inactive_few_devices": "A Few Devices are INACTIVE and will not be located or tracked", - "inactive_no_devices": "No Devices have been set up. Select `Add Device` and Submit", + "inactive_no_devices": "No Devices have been set up. On the `Update Devices` screen, select `Add a New Device`, then select `Submit` to display the screen to add an iCloud3 device_tracker entity.", "away_time_zone_dup_devices_1": "One of these devices is also selected in the Other Device List", "away_time_zone_dup_devices_2": "One of these devices is also selected in the Otner Device List", @@ -255,14 +256,15 @@ "add_device": { "title": "Add iCloud3 Device", "data": { - "ic3_devicename": "ICLOUD3 ENTITY ID - The HA device_tracker entity forto this device", - "fname": "FRIENDLY NAME - Displayed in HA entities and on the Event Log", + "ic3_devicename": "ICLOUD3 DEVICE_TRACKER ENTITY ID - The HA device_tracker entity for this device. (Example: gary_iphone)", + "fname": "FRIENDLY NAME - Displayed in HA entities and on the Event Log (Example: Gary-iPhone)", "device_type": "DEVICE TYPE - iPhone, iPad, Watch, etc.", "tracking_mode": "TRACKING MODE - Location request method (Tracked, Monitored, Inactive)", "mobapp": "MOBILE APP INSTALLED - HA Mobile App is installed on this device", "action_items": "⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ ACTION COMMANDS" }, "data_description": { + "ic3_devicename": "This is the device_tracker entity you are assigning to the iCloud3 device you want to track. The Apple Account and Mobile App devices to be associated with this device_tracker entity are selected on the next page. (Example: gary_iphone)" } }, "update_device": { diff --git a/custom_components/icloud3/support/config_file.py b/custom_components/icloud3/support/config_file.py index dd5c09c..3d1cf16 100644 --- a/custom_components/icloud3/support/config_file.py +++ b/custom_components/icloud3/support/config_file.py @@ -36,7 +36,7 @@ CONF_PICTURE_WWW_DIRS, PICTURE_WWW_STANDARD_DIRS, RANGE_DEVICE_CONF, RANGE_GENERAL_CONF, MIN, MAX, STEP, RANGE_UM, CF_PROFILE, CF_DATA, CF_TRACKING, CF_GENERAL, CF_SENSORS, - CONF_DEVICES, CONF_APPLE_ACCOUNTS, + CONF_DEVICES, CONF_APPLE_ACCOUNTS, DEFAULT_APPLE_ACCOUNTS_CONF, ) from ..support import start_ic3 @@ -197,8 +197,13 @@ def _reconstruct_conf_file(): ''' Gb.conf_profile[CONF_UPDATE_DATE] = datetime_now() - Gb.conf_tracking[CONF_PASSWORD] = \ - encode_password(Gb.conf_tracking[CONF_PASSWORD]) + # Gb.conf_tracking[CONF_PASSWORD] = \ + # encode_password(Gb.conf_tracking[CONF_PASSWORD]) + + # for apple_acct in Gb.conf_apple_accounts: + # apple_acct[CONF_PASSWORD] = encode_password(apple_acct[CONF_PASSWORD]) + + encode_all_passwords() Gb.conf_tracking[CONF_APPLE_ACCOUNTS] = Gb.conf_apple_accounts Gb.conf_tracking[CONF_DEVICES] = Gb.conf_devices @@ -370,6 +375,11 @@ def conf_apple_acct(idx_or_username): - conf_apple_acct_idx = index ''' try: + if len(Gb.conf_apple_accounts) == 0: + conf_apple_acct = DEFAULT_APPLE_ACCOUNTS_CONF.copy() + Gb.conf_apple_accounts = [conf_apple_acct] + return (conf_apple_acct, 0) + if type(idx_or_username) is int: if isbetween(idx_or_username, 0, len(Gb.conf_apple_accounts)-1): conf_apple_acct = Gb.conf_apple_accounts[idx_or_username].copy() @@ -377,9 +387,10 @@ def conf_apple_acct(idx_or_username): return (conf_apple_acct, idx_or_username) elif type(idx_or_username) is str: - conf_apple_acct = [apple_account for apple_account in Gb.conf_apple_accounts - if apple_account[CONF_USERNAME] == idx_or_username] - conf_apple_acct_username = [apple_account[CONF_USERNAME] for apple_account in Gb.conf_apple_accounts] + conf_apple_acct = [apple_acct for apple_acct in Gb.conf_apple_accounts + if apple_acct[CONF_USERNAME] == idx_or_username] + conf_apple_acct_username = [apple_account[CONF_USERNAME] + for apple_account in Gb.conf_apple_accounts] conf_apple_acct_idx = conf_apple_acct_username.index(idx_or_username) if conf_apple_acct != []: @@ -419,9 +430,9 @@ def apple_acct_password_for_username(username): return '' try: - return [apple_account[CONF_PASSWORD] - for apple_account in Gb.conf_apple_accounts - if apple_account[CONF_USERNAME] == username][0] + return [apple_acct[CONF_PASSWORD] + for apple_acct in Gb.conf_apple_accounts + if apple_acct[CONF_USERNAME] == username][0] except: return '' @@ -890,11 +901,11 @@ def _insert_into_conf_dict_parameter(dict_parameter, # #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> def encode_all_passwords(): + Gb.conf_tracking[CONF_PASSWORD] = encode_password(Gb.conf_tracking[CONF_PASSWORD]) for apple_acct in Gb.conf_apple_accounts: - apple_acct[CONF_PASSWORD] = \ - encode_password(apple_acct[CONF_PASSWORD]) + apple_acct[CONF_PASSWORD] = encode_password(apple_acct[CONF_PASSWORD]) #-------------------------------------------------------------------- def decode_all_passwords(): diff --git a/custom_components/icloud3/support/icloud_data_handler.py b/custom_components/icloud3/support/icloud_data_handler.py index d0c6ae6..7f6b6de 100644 --- a/custom_components/icloud3/support/icloud_data_handler.py +++ b/custom_components/icloud3/support/icloud_data_handler.py @@ -232,10 +232,11 @@ def update_PyiCloud_RawData_data(Device, results_msg_flag=True): device_id = None if Device.PyiCloud.locate_all_devices else Device.icloud_device_id locate_all_devices, device_id = _locate_all_or_acct_owner(Device) - if Device.PyiCloud.DeviceSvc: - Device.PyiCloud.DeviceSvc.refresh_client( requested_by_devicename=Device.devicename, - locate_all_devices=locate_all_devices, - device_id=device_id) + #if Device.PyiCloud.DeviceSvc: + #Device.PyiCloud.DeviceSvc.refresh_client( requested_by_devicename=Device.devicename, + Device.PyiCloud.refresh_icloud_data(requested_by_devicename=Device.devicename, + locate_all_devices=locate_all_devices, + device_id=device_id) if (Device.PyiCloud.response_code == 503 and Device.devicename not in Gb.username_pyicloud_503_connection_error): list_add(Gb.username_pyicloud_503_connection_error, Device.devicename) diff --git a/custom_components/icloud3/support/pyicloud_ic3.py b/custom_components/icloud3/support/pyicloud_ic3.py index 7617348..55205ec 100644 --- a/custom_components/icloud3/support/pyicloud_ic3.py +++ b/custom_components/icloud3/support/pyicloud_ic3.py @@ -89,6 +89,19 @@ 302: 'iCloud Server not Available (Connection Error)', } HTTP_RESPONSE_CODES_IDX = {str(code): code for code in HTTP_RESPONSE_CODES.keys()} + +DEVICE_DATA_FILTER_OUT = [ + 'features', 'scd', + 'rm2State', 'pendingRemoveUntilTS', 'repairReadyExpireTS', 'repairReady', 'lostModeCapable', 'wipedTimestamp', + 'encodedDeviceId', 'scdPh', 'locationCapable', 'trackingInfo', 'nwd', 'remoteWipe', 'canWipeAfterLock', 'baUUID', + 'snd', 'continueButtonTitle', 'alertText', 'cancelButtonTitle', 'createTimestamp', 'alertTitle', + 'lockedTimestamp', 'locFoundEnabled', 'lostDevice', 'pendingRemove', 'maxMsgChar', 'darkWake', 'wipeInProgress', + 'repairDeviceReason', 'deviceColor', 'deviceDiscoveryId', 'activationLocked', 'passcodeLength', + ] + # 'BTR', 'LLC', 'CLK', 'TEU', 'SND', 'ALS', 'CLT', 'PRM', 'SVP', 'SPN', 'XRM', 'NWF', 'CWP', + # 'MSG', 'LOC', 'LME', 'LMG', 'LYU', 'LKL', 'LST', 'LKM', 'WMG', 'SCA', 'PSS', 'EAL', 'LAE', 'PIN', + # 'LCK', 'REM', 'MCS', 'REP', 'KEY', 'KPD', 'WIP', + ''' https://developer.apple.com/library/archive/documentation/DataManagement/Conceptual/CloudKitWebServicesReference/ErrorCodes.html#//apple_ref/doc/uid/TP40015240-CH4-SW1 @@ -116,6 +129,7 @@ LOCK_FAIL_PASSCODE_NOT_SET_CONS_FAIL = 2403 LOCK_FAIL_NO_PASSCD_2 = 2406 + app specific password notes "appIdKey=ba2ec180e6ca6e6c6a542255453b24d6e6e5b2be0cc48bc1b0d8ad64cfe0228f&appleId=APPLE_ID&password=password2&protocolVersion=A1234&userLocale=en_US&format=plist" --header "application/x-www-form-urlencoded" "https://idmsa.apple.com/IDMSWebAuth/clientDAW.cgi" @@ -568,81 +582,81 @@ def __init__( self, username, password=None, verify_login=False, config_flow_login=False): - if is_empty(username): - msg = "Apple Account username is not specified/558" - Gb.authenticate_method = 'Invalid username/password' - raise PyiCloudFailedLoginException(msg) - if is_empty(password): - msg = "Apple Account password is not specified/562" - Gb.authenticate_method = 'Invalid username/password' - raise PyiCloudFailedLoginException(msg) - - self.setup_time = time_now() - self.user = {"accountName": username, "password": password} - self.apple_id = username - self.username = username - self.username_base = username.split('@')[0] - self.username_base6 = self.username_base if Gb.log_debug_flag else f"{username[:6]}…" - - username_password = f"{username}:{password}" - upw = username_password.encode('ascii') - username_password_b64 = base64.b64encode(upw) - self.username_password_b64 = username_password_b64.decode('ascii') - - self.password = password - - self.locate_all_devices = False if locate_all_devices is False else True - self.is_authenticated = False # ICloud access has been authenticated via password or token - # self.requires_2sa = self._check_2sa_needed - self.requires_2fa = False # This is set during the authentication function - self.response_code_pwsrp_err = 0 - self.response_code = 0 - self.token_pw_data = {} - self.token_password = password - self.account_locked = False # set from the locked data item when authenticating with a token - self.account_name = '' - self.verify_login = verify_login - self.verification_code = None - self.authentication_alert_displayed_flag = False - self.update_requested_by = '' - self.endpoint_suffix = endpoint_suffix if endpoint_suffix else Gb.icloud_server_endpoint_suffix - self.config_flow_login = config_flow_login # Indicates this PyiCloud object is beinging created from config_flow - - self.cookie_directory = cookie_directory or Gb.icloud_cookie_directory - self.session_directory = session_directory or Gb.icloud_session_directory - self.cookie_filename = "".join([c for c in self.username if match(r"\w", c)]) - self.session_data = {} - self.session_data_token = {} - self.dsid = '' - self.trust_token = '' - self.session_token = '' - self.session_id = '' - self.connection_error_retry_cnt = 0 - - self.findme_url_root = None # iCloud url initialized from the accountLogin response data - self.HOME_ENDPOINT = "https://www.icloud.com" - self.SETUP_ENDPOINT = "https://setup.icloud.com/setup/ws/1" - self.AUTH_ENDPOINT = "https://idmsa.apple.com/appleauth/auth" - #self.AUTH_PASSWORD_ENDPOINT = "https://setup.icloud.com/setup/authenticate" - - if self.endpoint_suffix in APPLE_SPECIAL_ICLOUD_SERVER_COUNTRY_CODE: - self._setup_url_endpoint_suffix() - - self.PyiCloudSession = None - self.DeviceSvc = None # PyiCloud_ic3 object for Apple Device Service used to refresh the device's location - - self._initialize_variables() - self._setup_password_filter(password) - self._setup_PyiCloudSession() + try: + if is_empty(username): + msg = "Apple Account username is not specified/558" + Gb.authenticate_method = 'Invalid username/password' + raise PyiCloudFailedLoginException(msg) + if is_empty(password): + msg = "Apple Account password is not specified/562" + Gb.authenticate_method = 'Invalid username/password' + raise PyiCloudFailedLoginException(msg) + + self.setup_time = time_now() + self.user = {"accountName": username, "password": password} + self.apple_id = username + self.username = username + self.username_base = username.split('@')[0] + self.username_base6 = self.username_base if Gb.log_debug_flag else f"{username[:6]}…" + + username_password = f"{username}:{password}" + upw = username_password.encode('ascii') + username_password_b64 = base64.b64encode(upw) + self.username_password_b64 = username_password_b64.decode('ascii') + self.password = password + + self.locate_all_devices = locate_all_devices if locate_all_devices is not None else True + + self.is_authenticated = False # ICloud access has been authenticated via password or token + # self.requires_2sa = self._check_2sa_needed + self.requires_2fa = False # This is set during the authentication function + self.response_code_pwsrp_err = 0 + self.response_code = 0 + self.token_pw_data = {} + self.token_password = password + self.account_locked = False # set from the locked data item when authenticating with a token + self.account_name = '' + self.verify_login = verify_login + self.verification_code = None + self.authentication_alert_displayed_flag = False + self.update_requested_by = '' + self.endpoint_suffix = endpoint_suffix if endpoint_suffix else Gb.icloud_server_endpoint_suffix + self.config_flow_login = config_flow_login # Indicates this PyiCloud object is beinging created from config_flow + + self.cookie_directory = cookie_directory or Gb.icloud_cookie_directory + self.session_directory = session_directory or Gb.icloud_session_directory + self.cookie_filename = "".join([c for c in self.username if match(r"\w", c)]) + self.session_data = {} + self.session_data_token = {} + self.dsid = '' + self.trust_token = '' + self.session_token = '' + self.session_id = '' + self.connection_error_retry_cnt = 0 + + self.findme_url_root = None # iCloud url initialized from the accountLogin response data + self.HOME_ENDPOINT = "https://www.icloud.com" + self.SETUP_ENDPOINT = "https://setup.icloud.com/setup/ws/1" + self.AUTH_ENDPOINT = "https://idmsa.apple.com/appleauth/auth" + #self.AUTH_PASSWORD_ENDPOINT = "https://setup.icloud.com/setup/authenticate" + + if self.endpoint_suffix in APPLE_SPECIAL_ICLOUD_SERVER_COUNTRY_CODE: + self._setup_url_endpoint_suffix() + + self.PyiCloudSession = None + self.DeviceSvc = None # PyiCloud_ic3 object for Apple Device Service used to refresh the device's location + + self._initialize_variables() + self._setup_password_filter(password) + self._setup_PyiCloudSession() + + Gb.PyiCloudLoggingInto = self # Identifies a partial login that failed + Gb.PyiCloud_by_username[username] = self + self.authenticate() + self.refresh_icloud_data(locate_all_devices=True) - Gb.PyiCloudLoggingInto = self # Identifies a partial login that failed - Gb.PyiCloud_by_username[username] = self - self.authenticate() - # if self.DeviceSvc is None: - # self.create_DeviceSvc_object() - self.create_DeviceSvc_object() - if self.DeviceSvc: - self.DeviceSvc.refresh_client() + except Exception as err: + log_exception(err) return @@ -774,7 +788,7 @@ def authenticate(self, refresh_session=False): self.authenticate_method += ", Password" - if login_successful is False: + # The Auth with Token is necessary to fill in the findme_url if self._authenticate_with_token(): login_successful = True self.authenticate_method += ", Token" @@ -818,9 +832,9 @@ def authenticate(self, refresh_session=False): self._update_token_pw_file(CONF_PASSWORD, encode_password(self.token_password)) log_info_msg( f"{self.username_base}, " - f"Authentication Successful, {self.username_base}" + f"Authentication Successful, {self.username_base}, " f"Method-{self.authenticate_method}") - # self.authenticate_method = f"{self.account_owner_short}, {self.authenticate_method}" + self.is_authenticated = self.is_authenticated or login_successful #---------------------------------------------------------------------------- @@ -1523,7 +1537,7 @@ def create_DeviceSvc_object(self, config_flow_login=False): ''' try: if self.DeviceSvc: - return + return self.DeviceSvc self.DeviceSvc = PyiCloud_DeviceSvc(self, self.PyiCloudSession, @@ -1539,13 +1553,29 @@ def create_DeviceSvc_object(self, config_flow_login=False): return None #---------------------------------------------------------------------------- - @property - def refresh_icloud_data(self): + def refresh_icloud_data(self, locate_all_devices=None, + requested_by_devicename=None, + device_id=None): ''' Refresh the iCloud device data for all devices and update the PyiCloud_RawData object for all locatible devices that are being tracked by iCloud3. ''' - self.DeviceSvc.refresh_client() + try: + if self.DeviceSvc is None: + self.create_DeviceSvc_object() + + locate_all_devices = locate_all_devices if locate_all_devices is not None \ + else self.locate_all_devices if self.locate_all_devices is not None \ + else True + + self.DeviceSvc.refresh_client( locate_all_devices=locate_all_devices, + requested_by_devicename=requested_by_devicename, + device_id=device_id) + + return + + except Exception as err: + log_exception(err) #---------------------------------------------------------------------------- def play_sound(self, device_id, subject="Find My iPhone Alert"): @@ -1629,18 +1659,18 @@ def __init__(self, PyiCloud, # This will generate an error if the table has not been defined from (init or start_ic3) # Init may be in the process of setting up the table and iCloud but then start_ic3/Stage 4 # thinks it is not done and resets everything. - if self.PyiCloud.device_id_by_icloud_dname != {}: + if isnot_empty(self.PyiCloud.device_id_by_icloud_dname): return except: pass - self.refresh_client(locate_all_devices=True) + self.refresh_client() Gb.devices_not_set_up = self._get_conf_icloud_devices_not_set_up() if Gb.devices_not_set_up == []: return - self.refresh_client(locate_all_devices=True) + #self.refresh_client() #---------------------------------------------------------------------------- def _get_conf_icloud_devices_not_set_up(self): @@ -1689,56 +1719,62 @@ def refresh_client(self, requested_by_devicename=None, = True - Locate all devices in the Family Sharing list (overrides device selection) = False - Locate only the devices belonging to this Apple acct ''' - if self.is_DeviceSvc_setup_complete is False: - return - - locate_all_devices = True if locate_all_devices is None else locate_all_devices - - if requested_by_devicename: - _Device = Gb.Devices_by_devicename[requested_by_devicename] - last_update_loc_time = _Device.last_update_loc_time - else: - last_update_loc_time = '?' + try: + if self.is_DeviceSvc_setup_complete is False: + return False - if locate_all_devices is False: - device_msg = f"OwnerDev-{len(Gb.owner_device_ids_by_username[self.PyiCloud.username])}" - else: - device_msg = f"AllDevices-{len(Gb.Devices_by_username.get(self.PyiCloud.username, []))}" + #locate_all_devices = True if locate_all_devices is None else locate_all_devices + locate_all_devices = locate_all_devices if locate_all_devices is not None \ + else self.PyiCloud.locate_all_devices if self.PyiCloud.locate_all_devices is not None \ + else True - log_debug_msg( f"Apple Acct > {self.PyiCloud.username_base}, " - f"RefreshRequestBy-{requested_by_devicename}, " - f"LocateAllDev-{locate_all_devices}, {device_msg}, LastLoc-{last_update_loc_time}") + if requested_by_devicename: + _Device = Gb.Devices_by_devicename[requested_by_devicename] + last_update_loc_time = _Device.last_update_loc_time + else: + last_update_loc_time = '?' - url = f"{self.PyiCloud.findme_url_root}{REFRESH_ENDPOINT}" - data = {"clientContext":{ - "fmly": locate_all_devices, - "shouldLocate": True, - "selectedDevice": device_id, + if locate_all_devices is False: + device_msg = f"OwnerDev-{len(Gb.owner_device_ids_by_username.get(self.PyiCloud.username, []))}" + else: + device_msg = f"AllDevices-{len(Gb.Devices_by_username.get(self.PyiCloud.username, []))}" + + log_debug_msg( f"Apple Acct > {self.PyiCloud.username_base}, " + f"RefreshRequestBy-{requested_by_devicename}, " + f"LocateAllDev-{locate_all_devices}, {device_msg}, LastLoc-{last_update_loc_time}") + + url = f"{self.PyiCloud.findme_url_root}{REFRESH_ENDPOINT}" + data = {"clientContext":{ + "fmly": locate_all_devices, + "shouldLocate": True, + "selectedDevice": device_id, + "deviceListVersion": 1, }, + "accountCountryCode": self.PyiCloud.session_data_token.get("account_country"), + "dsWebAuthToken": self.PyiCloud.session_data_token.get("session_token"), + "trustToken": self.PyiCloud.session_data_token.get("trust_token", ""), + "extended_login": True,} - "deviceListVersion": 1, }, - "accountCountryCode": self.PyiCloud.session_data_token.get("account_country"), - "dsWebAuthToken": self.PyiCloud.session_data_token.get("session_token"), - "trustToken": self.PyiCloud.session_data_token.get("trust_token", ""), - "extended_login": True,} + try: + devices_data = self.PyiCloudSession.post(url, params=self.params, data=data) + self.devices_data = devices_data.json() + + except Exception as err: + self.devices_data = {} + log_debug_msg(f"{self.PyiCloud.username_base}, No data returned from iCloud refresh request") + + if self.PyiCloudSession.response_code == 501: + self._set_service_available(False) + post_event( f"{EVLOG_ALERT}iCLOUD ALERT > {self.PyiCloud.account_owner}, " + f"Family Sharing Data Source is not available. " + f"The web url providing location data returned a " + f"Service Not Available error") + return None - try: - devices_data = self.PyiCloudSession.post(url, params=self.params, data=data) - self.devices_data = devices_data.json() + self.update_device_location_data(requested_by_devicename, self.devices_data.get("content", {})) + return isnot_empty(self.PyiCloud.RawData_by_device_id) except Exception as err: - self.devices_data = {} - log_debug_msg(f"{self.PyiCloud.username_base}, No data returned from iCloud refresh request") - - if self.PyiCloudSession.response_code == 501: - self._set_service_available(False) - post_event( f"{EVLOG_ALERT}iCLOUD ALERT > {self.PyiCloud.account_owner}, " - f"Family Sharing Data Source is not available. " - f"The web url providing location data returned a " - f"Service Not Available error") - return None - - self.PyiCloud.last_refresh_secs = time_now_secs() - self.update_device_location_data(requested_by_devicename, self.devices_data.get("content", {})) + log_exception(err) #---------------------------------------------------------------------------- def update_device_location_data(self, requested_by_devicename=None, devices_data=None): @@ -1799,20 +1835,21 @@ def update_device_location_data(self, requested_by_devicename=None, devices_data # else: device_msg = self._create_iCloud_RawData_object(device_id, device_data_name, device_data) monitor_msg += device_msg - continue + #continue # The PyiCloudSession is not recreated on a restart if it already is valid but we need to # initialize all devices, not just tracked ones on an iC3 restart. elif Gb.start_icloud3_inprocess_flag: device_msg = self._initialize_iCloud_RawData_object(device_id, device_data_name, device_data) monitor_msg += device_msg - continue + #continue # Non-tracked devices are not updated _RawData = self.PyiCloud.RawData_by_device_id[device_id] _Device = _RawData.Device - if _RawData.Device is None: + if (_RawData.Device is None + or 'location' not in _RawData.device_data): continue _RawData.save_new_device_data(device_data) @@ -1938,7 +1975,7 @@ def _initialize_iCloud_RawData_object(self, device_id, device_data_name, device_ log_debug_msg( f"Initialize RawData_icloud object " f"{self.PyiCloud.username_base}{LINK}<{_RawData.fname}, " f"{device_data_name}") - + # if ('all' in Gb.conf_general[CONF_LOG_LEVEL_DEVICES] # or _RawData.ic3_devicename in Gb.conf_general[CONF_LOG_LEVEL_DEVICES]): # Log all devices (no filter) on initialization @@ -2116,6 +2153,7 @@ def __init__(self, device_id, self.Device = Gb.Devices_by_devicename.get(self.ic3_devicename) self.device_data = device_data + self.status_code = 0 self.update_secs = time_now_secs() self.location_secs = 0 @@ -2317,8 +2355,16 @@ def save_new_device_data(self, device_data): except: self.last_loc_time_gps = '' - self.device_data.clear() - self.device_data.update(device_data) + try: + self.status_code = device_data['snd']['status_code'] + except: + pass + + filtered_device_data = {k: v for k, v in device_data.items() + if k not in DEVICE_DATA_FILTER_OUT} + + # self.device_data.clear() + self.device_data.update(filtered_device_data) self.set_located_time_battery_info() self.device_data[DATA_SOURCE] = self.data_source @@ -2330,7 +2376,7 @@ def status(self, additional_fields=[]): Returns status information for device. This returns only a subset of possible properties. ''' - self.DeviceSvc.refresh_client(self.device_id) + self.DeviceSvc.refresh_client(requested_by_devicename=self.device_id) fields = ["batteryLevel", "deviceDisplayName", "deviceStatus", "name"] fields += additional_fields diff --git a/custom_components/icloud3/support/pyicloud_ic3_interface.py b/custom_components/icloud3/support/pyicloud_ic3_interface.py index 5fcabf7..205ce97 100644 --- a/custom_components/icloud3/support/pyicloud_ic3_interface.py +++ b/custom_components/icloud3/support/pyicloud_ic3_interface.py @@ -45,9 +45,10 @@ def create_all_PyiCloudServices(): post_event('Log into Apple Accounts') for conf_apple_acct in Gb.conf_apple_accounts: + username = conf_apple_acct[CONF_USERNAME] password = Gb.PyiCloud_password_by_username[username] - locate_all = conf_apple_acct[CONF_LOCATE_ALL] + locate_all_devices = conf_apple_acct[CONF_LOCATE_ALL] if is_empty(username) or is_empty(password): continue @@ -62,7 +63,7 @@ def create_all_PyiCloudServices(): Gb.username_valid_by_username[username] = username_password_valid if Gb.username_valid_by_username[username]: - log_into_apple_account(username, password, locate_all) + log_into_apple_account(username, password, locate_all_devices) else: event_msg =(f"Apple Acct > " f"{username.split('@')[0]}, Invalid Username or Password") @@ -84,9 +85,9 @@ def retry_apple_acct_login(): for username in Gb.username_pyicloud_503_connection_error: conf_apple_acct, apple_acct_id = config_file.conf_apple_acct(username) password = conf_apple_acct[CONF_PASSWORD] - locate_all = conf_apple_acct[CONF_LOCATE_ALL] + locate_all_devices = conf_apple_acct[CONF_LOCATE_ALL] - PyiCloud = log_into_apple_account(username, password, locate_all) + PyiCloud = log_into_apple_account(username, password, locate_all_devices) if PyiCloud: post_event(f"{EVLOG_ERROR}Apple Acct > {PyiCloud.account_owner}, Login Successful") @@ -112,8 +113,9 @@ def verify_all_apple_accounts(): cnt = -1 for conf_apple_acct in Gb.conf_apple_accounts: cnt += 1 - username = conf_apple_acct[CONF_USERNAME] - password = Gb.PyiCloud_password_by_username[username] + + username = conf_apple_acct[CONF_USERNAME] + password = Gb.PyiCloud_password_by_username[username] if is_empty(username): Gb.username_valid_by_username[f"AA-NOTSPECIFIED-#{cnt}"] @@ -133,7 +135,7 @@ def verify_all_apple_accounts(): #-------------------------------------------------------------------- -def log_into_apple_account(username, password, locate_all=True): +def log_into_apple_account(username, password, locate_all_devices=None): ''' Log in and Authenticate the Apple Account via pyicloud @@ -147,7 +149,11 @@ def log_into_apple_account(username, password, locate_all=True): or password == ''): return - this_fct_error_flag = True + locate_all_devices = locate_all_devices \ + if locate_all_devices is not None \ + else PyiCloud.locate_all_devices if PyiCloud.locate_all_devices is not None \ + else True + login_err = 0 post_evlog_greenbar_msg(f"Apple Acct > Setting up {username.split('@')[0]}") @@ -172,7 +178,8 @@ def log_into_apple_account(username, password, locate_all=True): and PyiCloud.DeviceSvc): PyiCloud.dup_icloud_dname_cnt = {} - PyiCloud.DeviceSvc.refresh_client() + # PyiCloud.DeviceSvc.refresh_client() + PyiCloud.refresh_icloud_data(locate_all_devices=True) pyicloud_msg = f"{PyiCloud=} {PyiCloud.is_DeviceSvc_setup_complete=} {PyiCloud.DeviceSvc=}" if PyiCloud.DeviceSvc: @@ -184,36 +191,58 @@ def log_into_apple_account(username, password, locate_all=True): elif (PyiCloud and PyiCloud.is_DeviceSvc_setup_complete): - PyiCloud.create_DeviceSvc_object() - Gb.PyiCloud_by_username[username] = PyiCloud + try: + PyiCloud.create_DeviceSvc_object() + Gb.PyiCloud_by_username[username] = PyiCloud - pyicloud_msg = f"{PyiCloud=} {PyiCloud.is_DeviceSvc_setup_complete=} {PyiCloud.DeviceSvc=}" - if PyiCloud.DeviceSvc: - pyicloud_msg += f"{PyiCloud.RawData_by_device_id.values()=}" - log_debug_msg(f"{debug_msg_hdr}2, Create DeviceSvc, {pyicloud_msg}") - post_event(f"Apple Acct > {PyiCloud.account_owner}, iCloud Created & Refreshed") + pyicloud_msg = f"{PyiCloud=} {PyiCloud.is_DeviceSvc_setup_complete=} {PyiCloud.DeviceSvc=}" + if PyiCloud.DeviceSvc: + pyicloud_msg += f"{PyiCloud.RawData_by_device_id.values()=}" + log_debug_msg(f"{debug_msg_hdr}2, Create DeviceSvc, {pyicloud_msg}") + post_event(f"Apple Acct > {PyiCloud.account_owner}, iCloud Created & Refreshed") - return PyiCloud + return PyiCloud + + except Exception as err: + log_exception(err) # Setup PyiCloud and iCloud else: - PyiCloud = PyiCloudService( username, password, - locate_all_devices=locate_all, - cookie_directory=Gb.icloud_cookie_directory, - session_directory=Gb.icloud_session_directory) - - # Stage 4 checks to see if PyiCloud exists and it has RawData device info. These values exists - # if the __init__ login was completed. However, if it was not completed and they do not exist, - # Stage 4 will do another login and set these values when it finishes, which is before the - # __init__ is complete. Do not set them again when __init__ login finially completes. - - pyicloud_msg = (f"{PyiCloud.account_owner_username}, " - f"Complete={PyiCloud.is_DeviceSvc_setup_complete}, ") + try: + + PyiCloud = None + PyiCloud = PyiCloudService( username, password, + locate_all_devices=locate_all_devices, + cookie_directory=Gb.icloud_cookie_directory, + session_directory=Gb.icloud_session_directory) + + # Stage 4 checks to see if PyiCloud exists and it has RawData device info. These values exists + # if the __init__ login was completed. However, if it was not completed and they do not exist, + # Stage 4 will do another login and set these values when it finishes, which is before the + # __init__ is complete. Do not set them again when __init__ login finially completes. + + pyicloud_msg = (f"{PyiCloud.account_owner_username}, " + f"Complete={PyiCloud.is_DeviceSvc_setup_complete}, ") + + except Exception as err: + log_exception(err) + if PyiCloud.DeviceSvc: - rawdata_items = [_RawData.fname_device_id for _RawData in PyiCloud.RawData_by_device_id.values()] + if is_empty(PyiCloud.RawData_by_device_id): + PyiCloud.refresh_icloud_data(locate_all_devices=True) + + rawdata_items = [_RawData.fname_device_id + for _RawData in PyiCloud.RawData_by_device_id.values()] + + if is_empty(rawdata_items): + PyiCloud.refresh_icloud_data(locate_all_devices=True) + + rawdata_items = [_RawData.fname_device_id + for _RawData in PyiCloud.RawData_by_device_id.values()] + pyicloud_msg += f"RawDataItems-({list_to_str(rawdata_items)})" - log_debug_msg(f"{debug_msg_hdr}3, Setup PyiCloud, {pyicloud_msg}") + log_debug_msg(f"{debug_msg_hdr}3, Setup PyiCloud, {pyicloud_msg}") post_event(f"Apple Acct > {PyiCloud.account_owner}, Login Successful") verify_icloud_device_info_received(PyiCloud) @@ -274,7 +303,7 @@ def verify_icloud_device_info_received(PyiCloud): f"Family Sharing List Refresh " f"(#{Gb.get_ICLOUD_devices_retry_cnt} of 8)") - PyiCloud.DeviceSvc.refresh_client(locate_all_devices=True) + PyiCloud.refresh_icloud_data(locate_all_devices=True) if PyiCloud.DeviceSvc.devices_cnt >= 0: return True diff --git a/custom_components/icloud3/support/start_ic3.py b/custom_components/icloud3/support/start_ic3.py index 91c257d..5ca2d70 100644 --- a/custom_components/icloud3/support/start_ic3.py +++ b/custom_components/icloud3/support/start_ic3.py @@ -9,7 +9,7 @@ EVLOG_ALERT, EVLOG_IC3_STARTING, EVLOG_NOTICE, EVLOG_IC3_STAGE_HDR, CIRCLE_LETTERS_DARK, EVENT_RECDS_MAX_CNT_BASE, EVENT_RECDS_MAX_CNT_ZONE, - CRLF, CRLF_DOT, CRLF_CHK, CRLF_SP3_DOT, HDOT, CRLF_SP5_DOT, CRLF_HDOT, LINK, LLINK, RLINK, + CRLF, CRLF_DOT, CRLF_CHK, CRLF_SP3_DOT, HDOT, CRLF_SP5_DOT, CRLF_HDOT, LINK, LLINK, RLINK, CRLF_CIRCLE_X, CRLF_SP3_HDOT, CRLF_INDENT, CRLF_X, CRLF_TAB, DOT, CRLF_SP8_HDOT, CRLF_SP8_DOT, CRLF_RED_X, RED_X, CRLF_STAR, CRLF_YELLOW_ALERT, YELLOW_ALERT, UNKNOWN, RARROW, NBSP2, NBSP4, NBSP6, CIRCLE_STAR, INFO_SEPARATOR, DASH_20, CHECK_MARK, @@ -1147,7 +1147,7 @@ def create_Devices_object(): post_startup_alert(f"HA device_tracker entity id not configured for {icloud_dname}") post_event( f"{EVLOG_ALERT}CONFIGURATION ALERT > The device_tracker entity id (devicename) " f"has not been configured for {icloud_dname}/" - f"{DEVICE_TYPE_FNAME.get(conf_device[CONF_DEVICE_TYPE], conf_device[CONF_DEVICE_TYPE])}") + f"{DEVICE_TYPE_FNAME(conf_device[CONF_DEVICE_TYPE])}") continue Gb.conf_icloud_dnames.append(icloud_dname) @@ -1155,7 +1155,7 @@ def create_Devices_object(): if conf_device[CONF_TRACKING_MODE] == INACTIVE_DEVICE: post_event( f"{CIRCLE_STAR} {conf_device[CONF_FNAME]} ({devicename}) > " - f"{DEVICE_TYPE_FNAME.get(conf_device[CONF_DEVICE_TYPE], conf_device[CONF_DEVICE_TYPE])}, INACTIVE, " + f"{DEVICE_TYPE_FNAME(conf_device[CONF_DEVICE_TYPE])}, INACTIVE, " f"{CRLF_DOT}iCloud Device-{icloud_dname}" f"{CRLF_DOT}MobApp Entity-{conf_device[CONF_MOBILE_APP_DEVICE]}") continue @@ -1238,7 +1238,8 @@ def create_Devices_object(): _verify_away_time_zone_devicenames() Gb.startup_lists['Gb.Devices'] = Gb.Devices - Gb.startup_lists['Gb.DevDevices_by_devicename'] = Gb.Devices_by_devicename + Gb.startup_lists['Gb.DeviceTrackers_by_devicename']= Gb.DeviceTrackers_by_devicename + Gb.startup_lists['Gb.Devices_by_devicename'] = Gb.Devices_by_devicename Gb.startup_lists['Gb.conf_devicenames'] = Gb.conf_devicenames Gb.startup_lists['Gb.conf_icloud_dnames'] = Gb.conf_icloud_dnames Gb.startup_lists['Gb.devicenames_by_icloud_dname'] = Gb.devicenames_by_icloud_dname @@ -1297,6 +1298,10 @@ def setup_data_source_ICLOUD(retry=False): apple_acct_not_found_msg = '' for username, PyiCloud in Gb.PyiCloud_by_username.items(): + if is_empty(PyiCloud.RawData_by_device_id): + #PyiCloud.DeviceSvc.refresh_client(locate_all_devices=True) + PyiCloud.refresh_icloud_data() + if PyiCloud and Gb.username_valid_by_username.get(username): setup_tracked_devices_for_icloud(PyiCloud) set_device_data_source(PyiCloud) @@ -1459,7 +1464,7 @@ def _match_PyiCloud_devices_to_Device(PyiCloud, retry_match_devices=None): icloud_dname = conf_device[CONF_FAMSHR_DEVICENAME] Gb.devicenames_by_icloud_dname[icloud_dname] = devicename - Gb.icloud_dnames_by_devicename[devicename] = icloud_dname + Gb.icloud_dnames_by_devicename[devicename] = icloud_dname _RawData.Device = Device _RawData.ic3_devicename = devicename @@ -1513,7 +1518,8 @@ def _check_for_missing_find_devices(PyiCloud): retry_cnt = 0 if PyiCloud.DeviceSvc else 10 while retry_cnt < 5: retry_cnt += 1 - PyiCloud.DeviceSvc.refresh_client() + #PyiCloud.DeviceSvc.refresh_client() + PyiCloud.refresh_icloud_data() # See in the untracked devices are now available retry_match_devices = {} @@ -1691,10 +1697,13 @@ def _post_evlog_apple_acct_tracked_devices_info(PyiCloud): f"{devices_assigned_msg}" f"{devices_not_assigned_msg}") + famshr_crlf = CRLF_DOT if PyiCloud.locate_all_devices else CRLF_CIRCLE_X if owner_icloud_dnames: evlog_msg += f"{CRLF_DOT} myDevices-{owner_icloud_dnames}" if famshr_dnames_msg: - evlog_msg += f"{CRLF_DOT} FamShr List Devices-{famshr_dnames_msg}" + evlog_msg += f"{famshr_crlf} Family List-{famshr_dnames_msg}" + if PyiCloud.locate_all_devices is False: + evlog_msg += f"{CRLF_STAR} Family List Devices are Not Located" post_event(evlog_msg) @@ -2454,8 +2463,7 @@ def display_inactive_devices(): inactive_devices =[(f"{conf_device[CONF_IC3_DEVICENAME]} (" f"{conf_device[CONF_FNAME]}/" - f"{DEVICE_TYPE_FNAME.get( - conf_device[CONF_DEVICE_TYPE], conf_device[CONF_DEVICE_TYPE])})") + f"{DEVICE_TYPE_FNAME(conf_device[CONF_DEVICE_TYPE])})") for conf_device in Gb.conf_devices if conf_device[CONF_TRACKING_MODE] == INACTIVE_DEVICE] diff --git a/custom_components/icloud3/support/start_ic3_control.py b/custom_components/icloud3/support/start_ic3_control.py index 95e1f2d..d005d83 100644 --- a/custom_components/icloud3/support/start_ic3_control.py +++ b/custom_components/icloud3/support/start_ic3_control.py @@ -20,7 +20,7 @@ from ..helpers.messaging import (broadcast_info_msg, post_event, post_error_msg, log_error_msg, post_startup_alert, post_monitor_msg, post_internal_error, - log_start_finish_update_banner, + write_ic3log_recd, log_debug_msg, log_warning_msg, log_info_msg, log_exception, log_rawdata, _evlog, _log, more_info, format_filename, write_config_file_to_ic3log, @@ -37,6 +37,12 @@ def stage_1_setup_variables(): stage_title = f'Stage 1 > Initial Preparations' open_ic3log_file() + + # if Gb.prestartup_log: + # _prestartup_log = Gb.prestartup_log + # Gb.prestartup_log = '' + # write_ic3log_recd(f"$$$$$\n#####\n{_prestartup_log}") + log_info_msg(f"* > {EVLOG_IC3_STAGE_HDR}{stage_title}") broadcast_info_msg(stage_title) @@ -346,13 +352,13 @@ def _log_into_apple_accounts(retry=False): PyiCloud = pyicloud_ic3_interface.log_into_apple_account( username, Gb.PyiCloud_password_by_username[username], - locate_all=conf_apple_acct[CONF_LOCATE_ALL]) + locate_all_devices=conf_apple_acct[CONF_LOCATE_ALL]) if PyiCloud: Gb.PyiCloud_by_username[username] = PyiCloud if is_empty(Gb.devices_without_location_data): - post_event("Apple Acct > All Devices Located") + post_event(f"Apple Acct > {PyiCloud.username_base}, All Devices Located") else: post_event(f"Apple Acct > Devices not Located > {list_to_str(Gb.devices_without_location_data)}") return False @@ -380,8 +386,8 @@ def _are_all_devices_verified(retry=False): for devicename, Device in Gb.Devices_by_devicename.items() if Device.verified_flag is False and Device.isnot_inactive] - Gb.usernames_setup_error_retry_list = unverified_device_usernames - Gb.devicenames_setup_error_retry_list = unverified_devices + Gb.usernames_setup_error_retry_list = list(set(unverified_device_usernames)) + Gb.devicenames_setup_error_retry_list = list(set(unverified_devices)) Gb.startup_lists['_.usernames_setup_error_retry_list'] = Gb.usernames_setup_error_retry_list Gb.startup_lists['_.devicenames_setup_error_retry_list'] = Gb.devicenames_setup_error_retry_list diff --git a/custom_components/icloud3/support/zone_handler.py b/custom_components/icloud3/support/zone_handler.py index de0ed45..b8be73a 100644 --- a/custom_components/icloud3/support/zone_handler.py +++ b/custom_components/icloud3/support/zone_handler.py @@ -311,7 +311,9 @@ def is_same_or_overlapping_zone(zone1, zone2): if zone1 == zone2: return True - if (isnot_zone(zone1) or zone1 == 'not_set' or zone2 == 'not_set' + if (isnot_zone(zone1) + or zone1 not in Gb.Zones_by_zone or zone2 not in Gb.Zones_by_zone + or zone1 == 'not_set' or zone2 == 'not_set' or zone1 == "" or zone2 == ""): return False @@ -323,7 +325,7 @@ def is_same_or_overlapping_zone(zone1, zone2): return (zone_dist_m <= 2) except Exception as err: - log_exception(err) + #log_exception(err) return False #-------------------------------------------------------------------- diff --git a/custom_components/icloud3/translations/en.json b/custom_components/icloud3/translations/en.json index a0b639b..60d718c 100644 --- a/custom_components/icloud3/translations/en.json +++ b/custom_components/icloud3/translations/en.json @@ -69,6 +69,7 @@ "icloud_acct_logging_into": "Logging into Apple Account", "icloud_acct_logged_into": "✅ Logged into the Apple Account", "icloud_acct_already_logged_into": "Already Logged into the Apple Account", + "icloud_acct_locate_all_reqd": "All devices must be located. Other Family devices are tracked with this Apple Account", "icloud_acct_login_error_user_pw": "❌ Login Error, Invalid Username or Password", "icloud_acct_login_error_other": "❌ Login Error, Other Error or iCloud is not Available", "icloud_acct_login_error_503": "🍎 Apple is delaying displaying a new Verification code to prevent Suspicious Activity, probably due to too many requests. It should be displayed in about 20-30 minutes. Restart HA if it is not displayed", @@ -97,7 +98,7 @@ "inactive_most_devices": "MOST Devices are INACTIVE and will not be located or tracked", "inactive_some_devices": "Some Devices are INACTIVE and will not be located or tracked", "inactive_few_devices": "A Few Devices are INACTIVE and will not be located or tracked", - "inactive_no_devices": "No Devices have been set up. Select `Add Device` and Submit", + "inactive_no_devices": "No Devices have been set up. On the `Update Devices` screen, select `Add a New Device`, then select `Submit` to display the screen to add an iCloud3 device_tracker entity.", "away_time_zone_dup_devices_1": "One of these devices is also selected in the Other Device List", "away_time_zone_dup_devices_2": "One of these devices is also selected in the Otner Device List", @@ -255,14 +256,15 @@ "add_device": { "title": "Add iCloud3 Device", "data": { - "ic3_devicename": "ICLOUD3 ENTITY ID - The HA device_tracker entity forto this device", - "fname": "FRIENDLY NAME - Displayed in HA entities and on the Event Log", + "ic3_devicename": "ICLOUD3 DEVICE_TRACKER ENTITY ID - The HA device_tracker entity for this device. (Example: gary_iphone)", + "fname": "FRIENDLY NAME - Displayed in HA entities and on the Event Log (Example: Gary-iPhone)", "device_type": "DEVICE TYPE - iPhone, iPad, Watch, etc.", "tracking_mode": "TRACKING MODE - Location request method (Tracked, Monitored, Inactive)", "mobapp": "MOBILE APP INSTALLED - HA Mobile App is installed on this device", "action_items": "⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ ACTION COMMANDS" }, "data_description": { + "ic3_devicename": "This is the device_tracker entity you are assigning to the iCloud3 device you want to track. The Apple Account and Mobile App devices to be associated with this device_tracker entity are selected on the next page. (Example: gary_iphone)" } }, "update_device": {