v10: full version — HealthKit reading + POST to /ingest
This commit is contained in:
parent
7b7d2ac0b3
commit
efc1bc82ab
1 changed files with 159 additions and 20 deletions
|
|
@ -1,39 +1,178 @@
|
|||
// @ts-nocheck
|
||||
// Health Sync v9 — minimal test with timeout + IP fallback
|
||||
// Health Sync v10 — full version: read HealthKit + POST to /ingest
|
||||
|
||||
import { Script } from "scripting"
|
||||
import { Script, Notification } from "scripting"
|
||||
|
||||
// icon-color: deep-blue; icon-glyph: heart;
|
||||
|
||||
async function main() {
|
||||
console.log("v9: script started")
|
||||
const SERVER_URL = "http://chronos:9432/ingest"
|
||||
const API_KEY = "333f09ce904e9cbbe3b578fda393be92869122c7222dbccd"
|
||||
|
||||
// Try hostname first, then LAN IP
|
||||
const urls = [
|
||||
"http://chronos:9432/health",
|
||||
"http://192.168.1.148:9432/health",
|
||||
"http://100.120.136.16:9432/health"
|
||||
]
|
||||
console.log("v10: script started")
|
||||
|
||||
for (const url of urls) {
|
||||
console.log("v9: trying " + url)
|
||||
// --- Units ---
|
||||
const U = {
|
||||
kg: HealthUnit.gramUnit(HealthUnitPrefix.kilo),
|
||||
mmHg: HealthUnit.millimeterOfMercury(),
|
||||
bpm: HealthUnit.count().divided(HealthUnit.minute()),
|
||||
count: HealthUnit.count(),
|
||||
percent: HealthUnit.percent()
|
||||
}
|
||||
|
||||
const readLatest = async (type, unit) => {
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
allowInsecureRequest: true,
|
||||
timeout: 5
|
||||
const samples = await Health.queryQuantitySamples(type, {
|
||||
limit: 1,
|
||||
sortDescriptors: [{ key: "startDate", order: "reverse" }]
|
||||
})
|
||||
const text = await response.text()
|
||||
console.log("v9: SUCCESS from " + url + " = " + text)
|
||||
break
|
||||
if (!samples || samples.length === 0) {
|
||||
console.log(`readLatest(${type}): no samples`)
|
||||
return null
|
||||
}
|
||||
const s = samples[0]
|
||||
const value = s.quantityValue(unit)
|
||||
console.log(`readLatest(${type}): value=${value} date=${s.startDate}`)
|
||||
return { value, startDate: s.startDate }
|
||||
} catch (e) {
|
||||
console.log("v9: FAILED " + url + " = " + e)
|
||||
console.log(`readLatest(${type}): error ${e}`)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
console.log("v9: done")
|
||||
const readTodaySum = async (type, unit) => {
|
||||
try {
|
||||
const start = new Date()
|
||||
start.setHours(0, 0, 0, 0)
|
||||
const samples = await Health.queryQuantitySamples(type, {
|
||||
startDate: start,
|
||||
endDate: new Date()
|
||||
})
|
||||
if (!samples || samples.length === 0) {
|
||||
console.log(`readTodaySum(${type}): no samples`)
|
||||
return null
|
||||
}
|
||||
let total = 0
|
||||
for (const s of samples) {
|
||||
total += s.quantityValue(unit)
|
||||
}
|
||||
console.log(`readTodaySum(${type}): total=${total}`)
|
||||
return total
|
||||
} catch (e) {
|
||||
console.log(`readTodaySum(${type}): error ${e}`)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const todayDate = () => new Date().toISOString().slice(0, 10)
|
||||
|
||||
const notify = async (body) => {
|
||||
console.log(`notify: ${body}`)
|
||||
await Notification.schedule({ title: "Health Sync", body })
|
||||
}
|
||||
|
||||
// --- Read health metrics ---
|
||||
console.log("v10: reading health metrics...")
|
||||
|
||||
const weight = await readLatest("bodyMass", U.kg)
|
||||
const bpSys = await readLatest("bloodPressureSystolic", U.mmHg)
|
||||
const bpDia = await readLatest("bloodPressureDiastolic", U.mmHg)
|
||||
const hr = await readLatest("heartRate", U.bpm)
|
||||
const restingHr = await readLatest("restingHeartRate", U.bpm)
|
||||
const steps = await readTodaySum("stepCount", U.count)
|
||||
const spo2 = await readLatest("oxygenSaturation", U.percent)
|
||||
|
||||
console.log(`v10: weight=${weight?.value} bpSys=${bpSys?.value} bpDia=${bpDia?.value} hr=${hr?.value} rhr=${restingHr?.value} steps=${steps} spo2=${spo2?.value}`)
|
||||
|
||||
// --- Build samples ---
|
||||
const samples = []
|
||||
|
||||
if (weight) {
|
||||
samples.push({
|
||||
type: "weight",
|
||||
timestamp_utc: weight.startDate.toISOString(),
|
||||
value: weight.value,
|
||||
source: "apple_health"
|
||||
})
|
||||
}
|
||||
|
||||
if (bpSys && bpDia) {
|
||||
const bpValue = { systolic: bpSys.value, diastolic: bpDia.value }
|
||||
if (hr) bpValue.pulse = hr.value
|
||||
samples.push({
|
||||
type: "blood_pressure",
|
||||
timestamp_utc: bpSys.startDate.toISOString(),
|
||||
value: bpValue,
|
||||
source: "omron"
|
||||
})
|
||||
}
|
||||
|
||||
if (hr && !bpSys) {
|
||||
samples.push({
|
||||
type: "heart_rate",
|
||||
timestamp_utc: hr.startDate.toISOString(),
|
||||
value: hr.value,
|
||||
source: "apple_health"
|
||||
})
|
||||
}
|
||||
|
||||
if (restingHr) {
|
||||
samples.push({
|
||||
type: "resting_heart_rate",
|
||||
timestamp_utc: restingHr.startDate.toISOString(),
|
||||
value: restingHr.value,
|
||||
source: "apple_health"
|
||||
})
|
||||
}
|
||||
|
||||
if (steps !== null) {
|
||||
samples.push({
|
||||
type: "steps",
|
||||
timestamp_utc: todayDate(),
|
||||
value: steps,
|
||||
source: "apple_health"
|
||||
})
|
||||
}
|
||||
|
||||
if (spo2) {
|
||||
samples.push({
|
||||
type: "spo2",
|
||||
timestamp_utc: spo2.startDate.toISOString(),
|
||||
value: spo2.value * 100,
|
||||
source: "apple_health"
|
||||
})
|
||||
}
|
||||
|
||||
console.log(`v10: ${samples.length} samples to send`)
|
||||
|
||||
// --- POST to server ---
|
||||
if (samples.length === 0) {
|
||||
await notify("No health data found today")
|
||||
} else {
|
||||
try {
|
||||
const response = await fetch(SERVER_URL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${API_KEY}`,
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({ samples }),
|
||||
allowInsecureRequest: true,
|
||||
timeout: 10
|
||||
})
|
||||
const res = await response.json()
|
||||
console.log(`v10: server response = ${JSON.stringify(res)}`)
|
||||
await notify(`Sent ${res.ingested || samples.length} samples`)
|
||||
} catch (e) {
|
||||
console.log(`v10: POST error = ${e}`)
|
||||
await notify(`Error: ${e.message || e}`)
|
||||
}
|
||||
}
|
||||
|
||||
console.log("v10: done")
|
||||
}
|
||||
|
||||
main().then(() => Script.exit()).catch((e) => {
|
||||
console.log("v9: main() error = " + e)
|
||||
console.log("v10: main() error = " + e)
|
||||
Script.exit()
|
||||
})
|
||||
Loading…
Add table
Reference in a new issue