Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Health Parsers #1072

Merged
merged 2 commits into from
Mar 3, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 71 additions & 14 deletions scripts/artifacts/health.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,17 +178,28 @@
"paths": ('*Health/healthdb_secure.sqlite*',),
"output_types": "standard",
"artifact_icon": 'moon'
},
"healthSourceDevices": {
"name": "Health - Source Devices",
"description": "Parses Apple Health device info from the healthdb.sqlite database, including make/model/software information.",
"author": "@stark4n6",
"version": "0.1",
"date": "2025-03-03",
"requirements": "none",
"category": "Health",
"notes": "",
"paths": ('*Health/healthdb.sqlite*',),
"output_types": "standard",
"artifact_icon": 'smartphone'
}
}


import scripts.artifacts.artGlobals

from packaging import version
from scripts.builds_ids import OS_build, device_id
from scripts.ilapfuncs import artifact_processor, open_sqlite_db_readonly, attach_sqlite_db_readonly, convert_ts_human_to_timezone_offset


@artifact_processor
def healthWorkouts(files_found, report_folder, seeker, wrap_text, timezone_offset):
data_list = []
Expand Down Expand Up @@ -359,6 +370,11 @@ def healthWorkouts(files_found, report_folder, seeker, wrap_text, timezone_offse
CASE workout_activities.activity_type''' + activity_types + '''
ELSE "Unknown" || "-" || workout_activities.activity_type
END AS 'Type',
CASE workout_activities.location_type
WHEN 2 THEN 'Indoor'
WHEN 3 THEN 'Outdoor'
ELSE workout_activities.location_type
END AS 'Location Type',
strftime('%H:%M:%S', samples.end_date - samples.start_date, 'unixepoch') AS 'Total Time Duration',
strftime('%H:%M:%S', workout_activities.duration, 'unixepoch') AS 'Duration',
''' + distance_and_goals + '''
Expand Down Expand Up @@ -414,28 +430,28 @@ def healthWorkouts(files_found, report_folder, seeker, wrap_text, timezone_offse
for row in all_rows:
start_timestamp = convert_ts_human_to_timezone_offset(row[0], timezone_offset)
end_timestamp = convert_ts_human_to_timezone_offset(row[1], timezone_offset)
added_timestamp = convert_ts_human_to_timezone_offset(row[25], timezone_offset)
hardware = device_id.get(row[21], row[21])
added_timestamp = convert_ts_human_to_timezone_offset(row[26], timezone_offset)
hardware = device_id.get(row[22], row[22])
os_family = ''

if 'Watch' in row[21]:
if 'Watch' in row[22]:
os_family = 'watchOS '
elif 'iPhone' in row[21]:
elif 'iPhone' in row[22]:
os_family = 'iOS '

software_version = f'{os_family}{row[23]}'
software_version = f'{os_family}{row[24]}'

if row[15]:
celcius_temp = round(((row[15] - 32) * (5 / 9)), 2)
if row[16]:
celcius_temp = round(((row[16] - 32) * (5 / 9)), 2)

data_list.append(
(start_timestamp, end_timestamp, row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9],
row[10], row[11], row[12], row[13], row[14], celcius_temp, row[15], row[16], row[17], row[18],
row[19], row[20], hardware, row[22], software_version, row[24], added_timestamp)
(start_timestamp, end_timestamp, str(row[2]).title(), row[3], row[4], row[5], row[6], row[7], row[8], row[9],
row[10], row[11], row[12], row[13], row[14], row[15], celcius_temp, row[16], row[17], row[18], row[19],
row[20], row[21], hardware, row[23], software_version, row[25], added_timestamp)
)

data_headers = (
('Start Timestamp', 'datetime'), ('End Timestamp', 'datetime'), 'Type', 'Total Time Duration', 'Duration', 'Distance (in KM)',
('Start Timestamp', 'datetime'), ('End Timestamp', 'datetime'), 'Activity Type', 'Location Type','Total Time Duration', 'Duration', 'Distance (in KM)',
'Distance (in Miles)', 'Goal Type', 'Goal', 'Total Active Energy (kcal)', 'Total Resting Energy (kcal)', 'Average METs',
'Min. Heart Rate (BPM)', 'Max. Heart Rate (BPM)', 'Average Heart Rate (BPM)', 'Temperature (°C)', 'Temperature (°F)',
'Humidity (%)', 'Latitude', 'Longitude', 'Min. ground elevation (in Meters)', 'Max. ground elevation (in Meters)',
Expand Down Expand Up @@ -1060,8 +1076,49 @@ def healthWatchBySleepPeriod(files_found, report_folder, seeker, wrap_text, time
(start_timestamp, end_timestamp, row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11]))

data_headers = (
('Sleep Start Time'), ('Sleep End Time'), 'Time in Bed (HH:MM:SS)',
('Sleep Start Time', 'datetime'), ('Sleep End Time', 'datetime'), 'Time in Bed (HH:MM:SS)',
'Time Asleep (HH:MM:SS)', 'Awake Duration (HH:MM:SS)',
'REM Duration (HH:MM:SS)', 'Core Duration (HH:MM:SS)',
'Deep Duration (HH:MM:SS)', 'Awake %', 'REM %', 'Core %', 'Deep %')
return data_headers, data_list, healthdb_secure

@artifact_processor
def healthSourceDevices(files_found, report_folder, seeker, wrap_text, timezone_offset):
data_list = []
healthdb = ''

for file_found in files_found:
if file_found.endswith('healthdb.sqlite'):
healthdb = file_found
break

with open_sqlite_db_readonly(healthdb) as db:
cursor = db.cursor()
cursor.execute('''
select
DATETIME(creation_date + 978307200, 'UNIXEPOCH') AS creation_date,
name,
manufacturer,
model,
hardware,
firmware,
software,
localIdentifier,
sync_provenance,
sync_identity
from source_devices
where name not like '__NONE__' and localIdentifier not like '__NONE__'
''')

all_rows = cursor.fetchall()

for row in all_rows:
creation_date = convert_ts_human_to_timezone_offset(row[0], timezone_offset)
device = device_id.get(row[4], row[4])
localID = ''
if str(row[7]).endswith('-tacl'):
localID = str(row[7])[:-5]
data_list.append((creation_date, row[1], row[2], row[3], device, row[5], row[6], localID, row[8], row[9]))

data_headers = (('Creation Date', 'datetime'),'Device Name','Manufacturer','Model','Hardware','Firmware','Software','Local ID','Sync Provenance','Sync ID')
return data_headers, data_list, healthdb