diff --git a/README.md b/README.md index 60733fc..a4c9d6d 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ ------ -[![CurrentVersion](https://img.shields.io/badge/Current_Version-v3.0.4-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-Standard_Repository-orange.svg)](https://github.com/gcobb321/icloud3_v3) +[![CurrentVersion](https://img.shields.io/badge/Current_Version-v3.0.5-blue.svg)](https://github.com/gcobb321/icloud3) [![Type](https://img.shields.io/badge/Type-Custom_Component-orange.svg)](https://github.com/gcobb321/icloud3) [![HACS](https://img.shields.io/badge/HACS-Standard_Repository-orange.svg)](https://github.com/gcobb321/icloud3) -[![ProjectStage](https://img.shields.io/badge/Project_Stage-General_Availability-forestgreen.svg)](https://github/gcobb321/icloud3_v3) [![Released](https://img.shields.io/badge/Released-May,_2024-forestgreen.svg)](https://github.com/gcobb321/icloud3_v3) +[![ProjectStage](https://img.shields.io/badge/Project_Stage-General_Availability-forestgreen.svg)](https://github/gcobb321/icloud3) [![Released](https://img.shields.io/badge/Released-May,_2024-forestgreen.svg)](https://github.com/gcobb321/icloud3) diff --git a/custom_components/icloud3/ChangeLog.txt b/custom_components/icloud3/ChangeLog.txt index aa2a785..cef5df5 100644 --- a/custom_components/icloud3/ChangeLog.txt +++ b/custom_components/icloud3/ChangeLog.txt @@ -1,12 +1,26 @@ ### Important Links: -- iCloud3 Documentation is [here](https://gcobb321.github.io/icloud3_v3_docs/#/chapters/0.1-introduction) -- The *Installing iCloud3* chapter describes migrating from iCloud3 v2 and installing iCloud3 for the first time +**Migrating from v2.4.7_** - See [here](https://gcobb321.github.io/icloud3_v3_docs/#/chapters/3.1-migrating-v2-to-v3) for instructions on migrating from from an older version. +**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.0.5.1 +....................... +### Change Log - v3.0.5.1 (5/25/2024) +1. MOBILE APP (Fix) - Fixed a problem where the device's Mobile App data source would be disabled (reset to NotUsed) if the HA Mobile App Integration was set up after the Mobile App's initial locate had been completed. +2. TRANSLATION FILE (new) - Added Chinese (Simplified) translation (@MagicStarTrace) + +### Change Log - v3.0.5 (5/18/2024) +1. HACS UPUDATE ALERT (New) - The HACS Integration information will be check on a regular basis to see if a newer version of iCloud3 is available. +2. ICLOUD ACCOUNT AUTHENTICATION/FAMSHR DEVICES LIST (Fixed) - During startup ("Stage 4), the iCloud Account access is set up and the devices in the Family Sharing List is read. If a problem occurred, iCloud3 would retry this 10-times to see if the error was corrected. However, the FamShr data was not being reread and the old data was being used. The FamShr data is now reread correctly when trying to recover from this error. +3. UPDATE DEVICES SCREEN (Fixed) - When upgrading a device (iPhone, iPad, Watch) and both the old and new devices are still in the Family Sharing List, the new device was being set back to the old device the next time iCloud3 was started. +4. LOCKED ICLOUD ACCOUNT (New) - An error message is displayed in the HA logs and on the Event Log if the iCloud account is locked. +5. EVENT LOG (Fix) - An 'Unbound event_recd' error would display when the length of the the event text > 2000 characters (@ehendrix23). + v3.0.4 ....................... -### Change Log - v3.0.4 (not released) +### Change Log - v3.0.4 (5/11/2024) 1. ADD DEVICE (Fixed) - An 'Out of Range' error message was encountered adding the first device. 2. DIRECTION OF TRAVEL (Improvement) - Tweaked the AwayFrom direction override when approaching Home after the previous directions were Towards. 3. Event Type DEPRECIATED EOORO MESSAGE (Fixed) - This was a warning about the removal of the EventType from HA next year. It has been removed. diff --git a/custom_components/icloud3/__init__.py b/custom_components/icloud3/__init__.py index af83499..dd96cc0 100644 --- a/custom_components/icloud3/__init__.py +++ b/custom_components/icloud3/__init__.py @@ -21,7 +21,7 @@ from .const import (DOMAIN, PLATFORMS, ICLOUD3, MODE_PLATFORM, MODE_INTEGRATION, CONF_VERSION, CONF_SETUP_ICLOUD_SESSION_EARLY, CONF_EVLOG_BTNCONFIG_URL, SENSOR_EVENT_LOG_NAME, SENSOR_WAZEHIST_TRACK_NAME, - EVLOG_IC3_STARTING, VERSION, ) + EVLOG_IC3_STARTING, VERSION, VERSION_BETA,) CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN) @@ -75,7 +75,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: config_file.load_storage_icloud3_configuration_file() if Gb.conf_profile[CONF_VERSION] == 1: - Gb.HALogger.warning(f"Starting iCloud3 v{VERSION} > " + Gb.HALogger.warning(f"Starting iCloud3 v{VERSION}{VERSION_BETA} > " "Detected a `platform: icloud3` statement in the " "configuration.yaml file. This is depreciated and " "should be removed.") @@ -102,7 +102,7 @@ async def start_icloud3(event=None): log_msg =(f"iCloud3 v{Gb.version} Started") log_info_msg(log_msg) Gb.HALogger.info(f"Gb.HALogger-Setting up iCloud3 v{Gb.version}") - # Gb.HALogger.info(f"# Gb.HALogger-Setting up iCloud3 v{Gb.version}") + else: log_msg =(f"iCloud3 v{Gb.version} - Failed to Start") log_error_msg(log_msg) @@ -151,8 +151,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): await Gb.hass.async_add_executor_job( open_ic3log_file_init) - Gb.HALogger.info(f"Setting up iCloud3 v{Gb.version}") - log_info_msg(f"Setting up iCloud3 v{VERSION}") + Gb.HALogger.info(f"Setting up iCloud3 v{Gb.version}{VERSION_BETA}") + log_info_msg(f"Setting up iCloud3 v{VERSION}{VERSION_BETA}") if Gb.restart_ha_flag: log_error_msg("iCloud3 > Waiting for HA restart to remove legacy \ diff --git a/custom_components/icloud3/config_flow.py b/custom_components/icloud3/config_flow.py index 8c0e986..67e4ae2 100644 --- a/custom_components/icloud3/config_flow.py +++ b/custom_components/icloud3/config_flow.py @@ -304,7 +304,7 @@ def ensure_six_item_dict(dict_item): 'None': 'None - The Mobile App is not installed on this device', } LOG_ZONES_KEY_TEXT = { - '.fmf': f"{'-'*10} File Name Formats {'-'*10}", + # '.fmf': f"{'-'*10} File Name Formats {'-'*10}", 'name-zone': ' → [year]-[zone].csv', 'name-device': ' → [year]-[device].csv', 'name-device-zone': ' → [year]-[device]-[zone].csv', @@ -955,6 +955,9 @@ async def async_step_menu(self, user_input=None, errors=None): self.PyiCloud = Gb.PyiCloud Gb.PyiCloudConfigFlow = self.PyiCloud + if self.PyiCloud is None and self.username: + self.header_msg = 'icloud_acct_not_logged_into' + self.step_id = f"menu_{self.menu_page_no}" self.called_from_step_id_1 = self.called_from_step_id_2 ='' self.errors = {} @@ -2272,8 +2275,6 @@ async def async_step_icloud_account(self, user_input=None, errors=None, called_f self.actions_list_default = 'login_icloud_account' self.errors[CONF_USERNAME] = 'icloud_acct_username_password_error' - - elif action_item == 'logout_icloud_account': user_input = self._initialize_pyicloud_username_password(user_input) @@ -2505,7 +2506,7 @@ async def _log_into_icloud_account(self, user_input, called_from_step_id=None, r self.username, self.password, self.endpoint_suffix, - 'config_flow', + 'config', verify_password, request_verification_code) @@ -2949,7 +2950,6 @@ async def async_step_update_device(self, user_input=None, errors=None): f"{self.conf_device_selected[CONF_FNAME]}/" f"{DEVICE_TYPE_FNAME[self.conf_device_selected[CONF_DEVICE_TYPE]]}") post_event(event_msg) - Gb.conf_devices[self.conf_device_selected_idx] = self.conf_device_selected config_file.write_storage_icloud3_configuration_file() @@ -3099,21 +3099,19 @@ def _validate_update_device(self, user_input): and self.devicename_by_famshr_fmf[user_input[CONF_FAMSHR_DEVICENAME]] not in ui_old_devicename): self.errors[CONF_FAMSHR_DEVICENAME] = 'already_assigned' - if (user_input[CONF_FMF_EMAIL] in self.devicename_by_famshr_fmf - and self.devicename_by_famshr_fmf[user_input[CONF_FMF_EMAIL]] not in ui_old_devicename): - self.errors[CONF_FMF_EMAIL] = 'already_assigned' + # if (user_input[CONF_FMF_EMAIL] in self.devicename_by_famshr_fmf + # and self.devicename_by_famshr_fmf[user_input[CONF_FMF_EMAIL]] not in ui_old_devicename): + # self.errors[CONF_FMF_EMAIL] = 'already_assigned' if self.PyiCloud: _FamShr = self.PyiCloud.FamilySharing conf_famshr_fname = user_input[CONF_FAMSHR_DEVICENAME] - if conf_famshr_fname in _FamShr.device_model_info_by_fname: - raw_model, model, model_display_name = _FamShr.device_model_info_by_fname[conf_famshr_fname] - if (user_input.get(CONF_RAW_MODEL) != raw_model - or user_input[CONF_MODEL] != model - or user_input[CONF_MODEL_DISPLAY_NAME] != model_display_name): - user_input[CONF_RAW_MODEL] = raw_model - user_input[CONF_MODEL] = model - user_input[CONF_MODEL_DISPLAY_NAME] = model_display_name + device_id = self.PyiCloud.device_id_by_famshr_fname.get(conf_famshr_fname, '') + raw_model, model, model_display_name = self.PyiCloud.device_model_info_by_fname.get(conf_famshr_fname, ['', '', '']) + user_input[CONF_FAMSHR_DEVICE_ID] = device_id + user_input[CONF_RAW_MODEL] = raw_model + user_input[CONF_MODEL] = model + user_input[CONF_MODEL_DISPLAY_NAME] = model_display_name # Build 'log_zones' list if ('none' in user_input[CONF_LOG_ZONES] @@ -3251,7 +3249,7 @@ async def _build_update_device_selection_lists(self): self._build_zone_list() await self._build_famshr_devices_list() - self._build_fmf_devices_list() + # self._build_fmf_devices_list() self._build_devicename_by_famshr_fmf() #---------------------------------------------------------------------- @@ -3278,7 +3276,7 @@ async def _build_famshr_devices_list(self): if _FamShr := self.PyiCloud.FamilySharing: self._check_finish_v2v3conversion_for_famshr_fname() - sorted_famshr_info_by_famshr_fname = sort_dict_by_values(_FamShr.device_info_by_famshr_fname) + sorted_famshr_info_by_famshr_fname = sort_dict_by_values(self.PyiCloud.device_info_by_famshr_fname) self.famshr_list_text_by_fname_base.update(sorted_famshr_info_by_famshr_fname) self.famshr_list_text_by_fname = self.famshr_list_text_by_fname_base.copy() @@ -3304,7 +3302,7 @@ def _check_finish_v2v3conversion_for_famshr_fname(self): # Build a dictionary of the FamShr fnames to compare to the ic3_devicename {gary_iphone: Gary-iPhone} famshr_fname_by_ic3_devicename = {slugify(fname).strip(): fname - for fname in _FamShr.device_model_info_by_fname.keys()} + for fname in self.PyiCloud.device_model_info_by_fname.keys()} # Cycle thru conf_devices and see if there are any ic3_devicename = famshr_fname entries. # If so, they were just converted and the real famshr_devicename needs to be reset to the actual @@ -3322,12 +3320,13 @@ def _check_finish_v2v3conversion_for_famshr_fname(self): conf_device[CONF_FAMSHR_DEVICENAME] = famshr_fname raw_model, model, model_display_name = \ - _FamShr.device_model_info_by_fname[famshr_fname] + self.PyiCloud.device_model_info_by_fname[famshr_fname] + device_id = Gb.device_id_by_famshr_fname[famshr_fname] + conf_device[CONF_FAMSHR_DEVICE_ID] = device_id conf_device[CONF_MODEL] = model conf_device[CONF_MODEL_DISPLAY_NAME] = model_display_name conf_device[CONF_RAW_MODEL] = raw_model - conf_device[CONF_FAMSHR_DEVICE_ID] = _FamShr.device_id_by_famshr_fname[famshr_fname] update_conf_file_flag = True if update_conf_file_flag: @@ -3371,27 +3370,27 @@ def _build_devicename_by_famshr_fmf(self, current_devicename=None): self.famshr_list_text_by_fname = self.famshr_list_text_by_fname_base.copy() for famshr_devicename, famshr_text in self.famshr_list_text_by_fname_base.items(): devicename_msg = '' - devicename_msg_prefix = '' + devicename_msg_alert = '' try: if current_devicename != self.devicename_by_famshr_fmf[famshr_devicename]: - devicename_msg_prefix = f"{YELLOW_ALERT} " + devicename_msg_alert = f"{YELLOW_ALERT} " devicename_msg = ( f"{RARROW}ASSIGNED TO-" f"{self.devicename_by_famshr_fmf[famshr_devicename]}") except: pass self.famshr_list_text_by_fname[famshr_devicename] = \ - f"{devicename_msg_prefix}{famshr_text}{devicename_msg}" - - self.fmf_list_text_by_email = self.fmf_list_text_by_email_base.copy() - for fmf_email, fmf_text in self.fmf_list_text_by_email_base.items(): - devicename_msg = '' - try: - if current_devicename != self.devicename_by_famshr_fmf[fmf_email]: - devicename_msg = ( f"{RARROW}ASSIGNED TO-" - f"{self.devicename_by_famshr_fmf[fmf_email]}") - except: - pass - self.fmf_list_text_by_email[fmf_email] = f"{fmf_text}{devicename_msg}" + f"{devicename_msg_alert}{famshr_text}{devicename_msg}" + + # self.fmf_list_text_by_email = self.fmf_list_text_by_email_base.copy() + # for fmf_email, fmf_text in self.fmf_list_text_by_email_base.items(): + # devicename_msg = '' + # try: + # if current_devicename != self.devicename_by_famshr_fmf[fmf_email]: + # devicename_msg = ( f"{RARROW}ASSIGNED TO-" + # f"{self.devicename_by_famshr_fmf[fmf_email]}") + # except: + # pass + # self.fmf_list_text_by_email[fmf_email] = f"{fmf_text}{devicename_msg}" #---------------------------------------------------------------------- def _build_mobapp_entity_list(self): @@ -3490,8 +3489,8 @@ def _format_device_list_item(self, conf_device_data): if conf_device_data[CONF_FAMSHR_DEVICENAME] != 'None': device_info += f", FamShr-({conf_device_data[CONF_FAMSHR_DEVICENAME]})" - if conf_device_data[CONF_FMF_EMAIL] != 'None': - device_info += f", FmF-({conf_device_data[CONF_FMF_EMAIL]})" + # if conf_device_data[CONF_FMF_EMAIL] != 'None': + # device_info += f", FmF-({conf_device_data[CONF_FMF_EMAIL]})" if conf_device_data[CONF_MOBILE_APP_DEVICE] != 'None': device_info += f", MobApp-({conf_device_data[CONF_MOBILE_APP_DEVICE]})" @@ -4531,19 +4530,20 @@ def form_schema(self, step_id, actions_list=None, actions_list_default=None): self.errors['base'] = 'icloud_acct_not_available' # If conf_fmf_email is not in available fmf emails list, add it - fmf_email = self.conf_device_selected[CONF_FMF_EMAIL] - fmf_list_text_by_email = self.fmf_list_text_by_email.copy() - if fmf_email not in self.fmf_list_text_by_email: - error_key = f"{error_key}_fmf" - self.errors[CONF_FMF_EMAIL] = 'unknown_fmf' - fmf_list_text_by_email[fmf_email] = f"{fmf_email}{UNKNOWN_DEVICE_TEXT}" + # fmf_email = self.conf_device_selected[CONF_FMF_EMAIL] + # fmf_list_text_by_email = self.fmf_list_text_by_email.copy() + # if fmf_email not in self.fmf_list_text_by_email: + # error_key = f"{error_key}_fmf" + # self.errors[CONF_FMF_EMAIL] = 'unknown_fmf' + # fmf_list_text_by_email[fmf_email] = f"{fmf_email}{UNKNOWN_DEVICE_TEXT}" if self.PyiCloud: - try: - if self.PyiCloud.FindMyFriends.is_service_not_available: - fmf_list_text_by_email[fmf_email] = f"{fmf_email}{DATA_ENTRY_ALERT}{SERVICE_NOT_AVAILABLE}" - except: - fmf_list_text_by_email[fmf_email] = f"{fmf_email}{DATA_ENTRY_ALERT}{SERVICE_NOT_STARTED_YET}" + pass + # try: + # if self.PyiCloud.FindMyFriends.is_service_not_available: + # fmf_list_text_by_email[fmf_email] = f"{fmf_email}{DATA_ENTRY_ALERT}{SERVICE_NOT_AVAILABLE}" + # except: + # fmf_list_text_by_email[fmf_email] = f"{fmf_email}{DATA_ENTRY_ALERT}{SERVICE_NOT_STARTED_YET}" elif 'base' not in self.errors: self.errors['base'] = 'icloud_acct_not_available' diff --git a/custom_components/icloud3/const.py b/custom_components/icloud3/const.py index 9f7d359..adbdfdb 100644 --- a/custom_components/icloud3/const.py +++ b/custom_components/icloud3/const.py @@ -3,8 +3,15 @@ # Define the iCloud3 General Constants # #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +# +# Translations: +# en - @gcobb321 (Gary Cobb, iCloud3 author) +# zh-Hans - @MagicStarTrace (Magic) +# +#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -VERSION = '3.0.4' +VERSION = '3.0.5.1' +VERSION_BETA = '' #----------------------------------------- DOMAIN = 'icloud3' ICLOUD3 = 'iCloud3' @@ -251,7 +258,7 @@ lite_circled_letters = "Ⓐ Ⓑ Ⓒ Ⓓ Ⓔ Ⓕ Ⓖ Ⓗ Ⓘ Ⓙ Ⓚ Ⓛ Ⓜ Ⓝ Ⓞ Ⓟ Ⓠ Ⓡ Ⓢ Ⓣ Ⓤ Ⓥ Ⓦ Ⓧ Ⓨ Ⓩ" dark_circled_letters = "🅐 🅑 🅒 🅓 🅔 🅕 🅖 🅗 🅘 🅙 🅚 🅛 🅜 🅝 🅞 🅟 🅠 🅡 🅢 🅣 🅤 🅥 🅦 🅧 🅨 🅩 ✪" Symbols = ±▪•●▬⮾ ⊗ ⊘✓×ø¦ ▶◀ ►◄▲▼ ∙▪ »« oPhone=►▶→⟾➤➟➜➔➤🡆🡪🡺⟹🡆➔ᐅ◈🝱☒☢⛒⊘Ɵ⊗ⓧⓍ⛒🜔 -Important = ❗❌⚠️❓🛑⛔⚡⭐⭕ⓘ• ⍰ ‶″“”‘’‶″ 🕓 +Important =✔️❗❌✨➰⚠️❓⚽🛑⛔⚡⭐⭕ⓘ• ⍰ ‶″“”‘’‶″ 🕓 — –ᗒ ⁃ » ━▶ ━➤🡺 —> > > ❯↦ … ⋮ 🡪ᗕ ᗒ ᐳ ─🡢 ──ᗒ 🡢 ─ᐅ ↣ ➙ →《》◆◈◉● ▐‖ ▹▻▷◁◅◃‖╠ᐅ🡆▶▐🡆▐▶‖➤▐➤➜➔❰❰❱❱ ⠤ ² ⣇⠈⠉⠋⠛⠟⠿⡿⣿ https://www.fileformat.info/info/unicode/block/braille_patterns/utf8test.htm diff --git a/custom_components/icloud3/global_variables.py b/custom_components/icloud3/global_variables.py index fc10ed6..da51c05 100644 --- a/custom_components/icloud3/global_variables.py +++ b/custom_components/icloud3/global_variables.py @@ -23,7 +23,8 @@ # Gb.Zones_by_zone #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -from .const import (DEVICENAME_MOBAPP, VERSION, NOT_SET, HOME_FNAME, HOME, STORAGE_DIR, WAZE_USED, +from .const import (DEVICENAME_MOBAPP, VERSION, VERSION_BETA, + NOT_SET, HOME_FNAME, HOME, STORAGE_DIR, WAZE_USED, FAMSHR, FMF, FAMSHR_FMF, ICLOUD, MOBAPP, FNAME, HIGH_INTEGER, DEFAULT_GENERAL_CONF, CONF_UNIT_OF_MEASUREMENT, @@ -60,8 +61,10 @@ class GlobalVariables(object): Define global variables used in the various iCloud3 modules ''' # Fixed variables set during iCloud3 loading that will not change - version = VERSION + version = f"{VERSION}{VERSION_BETA}" + version_beta = VERSION_BETA version_evlog = '' + version_hacs = '' hass = None # hass: HomeAssistant set in __init__ config_entry = None # hass.config_entry set in __init__ (integration) @@ -94,9 +97,9 @@ class GlobalVariables(object): iC3Logger_last_check_exist_secs = 0 iC3EntityPlatform = None # iCloud3 Entity Platform (homeassistant.helpers.entity_component) - PyiCloud = None # iCloud Account service - PyiCloudInit = None # iCloud Account service when started from __init__ via executive job - PyiCloudConfigFlow = None # iCloud Account service when started from config_flow + PyiCloud = None # iCloud Account service + PyiCloudInit = None # iCloud Account service when started from __init__ via executive job + PyiCloudConfigFlow = None # iCloud Account service when started from config_flow Waze = None WazeHist = None @@ -112,7 +115,7 @@ class GlobalVariables(object): ha_storage_directory = '' # 'config/.storage' directory ha_storage_icloud3 = '' # 'config/.storage/icloud3' icloud3_config_filename = '' # 'config/.storage/icloud3.configuration' - iC3 Configuration File - icloud3_restore_state_filename = '' # 'config/.storage/icloud3.restore_state' + icloud3_restore_state_filename = '' # 'config/.storage/icloud3.restore_state' config_ic3_yaml_filename = '' # 'config/config_ic3.yaml' (v2 config file name) icloud3_directory = '' ha_config_www_directory = '' @@ -139,12 +142,25 @@ class GlobalVariables(object): Devices_by_icloud_device_id = {} # Devices by the icloud device_id receive from Apple Devices_by_ha_device_id = {} # Device by the device_id in the entity/device registry Devices_by_mobapp_devicename = {} # All verified Devices by the conf_mobapp_devicename + PairedDevices_by_paired_with_id = {} # Paired Devices by the paired_with_id (famshr prsID) id=[Dev1, Dev2] + + # FamShr Device information - These is used verify the device, display on the EvLog and in the Config Flow + # device selection list on the iCloud3 Devices screen + devices_not_set_up = [] + device_id_by_famshr_fname = {} # Example: {'Gary-iPhone': 'n6ofM9CX4j...'} + famshr_fname_by_device_id = {} # Example: {'n6ofM9CX4j...': 'Gary-iPhone14'} + device_info_by_famshr_fname = {} # Example: {'Gary-iPhone': 'Gary-iPhone (iPhone 14 Pro (iPhone15,2)'} + device_model_info_by_fname = {} # {'Gary-iPhone': [raw_model,model,model_display_name]} + dup_famshr_fname_cnt = {} # Used to create a suffix for duplicate devicenames + devices_without_location_data = [] + devicenames_x_famshr_devices = {} # All ic3_devicenames by conf_famshr_devices (both ways) devicenames_x_mobapp_devicenames = {} # All ic3_devicenames by conf_mobapp_devicename (both ways) + mobapp_fnames_x_mobapp_id = {} # All mobapp_fnames by mobapp_deviceid from HA hass.data MobApp entry (both ways) mobapp_fnames_disabled = [] mobile_app_device_fnames = [] # fname = name_by_user or name in mobile_app device entry - PairedDevices_by_paired_with_id = {} # Paired Devices by the paired_with_id (famshr prsID) id=[Dev1, Dev2] + Zones = [] # Zones object list Zones_by_zone = {} # Zone object by zone name for HA Zones and iC3 Pseudo Zones HAZones = [] # Zones object list for only valid HA Zones @@ -184,7 +200,7 @@ class GlobalVariables(object): startup_stage_status_controls = [] # A general list used by various modules for noting startup progress debug_log = {} # Log variable and dictionsry field/values to icloud3-0.log file - stage_4_no_devices_found_cnt = 0 # Retry count to connect to iCloud and retrieve FamShr devices + get_FAMSHR_devices_retry_cnt = 0 # Retry count to connect to iCloud and retrieve FamShr devices reinitialize_icloud_devices_flag= False # Set when no devices are tracked and iC3 needs to automatically restart reinitialize_icloud_devices_cnt = 0 initial_icloud3_loading_flag = False @@ -199,6 +215,7 @@ class GlobalVariables(object): info_notification = '' ha_notification = {} trace_prefix = '_INIT_' + trace_prefix_pyicloud = '' trace_group = False trace_text_change_1 = '' trace_text_change_2 = '' diff --git a/custom_components/icloud3/helpers/common.py b/custom_components/icloud3/helpers/common.py index 44f8c1c..b54da8c 100644 --- a/custom_components/icloud3/helpers/common.py +++ b/custom_components/icloud3/helpers/common.py @@ -322,3 +322,83 @@ def delete_file(file_desc, directory, filename, backup_extn=None, delete_old_sv_ except Exception as err: Gb.HALogger.exception(err) return "Delete error" + +#-------------------------------------------------------------------- +def encode_password(password): + ''' + Determine if the password is encoded. + + Return: + Decoded password + ''' + try: + if (password == '' or Gb.encode_password_flag is False): + return password + + return f"««{base64_encode(password)}»»" + + except Exception as err: + #log_exception(err) + password = password.replace('«', '').replace('»', '') + return password + +def base64_encode(string): + """ + Encode the string via base64 encoder + """ + # encoded = base64.urlsafe_b64encode(string) + # return encoded.rstrip("=") + + try: + string_bytes = string.encode('ascii') + base64_bytes = base64.b64encode(string_bytes) + return base64_bytes.decode('ascii') + + except Exception as err: + #log_exception(err) + password = password.replace('«', '').replace('»', '') + return password + + +#-------------------------------------------------------------------- +def decode_password(password): + ''' + Determine if the password is encoded. + + Return: + Decoded password + ''' + try: + # If the password in the configuration file is not encoded (no '««' or '»»') + # and it should be encoded, save the configuration file which will encode it + if (Gb.encode_password_flag + and password != '' + and (password.startswith('««') is False + or password.endswith('»»') is False)): + password = password.replace('«', '').replace('»', '') + Gb.conf_tracking[CONF_PASSWORD] = password + #write_storage_icloud3_configuration_file() + + # Decode password if it is encoded and has the '««password»»' format + if (password.startswith('««') or password.endswith('»»')): + password = password.replace('«', '').replace('»', '') + return base64_decode(password) + + except Exception as err: + #log_exception(err) + password = password.replace('«', '').replace('»', '') + + return password + +def base64_decode(string): + """ + Decode the string via base64 decoder + """ + # padding = 4 - (len(string) % 4) + # string = string + ("=" * padding) + # return base64.urlsafe_b64decode(string) + + base64_bytes = string.encode('ascii') + string_bytes = base64.b64decode(base64_bytes) + return string_bytes.decode('ascii') + diff --git a/custom_components/icloud3/helpers/messaging.py b/custom_components/icloud3/helpers/messaging.py index c6cc812..be3aa69 100644 --- a/custom_components/icloud3/helpers/messaging.py +++ b/custom_components/icloud3/helpers/messaging.py @@ -55,7 +55,7 @@ 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', + 'appleId', 'emails', 'phones', 'locked', 'deviceStatus', 'batteryStatus', 'batteryLevel', 'membersInfo', 'deviceModel', 'rawDeviceModel', 'deviceDisplayName', 'modelDisplayName', 'deviceClass', 'isOld', 'isInaccurate', 'timeStamp', 'altitude', 'location', 'latitude', 'longitude', @@ -182,6 +182,8 @@ def post_error_msg(devicename_or_Device, event_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, ". ") @@ -540,6 +542,7 @@ def format_msg_line(log_msg, area=None): source = f"{_called_from()}{program_area}" log_msg = format_startup_header_box(log_msg) msg_prefix= ' ' if log_msg.startswith('⡇') else \ + ' ❗' if source.startswith('[pyicloud') else \ ' ⡇ ' if Gb.trace_group else \ ' ' @@ -664,7 +667,7 @@ def log_rawdata_unfiltered(title, rawdata): rawdata_copy = rawdata['raw'].copy() if 'raw' in rawdata else rawdata.copy() except: - log_info_msg(f"{'─'*8} {title.upper()} {'─'*8}\n{rawdata}") + log_info_msg(f"{'_'*8} {title.upper()} {'_'*8}\n{rawdata}") return devices_data = {} @@ -675,7 +678,7 @@ def log_rawdata_unfiltered(title, rawdata): rawdata_copy['content'] = 'DeviceData...' - log_info_msg(f"{'─'*8} {title.upper()} {'─'*8}\n{rawdata_copy}") + log_info_msg(f"{'_'*8} {title.upper()} {'_'*8}\n{rawdata_copy}") for device_data in devices_data: log_msg = ( f"FamShr PyiCloud Data (unfiltered -- " @@ -703,10 +706,6 @@ def log_rawdata(title, rawdata, log_rawdata_flag=False): if Gb.log_rawdata_flag is False or rawdata is None: return - # log_info_msg(f"RAWDATA 706 {title=} {Gb.log_level_devices}") - # if Gb.log_rawdata_flag_unfiltered: - # log_rawdata_unfiltered(title, rawdata) - # return if (Gb.start_icloud3_inprocess_flag or 'all' in Gb.log_level_devices @@ -729,7 +728,7 @@ def log_rawdata(title, rawdata, log_rawdata_flag=False): try: if type(rawdata) is not dict: - log_info_msg(f"{'─'*8} {title.upper()} {'─'*8}\n{rawdata}") + log_info_msg(f"{'_'*8} {title.upper()} {'_'*8}\n{rawdata}") return if Gb.log_rawdata_flag_unfiltered: @@ -737,7 +736,7 @@ def log_rawdata(title, rawdata, log_rawdata_flag=False): return if 'raw' in rawdata or log_rawdata_flag: - log_info_msg(f"{'─'*8} {title.upper()} {'─'*8}\n{rawdata}") + log_info_msg(f"{'_'*8} {title.upper()} {'_'*8}\n{rawdata}") return rawdata_items = {k: v for k, v in rawdata['filter'].items() @@ -786,11 +785,12 @@ def log_rawdata(title, rawdata, log_rawdata_flag=False): else: log_msg = f"{rawdata[:15]}" - except: + except Exception as err: + log_exception(err) pass if log_msg != {}: - log_info_msg(f"{'─'*8} {title.upper()} {'─'*8}\n{log_msg}") + log_info_msg(f"{'_'*8} {title.upper()} {'_'*8}\n{log_msg}") return diff --git a/custom_components/icloud3/icloud3_main.py b/custom_components/icloud3/icloud3_main.py index 44ade92..725fb0a 100644 --- a/custom_components/icloud3/icloud3_main.py +++ b/custom_components/icloud3/icloud3_main.py @@ -34,11 +34,12 @@ # ================================================================= from .global_variables import GlobalVariables as Gb -from .const import (VERSION, +from .const import (VERSION, VERSION_BETA, HOME, NOT_HOME, NOT_SET, HIGH_INTEGER, RARROW, LT, NBSP3, CLOCK_FACE, CRLF, DOT, LDOT2, CRLF_DOT, CRLF_LDOT, CRLF_HDOT, CRLF_X, NL, NL_DOT, TOWARDS, EVLOG_IC3_STAGE_HDR, ICLOUD, ICLOUD_FNAME, TRACKING_NORMAL, FNAME, + CONF_USERNAME, CONF_PASSWORD, IPHONE, IPAD, WATCH, AIRPODS, IPOD, ALERT, CMD_RESET_PYICLOUD_SESSION, NEAR_DEVICE_DISTANCE, DISTANCE_TO_OTHER_DEVICES, DISTANCE_TO_OTHER_DEVICES_DATETIME, @@ -50,6 +51,7 @@ CONF_LOG_LEVEL, STATZONE_RADIUS_1M, ) from .const_sensor import (SENSOR_LIST_DISTANCE, ) +from .support import hacs_ic3 from .support import start_ic3 from .support import start_ic3_control from .support import stationary_zone as statzone @@ -82,7 +84,8 @@ def __init__(self): Gb.started_secs = time_now_secs() Gb.hass_configurator_request_id = {} - Gb.version = VERSION + Gb.version = f"{VERSION}{VERSION_BETA}" + Gb.version_beta = VERSION_BETA Gb.polling_5_sec_loop_running = False self.pyicloud_refresh_time = {} # Last time Pyicloud was refreshed for the trk method @@ -163,6 +166,7 @@ def start_icloud3(self): Gb.trace_prefix = '------' Gb.EvLog.display_user_message('', clear_evlog_greenbar_msg=True) + Gb.EvLog.startup_event_save_recd_flag = False Gb.initial_icloud3_loading_flag = False Gb.start_icloud3_inprocess_flag = False @@ -1169,62 +1173,74 @@ def _display_device_alert_evlog_greenbar_msg(self): Tracked device screen displayed - Show all alert messages Monitored device screen displayed - Show only that devices alerts ''' - evlog_greenbar_msg = evlog_startup_alert_attr = '' - evlog_tracked_alert_attr = evlog_monitored_alert_attr = '' - show_on_displayed_evlog_screen_flag = False + general_alert_msg = startup_alert_attr = '' + tracked_alert_attr = monitored_alert_attr = '' if Gb.startup_alerts != []: - evlog_greenbar_msg = f"{LDOT2}Errors Starting iCloud3" - evlog_startup_alert_attr = Gb.startup_alerts_str - - show_on_displayed_evlog_screen_flag = True + general_alert_msg = f"{LDOT2}Errors Starting iCloud3" + startup_alert_attr = Gb.startup_alerts_str + Gb.primary_data_source_ICLOUD + if Gb.PyiCloud is None: + if Gb.conf_tracking[CONF_USERNAME] or Gb.conf_tracking[CONF_PASSWORD]: + general_alert_msg += f"{CRLF}{LDOT2}iCloud acct not logged into, Invalid Username/Password" + elif Gb.primary_data_source_ICLOUD is False: + general_alert_msg += f"{CRLF}{LDOT2}iCloud acct not logged into, Possible Connection Error" + + elif Gb.version_hacs: + general_alert_msg = f"iCloud3 {Gb.version_hacs} is available on HACS, you are running v{Gb.version}" + + if (Gb.icloud_acct_error_cnt > 5 + and instr(general_alert_msg, 'errors accessing') is False): + general_alert_msg += "Internet or Apple may be down, errors accessing iCloud acct" for Device in Gb.Devices: - alert_msg = '' + device_alert_msg = '' if (Device.verified_flag is False or (Device.is_data_source_ICLOUD is False and Device.is_data_source_MOBAPP is False)): - alert_msg = "Not Verified, No Data Source, " + device_alert_msg = "Not Verified, No Data Source, " if Device.no_location_data: - alert_msg += "No GPS Data, " + device_alert_msg += "No GPS Data, " if Device.is_offline: - alert_msg += "Offline, " + device_alert_msg += "Offline, " - if alert_msg: - alert_msg = alert_msg[:-2] + if device_alert_msg: + device_alert_msg = device_alert_msg[:-2] elif Device.is_tracking_paused: - alert_msg = "Tracking Paused" + device_alert_msg = "Tracking Paused" elif mins_since(Device.loc_data_secs) > 300: - alert_msg = f"Location Very Old (>{format_age(Device.loc_data_secs)})" + device_alert_msg = f"Location Very Old (>{format_age(Device.loc_data_secs)})" elif isbetween(Device.dev_data_battery_level, 1, 19): - alert_msg = f"Low Battery (< 20%)" + device_alert_msg = f"Low Battery (< 20%)" - if alert_msg: - fname_alert_msg = f"{Device.fname} > {alert_msg}" - crlf = CRLF_LDOT if evlog_greenbar_msg else LDOT2 - evlog_greenbar_msg += f"{crlf}{fname_alert_msg}" + show_on_displayed_evlog_screen_flag = (general_alert_msg != '') + + if device_alert_msg: + fname = f"{Device.fname} > {device_alert_msg}" + crlf = CRLF_LDOT if general_alert_msg else LDOT2 + general_alert_msg += f"{crlf}{fname}" if Device.is_tracked: - nldot = NL_DOT if evlog_tracked_alert_attr else DOT - evlog_tracked_alert_attr += f"{nldot}{fname_alert_msg}" + nldot = NL_DOT if tracked_alert_attr else DOT + tracked_alert_attr += f"{nldot}{fname}" else: - nldot = NL_DOT if evlog_monitored_alert_attr else DOT - evlog_monitored_alert_attr += f"{nldot}{fname_alert_msg}" + nldot = NL_DOT if monitored_alert_attr else DOT + monitored_alert_attr += f"{nldot}{fname}" if (Device.device_type in [IPHONE, IPAD, WATCH] or Gb.EvLog.evlog_attrs[FNAME].startswith(Device.fname)): show_on_displayed_evlog_screen_flag = True - if alert_msg != Device.alert: - Device.alert = Device.sensors[ALERT] = alert_msg + if device_alert_msg != Device.alert: + Device.alert = Device.sensors[ALERT] = device_alert_msg Device.write_ha_device_tracker_state() - Gb.EvLog.evlog_attrs['alert_startup'] = Gb.EvLog.alert_attr_filter(evlog_startup_alert_attr) - Gb.EvLog.evlog_attrs['alert_tracked'] = Gb.EvLog.alert_attr_filter(evlog_tracked_alert_attr) - Gb.EvLog.evlog_attrs['alert_monitored'] = Gb.EvLog.alert_attr_filter(evlog_monitored_alert_attr) + Gb.EvLog.evlog_attrs['alert_startup'] = Gb.EvLog.alert_attr_filter(startup_alert_attr) + Gb.EvLog.evlog_attrs['alert_tracked'] = Gb.EvLog.alert_attr_filter(tracked_alert_attr) + Gb.EvLog.evlog_attrs['alert_monitored'] = Gb.EvLog.alert_attr_filter(monitored_alert_attr) - if (evlog_greenbar_msg != Gb.EvLog.greenbar_alert_msg + if (general_alert_msg != Gb.EvLog.greenbar_alert_msg and show_on_displayed_evlog_screen_flag): - post_evlog_greenbar_msg(evlog_greenbar_msg) - elif evlog_greenbar_msg == '' and Gb.EvLog.greenbar_alert_msg: + post_evlog_greenbar_msg(general_alert_msg) + elif general_alert_msg == '' and Gb.EvLog.greenbar_alert_msg: clear_evlog_greenbar_msg() #---------------------------------------------------------------------------- @@ -1290,6 +1306,9 @@ def _post_after_update_monitor_msg(self, Device): # #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> def _timer_tasks_every_hour(self): + # See if there is a new iCloud3 version on HACS + Gb.hass.loop.create_task(hacs_ic3.check_hacs_icloud3_update_available()) + # Clean out lingering StatZone Gb.StatZones_to_delete = [StatZone for StatZone in Gb.StatZones if StatZone.radius_m == STATZONE_RADIUS_1M] diff --git a/custom_components/icloud3/strings.json b/custom_components/icloud3/strings.json index 7fe4f17..3c5564b 100644 --- a/custom_components/icloud3/strings.json +++ b/custom_components/icloud3/strings.json @@ -60,16 +60,18 @@ "conf_updated": "iCloud3 Configuration Parameters were updated successfully", "conf_reloaded": "iCloud3 Configuration File was Reloaded", "icloud_acct_logging_into": "Logging into iCloud Account", - "icloud_acct_logged_into": "Successfully Logged into the iCloud Account", + "icloud_acct_logged_into": "Logged into the new iCloud Account. Select SAVE to save the changes and restart iCloud3", "icloud_acct_already_logged_into": "Already Logged into the iCloud Account", - "icloud_acct_login_error_user_pw": "Login Failed, Invalid Username or Password (err-400)", - "icloud_acct_login_error_other": "Login Failed, Other Error or iCloud is not Available", - "icloud_acct_login_error_connection": "Login Error, Failed to Connect to iCloud Server (err-302)", - "icloud_acct_not_available": "Login Error, iCloud Account is not Available", - "icloud_acct_not_logged_into": "iCloud Account is not Logged Into", + "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_connection": "Login Error, Failed to Connect to iCloud Server", + "icloud_acct_username_password_error": "Entry Error, Invalid Username or Password", + "icloud_acct_not_available": "Login Failed, iCloud Account is not Available", + "icloud_acct_not_logged_into": "Warning: iCloud Account is not Logged Into", + "icloud_acct_data_source_warning": "Warning: iCloud Account is not selected as a data source but username/password is setup", "icloud_acct_not_set_up": "iCloud Account Username or Password needs to be entered", "icloud_acct_no_data_source": "No Data Source (iCloud or Mobile App) has been selected", - "mobile_app_error": "The Mobile App Integration is not installed. The Mobile App will not be used as a data source; location data and zone enter/exit triggers will not be monitored", + "mobile_app_error": "Error, The Mobile App Integration is not installed. The Mobile App will not be used as a data source; location data and zone enter/exit triggers will not be monitored", "verification_code_requested": "The Apple ID Verification Code was requested, BROWSER REFRESH MAY BE NEEDED", "verification_code_requested2": "The Apple ID Verification Code was requested", @@ -221,7 +223,7 @@ "fname": "FRIENDLY NAME - Displayed in HA device_tracker and sensor names and on the Event Log", "device_type": "DEVICE TYPE - iPhone, iPad, Watch, etc.", "tracking_mode": "TRACKING MODE - How location requests should be done (Full tracking, Monitor, Inactive)", - "mobapp": "MOBILE APP INSTALLED - The HA Mobile App is installed on this device ", + "mobapp": "MOBILE APP INSTALLED - The HA Mobile App is installed on this device ", "action_items": "═════════════════════════════════════════════════════ ACTION COMMANDS" }, "data_description": { @@ -278,10 +280,10 @@ "title": "Display Location Time Zone when Away", "description": "The time displayed in the Event Log and Sensors show the time an event took place using the Home 'time zone' from your Home Assistant computer. When you are away from Home and in another time zone, your tracking events are still based on the time at your Home 'time zone', not time in your current location.\n\nThis screen lets you display time events using your current location's time zone.", "data": { - "away_time_zone_1_devices": "Devices that will display time events based on this location time", - "away_time_zone_1_offset": "Current Location Time", - "away_time_zone_2_devices": "Devices that will display time events based on this location time", - "away_time_zone_2_offset": "Current Location Time", + "away_time_zone_1_devices": "Devices in Away Time Zone #1", + "away_time_zone_1_offset": "Time & Time Zone Adjustment at Current Location #1", + "away_time_zone_2_devices": "Devices in Away Time Zone #2", + "away_time_zone_2_offset": "Time & Time Zone Adjustment at Current Location #2", "action_items": "═════════════════════════════════════════════════════ ACTION COMMANDS" }, "data_description": { @@ -294,6 +296,7 @@ "description": "Tracking activity, results and information messages are displayed in the Event log, sensors and device_tracker entities for tracked and monitored devices.\n\nThis screen us used to specify how these results should be displayed.", "data": { "log_level": "LOG LEVEL - The type of messages that are added to the HA log file by iCloud3", + "log_level_devices": "LOG LEVEL RAWDATA DEVICES FILTER - Dump rawdata for only these devices to log file", "display_zone_format": "EVENT LOG ZONE DISPLAY NAME - How the Zone name is displayed in sensors and the Event Log", "device_tracker_state_source": "DEVICE TRACKER STATE VALUE - How the device's device_tracker entity state value is determined", "time_format": "TIME FORMAT - How time fields are displayed in sensors and in the Event Log", @@ -341,7 +344,7 @@ "ipad": "IPAD", "watch": "APPLE WATCH", "airpods": "AIRPODS", - "no_mobapp": "MOBILE APP IS NOT INSTALLED ", + "no_mobapp": "MOBILE APP IS NOT INSTALLED", "other": "OTHER DEVICE TYPE", "distance_between_devices": "Determine the distance between devices. Use a near by device's tracking results", "action_items": "═════════════════════════════════════════════════════ ACTION COMMANDS" @@ -376,10 +379,10 @@ "data": { "stat_zone_header": "═════════════════════════════════════════════════════ STATIONARY ZONE", "stat_zone_fname": "FRIENDLY NAME BASE - Name to display when in a Stationary Zone (StatZone)", - "stat_zone_still_time": "NO MOVEMENT TIME ", - "stat_zone_inzone_interval": "INZONE INTERVAL ", + "stat_zone_still_time": "NO MOVEMENT TIME", + "stat_zone_inzone_interval": "INZONE INTERVAL", "passthru_zone_header": "═════════════════════════════════════════════════════ ENTER ZONE DELAY", - "passthru_zone_time": "ENTER ZONE DELAY TIME ", + "passthru_zone_time": "ENTER ZONE DELAY TIME", "track_from_base_zone_used": "═════════════════════════════════════════════════════ PRIMARY TRACK-FROM-HOME ZONE OVERRIDE", "track_from_base_zone": "TRACK FROM ZONE - Use this zone instead of Home for tracking results for all devices. Global setting", "track_from_home_zone": "TRACK FROM HOME ZONE - Keep tracking from the Home zone when the Primary Track From Zone is not Home", diff --git a/custom_components/icloud3/support/config_file.py b/custom_components/icloud3/support/config_file.py index 2d3ef09..fdea69e 100644 --- a/custom_components/icloud3/support/config_file.py +++ b/custom_components/icloud3/support/config_file.py @@ -8,8 +8,11 @@ CONF_INZONE_INTERVALS, CONF_FIXED_INTERVAL, CONF_EXIT_ZONE_INTERVAL, CONF_MOBAPP_ALIVE_INTERVAL, CONF_IOSAPP_ALIVE_INTERVAL, - CONF_IC3_VERSION, VERSION, CONF_EVLOG_CARD_DIRECTORY, CONF_EVLOG_CARD_PROGRAM, CONF_TRAVEL_TIME_FACTOR, - CONF_UPDATE_DATE, CONF_VERSION_INSTALL_DATE, CONF_PASSWORD, CONF_ICLOUD_SERVER_ENDPOINT_SUFFIX, + CONF_IC3_VERSION, VERSION, VERSION_BETA, + CONF_EVLOG_CARD_DIRECTORY, CONF_EVLOG_CARD_PROGRAM, CONF_TRAVEL_TIME_FACTOR, + CONF_EVLOG_VERSION, CONF_EVLOG_VERSION_RUNNING, CONF_EVLOG_BTNCONFIG_URL, + CONF_UPDATE_DATE, CONF_VERSION_INSTALL_DATE, + CONF_PASSWORD, CONF_ICLOUD_SERVER_ENDPOINT_SUFFIX, CONF_DEVICES, CONF_IC3_DEVICENAME, CONF_SETUP_ICLOUD_SESSION_EARLY, CONF_UNIT_OF_MEASUREMENT, CONF_TIME_FORMAT, CONF_LOG_LEVEL, CONF_LOG_LEVEL_DEVICES, CONF_DATA_SOURCE, CONF_DISPLAY_GPS_LAT_LONG, CONF_LOG_ZONES, @@ -29,7 +32,6 @@ CONF_WAZE_USED, CONF_WAZE_REGION, CONF_WAZE_MAX_DISTANCE, CONF_DISTANCE_METHOD, WAZE_SERVERS_BY_COUNTRY_CODE, WAZE_SERVERS_FNAME, CONF_EXCLUDED_SENSORS, CONF_OLD_LOCATION_ADJUSTMENT, CONF_DISTANCE_BETWEEN_DEVICES, - CONF_EVLOG_VERSION, CONF_EVLOG_VERSION_RUNNING, CONF_EVLOG_BTNCONFIG_URL, CONF_PICTURE_WWW_DIRS, PICTURE_WWW_STANDARD_DIRS, RANGE_DEVICE_CONF, RANGE_GENERAL_CONF, MIN, MAX, STEP, RANGE_UM, ) @@ -185,8 +187,8 @@ def config_file_check_new_ic3_version(): Check to see if this is a new iCloud3 version ''' update_config_file_flag = False - if Gb.conf_profile[CONF_IC3_VERSION] != VERSION: - Gb.conf_profile[CONF_IC3_VERSION] = VERSION + if Gb.conf_profile[CONF_IC3_VERSION] != f"{VERSION}{VERSION_BETA}": + Gb.conf_profile[CONF_IC3_VERSION] = f"{VERSION}{VERSION_BETA}" Gb.conf_profile[CONF_VERSION_INSTALL_DATE] = datetime_now() update_config_file_flag = True diff --git a/custom_components/icloud3/support/hacs_ic3.py b/custom_components/icloud3/support/hacs_ic3.py new file mode 100644 index 0000000..d67a813 --- /dev/null +++ b/custom_components/icloud3/support/hacs_ic3.py @@ -0,0 +1,136 @@ + + +from ..global_variables import GlobalVariables as Gb +from ..const import (STORAGE_DIR, + DISTANCE_TO_OTHER_DEVICES, DISTANCE_TO_OTHER_DEVICES_DATETIME, + HHMMSS_ZERO, AWAY, AWAY_FROM, NOT_SET, NOT_HOME, STATIONARY, STATIONARY_FNAME, ALERT, + ZONE, ZONE_DNAME, ZONE_FNAME, ZONE_NAME, ZONE_INFO, + LAST_ZONE, LAST_ZONE_DNAME, LAST_ZONE_FNAME, LAST_ZONE_NAME, + DIR_OF_TRAVEL, ) + +from ..helpers.common import (instr, ) +from ..helpers.messaging import (log_info_msg, log_debug_msg, log_exception, post_evlog_greenbar_msg, + _trace, _traceha, ) +from ..helpers.time_util import (datetime_now, secs_to_datetime, ) + +import os +import json +import logging +import asyncio +# _LOGGER = logging.getLogger(__name__) +_LOGGER = logging.getLogger(f"icloud3") + +#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +# +# .STORAGE/ICLOUD3.RESTORE_STATE FILE ROUTINES +# +#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +async def check_hacs_icloud3_update_available(): + + if Gb.version_hacs == '0.0.0': + return + + hacs_repository_file = Gb.hass.config.path(STORAGE_DIR, 'hacs.repositories') + if os.path.exists(hacs_repository_file) is False: + return None + + try: + hacs_ic3_items = _get_hacs_ic3_data(hacs_repository_file) + Gb.version_hacs = '' + + if 'icloud3_v3' in hacs_ic3_items: + version_hacs_ic3_dev = hacs_ic3_items['icloud3_v3'].get('last_version') + if 'icloud3' in hacs_ic3_items: + version_hacs_ic3 = hacs_ic3_items['icloud3'].get('last_version') + + ic3_dev_newer_running =_is_hacs_version_newer(Gb.version, version_hacs_ic3_dev) + ic3_newer_running =_is_hacs_version_newer(Gb.version, version_hacs_ic3) + dev_newer_ic3 =_is_hacs_version_newer(version_hacs_ic3, version_hacs_ic3_dev) + + if ic3_dev_newer_running: + Gb.version_hacs = version_hacs_ic3_dev + elif ic3_newer_running: + Gb.version_hacs = version_hacs_ic3 + + except Exception as err: + Gb.version_hacs = '' + + return None + +#------------------------------------------------------------------------------------------- +def _get_hacs_ic3_data(hacs_repository_file): + ''' + Read the config/.storage/.icloud3.restore_state file. + - Extract the data into the Global Variables. + - Restore each device's sensors values + - Reinitialize sensors that should not be restored + ''' + + try: + with open(hacs_repository_file, 'r') as f: + hacs_repository_file_data = json.load(f) + hacs_ic3_items = {hacs_item_data['full_name'].split('/')[1].replace(' ', '_'): hacs_item_data + for hacs_id, hacs_item_data in hacs_repository_file_data['data'].items() + if hacs_item_data['full_name'].startswith('gcobb321/icloud3')} + + except json.decoder.JSONDecodeError: + return {} + except Exception as err: + log_exception(err) + return {} + + return hacs_ic3_items + +#-------------------------------------------------------------------- +def _is_hacs_version_newer(version_1, version_2): + ''' + Compare the version_1 and _2 values for the newest one. + Return: + True - version_2 is newer than version_1 + False - version_1 is newer than version_2 + + version_2 version_1 Result + v3.0.3 v3.0.2 True + v3.0.3 v3.0.3 False + v3.0.3 v3.0.3b1 True + v3.0a v3.0.3 False + v3.0.3.1 v3.0.3 True + ''' + + if version_1 is None or version_2 is None: + return False + + v1_value, v1_beta = _get_version_value(version_1) + v2_value, v2_beta = _get_version_value(version_2) + + # version_2 is newer + if (v2_value > v1_value): + return True + + # version_2 is the same as version_1 but not beta or beta is newer + elif v2_value == v1_value: + if ((v2_beta == 0 and v1_beta > 0) + or v2_beta > v1_beta): + return True + + return False + +#-------------------------------------------------------------------- +def _get_version_value(version): + + version = version.replace('v', '') + if instr(version, 'b') is False: version += 'b0' + v_base, v_beta = version.split('b') + v_parts = (f"{v_base}.0.0.0").split('.') + v_value = 0 + + try: + v_beta = int(v_beta) + v_value += int(v_parts[0])*100000 + v_value += int(v_parts[1])*1000 + v_value += int(v_parts[2])*10 + v_value += int(v_parts[3]) + except: + pass + + return v_value, v_beta diff --git a/custom_components/icloud3/support/icloud_data_handler.py b/custom_components/icloud3/support/icloud_data_handler.py index 0a72b32..6fd9f2b 100644 --- a/custom_components/icloud3/support/icloud_data_handler.py +++ b/custom_components/icloud3/support/icloud_data_handler.py @@ -139,7 +139,7 @@ def request_icloud_data_update(Device): if (Gb.primary_data_source_ICLOUD is False or Device.is_data_source_ICLOUD is False or Gb.PyiCloud is None): - return False # beta 4/13b16 + return False devicename = Device.devicename @@ -149,20 +149,22 @@ def request_icloud_data_update(Device): Device.icloud_devdata_useable_flag = update_PyiCloud_RawData_data(Device) + # Retry in an error occurs if (Device.icloud_devdata_useable_flag is False and Device.icloud_initial_locate_done is False): Device.icloud_devdata_useable_flag = update_PyiCloud_RawData_data(Device) if Device.icloud_devdata_useable_flag is False: Device.display_info_msg("iCloud Location Not Available") - if Gb.icloud_acct_error_cnt > 3: + if Gb.icloud_acct_error_cnt > 5: error_msg = (f"iCloud3 Error > No Location Returned for {devicename}. " "iCloud may be down or there is an Authentication issue. ") post_error_msg(Device.devicename, error_msg) return True - except Exception as err: + + except (PyiCloud2FARequiredException, PyiCloudAPIResponseException) as err: Device.icloud_acct_error_flag = True Device.icloud_devdata_useable_flag = False @@ -172,7 +174,13 @@ def request_icloud_data_update(Device): f"issue. iCloud3 will try again later. ({err})") post_error_msg(Device.devicename, error_msg) - return False #beta 4/13b16 + except Exception as err: + Device.icloud_acct_error_flag = True + Device.icloud_devdata_useable_flag = False + + # log_exception(err) + + return False #---------------------------------------------------------------------------- def update_PyiCloud_RawData_data(Device, results_msg_flag=True): @@ -269,8 +277,9 @@ def update_PyiCloud_RawData_data(Device, results_msg_flag=True): post_error_msg(error_msg) except Exception as err: + Device.icloud_acct_error_flag = True + Device.icloud_devdata_useable_flag = False # log_exception(err) - pass return False @@ -398,9 +407,9 @@ def update_device_with_latest_raw_data(Device, all_devices=False): if famshr_secs > 0 and Gb.used_data_source_FAMSHR and _Device.dev_data_source != 'FamShr': other_times += f"FamShr-{famshr_time}" - if fmf_secs > 0 and Gb.used_data_source_FMF and _Device.dev_data_source != 'FmF': - if other_times != "": other_times += ", " - other_times += f"FmF-{fmf_time}" + # if fmf_secs > 0 and Gb.used_data_source_FMF and _Device.dev_data_source != 'FmF': + # if other_times != "": other_times += ", " + # other_times += f"FmF-{fmf_time}" if _Device.mobapp_monitor_flag and _Device.dev_data_source != 'MobApp': if other_times != "": other_times += ", " @@ -482,8 +491,8 @@ def is_PyiCloud_RawData_data_useable(Device, results_msg_flag=True): event_msg = f"{data_type} {useable_msg} > " if famshr_secs > 0 and Gb.used_data_source_FAMSHR: event_msg += f"FamShr-{famshr_time}, " - if fmf_secs > 0 and Gb.used_data_source_FMF: - event_msg += f"FmF-{fmf_time}, " + # if fmf_secs > 0 and Gb.used_data_source_FMF: + # event_msg += f"FmF-{fmf_time}, " if is_useable_flag is False: event_msg += "Requesting New Location" diff --git a/custom_components/icloud3/support/pyicloud_ic3.py b/custom_components/icloud3/support/pyicloud_ic3.py index a24650d..107087b 100644 --- a/custom_components/icloud3/support/pyicloud_ic3.py +++ b/custom_components/icloud3/support/pyicloud_ic3.py @@ -35,13 +35,13 @@ CONF_IC3_DEVICENAME, CONF_FNAME, CONF_FAMSHR_DEVICENAME, CONF_FMF_EMAIL, CONF_FAMSHR_DEVICE_ID, ) -from ..helpers.common import (instr, obscure_field, list_to_str, delete_file, ) +from ..helpers.common import (instr, obscure_field, list_to_str, delete_file, encode_password, decode_password) from ..helpers.time_util import (time_now_secs, secs_to_time, timestamp_to_time_utcsecs, secs_since, format_age ) from ..helpers.messaging import (post_event, post_monitor_msg, post_startup_alert, post_internal_error, _trace, _traceha, more_info, log_info_msg, log_error_msg, log_debug_msg, log_warning_msg, log_rawdata, log_exception, log_rawdata_unfiltered) -from .config_file import (encode_password, decode_password) +#from .config_file import () from uuid import uuid1 from requests import Session, adapters @@ -146,7 +146,7 @@ def __init__(self, Service): def request(self, method, url, **kwargs): # pylint: disable=arguments-differ - # Charge logging to the right service endpoint + # callee.function and callee.lineno provice calling function and the line number callee = inspect.stack()[2] module = inspect.getmodule(callee[0]) request_logger = logging.getLogger(module.__name__).getChild("http") @@ -162,7 +162,9 @@ def request(self, method, url, **kwargs): # pylint: disable=arguments-differ if Gb.log_rawdata_flag: log_msg = (f"{secs_to_time(time_now_secs())}, {method}, {url}, {self.prefilter_rawdata(kwargs)}") - log_rawdata(f"PyiCloud_ic3 iCloud Request ({obscure_field(self.Service.username)})", {'raw': log_msg}) + log_rawdata(f"PyiCloud_ic3 iCloud Request, {self.Service.instance}, " + f"{callee.function}/{callee.lineno}", + {'raw': log_msg}) try: response = None @@ -196,11 +198,17 @@ def request(self, method, url, **kwargs): # pylint: disable=arguments-differ try: if Gb.log_rawdata_flag_unfiltered: - log_rawdata_unfiltered("PyiCloud_ic3 iCloud Response-Header (Unfiltered)", {'raw': log_msg}) - log_rawdata_unfiltered("PyiCloud_ic3 iCloud Response-Data (Unfiltered)", {'raw': data}) + log_rawdata_unfiltered(f"PyiCloud_ic3 iCloud Response-Header (Unfiltered), \ + {self.Service.instance}, {callee.function}/{callee.lineno} ", + {'raw': log_msg}) + log_rawdata_unfiltered(f"PyiCloud_ic3 iCloud Response-Data (Unfiltered), \ + {self.Service.instance}, {callee.function}/{callee.lineno}", + {'raw': data}) elif data and ('userInfo' in data is False or 'webservices' in data): - log_rawdata("PyiCloud_ic3 iCloud Response-Data", {'filter': self.prefilter_rawdata(data)}) + log_rawdata(f"PyiCloud_ic3 iCloud Response-Data, \ + {self.Service.instance}, {callee.function}/{callee.lineno} ", + {'filter': self.prefilter_rawdata(data)}) except Exception as err: # log_exception(err) pass @@ -208,8 +216,7 @@ def request(self, method, url, **kwargs): # pylint: disable=arguments-differ for header in HEADER_DATA: if response.headers.get(header): session_arg = HEADER_DATA[header] - self.Service.session_data.update( - {session_arg: response.headers.get(header)}) + self.Service.session_data.update({session_arg: response.headers.get(header)}) with open(self.Service.session_directory_filename, "w") as outfile: json.dump(self.Service.session_data, outfile) @@ -425,7 +432,7 @@ def __init__( self, apple_id, password=None, cookie_directory=None, session_directory=None, endpoint_suffix=None, verify=True, client_id=None, with_family=True, - called_from='notset', + instance='notset', verify_password=False, request_verification_code=False): @@ -442,14 +449,14 @@ def __init__( self, apple_id, password=None, self.apple_id = apple_id self.username = apple_id self.password = password - self.requires_2sa = self._check_2sa_needed - self.requires_2fa = False # This is set during the authentication function + 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.token_password = password - self.called_from = called_from + self.account_locked = False # set from the locked data item when authenticating with a token self.verify_password = verify_password - self.is_authenticated = False # ICloud access has been authenticated via password or token self.update_requested_by = '' self.endpoint_suffix = endpoint_suffix if endpoint_suffix else Gb.icloud_server_endpoint_suffix + self.instance = instance # Module that created this PyiCloud object (initial, startup, config) self.HOME_ENDPOINT = f"https://www.icloud.com" self.SETUP_ENDPOINT = f"https://setup.icloud.com/setup/ws/1" @@ -472,16 +479,19 @@ def __init__( self, apple_id, password=None, self._setup_PyiCloudSession(session_directory) self._set_step_completed('Setup') - if called_from == 'config_flow': + if instance == 'config': Gb.PyiCloudConfigFlow = self - elif called_from == 'init': + elif instance == 'initial': Gb.PyiCloudInit = self else: Gb.PyiCloudInit = self Gb.PyiCloud = self + if 'Cancel' in self.init_step_complete: + return + if 'Authenticate' in self.init_step_needed: - post_monitor_msg(f"AUTHENTICATING iCloud Account Access, {obscure_field(apple_id)} ({called_from})") + post_monitor_msg(f"AUTHENTICATING iCloud Account Access, {obscure_field(apple_id)} ({instance})") self._set_step_inprocess('Authenticate') self.authenticate() self._set_step_completed('Authenticate') @@ -489,19 +499,22 @@ def __init__( self, apple_id, password=None, # config_flow is requesting a new verification code. Do not have to load FamShr & FmF data if request_verification_code: return + if 'Cancel' in self.init_step_complete: + return if 'FamShr' in self.init_step_needed: self._set_step_inprocess('FamShr') + self.create_FamilySharing_object() self._set_step_completed('FamShr') - if 'FmF' in self.init_step_needed: - self._set_step_inprocess('FmF') - self.create_FindMyFriends_object() - self._set_step_completed('FmF') + # if 'FmF' in self.init_step_needed: + # self._set_step_inprocess('FmF') + # self.create_FindMyFriends_object() + # self._set_step_completed('FmF') if self.init_step_needed == []: - self.init_step_complete.append('Complete') + self._set_step_completed('Complete') #---------------------------------------------------------------------------- def _set_step_inprocess(self, step): @@ -538,7 +551,17 @@ def _initialize_variables(self): self.RawData_by_device_id_famshr = {} self.RawData_by_device_id_fmf = {} - self.init_step_needed = ['Setup', 'Authenticate', 'FamShr', 'FmF'] + # FamShr Device information - These is used verify the device, display on the EvLog and in the Config Flow + # device selection list on the iCloud3 Devices screen + self.device_id_by_famshr_fname = {} # Example: {'Gary-iPhone': 'n6ofM9CX4j...'} + self.famshr_fname_by_device_id = {} # Example: {'n6ofM9CX4j...': 'Gary-iPhone14'} + self.device_info_by_famshr_fname = {} # Example: {'Gary-iPhone': 'Gary-iPhone (iPhone 14 Pro; iPhone15,2)'} + self.device_model_info_by_fname = {} # {'Gary-iPhone': [raw_model, model, model_display_name]} + self.dup_famshr_fname_cnt = {} # Used to create a suffix for duplicate devicenames + # {'Gary-iPhone': ['iPhone15,2', 'iPhone', 'iPhone 14 Pro']} + + self.init_step_needed = ['Setup', 'Authenticate', 'FamShr'] + # self.init_step_needed = ['Setup', 'Authenticate', 'FamShr', 'FmF'] self.init_step_complete = [] self.init_step_inprocess = '' @@ -566,14 +589,10 @@ def authenticate(self, refresh_session=False, service=None): and 'dsid' in self.params): log_info_msg("Checking session token validity") - # try: if self._validate_token(): login_successful = True self.authenticate_method += ", Token" - # except PyiCloudAPIResponseException: - # msg = "Invalid authentication token, will log in from scratch." - # Authenticate with Service if login_successful is False and service != None: app = self.data["apps"][service] @@ -582,19 +601,16 @@ def authenticate(self, refresh_session=False, service=None): log_debug_msg( f"AUTHENTICATING iCloud Account Access, " f"{obscure_field(self.user['accountName'])}, " f"Service-{service}") - # try: + if self._authenticate_with_password_service(service): login_successful = True self.authenticate_method += (f", Password") - # except: - else: - log_debug_msg("Could not log into service. Attempting brand new login.") - # Authenticate - Sign into icloud account (POST=/signin) if login_successful is False: - info_msg = f"Authenticating account {obscure_field(self.user['accountName'])}" - if self.endpoint_suffix != '': + info_msg = f"Authenticating account {obscure_field(self.user['accountName'])} with token" + #if self.endpoint_suffix != '': + if self.endpoint_suffix: info_msg += f", iCloudServerCountrySuffix-'{self.endpoint_suffix}' " log_info_msg(info_msg) @@ -605,45 +621,23 @@ def authenticate(self, refresh_session=False, service=None): login_successful = True if login_successful is False or self.verify_password: - try: - if self._authenticate_with_password(): - login_successful = False - self.authenticate_method += ", Password" - else: - login_successful = False - msg = f"Authenticate with Password Failed/611, err={self.response_code}" - raise PyiCloudFailedLoginException(msg) - - - except PyiCloudAPIResponseException as error: - if this_fct_error_flag is False: - log_exception(error) - return - - login_successful = False - msg = f"Verify Password Failed/616, err={self.response_code}" - raise PyiCloudFailedLoginException(msg) - - except Exception as err: - if this_fct_error_flag is False: - log_exception(error) - return + self._authenticate_with_password() + self.authenticate_method += ", Password" if self._authenticate_with_token(): login_successful = True self.authenticate_method += "+Token" - if login_successful == False: - if this_fct_error_flag is False: - log_exception(error) - return - - self.authenticate_method += ", Authentication Failed" + if login_successful is False: if self.response_code == 302: - msg = f"iCloud Server Connection Error/625, err={self.response_code}" + info_msg( f"iCloud Authentication Failed > " + f"iCloud Server Connection Error, Error={self.response_code}") else: - msg = f"Authentication Failed/628, err={self.response_code}" - raise PyiCloudFailedLoginException(msg) + info_msg = (f"iCloud Authentication Failed > " + f"Username or Password is not valid, " + f"Error-{self.response_code}") + log_info_msg(info_msg) + raise PyiCloudFailedLoginException(info_msg) self.requires_2fa = self.requires_2fa or self._check_2fa_needed @@ -670,34 +664,36 @@ def _authenticate_with_token(self): f"&clientId={self.client_id[5:]}", data=json.dumps(data)) + self.data = req.json() - except PyiCloudAPIResponseException as error: - if this_fct_error_flag is False: - log_exception(error) - return + if 'dsInfo' in self.data: + self.account_locked = self.data['dsInfo'].get('locked', False) - msg = "Invalid authentication token" + if 'webservices' not in self.data: + if (self.data.get('success', False) is False + or self.data.get('error', 1) == 1): + return False + + self._webservices = self.data["webservices"] + self._update_dsid(self.data) + + log_debug_msg( f"Authenticate.authenticate_with_token > Successful") + return True + + except PyiCloudAPIResponseException as err: + log_debug_msg( f"PyiCloudAPIResponseException.authenticate_with_token > " + f"Token is not valid, " + f"error-{err}, 2fa Needed-{self.requires_2fa}") return False - # raise PyiCloudFailedLoginException(msg, error) except Exception as err: if this_fct_error_flag is False: - log_exception(error) + log_exception(err) return return False - self.data = req.json() - - if 'webservices' not in self.data: - if (self.data.get('success', False) is False - or self.data.get('error', 1) == 1): - return False - self._webservices = self.data["webservices"] - self._update_dsid(self.data) - - - return True + return False #---------------------------------------------------------------------------- def _authenticate_with_password(self): @@ -723,27 +719,26 @@ def _authenticate_with_password(self): headers["X-Apple-ID-Session-Id"] = self.session_data.get("session_id") try: - req = self.Session.post( + response = self.Session.post( f"{self.AUTH_ENDPOINT}/signin", params={"isRememberMeEnabled": "true"}, data=json.dumps(data), headers=headers,) - + data = response.json + log_debug_msg( f"Authenticate.authenticate_with_password > Successful") return True - except PyiCloudAPIResponseException as error: - if this_fct_error_flag is False: - log_exception(error) - return - - login_successful = False - msg = "Authenticate with Password Error/709" - raise PyiCloudFailedLoginException(msg) + except PyiCloudAPIResponseException as err: + log_debug_msg( f"PyiCloudAPIResponseException.authenticate_with_password > " + f"Password is not valid, " + f"Error-{err}, 2fa Needed-{self.requires_2fa}") + raise PyiCloudFailedLoginException() except Exception as err: - if this_fct_error_flag is False: - log_exception(error) - return + log_debug_msg( f"PyiCloudAPIResponseException.authenticate_with_password > " + f"Other Error, {err}") + # log_exception(err) + return False return False @@ -761,30 +756,33 @@ def _authenticate_with_password_service(self, service): } try: - log_debug_msg(f"Authenticating Service with Credentials, Service-{service}") - + log_debug_msg(f"Authenticating Service with Password, Service-{service}") self.Session.post(f"{self.SETUP_ENDPOINT}/accountLogin" f"?clientBuildNumber=2021Project52&clientMasteringNumber=2021B29" f"&clientId={self.client_id[5:]}", data=json.dumps(data)) + log_debug_msg( f"Authenticate.authenticate_with_password_service > Successful") + return self._validate_token() - self._validate_token() + # return True - return True - - except PyiCloudAPIResponseException as error: - if this_fct_error_flag is False: - log_exception(error) - return + except PyiCloudAPIResponseException as err: + log_debug_msg( f"PyiCloudAPIResponseException.authenticate_with_password_service > " + f"Password is not valid, " + f"error-{err}, 2fa Needed-{self.requires_2fa}") + return False - log_exception(error) + log_exception(err) msg = "Authenticate Request Failed744" - raise PyiCloudFailedLoginException(msg, error) + raise PyiCloudFailedLoginException(msg, err) except Exception as err: - log_exception(err) + log_debug_msg( f"PyiCloudAPIResponseException.authenticate_with_password_service > " + f"Other Error, {err}") + # log_exception(err) + return False return False #---------------------------------------------------------------------------- @@ -792,11 +790,10 @@ def _validate_token(self): '''Checks if the current access token is still valid.''' log_debug_msg("Checking session token validity") - this_fct_error_flag = True try: - req = self.Session.post("%s/validate" % self.SETUP_ENDPOINT, data="null") - self.data = req.json + response = self.Session.post("%s/validate" % self.SETUP_ENDPOINT, data="null") + self.data = response.json self.requires_2fa = self.requires_2fa or self._check_2fa_needed @@ -805,12 +802,16 @@ def _validate_token(self): return True except PyiCloudAPIResponseException as err: - if this_fct_error_flag is False: - log_exception(err) - return + log_debug_msg( f"PyiCloudAPIResponseException.validate_token > " + f"Token is not valid, " + f"Error-{err}, 2fa Needed-{self.requires_2fa}") + return False - log_debug_msg("Invalid authentication token") - raise err + except Exception as err: + log_debug_msg( f"PyiCloudAPIResponseException.validate_token > " + f"Other Error, {err}") + # log_exception(err) + return False return False @@ -822,7 +823,8 @@ def _update_dsid(self, data): if 'dsid' in data['dsInfo']: self.params["dsid"]= str(data["dsInfo"]["dsid"]) else: - # if no dsid given delete it from self.params - until returned. Otherwise is passing default incorrect dsid + # if no dsid given delete it from self.params - until returned. + # Otherwise is passing default incorrect dsid if 'dsid' in self.params: self.params.pop("dsid") @@ -847,7 +849,6 @@ def _get_auth_headers(self, overrides=None): "X-Apple-OAuth-State": self.client_id, "X-Apple-Widget-Key": "d39ba9916b7251055b22c7f910e2ea796ee65e98b2ddecea8f5dde8d9d1a815d", } - #"X-Apple-OAuth-State": "auth-" + self.client_id, if overrides: headers.update(overrides) @@ -907,12 +908,8 @@ def _setup_PyiCloudSession(self, session_directory): else: self.session_data.update({"client_id": self.client_id}) - self.Session = PyiCloudSession(self) - - #if Gb.icloud_server_endpoint_suffix in APPLE_SPECIAL_ICLOUD_SERVER_COUNTRY_CODE: - #if self.endpoint_suffix in APPLE_SPECIAL_ICLOUD_SERVER_COUNTRY_CODE: self._setup_url_endpoint_suffix() self.Session.verify = True @@ -936,7 +933,6 @@ def _setup_url_endpoint_suffix(self): if (self.endpoint_suffix and self.HOME_ENDPOINT.endswith(self.endpoint_suffix)): return - #self.endpoint_suffix = Gb.icloud_server_endpoint_suffix if self.endpoint_suffix in APPLE_SPECIAL_ICLOUD_SERVER_COUNTRY_CODE: self.endpoint_suffix = f".{self.endpoint_suffix}" post_event(f"iCloud Web Server URL Country Suffix > {self.endpoint_suffix}") @@ -977,7 +973,8 @@ def _update_token_password_file(self): token_pw = {'tokenpw': encode_password(self.token_password)} json.dump(token_pw, f, indent=4, ensure_ascii=False) - except: + except Exception as err: + log_exception(err) log_warning_msg(f"Failed to update tokenpw file {self.tokenpw_directory_filename}") #---------------------------------------------------------------------------- @@ -1080,11 +1077,11 @@ def response_code(self): def send_verification_code(self, device): '''Requests that a verification code is sent to the given device.''' data = json.dumps(device) - request = self.Session.post("%s/sendVerificationCode" % self.SETUP_ENDPOINT, + response = self.Session.post("%s/sendVerificationCode" % self.SETUP_ENDPOINT, params=self.params, data=data,) - return request.json().get("success", False) + return response.json().get("success", False) #---------------------------------------------------------------------------- def validate_2fa_code(self, code): @@ -1100,7 +1097,7 @@ def validate_2fa_code(self, code): headers["X-Apple-ID-Session-Id"] = self.session_data.get("session_id") try: - req = self.Session.post(f"{self.AUTH_ENDPOINT}/verify/trusteddevice/securitycode", + response = self.Session.post(f"{self.AUTH_ENDPOINT}/verify/trusteddevice/securitycode", data=json.dumps(data), headers=headers,) @@ -1116,7 +1113,7 @@ def validate_2fa_code(self, code): return False try: - data = req.json() + data = response.json() except ValueError: data = {} @@ -1165,7 +1162,6 @@ def _get_webservice_url(self, ws_key): try: if self._webservices.get(ws_key) is None: return None - # raise PyiCloudServiceNotActivatedException("Webservice not available", ws_key) return self._webservices[ws_key]["url"] except: @@ -1232,6 +1228,7 @@ def create_FindMyFriends_object(self): # self._get_webservice_url("contacts"), # self._get_webservice_url("cksharews"), + except Exception as err: log_exception(err) @@ -1248,7 +1245,7 @@ def play_sound(self, device_id, subject="Find My iPhone Alert"): #---------------------------------------------------------------------------- def __repr__(self): try: - return (f"") + return (f"") except: return (f"") @@ -1275,23 +1272,16 @@ def __init__(self, PyiCloud, sounds=False, number="", newpasscode=""): - self.Session = Session - self.PyiCloud = PyiCloud - self.params = params - self.with_family = with_family - self.task = task - self.device_id = device_id + self.Session = Session + self.PyiCloud = PyiCloud + self.instance = PyiCloud.instance + self.params = params + self.with_family = with_family + self.task = task + self.device_id = device_id + self.devices_data = {} - # FamShr Device information - These is used verify the device, display on the EvLog and in the Config Flow - # device selection list on the iCloud3 Devices screen - self.devices_not_set_up = [] - self.device_id_by_famshr_fname = {} # Example: {'Gary-iPhone': 'n6ofM9CX4j...'} - self.famshr_fname_by_device_id = {} # Example: {'n6ofM9CX4j...': 'Gary-iPhone14'} - self.device_info_by_famshr_fname = {} # Example: {'Gary-iPhone': 'Gary-iPhone (iPhone 14 Pro (iPhone15,2)'} - self.device_model_info_by_fname = {} # {'Gary-iPhone': [raw_model,model,model_display_name]} - self.dup_famshr_fname_cnt = {} # Used to create a suffix for duplicate devicenames - # {'Gary-iPhone': ['iPhone15,2', 'iPhone', 'iPhone 14 Pro']} - self.devices_without_location_data = [] + Gb.devices_without_location_data = [] try: self.is_service_available = True @@ -1300,14 +1290,10 @@ def __init__(self, PyiCloud, except Exception as err: log_exception(err) - # if Gb.conf_data_source_FAMSHR is False: - # self._set_service_available(False) - # return - if self.is_service_not_available: log_msg = ( f"{EVLOG_ALERT}iCLOUD ALERT > Family Sharing Data Source is not available. " f"The web url providing location data returned a Service Not Available error " - f"({self.PyiCloud.called_from})") + f"({self.PyiCloud.instance})") post_event(log_msg) return @@ -1341,7 +1327,7 @@ 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 FamShr but then start_ic3/Stage 4 # thinks it is not done and resets everything. - if self.device_id_by_famshr_fname != {}: + if self.PyiCloud.device_id_by_famshr_fname != {}: return except: pass @@ -1350,11 +1336,11 @@ def __init__(self, PyiCloud, if self.is_service_not_available: return - self.devices_not_set_up = self._conf_famshr_devices_not_set_up() - if self.devices_not_set_up == []: + Gb.devices_not_set_up = self._conf_famshr_devices_not_set_up() + if Gb.devices_not_set_up == []: return - if self.PyiCloud.called_from == 'init': + if self.PyiCloud.instance == 'initial': self.PyiCloud.init_step_needed.append('FamShr') return @@ -1374,7 +1360,7 @@ def _conf_famshr_devices_not_set_up(self): return [conf_device[CONF_IC3_DEVICENAME] for conf_device in Gb.conf_devices if (conf_device[CONF_FAMSHR_DEVICENAME] != NONE_FNAME - and conf_device[CONF_FAMSHR_DEVICENAME] not in self.device_id_by_famshr_fname)] + and conf_device[CONF_FAMSHR_DEVICENAME] not in self.PyiCloud.device_id_by_famshr_fname)] #---------------------------------------------------------------------------- @property @@ -1385,6 +1371,16 @@ def timestamp_field(self): def data_source(self): return FAMSHR_FNAME + @property + def devices_cnt(self): + # Simulate no devices returned for the first 4 tries + # if Gb.get_FAMSHR_devices_retry_cnt < 4: + # return -1 + if 'content' in self.devices_data: + return len(self.devices_data.get('content', {})) + else: + return -1 + #---------------------------------------------------------------------------- def refresh_client(self, requested_by_devicename=None, _device_id=None, _with_family=None, refreshing_poor_loc_flag=False): @@ -1397,7 +1393,8 @@ def refresh_client(self, requested_by_devicename=None, _device_id=None, selected_device = _device_id if _device_id else "all" fmly_param = _with_family if _with_family is not None else self.with_family - response = self.Session.post( + try: + devices_data = self.Session.post( self._fmip_refresh_url, params=self.params, data=json.dumps({"clientContext": @@ -1406,23 +1403,22 @@ def refresh_client(self, requested_by_devicename=None, _device_id=None, "selectedDevice": selected_device, "deviceListVersion": 1, }}),) - try: - self.response = response.json() + self.devices_data = devices_data.json() except Exception as err: - self.response = {} + self.devices_data = {} log_debug_msg("No data returned from FamShr refresh request") if self.Session.response_status_code == 501: self._set_service_available(False) log_msg = ( f"{EVLOG_ALERT}iCLOUD ALERT > Family Sharing Data Source is not available. " f"The web url providing location data returned a Service Not Available error " - f"({self.PyiCloud.called_from})") + f"({self.PyiCloud.instance})") post_event(log_msg) return None Gb.pyicloud_refresh_time[FAMSHR] = time_now_secs() - self.update_device_location_data(requested_by_devicename, self.response.get("content", {})) + self.update_device_location_data(requested_by_devicename, self.devices_data.get("content", {})) #---------------------------------------------------------------------------- def update_device_location_data(self, requested_by_devicename=None, devices_data=None): @@ -1454,8 +1450,8 @@ def update_device_location_data(self, requested_by_devicename=None, devices_data elif (LOCATION not in device_data or device_data[LOCATION] == {} or device_data[LOCATION] is None): - if device_id not in self.devices_without_location_data: - self.devices_without_location_data.append(device_id) + if device_id not in Gb.devices_without_location_data: + Gb.devices_without_location_data.append(device_id) if device_data[ICLOUD_DEVICE_STATUS] == 203: monitor_msg += f"{CRLF_STAR}OFFLINE > " else: @@ -1464,12 +1460,9 @@ def update_device_location_data(self, requested_by_devicename=None, devices_data f"{device_data['modelDisplayName']} " f"({device_data['rawDeviceModel']})") - log_rawdata(f"FamShr Device - Offline/No Location Data - " + log_rawdata(f"FamShr Device - Offline/No Location Data, {self.instance} " f"<{device_data_name}>", device_data) - if device_data_name not in Gb.conf_famshr_devicenames: - continue - if device_id not in self.PyiCloud.RawData_by_device_id: # if device_data_name == 'Gary-iPad': # self._create_test_data(device_id, device_data_name, device_data) @@ -1479,12 +1472,16 @@ def update_device_location_data(self, requested_by_devicename=None, devices_data continue # Non-tracked devices are not updated - if (_Device := Gb.Devices_by_icloud_device_id.get(device_id)) is None: + if device_data_name not in Gb.devicenames_x_famshr_devices: continue _RawData = self.PyiCloud.RawData_by_device_id[device_id] _RawData.save_new_device_data(device_data) + devicename = Gb.devicenames_x_famshr_devices[device_data_name] + _Device = Gb.Devices_by_devicename[devicename] + Gb.Devices_by_icloud_device_id[device_id] = _Device + if _RawData.location_secs ==0: continue elif _RawData.last_loc_time_gps == _RawData.loc_time_gps: @@ -1515,8 +1512,8 @@ def update_device_location_data(self, requested_by_devicename=None, devices_data elif _RawData.location_secs > 0: post_event(_Device.devicename, event_msg) - log_rawdata(f"FamShr Data - <{device_data_name}/{_Device.devicename}>", - _RawData.device_data) + log_rawdata(f"FamShr Data, {self.instance} - " + f"<{device_data_name}/{_Device.devicename}>", _RawData.device_data) except Exception as err: log_exception(err) @@ -1564,13 +1561,7 @@ def _create_RawData_famshr_object(self, device_id, device_data_name, device_data message_url=self._fmip_message_url,) - self.PyiCloud.RawData_by_device_id[device_id] = _RawData - self.PyiCloud.RawData_by_device_id_famshr[device_id] = _RawData - - self.device_id_by_famshr_fname[_RawData.fname] = device_id - self.famshr_fname_by_device_id[device_id] = _RawData.fname - self.device_info_by_famshr_fname[_RawData.fname] = _RawData.famshr_device_info - self.device_model_info_by_fname[_RawData.fname] = _RawData.famshr_device_model_info + self.set_famshr_rawdata_fields(device_id, _RawData) log_rawdata(f"FamShr Data - <{_RawData.fname}>", _RawData.device_data) @@ -1579,6 +1570,21 @@ def _create_RawData_famshr_object(self, device_id, device_data_name, device_data return (f"{CRLF_DOT}ADDED > {device_data_name}{dup_msg}, " f"{_RawData.loc_time_gps}") +#---------------------------------------------------------------------- + def set_famshr_rawdata_fields(self, device_id, _RawData): + ''' + The FamShr dictionaries contain info about the devices that is set + up when the RawData object for the device is created. If the FamShr + object is recreated during error, the device's RawData object already + exists and is not recreated. The FamShr dictionaries need to be + set up again. ''' + self.PyiCloud.RawData_by_device_id[device_id] = _RawData + self.PyiCloud.RawData_by_device_id_famshr[device_id] = _RawData + self.PyiCloud.device_id_by_famshr_fname[_RawData.fname] = device_id + self.PyiCloud.famshr_fname_by_device_id[device_id] = _RawData.fname + self.PyiCloud.device_info_by_famshr_fname[_RawData.fname] = _RawData.famshr_device_info + self.PyiCloud.device_model_info_by_fname[_RawData.fname] = _RawData.famshr_device_model_info + #---------------------------------------------------------------------- @staticmethod def _remove_special_chars(name): @@ -1647,7 +1653,7 @@ def lost_device(self, device_id, number, message="This iPhone has been lost. Ple #---------------------------------------------------------------------------- def __repr__(self): try: - return (f"") + return (f"") except: return (f"") @@ -1686,7 +1692,7 @@ def contacts__init__(self, PyiCloud, self.fmf_email_by_device_id = {} self.device_info_by_fmf_email = {} self.device_form_icloud_fmf_list = [] - self.devices_without_location_data = [] + Gb.devices_without_location_data = [] self._service_root = service_root self._contacts_endpoint = "%s/co" % self._service_root @@ -1748,7 +1754,7 @@ def __init__(self, PyiCloud, self.fmf_email_by_device_id = {} self.device_info_by_fmf_email = {} self.device_form_icloud_fmf_list = [] - self.devices_without_location_data = [] + Gb.devices_without_location_data = [] # self.is_service_available = True # self.is_service_not_available = False @@ -1763,7 +1769,7 @@ def __init__(self, PyiCloud, if self.is_service_not_available: # log_msg = ( f"{EVLOG_ALERT}iCLOUD ALERT > Find-my-Friends Data Source is not available. " # f"The web url providing location data returned a Service Not Available error " - # f"({self.PyiCloud.called_from})") + # f"({self.PyiCloud.instance})") # post_event(log_msg) return @@ -1774,7 +1780,7 @@ def __init__(self, PyiCloud, if self.is_service_not_available: log_msg = ( f"{EVLOG_ALERT}iCLOUD ALERT > Find-my-Friends Data Source is not available. " f"The web url providing location data returned a Service Not Available error " - f"({self.PyiCloud.called_from})") + f"({self.PyiCloud.instance})") post_event(log_msg) return @@ -1790,7 +1796,7 @@ def __init__(self, PyiCloud, f"{devices_not_set_up}") post_event(log_msg) - if self.PyiCloud.called_from == 'init': + if self.PyiCloud.instance == 'initial': self.PyiCloud.init_step_needed.append('FmF') return @@ -1890,7 +1896,7 @@ def refresh_client(self, requested_by_devicename=None, refreshing_poor_loc_flag= device_data_name = '' # Device was already set up or rejected - if device_id in self.devices_without_location_data: + if device_id in Gb.devices_without_location_data: continue # Update PyiCloud_RawData with data just received for tracked devices @@ -1924,10 +1930,6 @@ def refresh_client(self, requested_by_devicename=None, refreshing_poor_loc_flag= f"{requested_by_flag}") post_monitor_msg(monitor_msg) - # if Gb.EvLog: - # post_monitor_msg(monitor_msg) - # else: - # log_debug_msg(monitor_msg) return self.response @@ -2068,10 +2070,6 @@ def my_prefs(self): '''Returns a list of your own preferences details''' return self.response.get("myPrefs") - @property - def device_identifier(self): - return (f"{self.response.get('firstName', '')} {self.response.get('lastName', '')}").strip() - def __repr__(self): try: return (f"") @@ -2114,7 +2112,8 @@ def __init__(self, device_id, self.params = params self.data_source = data_source self.timestamp_field = timestamp_field - self.FamShr_FmF = FamShr_FmF + self.PyiCloud = FamShr_FmF.PyiCloud # PyiCloud object (iCloud Acct) with the device data + self.FamShr_FmF = FamShr_FmF # FamShr object or FmF object creating this RawData object self.name = device_data_name self.fname = self.device_data_fname_dup_check # Clean up fname and check for duplicates self.fname_dup_suffix= '' # Suffix added to fname if duplicates @@ -2147,32 +2146,6 @@ def __init__(self, device_id, def device_id8(self): return self.device_id[:8] -#---------------------------------------------------------------------- - @property - def device_identifier(self): - ''' - Format device name: - - iPhone 14,2 (iPhone15,2) - - Gary-iPhone - ''' - if self.is_data_source_FAMSHR: - display_name = self.device_data['deviceDisplayName'].split(' (')[0] - display_name = display_name.replace('Series ', '') - if self.device_data.get('rawDeviceModel').startswith(AIRPODS_FNAME): - device_class = AIRPODS_FNAME - else: - device_class = self.device_data.get('deviceClass', '') - raw_model = self.device_data.get('rawDeviceModel', device_class).replace('_', '') - - return (f"{display_name} ({raw_model}").replace("’", "'") - - elif self.is_data_source_FMF: - full_name = (f"{self.device_data.get('firstName', '')} {self.device_data.get('lastName', '')}").strip() - return full_name.replace("’", "'") - - else: - return self.name.replace("’", "'") - #---------------------------------------------------------------------- @property def devicename(self): @@ -2199,13 +2172,12 @@ def device_data_fname_dup_check(self): _FamShr = self.FamShr_FmF - if fname not in _FamShr.dup_famshr_fname_cnt: - _FamShr.dup_famshr_fname_cnt[fname] = 1 + if fname not in self.PyiCloud.dup_famshr_fname_cnt: + self.PyiCloud.dup_famshr_fname_cnt[fname] = 1 else: - _FamShr.dup_famshr_fname_cnt[fname] += 1 - self.fname_dup_suffix = f"({_FamShr.dup_famshr_fname_cnt[fname]}0" + self.PyiCloud.dup_famshr_fname_cnt[fname] += 1 + self.fname_dup_suffix = f"({self.PyiCloud.dup_famshr_fname_cnt[fname]})" return f"{fname}{self.fname_dup_suffix}" - return fname #---------------------------------------------------------------------- @@ -2214,31 +2186,58 @@ def _remove_special_chars(name): name = name.replace("’", "'") name = name.replace(u'\xa0', ' ') name = name.replace(u'\2019', "'") - return name + #---------------------------------------------------------------------- @property def famshr_device_info(self): - info = f"{self.fname} ({self.device_identifier})" + return f"{self.fname} ({self.device_identifier})" - return info +#---------------------------------------------------------------------- + @property + def device_identifier(self): + ''' + Format device name: + - iPhone 14,2; iPhone15,2) + - Gary-iPhone + ''' + if self.is_data_source_FAMSHR: + display_name = self.device_data['deviceDisplayName'].split(' (')[0] + display_name = display_name.replace('Series ', '') + if self.device_data.get('rawDeviceModel').startswith(AIRPODS_FNAME): + device_class = AIRPODS_FNAME + else: + device_class = self.device_data.get('deviceClass', '') + raw_model = self.device_data.get('rawDeviceModel', device_class).replace('_', '') + + return (f"{display_name}; {raw_model}").replace("’", "'") + + elif self.is_data_source_FMF: + full_name = (f"{self.device_data.get('firstName', '')} {self.device_data.get('lastName', '')}").strip() + return full_name.replace("’", "'") + + else: + return self.name.replace("’", "'") + +#---------------------------------------------------------------------- + # @property + # def device_identifier(self): + # return (f"{self.response.get('firstName', '')} " + # f"{self.response.get('lastName', '')}").strip() #---------------------------------------------------------------------- @property def famshr_device_display_name(self): display_name = self.device_data['deviceDisplayName'].split(' (')[0] display_name = display_name.replace('Series ', '') - return display_name #---------------------------------------------------------------------- @property def famshr_device_model_info(self): - model_info = [ self.device_data['rawDeviceModel'].replace("_", ""), # iPhone15,2 - self.device_data['modelDisplayName'], # iPhone - self.famshr_device_display_name] # iPhone 14 Pro - - return model_info + return [self.device_data['rawDeviceModel'].replace("_", ""), # iPhone15,2 + self.device_data['modelDisplayName'], # iPhone + self.famshr_device_display_name] # iPhone 14 Pro #---------------------------------------------------------------------- @property diff --git a/custom_components/icloud3/support/pyicloud_ic3_interface.py b/custom_components/icloud3/support/pyicloud_ic3_interface.py index 9366c48..9b81ec4 100644 --- a/custom_components/icloud3/support/pyicloud_ic3_interface.py +++ b/custom_components/icloud3/support/pyicloud_ic3_interface.py @@ -12,7 +12,7 @@ from ..support.pyicloud_ic3 import (PyiCloudService, PyiCloudFailedLoginException, PyiCloudNoDevicesException, PyiCloudAPIResponseException, PyiCloud2FARequiredException,) -from ..helpers.common import (instr, list_to_str, delete_file, ) +from ..helpers.common import (instr, list_to_str, list_add, list_del, delete_file, ) from ..helpers.messaging import (post_event, post_error_msg, post_monitor_msg, post_startup_alert, log_debug_msg, log_info_msg, log_exception, log_error_msg, internal_error_msg2, _trace, _traceha, ) from ..helpers.time_util import (time_now_secs, secs_to_time, format_age, @@ -34,10 +34,10 @@ def create_PyiCloudService_executor_job(): ''' This is the entry point for the hass.async_add_executor_job statement from __init__ ''' - create_PyiCloudService(Gb.PyiCloudInit, called_from='init') + create_PyiCloudService(Gb.PyiCloudInit, instance='initial') #-------------------------------------------------------------------- -def create_PyiCloudService(PyiCloud, called_from='unknown'): +def create_PyiCloudService(PyiCloud, instance='unknown'): #See if pyicloud_ic3 is available Gb.pyicloud_authentication_cnt = 0 @@ -47,18 +47,19 @@ def create_PyiCloudService(PyiCloud, called_from='unknown'): if Gb.username == '' or Gb.password == '': return - authenticate_icloud_account(PyiCloud, called_from=called_from, initial_setup=True) - - if Gb.PyiCloud or Gb.PyiCloudInit: - event_msg =(f"iCloud Location Services interface > Verified ({called_from})") - post_event(event_msg) + if authenticate_icloud_account(PyiCloud, instance=instance, initial_setup=True): + if ((Gb.PyiCloud and Gb.PyiCloud.is_authenticated) + or (Gb.PyiCloudInit and Gb.PyiCloudInit.is_authenticated)): + event_msg =(f"iCloud Location Service interface > Verified ({instance})") + post_event(event_msg) + if Gb.PyiCloud: + log_debug_msg(f"PyiCloud Instance Verified > {Gb.PyiCloud.instance}") + if Gb.PyiCloudInit: + log_debug_msg(f"PyiCloudInit Instance Verified > {Gb.PyiCloudInit.instance}") else: - event_msg =(f"iCLOUD3 ERROR > Apple ID Verification is needed or " - f"another error occurred. The MobApp tracking method will be " - f"used until the Apple ID Verification code has been entered. See the " - f"HA Notification area to continue. iCloud3 will then restart.") - post_error_msg(event_msg) + event_msg =(f"iCloud Location Service interface > Not Verified ({instance})") + post_event(event_msg) #-------------------------------------------------------------------- def verify_pyicloud_setup_status(): @@ -81,57 +82,94 @@ def verify_pyicloud_setup_status(): the PyiCloud session data requests must be run in the event loop. ''' + # iCloud was never started + if Gb.PyiCloudInit is None: + return False + # The verify can be requested during started or after a restart request before # the restart has begun - if (Gb.PyiCloudInit - and Gb.restart_icloud3_request_flag + if (Gb.restart_icloud3_request_flag and Gb.start_icloud3_inprocess_flag): - Gb.PyiCloudInit.init_step_needed = ['FamShr', 'FmF'] + Gb.PyiCloudInit.init_step_needed = ['FamShr'] Gb.PyiCloudInit.init_step_complete = ['Setup', 'Authenticate'] - init_step_needed = list_to_str(Gb.PyiCloudInit.init_step_needed) - init_step_complete = list_to_str(Gb.PyiCloudInit.init_step_complete) - - # PyiCloud is started early in __init__ and set up is complete - event_msg = f"iCloud Location Svcs Interface > Started during initialization" - if Gb.PyiCloudInit and 'Complete' in Gb.PyiCloudInit.init_step_complete: + if 'Complete' in Gb.PyiCloudInit.init_step_complete: Gb.PyiCloud = Gb.PyiCloudInit - event_msg += f"{CRLF_DOT}All steps completed" - # Authenticate is completed, continue with setup of FamShr and FmF objects - elif Gb.PyiCloudInit and 'Authenticate' in Gb.PyiCloudInit.init_step_complete: - Gb.PyiCloud = Gb.PyiCloudInit + log_debug_msg(f"PyiCloud Instance Selected > {Gb.PyiCloud.instance}") + if _get_famshr_devices(Gb.PyiCloud): + return True - event_msg += ( f"{CRLF_DOT}Completed: {init_step_complete}" - f"{CRLF_DOT}Inprocess: {Gb.PyiCloudInit.init_step_inprocess}" - f"{CRLF_DOT}Needed: {init_step_needed}") + if Gb.get_FAMSHR_devices_retry_cnt == 0: + event_msg = f"iCloud Location Svcs > Setup Complete" + post_event(event_msg) + return + else: + if Gb.PyiCloud.FamilySharing: + Gb.PyiCloud.FamilySharing.refresh_client() + event_msg = f"iCloud Location Svcs > Refreshing FamShr Data (#{Gb.get_FAMSHR_devices_retry_cnt})" + post_event(event_msg) + return True + else: + Gb.PyiCloudInit.init_step_needed = ['FamShr'] + + if 'Authenticate' not in Gb.PyiCloudInit.init_step_complete: + Gb.PyiCloudInit._set_step_completed('Cancel') + create_PyiCloudService(Gb.PyiCloud, instance='startup') + + Gb.PyiCloud = Gb.PyiCloud or Gb.PyiCloudInit + if 'Authenticate' not in Gb.PyiCloud.init_step_complete: + create_PyiCloudService(Gb.PyiCloud, instance='startup') + Gb.PyiCloud = Gb.PyiCloud or Gb.PyiCloudInit + if Gb.PyiCloud is None: + return False - Gb.PyiCloud.__init__(Gb.username, Gb.password, - cookie_directory=Gb.icloud_cookies_dir, - session_directory=(f"{Gb.icloud_cookies_dir}/session"), - called_from='stage4') + log_debug_msg(f"PyiCloud Instance Created > {Gb.PyiCloud.instance}") - else: - if Gb.PyiCloudInit: - # __init__ set up was not authenticated, start all over - event_msg += ( f"{CRLF_DOT}Completed: {init_step_complete}" - f"{CRLF_DOT}Inprocess: {Gb.PyiCloudInit.init_step_inprocess}" - f"{CRLF_DOT}Needed: {init_step_needed}") + # FamShare object exists, check/refresh the devices list + Gb.PyiCloud = Gb.PyiCloud or Gb.PyiCloudInit + if 'FamShr' in Gb.PyiCloud.init_step_complete: + if _get_famshr_devices(Gb.PyiCloud): + return True - post_event(event_msg) + # Create FamShare object and then check/refresh the devices list + Gb.PyiCloud.create_FamilySharing_object() + Gb.PyiCloud.init_step_needed == [] + Gb.PyiCloud._set_step_completed('FamShr') + Gb.PyiCloud._set_step_completed('Complete') + if _get_famshr_devices(Gb.PyiCloud): + return True - create_PyiCloudService(Gb.PyiCloud, called_from='stage4') + return False + +#-------------------------------------------------------------------- +def _get_famshr_devices(PyiCloud): + if PyiCloud is None: + return False + if PyiCloud.FamilySharing.devices_cnt >= 0: + return True - post_event(event_msg) + Gb.get_FAMSHR_devices_retry_cnt = 0 + while Gb.get_FAMSHR_devices_retry_cnt < 8: + Gb.get_FAMSHR_devices_retry_cnt += 1 + if Gb.get_FAMSHR_devices_retry_cnt > 1: + post_event( f"Family Sharing List Refresh " + f"(#{Gb.get_FAMSHR_devices_retry_cnt} of 8)") + + PyiCloud.FamilySharing.refresh_client() + if PyiCloud.FamilySharing.devices_cnt >= 0: + return True + + return (PyiCloud.FamilySharing.devices_cnt >= 0) #-------------------------------------------------------------------- -def authenticate_icloud_account(PyiCloud, called_from='unknown', initial_setup=False): +def authenticate_icloud_account(PyiCloud, instance='unknown', initial_setup=False): ''' Authenticate the iCloud Account via pyicloud Arguments: - PyiCloud - Gb.PyiCloud or Gb.PyiCloudInit object depending on called_from module - called_from - Called from module (init or start_ic3) + PyiCloud - Gb.PyiCloud or Gb.PyiCloudInit object depending on instance module + instance - Called from module (init or start_ic3) If successful - Gb.PyiCloud or Gb.PyiCloudInit = PyiCloudService object If not - Gb.PyiCloud or Gb.PyiCloudInit = None @@ -154,14 +192,18 @@ def authenticate_icloud_account(PyiCloud, called_from='unknown', initial_setup=F PyiCloud.__init__(Gb.username, Gb.password, cookie_directory=Gb.icloud_cookies_dir, session_directory=(f"{Gb.icloud_cookies_dir}/session"), - called_from=called_from) + instance=instance) + log_debug_msg(f"PyiCloud Instance Initialized > {PyiCloud.instance}") else: - log_info_msg(f"Connecting to and Authenticating iCloud Location Services Interface ({called_from})") + log_info_msg(f"Connecting to and Authenticating iCloud Location Service Interface ({instance})") PyiCloud = PyiCloudService(Gb.username, Gb.password, cookie_directory=Gb.icloud_cookies_dir, session_directory=(f"{Gb.icloud_cookies_dir}/session"), - called_from=called_from) + instance=instance) + + #PyiCloud.instance = instance #f"{instance}-{str(id(PyiCloud))[-5:]}" + log_debug_msg(f"PyiCloud Instance Created > {PyiCloud.instance}") is_authentication_2fa_code_needed(PyiCloud, initial_setup=True) display_authentication_msg(PyiCloud) @@ -176,16 +218,13 @@ def authenticate_icloud_account(PyiCloud, called_from='unknown', initial_setup=F except PyiCloudFailedLoginException as err: event_msg =(f"{EVLOG_ALERT}iCloud3 Error > An error occurred logging into the iCloud Account. " - f"Authentication Process, Error-({Gb.PyiCloud.authenticate_method[2:]})") + f"{err})") + # f"Authentication Process, Error-({Gb.PyiCloud.authenticate_method[2:]})") post_error_msg(event_msg) - post_startup_alert('iCloud Account Loggin Error') + post_startup_alert('iCloud Account Login Error') - if Gb.PyiCloud.authenticate_method in ['', 'Invalid username/password']: - Gb.PyiCloud = PyiCloud = None - Gb.username = Gb.password = '' - return False - - check_all_devices_online_status() + Gb.PyiCloud = Gb.PyiCloudInit = PyiCloud = None + Gb.username = Gb.password = '' return False except PyiCloud2FARequiredException as err: @@ -194,13 +233,13 @@ def authenticate_icloud_account(PyiCloud, called_from='unknown', initial_setup=F except Exception as err: if this_fct_error_flag is False: - log_exception(err) - return + log_exception(err) + return event_msg =(f"{EVLOG_ALERT}iCloud3 Error > An error occurred logging into the iCloud Account. " f"Error-{err}") post_error_msg(event_msg) - # log_exception(err) + log_exception(err) return False return True @@ -219,9 +258,6 @@ def display_authentication_msg(PyiCloud): return last_authenticated_time = Gb.authenticated_time - # last_authenticated_time = last_authenticated_age = Gb.authenticated_time - # if last_authenticated_time > 0: - # last_authenticated_age = time_now_secs() - last_authenticated_time Gb.authenticated_time = time_now_secs() Gb.pyicloud_authentication_cnt += 1 @@ -248,7 +284,6 @@ def is_authentication_2fa_code_needed(PyiCloud, initial_setup=False): return False elif PyiCloud.requires_2fa: pass - # elif Gb.data_source_MOBAPP is False: (beta 17) elif Gb.conf_data_source_MOBAPP is False: return False elif initial_setup: @@ -369,7 +404,7 @@ def pyicloud_reset_session(PyiCloud=None): cookie_directory=Gb.icloud_cookies_dir, session_directory=(f"{Gb.icloud_cookies_dir}/session"), with_family=True, - called_from='reset') + instance='reset') # Initialize PyiCloud object to force a new one that will trigger the 2fa process PyiCloud = None @@ -406,7 +441,7 @@ def delete_pyicloud_cookies_session_files(cookie_filename=None): #-------------------------------------------------------------------- def create_PyiCloudService_secondary(username, password, - endpoint_suffix, called_from, + endpoint_suffix, instance, verify_password, request_verification_code=False): ''' Create the PyiCloudService object without going through the error checking and @@ -417,10 +452,13 @@ def create_PyiCloudService_secondary(username, password, cookie_directory=Gb.icloud_cookies_dir, session_directory=(f"{Gb.icloud_cookies_dir}/session"), endpoint_suffix=endpoint_suffix, - called_from=called_from, + instance=instance, verify_password=verify_password, request_verification_code=request_verification_code) + #PyiCloud.instance = instance #f"{instance}-{str(id(PyiCloud))[-5:]}" + + log_debug_msg(f"PyiCloud Instance Created > {PyiCloud.instance}") return PyiCloud def create_FamilySharing_secondary(PyiCloud, config_flow_login): diff --git a/custom_components/icloud3/support/start_ic3.py b/custom_components/icloud3/support/start_ic3.py index 03b7139..2a07531 100644 --- a/custom_components/icloud3/support/start_ic3.py +++ b/custom_components/icloud3/support/start_ic3.py @@ -62,6 +62,7 @@ from ..helpers import entity_io from ..support import mobapp_interface from ..support import mobapp_data_handler +from ..support import pyicloud_ic3_interface from ..support import service_handler from ..support import zone_handler from ..support import stationary_zone as statzone @@ -138,9 +139,8 @@ def process_config_flow_parameter_updates(): config_flow_updated_parms = Gb.config_flow_updated_parms Gb.config_flow_updated_parms = {''} - event_msg =(f"Configuration Loading > " + post_event( f"Configuration Loading > " f"Type-{list_to_str(config_flow_updated_parms).title()}") - post_event(event_msg) if 'restart' in config_flow_updated_parms: initialize_icloud_data_source() @@ -520,7 +520,7 @@ def initialize_icloud_data_source(): Gb.devices = Gb.conf_devices Gb.icloud_force_update_flag = False - Gb.stage_4_no_devices_found_cnt = 0 + Gb.get_FAMSHR_devices_retry_cnt = 0 def icloud_server_endpoint_suffix(endpoint_suffix): ''' @@ -606,9 +606,8 @@ def check_mobile_app_integration(ha_started_check=None): if Gb.mobile_app_device_fnames: ha_started_check = True - event_msg =(f"Checking Mobile App Integration > Loaded, " + post_event( f"Checking Mobile App Integration > Loaded, " f"Devices-{list_to_str(Gb.mobile_app_device_fnames)}") - post_event(event_msg) Gb.debug_log['Gb.mobile_app_device_fnames'] = Gb.mobile_app_device_fnames Gb.debug_log['Gb.mobapp_fnames_x_mobapp_id'] = Gb.mobapp_fnames_x_mobapp_id @@ -621,9 +620,8 @@ def check_mobile_app_integration(ha_started_check=None): # not loaded, it is not available and the Mobile App data source is not available. # Display an error message since there are devices that use the Mobile App. if ha_started_check is None: - event_msg =(f"Checking Mobile App Integration > Not Loaded. " + post_event( f"Checking Mobile App Integration > Not Loaded. " f"Will check again when HA is started") - post_event(event_msg) return # Mobile App Integration not loaded @@ -636,7 +634,9 @@ def check_mobile_app_integration(ha_started_check=None): if conf_device[CONF_MOBILE_APP_DEVICE] == 'None': continue Device = Gb.Devices_by_devicename[conf_device[CONF_IC3_DEVICENAME]] - if Device.conf_mobapp_fname not in Gb.mobile_app_device_fnames: + if Device.conf_mobapp_fname in Gb.mobile_app_device_fnames: + Device.mobapp_monitor_flag = True + else: Device.mobapp_monitor_flag = False mobile_app_error_msg +=(f"{CRLF_DOT}{conf_device[CONF_MOBILE_APP_DEVICE]}" f"{RARROW}Assigned to {Device.fname_devicename}") @@ -860,11 +860,10 @@ def check_ic3_event_log_file_version(): if ic3_version_text == 'Not Installed': Gb.version_evlog = f' Not Found: {ic3_evlog_js_filename_msg}' - event_msg =(f"iCloud3 Event Log > " + post_event( f"iCloud3 Event Log > " f"{CRLF_DOT}Current Version Installed-v{www_version_text}" f"{CRLF_DOT}WARNING: SOURCE FILE NOT FOUND" f"{CRLF_DOT}...{ic3_evlog_js_filename_msg}") - post_event(event_msg) else: Gb.version_evlog = ic3_version_text @@ -891,10 +890,9 @@ def check_ic3_event_log_file_version(): current_version_installed_flag = False if current_version_installed_flag: - event_msg =(f"iCloud3 Event Log > " + post_event( f"iCloud3 Event Log > " f"{CRLF_DOT}Current Version Installed-v{www_version_text}" f"{CRLF_DOT}File-{format_filename(www_evlog_js_filename_msg)}") - post_event(event_msg) if Gb.evlog_version != www_version_text: Gb.evlog_version = Gb.conf_profile['event_log_version'] = www_version_text @@ -910,7 +908,7 @@ def check_ic3_event_log_file_version(): config_file.write_storage_icloud3_configuration_file() post_startup_alert('Event Log was updated. Browser refresh needed') - event_msg =(f"{EVLOG_ALERT}" + post_event( f"{EVLOG_ALERT}" f"BROWSER REFRESH NEEDED > iCloud3 Event Log was updated to v{ic3_version_text}" f"{more_info('refresh_browser')}" f"{CRLF}{'-'*75}" @@ -918,7 +916,6 @@ def check_ic3_event_log_file_version(): f"{CRLF_DOT}New Version - v{ic3_version_text}" f"{CRLF_DOT}Copied From - {format_filename(ic3_evlog_js_directory_msg)}/" f"{CRLF_DOT}Copied To.... - {format_filename(www_evlog_js_directory_msg)}/") - post_event(event_msg) Gb.info_notification = (f"Event Log Card updated to v{ic3_version_text}. " "See Event Log for more info.") @@ -1114,9 +1111,8 @@ def create_Zones_object(): post_event(f"{log_msg}{zone_msg}") if Gb.is_track_from_base_zone_used and Gb.track_from_base_zone != HOME: - event_msg = ( f"Primary 'Home' Zone > {zone_dname(Gb.track_from_base_zone)} " - f"{circle_letter(Gb.track_from_base_zone)}") - post_event(event_msg) + post_event( f"Primary 'Home' Zone > {zone_dname(Gb.track_from_base_zone)} " + f"{circle_letter(Gb.track_from_base_zone)}") event_msg = "Special Zone Setup >" if Gb.is_passthru_zone_used: @@ -1133,7 +1129,6 @@ def create_Zones_object(): f"MinDistFromAnotherZone-{format_dist_km(Gb.statzone_min_dist_from_zone_km)}") else: event_msg += f"{CRLF_DOT}STATIONARY ZONES ARE NOT USED" - post_event(event_msg) # Cycle thru the Device's conf and get all zones that are tracked from for all devices @@ -1224,28 +1219,34 @@ def create_Devices_object(): Gb.Devices_by_devicename = {} Gb.conf_devicenames = [] Gb.conf_famshr_devicenames = [] + Gb.devicenames_x_famshr_devices = {} for conf_device in Gb.conf_devices: devicename = conf_device[CONF_IC3_DEVICENAME] if devicename == '': post_startup_alert(f"HA device_tracker entity id not configured for {conf_device[CONF_FAMSHR_DEVICENAME]}") - alert_msg =(f"{EVLOG_ALERT}CONFIGURATION ALERT > The device_tracker entity id (devicename) " + post_event( f"{EVLOG_ALERT}CONFIGURATION ALERT > The device_tracker entity id (devicename) " f"has not been configured for {conf_device[CONF_FAMSHR_DEVICENAME]}/" f"{conf_device[CONF_DEVICE_TYPE]}") - post_event(alert_msg) continue Gb.conf_famshr_devicenames.append(conf_device[CONF_FAMSHR_DEVICENAME]) broadcast_info_msg(f"Set up Device > {devicename}") if conf_device[CONF_TRACKING_MODE] == INACTIVE_DEVICE: - event_msg = (f"{CIRCLE_STAR}{devicename} > {conf_device[CONF_FNAME]}/{conf_device[CONF_DEVICE_TYPE]}, INACTIVE, " + post_event( f"{CIRCLE_STAR}{devicename} > {conf_device[CONF_FNAME]}/{conf_device[CONF_DEVICE_TYPE]}, INACTIVE, " f"{CRLF_SP3_DOT}FamShr Device-{conf_device[CONF_FAMSHR_DEVICENAME]}" # f"{CRLF_SP3_DOT}FmF Device-{conf_device[CONF_FMF_EMAIL]}" f"{CRLF_SP3_DOT}MobApp Entity-{conf_device[CONF_MOBILE_APP_DEVICE]}") - post_event(event_msg) continue + # This list is based on the configuration, it will be rebuilt when the devices are verified + # after the FamShr data has been read + devicename = conf_device.get(CONF_IC3_DEVICENAME) + famshr_name = conf_device.get(CONF_FAMSHR_DEVICENAME) + Gb.devicenames_x_famshr_devices[devicename] = famshr_name + Gb.devicenames_x_famshr_devices[famshr_name] = devicename + # Reinitialize or add device, preserve the Sensor object if reinitializing if devicename in old_Devices_by_devicename: Device = old_Devices_by_devicename[devicename] @@ -1338,7 +1339,28 @@ def _verify_away_time_zone_devicenames(): # ICLOUD3 STARTUP MODULES -- STAGE 4 # #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +def setup_data_source_ICLOUD(): + + if Gb.PyiCloud is None and Gb.PyiCloudInit is None: + pyicloud_ic3_interface.create_PyiCloudService(Gb.PyiCloudInit, instance='startup') + post_event("Setting up iCloud Data Source") + + if pyicloud_ic3_interface.verify_pyicloud_setup_status() is False: + if Gb.PyiCloud is None or Gb.PyiCloudInit is None: + if pyicloud_ic3_interface.verify_pyicloud_setup_status() is False: + return False + if (Gb.PyiCloud is None + or Gb.PyiCloud.FamilySharing is None + or Gb.PyiCloud.FamilySharing.devices_cnt == -1): + return False + + setup_tracked_devices_for_famshr() + # setup_tracked_devices_for_fmf() + set_device_data_source_famshr_fmf() + # tune_device_data_source_famshr_fmf() + +#-------------------------------------------------------------------- def setup_tracked_devices_for_famshr(PyiCloud=None): ''' The Family Share device data is available from PyiCloud when logging into the iCloud @@ -1361,25 +1383,15 @@ def setup_tracked_devices_for_famshr(PyiCloud=None): _FamShr = PyiCloud.FamilySharing if Gb.conf_data_source_FAMSHR is False: - event_msg = "Family Sharing Devices > Not used as a data source" - post_event(event_msg) + post_event("Family Sharing Devices > Not used as a data source") return if Gb.conf_famshr_device_cnt == 0: return - elif _FamShr is None or _FamShr.famshr_fname_by_device_id == {}: - Gb.stage_4_no_devices_found_cnt += 1 - if Gb.stage_4_no_devices_found_cnt > 10: - Gb.reinitialize_icloud_devices_flag = (Gb.conf_famshr_device_cnt > 0) - event_msg =(f"ICLOUD FAMILY SHARING DEVICES ERROR > " - f"{CRLF_DOT}No devices were returned from iCloud Account " - f"Family Sharing List, Retry #{Gb.stage_4_no_devices_found_cnt}") - post_event(f"{EVLOG_ALERT}{event_msg}") - return False - - Gb.debug_log['FamShr.device_id_by_famshr_fname'] = {k: v[:10] for k, v in _FamShr.device_id_by_famshr_fname.items()} - Gb.debug_log['FamShr.famshr_fname_by_device_id'] = {k[:10]: v for k, v in _FamShr.famshr_fname_by_device_id.items()} + post_event(f"iCloud Location Service interface > Selected ({Gb.PyiCloud.instance})") + Gb.debug_log['Gb.PyiCloud.device_id_by_famshr_fname'] = {k: v[:10] for k, v in Gb.PyiCloud.device_id_by_famshr_fname.items()} + Gb.debug_log['Gb.PyiCloud.device_id_by_famshr_fname'] = {k[:10]: v for k, v in Gb.PyiCloud.device_id_by_famshr_fname.items()} _check_renamed_famshr_devices(_FamShr) _check_conf_famshr_devices_not_set_up(_FamShr) @@ -1394,22 +1406,21 @@ def _check_renamed_famshr_devices(_FamShr): ''' renamed_devices = [(f"{conf_device[CONF_IC3_DEVICENAME]} > " f"Renamed: {conf_device[CONF_FAMSHR_DEVICENAME]} " - f"{RARROW}{_FamShr.famshr_fname_by_device_id[conf_device[CONF_FAMSHR_DEVICE_ID]]}") + f"{RARROW}{Gb.PyiCloud.device_id_by_famshr_fname[conf_device[CONF_FAMSHR_DEVICE_ID]]}") for conf_device in Gb.conf_devices - if (conf_device[CONF_FAMSHR_DEVICE_ID] in _FamShr.famshr_fname_by_device_id) + if (conf_device[CONF_FAMSHR_DEVICE_ID] in Gb.PyiCloud.device_id_by_famshr_fname) and conf_device[CONF_FAMSHR_DEVICENAME] != \ - _FamShr.famshr_fname_by_device_id[conf_device[CONF_FAMSHR_DEVICE_ID]]] + Gb.PyiCloud.device_id_by_famshr_fname[conf_device[CONF_FAMSHR_DEVICE_ID]]] if renamed_devices == []: return renamed_devices_str = list_to_str(renamed_devices, CRLF_DOT) - log_msg = ( f"{EVLOG_ALERT}FAMSHR DEVICE NAME CHANGED > The FamShr device name returned " + post_event( f"{EVLOG_ALERT}FAMSHR DEVICE NAME CHANGED > The FamShr device name returned " f"from your Apple iCloud account Family Sharing List has a new name. " f"The iCloud3 configuration file will be updated." f"{renamed_devices_str}") - post_event(log_msg) try: # Update the iCloud3 configuration file with the new FamShr devicename @@ -1431,11 +1442,11 @@ def _check_renamed_famshr_devices(_FamShr): Gb.conf_famshr_devicenames.append(new_famshr_devicename) conf_device[CONF_FAMSHR_DEVICENAME] = \ - _FamShr.device_id_by_famshr_fname[conf_device[CONF_FAMSHR_DEVICENAME]] = \ + Gb.PyiCloud.device_id_by_famshr_fname[conf_device[CONF_FAMSHR_DEVICENAME]] = \ new_famshr_devicename - _FamShr.device_id_by_famshr_fname[new_famshr_devicename] = conf_device[CONF_FAMSHR_DEVICE_ID] - _FamShr.device_id_by_famshr_fname.pop(old_famshr_devicename, None) + Gb.PyiCloud.device_id_by_famshr_fname[new_famshr_devicename] = conf_device[CONF_FAMSHR_DEVICE_ID] + Gb.PyiCloud.device_id_by_famshr_fname.pop(old_famshr_devicename, None) config_file.write_storage_icloud3_configuration_file() @@ -1449,7 +1460,7 @@ def _check_conf_famshr_devices_not_set_up(_FamShr): f"Unknown: {conf_device[CONF_FAMSHR_DEVICENAME]}") for conf_device in Gb.conf_devices if (conf_device[CONF_FAMSHR_DEVICENAME] != NONE_FNAME - and conf_device[CONF_FAMSHR_DEVICENAME] not in _FamShr.device_id_by_famshr_fname)] + and conf_device[CONF_FAMSHR_DEVICENAME] not in Gb.PyiCloud.device_id_by_famshr_fname)] if devices_not_set_up == []: return [] @@ -1459,12 +1470,14 @@ def _check_conf_famshr_devices_not_set_up(_FamShr): devices_not_set_up_str = list_to_str(devices_not_set_up, CRLF_X) post_startup_alert( f"FamShr Config Error > Device not found" f"{devices_not_set_up_str.replace(CRLF_X, CRLF_DOT)}") - log_msg = ( f"{EVLOG_ALERT}FAMSHR DEVICES ERROR > Your Apple iCloud Account Family Sharing List did " - f"not return any information for some of configured devices. FamShr will not be used " - f"to track these devices." - f"{devices_not_set_up_str}" - f"{more_info('famshr_device_not_available')}") - post_event(log_msg) + + if _FamShr.devices_cnt == -1: + post_event( f"{EVLOG_ALERT}FAMSHR DEVICES ERROR > Your Apple iCloud Account Family Sharing List did " + f"not return any information for some of configured devices. FamShr will not be used " + f"to track these devices." + f"{devices_not_set_up_str}" + f"{more_info('famshr_device_not_available')}") + log_error_msg(f"iCloud3 Error > Some FamShr Devices were not Initialized > " f"{devices_not_set_up_str.replace(CRLF, ', ')}. " f"See iCloud3 Event Log > Startup Stage 4 for more info.") @@ -1476,24 +1489,15 @@ def _display_devices_verification_status(PyiCloud, _FamShr): # through the PyiCloud_RawData so we get devices without location info event_msg =(f"iCloud Acct Family Sharing Devices > " f"{Gb.conf_famshr_device_cnt} of " - f"{len(_FamShr.device_id_by_famshr_fname)} used by iCloud3") + f"{_FamShr.devices_cnt} used by iCloud3") + # f"{len(Gb.PyiCloud.device_id_by_famshr_fname)} used by iCloud3") Gb.famshr_device_verified_cnt = 0 Gb.devicenames_x_famshr_devices = {} - sorted_device_id_by_famshr_fname = OrderedDict(sorted(_FamShr.device_id_by_famshr_fname.items())) + sorted_device_id_by_famshr_fname = OrderedDict(sorted(Gb.PyiCloud.device_id_by_famshr_fname.items())) for famshr_fname, device_id in sorted_device_id_by_famshr_fname.items(): - _RawData = PyiCloud.RawData_by_device_id_famshr.get(device_id, None) - try: - raw_model, model, model_display_name = \ - _FamShr.device_model_info_by_fname[famshr_fname] - except: - log_debug_msg( f"Error extracting device info, " - f"source-{_FamShr.device_model_info_by_fname[famshr_fname]}, " - f"fname-{famshr_fname}") - continue - broadcast_info_msg(f"Set up FamShr Device > {famshr_fname}") conf_device = _verify_conf_device(famshr_fname, device_id, _FamShr) @@ -1502,6 +1506,7 @@ def _display_devices_verification_status(PyiCloud, _FamShr): famshr_name = conf_device.get(CONF_FAMSHR_DEVICENAME) Gb.devicenames_x_famshr_devices[devicename] = famshr_name Gb.devicenames_x_famshr_devices[famshr_name] = devicename + _RawData.ic3_devicename = devicename _RawData.Device = Gb.Devices_by_devicename.get(devicename) @@ -1522,7 +1527,7 @@ def _display_devices_verification_status(PyiCloud, _FamShr): if exception_msg: event_msg += ( f"{CRLF_X}" - f"{famshr_fname}, {model_display_name} ({raw_model}) >" + f"{famshr_fname} ({_RawData.device_identifier}) >" f"{CRLF_SP8_HDOT}{exception_msg}") continue @@ -1542,20 +1547,19 @@ def _display_devices_verification_status(PyiCloud, _FamShr): if _RawData and Gb.log_rawdata_flag: log_title = ( f"FamShr PyiCloud Data (device_data -- " - f"{devicename}/{famshr_fname}), " - f"{model_display_name} ({raw_model})") + f"{devicename}/{famshr_fname} " + f"({_RawData.device_identifier})") log_rawdata(log_title, {'filter': _RawData.device_data}) # if devicename not in Gb.Devices_by_devicename: Device = Gb.Devices_by_devicename.get(devicename) if Device is None: if exception_msg == '': exception_msg = ', Unknown Device or Other Device setup error' - event_msg += ( f"{CRLF_X}{famshr_fname}, {model_display_name} ({raw_model}) >" + event_msg += ( f"{CRLF_X}{famshr_fname} ({_RawData.device_identifier}) >" f"{CRLF_SP8_HDOT}{devicename}" f"{exception_msg}") continue - # Device = Gb.Devices_by_devicename[devicename] Device.device_id_famshr = device_id # rc9 Set verify status to a valid device_id exists instead of always True @@ -1576,7 +1580,7 @@ def _display_devices_verification_status(PyiCloud, _FamShr): Gb.famshr_device_verified_cnt += 1 event_msg += ( f"{CRLF_CHK}" - f"{famshr_fname}, {model_display_name} ({raw_model}) >" + f"{famshr_fname} ({_RawData.device_identifier}) >" f"{CRLF_SP8_HDOT}{devicename}, {Device.fname} " f"{Device.tracking_mode_fname}" f"{exception_msg}") @@ -1615,7 +1619,7 @@ def _verify_conf_device(famshr_fname, device_id, _FamShr): # Get the model info from PyiCloud data and update it if necessary raw_model, model, model_display_name = \ - _FamShr.device_model_info_by_fname[famshr_fname] + Gb.PyiCloud.device_model_info_by_fname[famshr_fname] if (conf_device[CONF_RAW_MODEL] != raw_model or conf_device[CONF_MODEL] != model @@ -1654,7 +1658,7 @@ def _check_duplicate_device_names(PyiCloud, _FamShr): # Make a list of all device fnames without the (##) suffix that was added on try: famshr_fnames_base = [_fname_base(famshr_fname) - for famshr_fname in _FamShr.device_id_by_famshr_fname.keys()] + for famshr_fname in Gb.PyiCloud.device_id_by_famshr_fname.keys()] # Count each one, then drop the ones with a count = 1 to keep the duplicates famshr_fnames_count = {famshr_fname:famshr_fnames_base.count(famshr_fname) @@ -1727,16 +1731,15 @@ def _check_duplicate_device_names(PyiCloud, _FamShr): f"{famshr_fname_last_located}") conf_device[CONF_FAMSHR_DEVICENAME] = famshr_fname_last_located - conf_device[CONF_FAMSHR_DEVICE_ID] = _FamShr.device_id_by_famshr_fname[famshr_fname_last_located] + conf_device[CONF_FAMSHR_DEVICE_ID] = Gb.PyiCloud.device_id_by_famshr_fname[famshr_fname_last_located] update_conf_file_flag = True Device = Gb.Devices_by_devicename[conf_device[CONF_IC3_DEVICENAME]] Device.set_fname_alert(YELLOW_ALERT) except Exception as err: - event_msg =( f"Error resolving similar device names, " + post_event( f"Error resolving similar device names, " f"{famshr_fname_base}/{famshr_fname_last_located}, " f"{err}") - post_event(event_msg) # Print dups msg with info and update config file if update_conf_file_flag: @@ -1783,6 +1786,7 @@ def setup_tracked_devices_for_fmf(PyiCloud=None): return broadcast_info_msg(f"Stage 3 > Set up Find-my-Friends Devices") + _trace(f"Stage 3 > Set up Find-my-Friends Devices") # devices_desc = get_fmf_devices(PyiCloud) # device_id_by_fmf_email = devices_desc[0] @@ -1795,22 +1799,28 @@ def setup_tracked_devices_for_fmf(PyiCloud=None): return PyiCloud = Gb.PyiCloud + _trace(f"{PyiCloud.FindMyFriends=}") + PyiCloud.create_FindMyFriends_object() _FmF = PyiCloud.FindMyFriends + # _Fmf._set_service_available(True) + Gb.conf_data_source_FMF = True + _FmF.refresh_client() + _trace(f"{PyiCloud.FindMyFriends=}") event_msg = "Find-My-Friends Devices > " - if Gb.conf_data_source_FMF is False: - event_msg += "Not used as a data source" - post_event(event_msg) - return - event_msg += f"{Gb.conf_fmf_device_cnt} of {len(Gb.conf_devices)} iCloud3 Devices Configured" - post_event(event_msg) - if Gb.conf_fmf_device_cnt == 0: - return - - elif _FmF is None or _FmF.device_id_by_fmf_email == {}: - event_msg += "NO DEVICES FOUND" - post_event(f"{EVLOG_ALERT}{event_msg}") - return + # if Gb.conf_data_source_FMF is False: + # event_msg += "Not used as a data source" + # post_event(event_msg) + # return + # event_msg += f"{Gb.conf_fmf_device_cnt} of {len(Gb.conf_devices)} iCloud3 Devices Configured" + # post_event(event_msg) + # if Gb.conf_fmf_device_cnt == 0: + # return + + # elif _FmF is None or _FmF.device_id_by_fmf_email == {}: + # event_msg += "NO DEVICES FOUND" + # post_event(f"{EVLOG_ALERT}{event_msg}") + # return try: Gb.fmf_device_verified_cnt = 0 @@ -1925,25 +1935,25 @@ def get_famshr_devices_pyicloud(PyiCloud): if PyiCloud is None: PyiCloud = Gb.PyiCloud _FamShr = PyiCloud.FamilySharing - return [_FamShr.device_id_by_famshr_fname, - _FamShr.famshr_fname_by_device_id, - _FamShr.device_info_by_famshr_fname, - _FamShr.device_model_info_by_fname] + return [Gb.PyiCloud.device_id_by_famshr_fname, + Gb.PyiCloud.device_id_by_famshr_fname, + Gb.PyiCloud.device_info_by_famshr_fname, + Gb.PyiCloud.device_model_info_by_fname] #---------------------------------------------------------------------- -def get_fmf_devices_pyicloud(PyiCloud): - ''' - The device information tables are built when the devices are added the when - the FamilySharing object and RawData objects are created when logging into - the iCloud account. - ''' +# def get_fmf_devices_pyicloud(PyiCloud): +# ''' +# The device information tables are built when the devices are added the when +# the FamilySharing object and RawData objects are created when logging into +# the iCloud account. +# ''' - if PyiCloud is None: PyiCloud = Gb.PyiCloud +# if PyiCloud is None: PyiCloud = Gb.PyiCloud - _FmF = PyiCloud.FindMyFriends - return (_FmF.device_id_by_fmf_email, - _FmF.fmf_email_by_device_id, - _FmF.device_info_by_fmf_email) +# _FmF = PyiCloud.FindMyFriends +# return (_FmF.device_id_by_fmf_email, +# _FmF.fmf_email_by_device_id, +# _FmF.device_info_by_fmf_email) #-------------------------------------------------------------------- @@ -1990,13 +2000,13 @@ def set_device_data_source_famshr_fmf(PyiCloud=None): Gb.Devices_by_icloud_device_id[device_id] = Device _RawData = PyiCloud.RawData_by_device_id[device_id] - if Device.device_id_fmf: - device_id = Device.device_id_fmf - if device_id in PyiCloud.RawData_by_device_id: - if data_source is None: - data_source = FMF - Gb.Devices_by_icloud_device_id[device_id] = Device - _RawData = PyiCloud.RawData_by_device_id[device_id] + # if Device.device_id_fmf: + # device_id = Device.device_id_fmf + # if device_id in PyiCloud.RawData_by_device_id: + # if data_source is None: + # data_source = FMF + # Gb.Devices_by_icloud_device_id[device_id] = Device + # _RawData = PyiCloud.RawData_by_device_id[device_id] if (Device.mobapp_monitor_flag and data_source is None): data_source = MOBAPP @@ -2004,8 +2014,8 @@ def set_device_data_source_famshr_fmf(PyiCloud=None): if data_source != MOBAPP: info_msg = (f"Set PyiCloud Device Id > {Device.devicename}, " f"DataSource-{data_source}, " - f"{CRLF}FamShr-({Device.device_id8_famshr}), " - f"FmF-({Device.device_id8_fmf})") + f"{CRLF}FamShr-({Device.device_id8_famshr})") + # f"FmF-({Device.device_id8_fmf})") post_monitor_msg(info_msg) #Device.data_source = data_source @@ -2030,6 +2040,9 @@ def tune_device_data_source_famshr_fmf(): 2. If set to fmf but it also has a famshr id, change to famshr. 2. If set to fmf and no famshr id, leave as fmf. ''' + + return + broadcast_info_msg(f"Stage 3 > Tune Tracking Method") try: @@ -2069,13 +2082,11 @@ def tune_device_data_source_famshr_fmf(): pass elif cnt_famshr >= cnt_fmf: for Device in Devices_fmf_to_famshr: - #Device.data_source = FAMSHR Device.primary_data_source = FAMSHR Gb.Devices_by_icloud_device_id.pop(Device.device_id_fmf) Gb.Devices_by_icloud_device_id[Device.device_id_famshr] = Device else: for Device in Devices_famshr_to_fmf: - #Device.data_source = FMF Device.primary_data_source = FMF Gb.Devices_by_icloud_device_id.pop(Device.device_id_famshr) Gb.Devices_by_icloud_device_id[Device.device_id_fmf] = Device @@ -2295,7 +2306,7 @@ def _search_for_mobapp_device(devicename, Device, mobapp_id_by_mobapp_devicename post_startup_alert( f"Mobile App device_tracker entity scan found several devices: " f"{Device.fname_devicename}") - alert_msg =(f"{EVLOG_ALERT}DUPLICATE MOBAPP DEVICES FOUND > More than one Device Tracker Entity " + post_event( f"{EVLOG_ALERT}DUPLICATE MOBAPP DEVICES FOUND > More than one Device Tracker Entity " f"was found during the scan of the HA Device Registry." f"{CRLF_X}AssignedTo-{Device.fname_devicename}" f"{CRLF}{more_info('mobapp_error_multiple_devices_on_scan')}" @@ -2303,7 +2314,6 @@ def _search_for_mobapp_device(devicename, Device, mobapp_id_by_mobapp_devicename f"{CRLF}Count-{len(matched_mobapp_devices)}, " f"{CRLF}Entities-{', '.join(matched_mobapp_devices)}, " f"{CRLF}Monitored-{mobapp_devicename}") - post_event(alert_msg) log_error_msg(f"iCloud3 Error > Mobile App Config Error > Dev Trkr Entity not found " f"during Search {conf_mobapp_device}_???. " @@ -2322,14 +2332,13 @@ def _display_any_mobapp_errors( mobapp_error_mobile_app_msg, post_startup_alert( f"Mobile App Integration missing trigger or battery sensors" f"{mobapp_error_mobile_app_msg.replace(CRLF_X, CRLF_DOT)}") - alert_msg =(f"{EVLOG_ALERT}MOBILE APP INTEGRATION (Mobile App) SET UP PROBLEM > The Mobile App " + post_event( f"{EVLOG_ALERT}MOBILE APP INTEGRATION (Mobile App) SET UP PROBLEM > The Mobile App " f"Integration `last_update_trigger` and `battery_level` sensors are disabled or " f"were not found during the scan of the HA Entities Registry for a Device." f"iCloud3 will use the Mobile App for Zone (State) changes but update triggers and/or " f"battery information will not be available for these devices." f"{mobapp_error_mobile_app_msg}" f"{more_info('mobapp_error_mobile_app_msg')}") - post_event(alert_msg) log_error_msg(f"iCloud3 Error > The Mobile App Integration has not been set up or the " f"battery_level or last_update_trigger sensor entities were not found." @@ -2339,11 +2348,10 @@ def _display_any_mobapp_errors( mobapp_error_mobile_app_msg, post_startup_alert( f"Mobile App Config Error > Device Tracker Entity not found" f"{mobapp_error_search_msg.replace(CRLF_X, CRLF_DOT)}") - alert_msg =(f"{EVLOG_ALERT}MOBAPP DEVICE NOT FOUND > An MobApp device_tracker " + post_event( f"{EVLOG_ALERT}MOBAPP DEVICE NOT FOUND > An MobApp device_tracker " f"entity was not found in the `Scan for mobile_app devices` in the HA Device Registry." f"{mobapp_error_search_msg}" f"{more_info('mobapp_error_search_msg')}") - post_event(alert_msg) log_error_msg(f"iCloud3 Error > Mobile App Device Tracker Entity not found " f"in the HA Devices List." @@ -2354,11 +2362,10 @@ def _display_any_mobapp_errors( mobapp_error_mobile_app_msg, post_startup_alert( f"Mobile App Config Error > Device Tracker Entity not found" f"{mobapp_error_not_found_msg.replace(CRLF_X, CRLF_DOT)}") - alert_msg =(f"{EVLOG_ALERT}MOBAPP DEVICE NOT FOUND > The device tracker entity " + post_event( f"{EVLOG_ALERT}MOBAPP DEVICE NOT FOUND > The device tracker entity " f"was not found during the scan of the HA Device Registry." f"{mobapp_error_not_found_msg}" f"{more_info('mobapp_error_not_found_msg')}") - post_event(alert_msg) log_error_msg(f"iCloud3 Error > Mobile App Device Tracker Entity was not found " f"in the HA Devices List." @@ -2369,12 +2376,11 @@ def _display_any_mobapp_errors( mobapp_error_mobile_app_msg, post_startup_alert( f"Mobile App Config Error > Device Tracker Entity disabled" f"{mobapp_error_disabled_msg.replace(CRLF_X, CRLF_DOT)}") - alert_msg =(f"{EVLOG_ALERT}MOBILE APP DEVICE DISABLED > The device tracker entity " + post_event( f"{EVLOG_ALERT}MOBILE APP DEVICE DISABLED > The device tracker entity " f"is disabled so the mobile_app last_update_trigger and bettery_level sensors " f"can not be monitored. iCloud3 will not use the Mobile App for these devices." f"{mobapp_error_disabled_msg}" f"{more_info('mobapp_error_disabled_msg')}") - post_event(alert_msg) log_error_msg(f"iCloud3 Error > Mobile App Device Tracker Entity is disabled and " f"can not be monitored by iCloud3." @@ -2404,6 +2410,7 @@ def setup_notify_service_name_for_mobapp_devices(post_event_msg=False): mobapp_devicename = mobile_app_notify_devicename.replace('mobile_app_', '') for devicename, Device in Gb.Devices_by_devicename.items(): if instr(mobapp_devicename, devicename) or instr(devicename, mobapp_devicename): + Device.mobapp_monitor_flag = True if Device.mobapp[NOTIFY] == '': Device.mobapp[NOTIFY] = mobile_app_notify_devicename setup_msg += (f"{CRLF_DOT}{Device.devicename_fname}{RARROW}{mobile_app_notify_devicename}") @@ -2573,9 +2580,9 @@ def _display_all_devices_config_info(): Gb.used_data_source_FAMSHR = True event_msg += f"{CRLF_HDOT}FamShr Device: {Device.conf_famshr_name}" - if Device.is_data_source_FMF: - Gb.used_data_source_FMF = True - event_msg += f"{CRLF_HDOT}FmF Device: {Device.conf_fmf_email}" + # if Device.is_data_source_FMF: + # Gb.used_data_source_FMF = True + # event_msg += f"{CRLF_HDOT}FmF Device: {Device.conf_fmf_email}" if Device.PairedDevice is not None: event_msg += (f"{CRLF_HDOT}Paired Device: {Device.PairedDevice.fname_devicename}") @@ -2656,9 +2663,8 @@ def display_inactive_devices(): if len(inactive_devices) == len(Gb.conf_devices): post_startup_alert('All devices are Inactive and nothing will be tracked') - event_msg =(f"{EVLOG_ALERT}ALL DEVICES ARE INACTIVE > " + post_event( f"{EVLOG_ALERT}ALL DEVICES ARE INACTIVE > " f"{more_info('all_devices_inactive')}") - post_event(event_msg) #------------------------------------------------------------------------------ def display_object_lists(): @@ -2700,7 +2706,6 @@ def post_restart_icloud3_complete_msg(): for devicename, Device in Gb.Devices_by_devicename.items(): # Device.display_info_msg("Setup Complete, Locating Device") - event_msg =(f"{EVLOG_IC3_STARTING}Initializing iCloud3 v{Gb.version} > Complete") - post_event(event_msg) + post_event(f"{EVLOG_IC3_STARTING}Initializing iCloud3 v{Gb.version} > Complete") Gb.EvLog.update_event_log_display("") \ No newline at end of file diff --git a/custom_components/icloud3/support/start_ic3_control.py b/custom_components/icloud3/support/start_ic3_control.py index 19ac422..348eaf4 100644 --- a/custom_components/icloud3/support/start_ic3_control.py +++ b/custom_components/icloud3/support/start_ic3_control.py @@ -1,15 +1,16 @@ from ..global_variables import GlobalVariables as Gb from ..const import (NOT_SET, IC3LOG_FILENAME, CRLF, CRLF_DOT, CRLF_HDOT, CRLF_X, NL, NL_DOT, - EVLOG_ALERT, EVLOG_IC3_STARTING, EVLOG_IC3_STAGE_HDR, + EVLOG_ALERT, EVLOG_ERROR, EVLOG_IC3_STARTING, EVLOG_IC3_STAGE_HDR, SETTINGS_INTEGRATIONS_MSG, INTEGRATIONS_IC3_CONFIG_MSG, CONF_VERSION, ICLOUD_FNAME, ZONE_DISTANCE, FAMSHR_FNAME, FMF_FNAME, MOBAPP_FNAME, ) +from ..support import hacs_ic3 from ..support import start_ic3 from ..support import config_file -from ..support import pyicloud_ic3_interface +#from ..support import pyicloud_ic3_interface from ..support import icloud_data_handler from ..support import determine_interval as det_interval @@ -82,6 +83,7 @@ def stage_1_setup_variables(): start_ic3.display_platform_operating_mode_msg() Gb.hass.loop.create_task(start_ic3.update_lovelace_resource_event_log_js_entry()) + Gb.hass.loop.create_task(hacs_ic3.check_hacs_icloud3_update_available()) start_ic3.check_ic3_event_log_file_version() post_monitor_msg(f"LocationInfo-{Gb.ha_location_info}") @@ -161,16 +163,16 @@ def stage_3_setup_configured_devices(): # Make sure a full restart is done if all of the devices were not found in the iCloud data data_sources = '' if Gb.conf_data_source_FAMSHR: data_sources += f"{FAMSHR_FNAME}, " - if Gb.conf_data_source_FMF : data_sources += f"{FMF_FNAME}, " + # if Gb.conf_data_source_FMF : data_sources += f"{FMF_FNAME}, " if Gb.conf_data_source_MOBAPP: data_sources += f"{MOBAPP_FNAME}, " data_sources = data_sources[:-2] if data_sources else 'NONE' post_event(f"Data Sources > {data_sources}") if Gb.config_track_devices_change_flag: pass - elif (Gb.conf_data_source_FMF - and Gb.fmf_device_verified_cnt < len(Gb.Devices)): - Gb.config_track_devices_change_flag = True + # elif (Gb.conf_data_source_FMF + # and Gb.fmf_device_verified_cnt < len(Gb.Devices)): + # Gb.config_track_devices_change_flag = True elif (Gb.conf_data_source_FAMSHR and Gb.famshr_device_verified_cnt < len(Gb.Devices)): Gb.config_track_devices_change_flag = True @@ -191,7 +193,7 @@ def stage_3_setup_configured_devices(): def stage_4_setup_data_sources(retry=False): Gb.trace_prefix = 'STAGE4' - stage_title = f'Stage 4 > Setup iCloud & MobApp Data Source' + stage_title = f"Stage 4 > Setup iCloud & MobApp Data Source" log_info_msg(f"* > {EVLOG_IC3_STAGE_HDR}{stage_title}") # Missing username/password, PyiCloud can not be started @@ -200,42 +202,31 @@ def stage_4_setup_data_sources(retry=False): Gb.conf_data_source_FAMSHR = False Gb.conf_data_source_FMF = False Gb.primary_data_source_ICLOUD = False - post_startup_alert('iCloud username/password not set up or incorrect') + post_startup_alert('iCloud username/password invalid or not set up') event_msg =(f"{EVLOG_ALERT}CONFIGURATION ALERT > The iCloud username or password has not been " f"set up or is incorrect. iCloud will not be used for location tracking") post_event(event_msg) return_code = True - retry_msg = ', Retrying iCloud Device Setup' if retry else '' Gb.EvLog.display_user_message(stage_title) broadcast_info_msg(stage_title) try: if Gb.primary_data_source_ICLOUD: - post_event(f"iCloud Account Used > {obscure_field(Gb.username)}") - - if Gb.PyiCloud is None and Gb.PyiCloudInit is None: - pyicloud_ic3_interface.create_PyiCloudService(Gb.PyiCloudInit, called_from='stage4') - - pyicloud_ic3_interface.verify_pyicloud_setup_status() - - if Gb.PyiCloud: - if start_ic3.setup_tracked_devices_for_famshr() is False: - if Gb.stage_4_no_devices_found_cnt <= 10: - return stage_4_setup_data_sources() + post_event(f"iCloud Account > Logged Into-{obscure_field(Gb.username)}") + start_ic3.setup_data_source_ICLOUD() - start_ic3.setup_tracked_devices_for_fmf() - start_ic3.set_device_data_source_famshr_fmf() - start_ic3.tune_device_data_source_famshr_fmf() - else: - event_msg = 'iCloud Location Services > Not used as a data source' - post_event(event_msg) + if Gb.PyiCloud is None: + post_event('iCloud Location Service > Not used as a data source') + elif Gb.PyiCloud.account_locked: + post_error_msg( f"{EVLOG_ERROR}iCloud Account is Locked. Log onto www.icloud.com " + f"and unlock your account to reauthorize location services. ") + post_startup_alert('iCloud Account is Locked') if Gb.conf_data_source_MOBAPP: start_ic3.setup_tracked_devices_for_mobapp() else: - event_msg = 'Mobile App > Not used as a data source' - post_event(event_msg) + post_event('Mobile App > Not used as a data source') start_ic3.set_devices_verified_status() return_code = _are_all_devices_verified(retry=retry) @@ -246,7 +237,7 @@ def stage_4_setup_data_sources(retry=False): write_debug_log() - post_event(f"{EVLOG_IC3_STAGE_HDR}{stage_title}{retry_msg}") + post_event(f"{EVLOG_IC3_STAGE_HDR} {stage_title}") Gb.EvLog.update_event_log_display("") return return_code @@ -280,7 +271,7 @@ def _are_all_devices_verified(retry=False): f"tracking. If not, check the device parameters in the iCloud3 Configure Settings:" f"{more_info('configure_icloud3')}") else: - event_msg = (f"{EVLOG_ALERT}ALERT > Some devices could not be verified. iCloud Location Services " + event_msg = (f"{EVLOG_ALERT}ALERT > Some devices could not be verified. iCloud Location Service " f"will be reinitialized") event_msg += (f"{CRLF_DOT}Unverified Devices > {', '.join(unverified_devices)}") post_event(event_msg) @@ -295,6 +286,9 @@ def stage_5_configure_tracked_devices(): stage_title = f'Stage 5 > Device Configuration Summary' log_info_msg(f"* > {EVLOG_IC3_STAGE_HDR}{stage_title}") + if Gb.PyiCloud: + log_debug_msg(f"PyiCloud Instance Finialized > {Gb.PyiCloud.instance}") + try: Gb.EvLog.display_user_message(stage_title) broadcast_info_msg(stage_title) @@ -444,7 +438,7 @@ def reinitialize_icloud_devices(): post_event(alert_msg) - event_msg =(f"{EVLOG_IC3_STARTING}Restarting iCloud Location Services") + event_msg =(f"{EVLOG_IC3_STARTING}Restarting iCloud Location Service") post_event(event_msg) if Gb.PyiCloud and Gb.PyiCloud.FamilySharing: diff --git a/custom_components/icloud3/translations/zh-Hans.json b/custom_components/icloud3/translations/zh-Hans.json new file mode 100644 index 0000000..8fd6e8a --- /dev/null +++ b/custom_components/icloud3/translations/zh-Hans.json @@ -0,0 +1,508 @@ +{ + "title": "iCloud3 v3:iDevice 跟踪器", + "config": { + "abort": { + "config_update_complete": "iCloud3 配置更新成功", + "already_configured": "iCloud3 已经安装,无法再次安装。请在 iCloud3 集成条目中选择 CONFIGURE 来配置 iCloud3。\n\n如果您正在删除然后重新安装,请先重启 HA 然后再重新安装 iCloud3", + "disabled": "iCloud3 已禁用,不能再次安装。启用 iCloud3,然后在 iCloud3 集成条目中选择 CONFIGURE 来配置 iCloud3。\n\n如果您正在删除然后重新安装,请先重启 HA 然后再重新安装 iCloud3", + "login_error": "登录 iCloud 账户时发生错误。请验证用户名和密码。", + "reauth_successful": "重新认证成功完成", + "verification_code_accepted": "苹果 ID 验证码已接受。重新认证完成", + "verification_code_cancelled": "苹果 ID 验证已取消", + "update_cancelled": "更新已取消", + "icloud3_init_error": "初始化 iCloud3 配置设置屏幕时遇到问题。iCloud3 可能在初始化期间遇到错误并未启动。检查 'home-assistant.log' 文件以获取与 iCloud3 相关的任何错误" + }, + "error": { + "invalid_path": "提供的路径无效。应为 'user/repo-name' 格式", + "verification_code_send_error": "发送苹果 ID 验证码失败", + "verification_code_requested2": "已请求苹果 ID 验证码", + "verification_code_invalid": "验证码不正确。请重新输入或请求新的验证码", + "icloud_no_devices": "在 iCloud '家庭共享' 列表中未找到设备", + "icloud_other_error": "认证 iCloud 账户时遇到未知错误。请稍后再试" + }, + "step": { + "user": { + "data": { + "continue": "选择继续 iCloud3 安装(及参数迁移)", + "continue_restart_ha": "选择继续 iCloud3 安装。然后重启家庭助理。" + }, + "description": "恭喜!iCloud3 v3 集成已添加到家庭助理。下一步是配置您想要跟踪的设备。如果您正在运行 iCloud3 v2,您的配置将从 v2 迁移到 v3。\n\n选择下面的提交按钮,然后选择 CONFIGURE 来配置 iCloud3。\n\niCloud3 用户指南将帮助您开始:\n• ICLOUD3 文档 - 在此屏幕右上角选择问号 (?) 打开 'iCloud 用户指南'\n• 从 v2 迁移 - 按照 '安装 iCloud3 > 从 v2 到 v3 的迁移' 章节中的步骤\n• 新安装 - 按照 '安装 iCloud3 > 作为新安装' 章节中的步骤", + "title": "iCloud3 v3 集成安装程序" + }, + "reauth": { + "title": "苹果 ID 验证码(家庭助理通知)", + "description": "输入您刚从苹果收到的六位数验证码", + "data": { + "verification_code": "ICLOUD 账户验证代码", + "action_items": "═════════════════════════════════════════════════════" + } + }, + "restart_ha": { + "title": "确认重新启动家庭助理", + "description": "iCloud3 需要重新启动家庭助理以防止 device_tracker 实体名称冲突", + "data": { + "action_items": "" + } + } + } + }, + "options": { + "abort": { + "config_update_complete": "iCloud3 配置更新成功", + "already_configured": "iCloud3 已经安装,不能再次安装。请在 iCloud3 集成条目中选择 CONFIGURE 来配置 iCloud3", + "disabled": "iCloud3 已禁用,不能再次安装。启用 iCloud,然后在 iCloud3 集成条目中选择 CONFIGURE 来配置 iCloud3", + "ha_restarting": "家庭助理正在重新启动\n\n重新启动后必须刷新 iCloud3 屏幕", + "ic3_reloading": "正在重新加载 iCloud3\n\n重新启动后必须刷新 iCloud3 屏幕", + "reauth_successful": "重新认证成功完成" + }, + "error": { + "update_aborted": "更新中止,检测到一个数据字段中的错误", + "conf_updated": "iCloud3 配置参数已成功更新", + "conf_reloaded": "iCloud3 配置文件已重新加载", + "icloud_acct_logging_into": "正在登录 iCloud 账户", + "icloud_acct_logged_into": "已登录新的 iCloud 账户。选择 SAVE 以保存更改并重启 iCloud3", + "icloud_acct_already_logged_into": "已登录 iCloud 账户", + "icloud_acct_login_error_user_pw": "登录错误,用户名或密码无效", + "icloud_acct_login_error_other": "登录错误,其他错误或 iCloud 不可用", + "icloud_acct_login_error_connection": "登录错误,无法连接到 iCloud 服务器", + "icloud_acct_username_password_error": "输入错误,用户名或密码无效", + "icloud_acct_not_available": "登录失败,iCloud 账户不可用", + "icloud_acct_not_logged_into": "警告:iCloud 账户未登录", + "icloud_acct_data_source_warning": "警告:未选择 iCloud 账户作为数据来源,但已设置用户名/密码", + "icloud_acct_not_set_up": "需要输入 iCloud 账户用户名或密码", + "icloud_acct_no_data_source": "未选择数据来源(iCloud 或移动应用)", + "mobile_app_error": "错误,移动应用集成未安装。移动应用将不会被用作数据来源;位置数据和区域进出触发器将不会被监控", + "verification_code_requested": "已请求苹果 ID 验证码,可能需要刷新浏览器", + "verification_code_requested2": "已请求苹果 ID 验证码", + "verification_code_needed": "需要苹果 ID 验证码", + "verification_code_accepted": "苹果 ID 验证码已接受", + "verification_code_invalid": "验证码不正确。请重新输入或请求新的验证码", + "verification_code_send_error": "发送苹果 ID 验证码失败", + + "inactive_device": "设备处于非活动状态。更改为 'Track' 以定位和跟踪此设备", + "inactive_all_devices": "✪✪ 所有设备均处于非活动状态。将不进行跟踪 ✪✪", + "inactive_most_devices": "大多数设备处于非活动状态,将不会被定位或跟踪", + "inactive_some_devices": "一些设备处于非活动状态,将不会被定位或跟踪", + "inactive_few_devices": "少数设备处于非活动状态,将不会被定位或跟踪", + "inactive_no_devices": "未设置设备。选择 'Add Device' 并提交", + + "away_time_zone_dup_devices_1": "这些设备中的一个也在其他设备列表中选择", + "away_time_zone_dup_devices_2": "这些设备中的一个也在其他设备列表中选择", + + "review_filledin_fields": "复查已填写的字段", + "not_numeric": "输入的值不是数字", + "waze_server_error_us": "您所在地的正确服务器是:美国,加拿大", + "waze_server_error_il": "您所在地的正确服务器是:以色列", + "waze_server_error_row": "您所在地的正确服务器是:世界其他地区", + "required_field": "必须指定此参数", + "required_field_device": "必须从家庭共享、查找我的朋友或移动应用设备列表中选择提供位置数据的设备", + "no_device_selected": "必须选择提供位置数据的设备", + "no_add_entities_device_tracker_fct": "添加设备的 HA 组件不可用。必须重启 HA", + "unknown_devicename": "先前选择的设备无法识别。它的名称可能已更改或已被删除。重新选择要跟踪或监控的设备", + "unknown_value": "iCloud3 配置文件中此参数的值未知或无效。必须重新选择", + "unknown_famshr": "当 iCloud3 启动时,未从 iCloud 返回 FamShr 设备。检查 FindMy 设备列表和家庭共享列表。查看启动阶段 4 的事件日志以获取更多信息以及从 iCloud 账户返回的设备列表", + "unknown_fmf": "当 iCloud3 启动时,未从 iCloud 返回 FmF 设备。检查 FindMy 应用设备共享位置信息。查看启动阶段 4 的事件日志以获取更多信息以及从 iCloud 账户返回的设备列表", + "unknown_mobapp": "在 HA 设备注册扫描期间未找到 mobile_app device_tracker 实体。检查 HA 设置 > 设备和服务 > 设备中的移动应用设备列表。查看启动阶段 4 的事件日志以获取更多信息以及在 HA 设备注册中找到的 mobile_app 设备列表", + "unknown_picture": "未在 HA config/www 目录中找到图片文件名。重新选择文件名或检查是否已被删除", + + "unknown_famshr_fmf": "检查 FamShr 和 FmF 参数值(未找到或无效)", + "unknown_famshr_fmf_mobapp": "检查 FamShr、FmF 和移动应用参数值(未找到或无效)", + "unknown_famshr_fmf_mobapp_picture": "检查 FamShr、FmF、移动应用和图片参数值(未找到或无效)", + "unknown_famshr_mobapp": "检查 FamShr 和移动应用参数值(未找到或无效)", + "unknown_famshr_mobapp_picture": "检查 FamShr、移动应用和图片参数值(未找到或无效)", + "unknown_famshr_picture": "检查 FamShr 和图片参数值(未找到或无效)", + "unknown_fmf_mobapp": "检查 FmF 和移动应用参数值(未找到或无效)", + "unknown_fmf_mobapp_picture": "检查 FmF、移动应用和图片参数值(未找到或无效)", + "unknown_fmf_picture": "检查 FmF 和图片参数值(未找到或无效)", + "unknown_mobapp_picture": "检查移动应用和图片参数值(未找到或无效)", + + "tfz_selection_invalid": "该值必须是被跟踪的区域", + "time_factor_invalid_range": "'旅行时间乘数' 必须在 .1 到 .9 之间", + "fixed_interval_invalid_range": "'固定间隔' 必须为 0 (未使用) 或 >= 3 (推荐 > 5)", + "display_text_as_no_gtsign": "实际文本和显示文本之间缺少 '>'", + "display_text_as_no_actual": "未指定 '实际文本'", + "display_text_as_no_display_as": "未指定 '显示文本'", + "not_found_directory": "未找到目录", + "not_found_file": "未找到文件", + "duplicate_ic3_devicename": "此名称已被另一台 iCloud3 设备使用", + "already_assigned": "此选择已分配给另一台设备", + "mobapp_search_error": "警告:搜索失败 - 未找到以 iCloud3 或 FamShr 设备名开头的移动应用设备。选择 '无' 或要使用的设备。", + "duplicate_other_devicename": "此名称已在另一集成或平台中使用", + "action_completed": "请求的操作已完成", + "action_cancelled": "请求的操作已取消", + "restart_ha": "需要重启家庭助理以继续", + "excluded_sensors_ha_restart": "已更新排除的传感器。需要重启 HA" + }, + "step": { + "menu": { + "title": "iCloud3 配置设置", + "data": { + "menu_items": "", + "action_items": "═════════════════════════════════════════════════════" + } + }, + "menu_0": { + "title": "配置设备和传感器菜单", + "data": { + "menu_items": "", + "action_items": "═════════════════════════════════════════════════════" + } + }, + "menu_1": { + "title": "配置参数菜单", + "data": { + "menu_items": "", + "action_items": "═════════════════════════════════════════════════════" + } + }, + "restart_icloud3": { + "title": "确认重新启动 iCloud3", + "description": "注意:跟踪设备的变更需要重新启动 iCloud3", + "data": { + "action_items": "═════════════════════════════════════════════════════" + } + }, + "restart_ha_ic3": { + "title": "重新启动家庭助理或 iCloud3", + "description": "重新启动家庭助理或重新加载并重新初始化 iCloud3 集成\n\n重新启动后必须刷新 iCloud3 仪表板屏幕", + "data": { + "action_items": "═════════════════════════════════════════════════════" + } + }, + "restart_ha_ic3_load_error": { + "title": "iCloud3 加载错误", + "description": "当 HA 启动时,iCloud3 未加载和初始化。再次重新加载 iCloud3 或重新启动 HA", + "data": { + "action_items": "═════════════════════════════════════════════════════" + } + }, + "confirm_action": { + "title": "确认所选操作", + "data": { + "action_items": "═════════════════════════════════════════════════════" + } + }, + "icloud_account": { + "title": "iCloud 账户和移动应用数据源", + "description": "数据源提供 iCloud3 用于跟踪 iDevice 的位置和其他信息。\n\n它们包括:\n• ICLOUD 账户 - 苹果 iCloud Web 服务为家庭共享列表中的设备提供位置和其他信息。\n• 移动应用 - 安装在 iPhone 和 iPad 上的 HA 伴侣应用提供区域进入/退出触发器和位置信息。使用此数据服务需要安装移动应用集成。\n\n家庭共享列表和移动应用中的设备被分配给 iCloud3 在更新设备屏幕上跟踪的每个设备。", + "data": { + "data_source_icloud": "═════════════════════════════════════════════════════ ICLOUD 账户 - 位置数据由苹果 iCloud 账户提供", + "username": "APPLE ID (用户名) - 用于登录 iCloud 账户的电子邮件地址", + "password": "密码 - iCloud 账户密码", + "url_suffix_china": "中国用户 - 使用位于中国的苹果 iCloud Web 服务器 (.cn URL 后缀)", + "data_source_mobapp": "═════════════════════════════════════════════════════ 移动应用 - 位置数据由 HA 移动应用提供", + "action_items": "═════════════════════════════════════════════════════ 操作命令" + }, + "data_description": {} + }, + "reauth": { + "title": "苹果 ID 验证码", + "description": "输入您刚从苹果收到的六位数验证码", + "data": { + "verification_code": "ICLOUD 账户验证代码", + "action_items": "═════════════════════════════════════════════════════ 操作命令" + } + }, + "device_list": { + "title": "iCloud3 设备", + "description": "iCloud3 可跟踪或监控最多 10 个设备。它们在此列出。\n\n此屏幕用于选择需要更新的设备,添加新设备并删除不再跟踪的设备。", + "data": { + "xdevices": "═════════════════════════════════════════════════════ ICLOUD3 设备", + "devices": "═════════════════════════════════════════════════════", + "action_items": "═════════════════════════════════════════════════════ 操作命令" + } + }, + "add_device": { + "title": "添加 iCloud3 设备", + "data": { + "ic3_devicename": "ICLOUD3 DEVICE_TRACKER ENTITY ID - 分配给此设备的 HA device_tracker 实体", + "fname": "友好名称 - 显示在 HA device_tracker 和传感器名称以及事件日志中", + "device_type": "设备类型 - iPhone,iPad,Watch 等", + "tracking_mode": "跟踪模式 - 应如何执行位置请求(完全跟踪,监控,非活动)", + "mobapp": "已安装移动应用 - 此设备上安装了 HA 移动应用", + "action_items": "═════════════════════════════════════════════════════ 操作命令" + }, + "data_description": {} + }, + "update_device": { + "title": "更新 iCloud3 设备", + "description": "此屏幕允许您配置可以使用 iCloud3 跟踪或监控的每个设备", + "data": { + "ic3_devicename": "ICLOUD3 DEVICE_TRACKER ENTITY ID - 分配给此设备的 HA device_tracker 实体", + "fname": "友好名称 - 显示在 HA device_tracker 和传感器名称以及事件日志中", + "device_type": "设备类型 - iPhone,iPad,Watch 等", + "tracking_mode": "跟踪模式 - 应如何执行位置请求(完全跟踪,监控,非活动)", + "famshr_devicename": "家庭共享列表设备 - 使用此 iCloud 账户家庭共享成员的位置数据", + "fmf_email": "查找我的朋友设备 - 使用与您共享其信息的 FindMy 设备的位置数据", + "mobile_app_device": "移动应用 DEVICE_TRACKER ENTITY - 使用此 HA 设备的位置数据和区域进入/退出触发器", + "log_zones": "记录区域活动 - 保存进入/退出区域信息(日期,时间,距离)到电子表格 .csv 文件", + "track_from_zones": "从区域跟踪 - 从家和其他区域跟踪旅行时间和距离", + "track_from_base_zone": "主要从家区域跟踪覆盖 - 使用此区域而不是家作为跟踪结果", + "picture": "图片 - 通常使用此设备的人的照片图像(44x44 像素是一个好的尺寸)", + "inzone_interval": "区域内间隔", + "fixed_interval": "固定间隔", + "action_items": "═════════════════════════════════════════════════════ 操作命令" + }, + "data_description": { + "inzone_interval": "区域内位置请求之间的时间", + "fixed_interval": "非区域内位置请求之间的固定时间。iCloud3 计算下一次定位请求的间隔并使用计算值(如果未设置 (= 0))。当计算间隔小于 5 分钟、当前位置数据过时、设备离线或设备不在区域内时,将不使用此值。", + "track_from_base_zone": "通常,家区域用作所有跟踪的主要跟踪区域(旅行时间,距离等)。然而,如果您长时间离家或设备通常在另一个位置(度假屋,第二家,父母家等),可以使用不同的区域作为主要跟踪区域。这可以在特殊区域屏幕上为所有设备全局设置。" + } + }, + "delete_device": { + "title": "删除 iCloud3 设备", + "data": { + "action_items": "═════════════════════════════════════════════════════ 删除选项" + } + }, + "review_inactive_devices": { + "title": "查看未跟踪(非活动)设备", + "description": "设备的 '跟踪模式' 设置为 '非活动',将不会被定位或跟踪。", + "data": { + "inactive_devices": "═════════════════════════════════════════════════════ INACTIVE ICLOUD3 设备", + "action_items": "═════════════════════════════════════════════════════ 操作命令" + } + }, + "change_device_order": { + "title": "事件日志设备显示顺序", + "description": "设备在事件日志标题区域和各种事件日志消息中按以下顺序显示。\n\n此屏幕允许您更改设备的顺序。", + "data": { + "device_desc": "═════════════════════════════════════════════════════ ICLOUD3 设备", + "action_items": "═════════════════════════════════════════════════════ 操作命令" + } + }, + "away_time_zone": { + "title": "外出时显示位置时区", + "description": "事件日志和传感器显示的时间显示事件发生时使用的家庭助理计算机的 '家庭时区' 时间。当您远离家并处于另一个时区时,您的跟踪事件仍然基于您的家庭 '时区' 时间,而不是您当前位置的时间。\n\n此屏幕允许您使用您当前位置的时区显示时间事件。", + "data": { + "away_time_zone_1_devices": "外出时区 #1 的设备", + "away_time_zone_1_offset": "当前位置 #1 的时间和时区调整", + "away_time_zone_2_devices": "外出时区 #2 的设备", + "away_time_zone_2_offset": "当前位置 #2 的时间和时区调整", + "action_items": "═════════════════════════════════════════════════════ 操作命令" + }, + "data_description": { + "time_zone_1_offset": "主要设备和位置时间 - 外出并处于另一个时区时的设备和当前位置时间", + "time_zone_2_offset": "次要设备和位置时间 - 外出并处于另一个时区时的设备和当前位置时间" + } + }, + "format_settings": { + "title": "格式设置", + "description": "跟踪活动、结果和信息消息显示在事件日志、传感器和跟踪和监控设备的 device_tracker 实体中。\n\n此屏幕用于指定如何显示这些结果。", + "data": { + "log_level": "日志级别 - iCloud3 添加到 HA 日志文件的消息类型", + "log_level_devices": "日志级别 RAWDATA 设备过滤器 - 仅为这些设备将原始数据转储到日志文件", + "display_zone_format": "事件日志区域显示名称 - 传感器和事件日志中区域名称的显示方式", + "device_tracker_state_source": "设备跟踪器状态值 - 确定设备的 device_tracker 实体状态值的方式", + "time_format": "时间格式 - 传感器和事件日志中时间字段的显示方式", + "unit_of_measurement": "度量单位 - 传感器和事件日志中距离字段的显示方式", + "display_gps_lat_long2": "显示 GPS 坐标 - 在事件日志中显示 GPS(纬度、经度/±精度)或仅显示 GPS(/±精度)", + "display_gps_lat_long": "显示 GPS 坐标 - 在事件日志中显示 GPS-(22.32771, -76.33073/±35m) 而不是 GPS-/±35m", + "action_items": "═════════════════════════════════════════════════════ 操作命令" + }, + "data_description": { + "device_tracker_state_source": "HA 使用设备的 GPS 坐标来确定区域。不考虑 GPS 精度,因此当 GPS 漫游到区域外时可能会退出区域。iCloud3 考虑 GPS 精度,在发生这种情况时不会退出区域。iCloud3 将显示区域的友好名称或在事件日志中显示的区域名称。" + } + }, + "display_text_as": { + "title": "事件日志 '显示文本为'", + "description": "事件日志屏幕上显示的某些文本字段,例如电子邮件地址或电话号码,是私人的,不应显示。例如,您可以将 'geekstergary@apple.com' 替换为 'gary@email.com'。\n\n此屏幕用于指定原始文本和应显示的文本。", + "data": { + "display_text_as": "═════════════════════════════════════════════════════ TEXT REPLACEMENT FIELDS - [Actual text > Displayed text]", + "action_items": "═════════════════════════════════════════════════════ 操作命令" + } + }, + "display_text_as_update": { + "title": "更新事件日志 '显示文本为' 值", + "data": { + "text_from": "原始文本 - 要替换的文本 (例如: gary_real_email@gmail.com)", + "text_to": "显示文本- 要显示的文本 (显示: gary@email.com)", + "action_items": "═════════════════════════════════════════════════════ 操作命令" + }, + "data_description": {} + }, + "actions": { + "title": "iCloud3 操作命令", + "data": { + "ic3_actions": "═════════════════════════════════════════════════════ ICLOUD3 GENERAL CONTROL ACTIONS", + "debug_actions": "═════════════════════════════════════════════════════ DEBUG LOG ACTIONS", + "other_actions": "═════════════════════════════════════════════════════ OTHER ACTIONS", + "action_items": "═════════════════════════════════════════════════════ 操作命令" + } + }, + "inzone_intervals": { + "title": "inZone 参数和默认间隔", + "description": "inZone 间隔是设备在区域内时位置请求之间的时间。\n\n此屏幕用于为不同类型的设备设置默认值。添加设备时会将此值分配给设备。\n\n注意:在更新设备屏幕上,可以为每个设备设置不同的 inZone 间隔。", + "data": { + "iphone": "IPHONE & IPOD", + "ipad": "IPAD", + "watch": "APPLE WATCH", + "airpods": "AIRPODS", + "no_mobapp": "移动应用未安装", + "other": "其他设备类型", + "distance_between_devices": "确定设备之间的距离。使用附近设备的跟踪结果", + "action_items": "═════════════════════════════════════════════════════ 操作命令" + }, + "data_description": { + "no_mobapp": "如果不使用移动应用进行位置监控和区域进入/退出触发器,则默认间隔", + "other": "未指定设备类型的 inzone 间隔" + } + }, + "waze_main": { + "title": "Waze - 路线服务旅行时间/距离", + "data": { + "waze_used": "═════════════════════════════════════════════════════ WAZE 路线服务", + "waze_region": "路线服务器位置 - 您所在地区的 Waze 路线服务器位置", + "waze_realtime": "使用实时数据 - Waze 在确定旅行时间时应考虑交通延迟", + "waze_min_distance": "WAZE 最小距离", + "waze_max_distance": "WAZE 最大距离", + "waze_history_database_used": "═════════════════════════════════════════════════════ WAZE 历史数据库", + "waze_history_track_direction": "一般旅行方向 - 用于在保存位置之间显示 '地图追踪线'", + "waze_history_max_distance": "历史最大距离", + "action_items": "═════════════════════════════════════════════════════ 操作命令" + }, + "data_description": { + "waze_min_distance": "当区域距离大于此值时使用 Waze 路线服务", + "waze_max_distance": "当区域距离大于此值时不使用 Waze 路线服务", + "waze_history_max_distance": "如果距离大于此值,则不将 Waze 旅行时间和距离保存到 Waze 历史数据库" + } + }, + "special_zones": { + "title": "特殊区域", + "description": "此屏幕用于配置:\n• 静止区域 - 当设备在短时间内位于同一位置时创建\n• 进入区域延迟时间 - 延迟处理区域进入触发器\n• 在另一个位置的临时\"家\"区域", + "data": { + "stat_zone_header": "═════════════════════════════════════════════════════ 静止区域", + "stat_zone_fname": "友好名称基础 - 在静止区域时显示的名称 (StatZone)", + "stat_zone_still_time": "无移动时间", + "stat_zone_inzone_interval": "区域内间隔", + "passthru_zone_header": "═════════════════════════════════════════════════════ 进入区域延迟", + "passthru_zone_time": "进入区域延迟时间", + "track_from_base_zone_used": "═════════════════════════════════════════════════════ 主要从家区域跟踪覆盖", + "track_from_base_zone": "跟踪来源区域 - 使用此区域而不是家作为所有设备的跟踪结果。全局设置", + "track_from_home_zone": "从家区域跟踪 - 当主要跟踪来源区域不是家时,保持从家区域跟踪", + "action_items": "═════════════════════════════════════════════════════ 操作命令" + }, + "data_description": { + "passthru_zone_time": "延迟处理您可能正在驾驶通过而实际上并未进入的进入区域触发器", + "stat_zone_still_time": "在同一位置停留一段时间后进入静止区域", + "stat_zone_inzone_interval": "在静止区域内位置请求之间的时间间隔", + "stat_zone_fname": "在设备处于静止区域时,在事件日志、device_tracker、旅行方向和区域实体中显示此值。iCloud3 为静止区域分配编号。使用通配符字符 '#' 在静止区域的名称中显示此值 (StatZon1, StatZon2, etc)。注意:7 个字母的名称留出空间以便在 iPhone 屏幕上显示名称和编号而不被截断。" + } + }, + "sensors": { + "title": "传感器", + "description": "许多传感器用于显示设备的跟踪结果和其他信息。\n\n此屏幕用于选择应创建的传感器。", + "data": { + "monitored_devices": "═════════════════════════════════════════════════════ MONITORED DEVICE SENSORS - 为受监控设备选择要创建的传感器类型", + "device": "═════════════════════════════════════════════════════ DEVICE SENSORS - 设备状态和信息", + "tracking_update": "═════════════════════════════════════════════════════ LOCATION UPDATE SENSORS - 设备位置更新时间", + "tracking_time": "═════════════════════════════════════════════════════ TIME SENSORS - 设备跟踪定时器", + "tracking_distance": "═════════════════════════════════════════════════════ DISTANCE SENSORS - 设备跟踪距离", + "track_from_zones": "═════════════════════════════════════════════════════ TRACK FROM MULTIPLE ZONE SENSORS - 当从多个区域跟踪时使用(仅从家区域跟踪时不需要)", + "tracking_other": "═════════════════════════════════════════════════════ OTHER TRACKING SENSORS - 通常不使用但可用", + "zone": "═════════════════════════════════════════════════════ ZONE SENSORS - 设备区域状态和信息", + "other": "═════════════════════════════════════════════════════ OTHER SENSORS - 不在上述区域中的传感器", + "excluded_sensors": "═════════════════════════════════════════════════════ EXCLUDED SENSORS - 当 iCloud3 启动时不会创建的传感器", + "action_items": "═════════════════════════════════════════════════════ 操作命令" + } + }, + "exclude_sensors": { + "title": "排除传感器", + "description": "为设备创建了许多传感器,但有时您可能希望不为特定设备创建传感器。例如,您可能希望建立一个电池传感器,但除了一个设备外。\n\n此屏幕让您指定不应创建的传感器实体名称。", + "data": { + "excluded_sensors": "EXCLUDED SENSORS - 当 iCloud3 启动时不会创建的传感器", + "filter": "FILTER DISPLAYED SENSORS - 选择应显示的传感器", + "filtered_sensors": "ICLOUD3 SENSORS - 当 iCloud3 启动时创建的传感器列表", + "action_items": "═════════════════════════════════════════════════════ ACTION COMMANDS" + } + }, + "tracking_parameters": { + "title": "跟踪和其他参数", + "description": "一些参数不属于其他任何一般类别,很少更改。\n\n此屏幕用于指定这些参数。", + "data": { + "log_level": "LOG LEVEL - 在 iCloud3 操作期间添加到 HA 日志文件的消息类型", + "gps_accuracy_threshold": "GPS 精度阈值", + "old_location_threshold": "旧位置阈值", + "old_location_adjustment": "旧位置调整", + "distance_between_devices": "使用附近设备的位置结果", + "max_interval": "最大间隔", + "exit_zone_interval": "退出区域间隔", + "mobapp_alive_interval": "请求移动应用位置间隔", + "tfz_tracking_max_distance": "跟踪来源区域显示距离", + "offline_interval": "设备离线间隔", + "travel_time_factor": "旅行时间间隔和下一次位置更新乘数", + "discard_poor_gps_inzone": "丢弃区域内 GPS 精度差的结果 - 当在区域内时丢弃 GPS 精度差的位置更新", + "picture_www_dirs": "包含图片图像的 WWW 目录 - Update Devices > Pictures 文件位置的筛选器", + "event_log_card_directory": "EVENT LOG CARD LOVELACE RESOURCES DIRECTORY - 事件日志自定义卡片 .js 文件目录", + "event_log_btnconfig_url": "EVENT LOG CONFIGURE BUTTON (GEAR) URL > 显示 HA 配置设置屏幕的特殊 URL", + "action_items": "═════════════════════════════════════════════════════ 操作命令" + }, + "data_description": { + "old_location_threshold": "早于此值的位置将被丢弃", + "old_location_adjustment": "增加这个时间来决定位置是否旧", + "distance_between_devices": "当更新跟踪结果时,会识别出附近的设备。当更新这些设备的跟踪结果时,可以使用最初更新的跟踪结果代替。这样可以提高性能,因为不需要再次请求 Waze 路线时间和距离", + "gps_accuracy_threshold": "GPS 精度高于此值的位置将被丢弃", + "tfz_tracking_max_distance": "通常显示家区域的时间和距离数据在设备的 device_tracker 和传感器实体上。当设备在跟踪来源区域的此距离内时,显示跟踪来源区域而不是家区域", + "max_interval": "位置请求之间的最大时间", + "exit_zone_interval": "退出区域后的第一次位置请求时间", + "mobapp_alive_interval": "如果在此时间后没有联系,则向移动应用发送位置请求。这将检查移动应用是否响应位置请求或是否休眠且未运行。", + "offline_interval": "离线时的位置请求间隔(飞行模式,无手机区域等)", + "travel_time_factor": "这用于计算前往家的间隔和下一个位置时间。较小的值将减少间隔时间并增加位置请求,较大的值将增加间隔并减少位置请求。", + "event_log_btnconfig_url": "通常,这是空白的,iCloud3 将确定其配置设置屏幕的 URL。然而,如果因在虚拟环境、docker 或另一设备上运行 HA 而出现问题,并且无法确定实际 URL,可能会遇到 404 未找到错误。如果发生这种情况,以正常方式选择它(HA 设备和服务 > 集成 > iCloud3 > 配置设置齿轮),并将浏览器中的 URL 复制到此字段。" + } + } + } + }, + "services": { + "action": { + "name": "Action", + "description": "此服务将向 iCloud3 发送操作命令", + "fields": { + "command": { + "name": "Command", + "description": "(必需) 要发送到 iCloud3 的操作命令" + }, + "device_name": { + "name": "Device Name", + "description": "(可选) 适用于所有设备或仅适用于所选设备" + } + } + }, + "update": { + "name": "Update", + "description": "更新服务已由 Action 服务替代" + }, + "restart": { + "name": "Restart", + "description": "此服务将重启 iCloud3" + }, + "find_iphone_alert": { + "name": "Find iPhone Alert Tone", + "description": "此服务将向您想要找到的设备发送警报音", + "fields": { + "device_name": { + "name": "Device Name", + "description": "应发送 Find iPhone 警报和消息的设备" + } + } + }, + "lost_device_alert": { + "name": "Send Lost Device Message", + "description": "此服务将向丢失的 iPhone 发送消息和电话号码", + "fields": { + "device_name": { + "name": "Device Name", + "description": "应发送 Find iPhone 警报和消息的设备" + }, + "number": { + "name": "Phone Number", + "description": "要发送消息的电话号码" + }, + "message": { + "name": "Message", + "description": "要发送的消息" + } + } + } + } +} \ No newline at end of file