Skip to content

Commit

Permalink
Merge pull request #1072 from stark4n6/main
Browse files Browse the repository at this point in the history
Update Health Parsers
  • Loading branch information
stark4n6 authored Mar 3, 2025
2 parents dad3ad5 + 9b9dab6 commit ffa8852
Showing 1 changed file with 71 additions and 14 deletions.
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

0 comments on commit ffa8852

Please sign in to comment.