Repository navigation
[WiP] Huawei API integration - #204
Draft
yatharthranjan wants to merge 48 commits into
Draft
yatharthranjan wants to merge 48 commits into
yatharthranjan wants to merge 48 commits into
Conversation
…erns Captures the two connector generations (generic kafka-connect-rest-source framework used by Fitbit vs. the newer oura-library + thin Connect glue split used by Oura), the runtime polling/auth/conversion flow, config and Docker/CI setup, and a checklist for adding a new vendor integration such as Huawei following the Oura pattern.
Registers the two new Gradle modules, pins radar-schemas-commons 0.9.0-SNAPSHOT (huawei_schemas branch not yet released) as a separate version-catalog entry so existing modules keep the stable release, and adds the user/UserRepository domain types mirroring oura-library's pattern.
Adds the domain logic for the Huawei Health Kit connector, mirroring oura-library's split of pure-Kotlin logic from Kafka Connect glue: - request/: RestRequest, RequestGenerator, HuaweiRequestGenerator (offset tracking, per-route backoff on 401/403/429/etc), HuaweiResult/HuaweiError. - route/: HuaweiRoute base (OAuth2-authorized GET/POST + time-range chunking), three concrete route kinds covering the Health Kit Data API's endpoints - HuaweiSampleSetRoute (POST sampleSet:polymerize, raw or groupByTime-aggregated), HuaweiHealthRecordRoute (GET healthRecords), HuaweiActivityRecordRoute (GET activityRecords) - and HuaweiRouteFactory, a single registry mapping all ~54 Huawei data types from the radar-huawei-connector schema spec to their endpoint, dataTypeName, and Avro record builder. - converter/: FieldValues (typed accessor for Huawei's field-value sample point format) and generic converters that turn API responses into TopicData for the registered Avro records. Field-value key names are best-effort (Huawei HiHealth Field naming convention); this was verified to compile against a locally-published radar-schemas-commons 0.9.0-SNAPSHOT (huawei_schemas branch) since none of the real snapshot hosts are reachable from this sandbox - flagged in comments for verification against a live API response.
Mirrors kafka-connect-oura-source: HuaweiSourceConnector (periodic user refresh + task reconfiguration), HuaweiSourceTask (round-robin polling across routes, JSON->Avro->SourceRecord conversion via AvroData), KafkaOffsetManager, and HuaweiServiceUserRepository (Ktor-based rest-source-authorizer client with OAuth2 client-credentials auth and cached user/token lookups). HuaweiRestSourceConnectorConfig is written in Kotlin and loop-generates its ~110 per-data-type `huawei.<key>.enabled`/`huawei.<key>.topic` ConfigDef entries from huawei-library's HuaweiRouteFactory.definitions registry, rather than hand-duplicating a static field per topic as Fitbit/Oura do - the same registry also drives which routes HuaweiSourceTask actually builds, so the connector config and the set of polled routes can never drift out of sync. Could not compile this module in this sandbox: packages.confluent.io (needed for kafka-connect-api/kafka-connect-avro-converter) is blocked by the sandbox's egress policy - confirmed this is pre-existing and applies equally to kafka-connect-oura-source, not something introduced here. huawei-library (the part of this change with real logic to verify) does compile cleanly against a locally-published radar-schemas-commons 0.9.0-SNAPSHOT.
Adds docker/source-huawei.properties.template, a radar-huawei-connector docker-compose service, a README section, and registers the kafka-connect-huawei-source image in both CI workflow matrices. Updates ARCHITECTURE.md to document the Huawei module and the route-registry config-generation pattern it introduces for connectors with a large number of data types.
huawei-library tests (verified passing in this sandbox against a locally published radar-schemas-commons 0.9.0-SNAPSHOT): - FieldValuesTest: typed-value array and flattened-object parsing. - HuaweiRouteFactoryTest: builds every one of the ~54 registered route definitions, feeds each a fixture payload shaped for its endpoint kind, and asserts the converter produces exactly one record on the expected topic without throwing - this caught two real bugs during development (a bad endTime field on HuaweiCgmBloodGlucose, and a private extension function shadowing HuaweiDataConverter's). kafka-connect-huawei-source gets a config test mirroring the existing *RestSourceConnectorConfigTest convention, plus checks that enabledTopics() reflects the shared HuaweiRouteFactory.definitions registry and honors per-type huawei.<key>.enabled overrides. Could not run this one in-sandbox (see prior commit: packages.confluent.io is blocked here for every kafka-connect-*-source module, pre-existing and unrelated to this change). Also switches huawei-library's JUnit integration from kotlin-test-junit (JUnit4) to kotlin-test-junit5, since oura-library's copy-pasted dependency block conflicts with the JUnit Platform the radar-kotlin Gradle plugin configures the test task with - previously unnoticed only because no *-library module had tests yet.
`./gradlew check` runs ktlint over every module; auto-formatted the mechanical issues (semicolons, wrapping) and manually split a handful of lines ktlint couldn't auto-correct. huawei-library:check is now fully green (ktlint + all 5 tests) in this sandbox.
ktlint is static analysis and doesn't need dependency resolution to run, so this module's Kotlin sources could be linted even though they can't be compiled in this sandbox. Wraps long ConfigDef.define() calls and doc-string constants, and simplifies the getUserRepository()/ initialize() flow slightly in the process.
Adds yatharthranjan@onsentia.com alongside the existing KCL address in the Huawei Docker image label and CI workflow author fields.
yatharthranjan
marked this pull request as draft
July 14, 2026 13:16
- Wrap the okhttp3.HttpUrl from getHuaweiUserRepositoryUrl() in URLBuilder(...).build() before passing it as createClient's Ktor Url parameter, matching how the token URL was already handled (and how OuraServiceUserRepository does it). - Throwable.message is nullable; fall back to a default string before passing it to UserNotAuthorizedException's non-null constructor. These only surfaced in real CI since packages.confluent.io is blocked in this sandbox, so kafka-connect-huawei-source couldn't be compiled here - verified via ktlint (which needs no dependency resolution) that the fix introduces no new style issues.
ktlintKotlinScriptCheck also lints .gradle.kts files; wraps the two GitHub Packages credential lines that exceeded 100 chars.
Applies a standard Apache-2.0 copyright header (Copyright 2026 Onsentia) to every .kt/.java file in huawei-library and kafka-connect-huawei-source - the two modules added for the Huawei Health Kit integration. Files that had copied The Hyve's 2018 header from the Oura pattern get it replaced; files with no header get one prepended. Pre-existing Fitbit/Oura files are left untouched since their copyright belongs to their original authors.
Was still copied from Oura's Dockerfile (Copyright 2018 The Hyve); updates to match the Onsentia header applied to the rest of this module's files.
Adds an @author yatharthranjan KDoc/Javadoc tag to the primary class/interface/object of every .kt/.java file in huawei-library and kafka-connect-huawei-source: appended to existing class-level doc comments where present, added as a new minimal doc comment otherwise.
Lets the Huawei connector run against per-user YAML credential files under huawei.user.dir, mirroring Fitbit's YamlUserRepository, so it can be tested locally without a rest-source-authorizer webservice. Adds a docker/huawei-user.yml.template and README instructions for the flow. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
isAuthorizedOverride was mapped to the same JSON key ("isAuthorized")
as the isAuthorized computed property, causing Jackson to fail with
"Conflicting getter definitions" when reading user YAML files. Rename
the manual-override field's JSON key to "authorized".
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
The 400/401/403 branches previously logged only a hardcoded generic message, discarding the real error body Huawei's API returned. This made it impossible to diagnose the actual cause of a failed request (e.g. wrong client/app, invalid scope) from the logs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
HuaweiHealthRecordRoute sent the health record type identifier under a "subDataTypeName" query parameter, but Huawei's healthRecords API expects it under "dataTypeName" - the API was silently rejecting every health_record_* request with "DataTypeName is null" since it never received the parameter it actually looks for. Renamed the parameter (and the route/factory field) to dataTypeName throughout. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
Per Huawei's official "Querying Health Records of a Data Type" spec: - The endpoint is on API version v2, not v1. - The data type query parameter is named "dataType", not "dataTypeName" (and not "subDataTypeName" as it was before that). - startTime/endTime, both in the request and in each returned record, are in nanoseconds since the epoch, not milliseconds. This was causing every health_record_* request to fail with "DataTypeName is null", and would have produced wildly wrong timestamps for any record that did come back. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
The generic error branch (405/409/500/502/503/590, and anything else not explicitly handled) hardcoded its HuaweiGenericError's code to "500" regardless of the actual response status, which was misleading for diagnostics. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
Per the official Health Kit REST API reference (Postman "HMS Core" collection, "Querying Sampling Data Statistics of Multiple Days"), the groupByTime-aggregated variant of a data type is obtained by polymerizing the underlying *raw* dataTypeName with groupByTime, not by sending a literal "*.statistics"-suffixed dataTypeName - that suffix is only RADAR-Schemas'/this connector's own label for "the daily-aggregated route", not a real Huawei data type identifier. Sending it verbatim is exactly why Huawei's API had no dataCollector for e.g. "com.huawei.continuous.heart_rate.statistics", "com.huawei.vo2max.statistics", "com.huawei.resting_calories.statistics", etc. - across every single statistics route built via sampleSetDefinition(). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
…erize Huawei's live API confirmed the root cause behind most "no default dataCollector found"/"Invalid dataTypeName" errors on statistics routes: sampleSet:polymerize does not support a groupByTime-aggregated query for every data type (confirmed live: "com.huawei.resting_calories does not support the query mode, please use dailyPolymerize API"). Per the official REST API reference for "Querying Sampling Data Statistics of Multiple Days", the day-aggregated variant of a data type must instead go through the dedicated POST /healthkit/v2/sampleSet:dailyPolymerize endpoint, which takes a startDay/endDay (yyyyMMdd) + timeZone request body and returns a differently-shaped, doubly-nested group[].sampleSet[].samplePoints[] response (with group-level times in milliseconds but sample-point times in nanoseconds). Adds HuaweiDailyPolymerizeRoute/HuaweiDailyPolymerizeConverter and routes every "*.statistics" definition through it instead of HuaweiSampleSetRoute, which now only handles raw (non-statistics) data types and has had its now-dead groupByTime support removed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
Per Huawei's "Querying Created Exercise Records" spec: - The endpoint is on API version v2, not v1. - The response's array of records is under the key "activityRecord" (singular), not "activityRecords" - we were reading the wrong key, so every activity_record request was silently producing zero records regardless of what the API actually returned, with no error logged. - Each record's description field is "desc", not "description". Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
"com.huawei.daily_activity_summary" is not a real sampleSet dataTypeName (confirmed live: "no default dataCollector found"). The goal fields this route maps to actually belong to a separate endpoint (GET /healthkit/v2/sampleConfigs?type=9002&id=<...>, "Querying Activity Goals" - one call per goal type), while the achieved-value fields would need to come from the existing continuous/statistics routes. That needs a route that issues multiple requests and merges them, which is a real design decision rather than an endpoint/field-name fix like the other routes touched this session, so disable it by default (huawei.daily_activity_summary.enabled=false) rather than leave it erroring or guess at a composite implementation. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
"*.total" is never a valid Huawei request dataTypeName (confirmed live: "no default dataCollector found for: com.huawei.continuous.steps.total"), matching the official dailyPolymerize doc's own example: it requests "com.huawei.continuous.steps.delta" and gets back a response labelled "com.huawei.continuous.steps.total". Adds queryDataTypeSuffix/ useDailyPolymerize overrides to sampleSetDefinition() and points continuous_steps_total, continuous_distance_total, and continuous_calories_burnt_total at their sibling raw/delta data type through the dailyPolymerize endpoint instead of requesting the ".total" name directly via plain polymerize. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
Per the official "SpO2" data type reference: the statistics data type is documented under the "continuous." namespace (com.huawei.continuous.spo2.statistics), but its underlying raw detailed data type is under a different namespace entirely (com.huawei.instantaneous.spo2, not com.huawei.continuous.spo2, which doesn't exist - matching the live "Invalid dataTypeName." error). Also corrects the field-value keys read from the response (saturation_avg/max/min/last, not the generic avg/max/min/last this route had guessed). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
…ve_hours Per the official "Daily Activity" data type reference pages: - daily_activity_summary is itself a real "Atomic Sampling Statistical Data Type" queried by day via dailyPolymerize - it's not a derived label needing a different raw type, and not the separate sampleConfigs-based "Workout Goals" endpoint as previously assumed. Re-enabled by default and switched to dailyPolymerize, with field keys corrected to the documented camelCase names (steps, activeCalories, exerciseTime, activeHours, stepsGoal, activeCaloriesGoal, exerciseTimeGoal, activeHoursGoal), matching the Avro schema's own field names. - continuous_distance_total's single field is "distance", not "distance_total". - continuous_altitude_statistics's underlying raw data type is "com.huawei.instantaneous.altitude" (a different namespace than its "continuous."-labelled statistics name), the same namespace mismatch already fixed for SpO2. - active_hours (raw) and active_hours_statistics were incorrectly sharing one builder function reading the same field keys, but they have genuinely different response shapes: the raw type's only field is "isActive", while the statistics type's only field is "activeHours" (with no moderate/high intensity minute breakdown on either, unlike what the shared builder assumed). Split into two builder functions with the correct field keys for each. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
…ityMap schema
RADAR-Schemas PR #430 replaced HuaweiContinuousExerciseIntensityV2Statistics's
five fixed heart-rate-zone fields (zone1Duration..zone5Duration) with a
single intensityMap field (Map<String, Int> of Huawei exercise type
code to duration in minutes), matching what Huawei's API actually
returns for this data type - not per-HR-zone durations.
Adds FieldValues.getIntMap() to read a Map<Integer, Integer>-typed
field from the sampleSet response, and wires the route to populate
intensityMap from the "intensity" field instead of the old per-zone
keys. The wire shape for a map-typed field ("mapValue", by the same
<type>Value convention as the scalar types) is a best-effort guess
pending live verification, same caveat as documented on FieldValues.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
…e statistics Per the official "Health Sampling" data type reference pages, three more ".statistics" routes have the same "continuous."-labelled statistics name vs. "instantaneous."-namespaced raw detailed type mismatch already confirmed and fixed for SpO2 and Altitude: - continuous_body_temperature_statistics's raw type is com.huawei.instantaneous.body.temperature, not com.huawei.continuous.body.temperature. - continuous_skin_temperature_statistics's raw type is com.huawei.instantaneous.skin.temperature. - continuous_breathe_rate_statistics's raw type is com.huawei.instantaneous.breathe_rate. The first two were previously built through the shared genericStatisticsTypes loop, which has no way to override the query data type, so they're pulled out into their own definitions (same HuaweiStatistics schema and populateCommon builder) alongside the explicit queryDataTypeSuffix override. Also confirmed (no code change needed): resting_calories_statistics already resolves correctly - com.huawei.resting_calories is a real, separately-documented raw data type, and continuous.resting_heart_rate.statistics's raw type (com.huawei.instantaneous.resting_heart_rate) already matches what our default suffix-stripping produces. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
Per the official "Blood Glucose" data type reference: the raw com.huawei.cgm_blood_glucose type only has a "level" field, while its .statistics variant only has avg/max/min/last (no "level"). Both builders were reading all five keys regardless, so each always left some fields silently null - harmless, but misleading. Each builder now only reads the fields its own data type actually returns. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
Per the official "Low SpO2 Level Data", "ABPM Reports", and "High Body Temperature Data" references, every health.record.* data type uses camelCase field keys matching the Avro field names directly - the same convention already confirmed for daily_activity_summary and active_hours.statistics, and distinct from the snake_case convention used by continuous.*/instantaneous.* sampleSet types. Confirmed and fixed directly from these docs: - health_record_low_spo2_alert: maxSpO2/minSpO2 (was max_spo2/min_spo2) - health_record_hyperthermia: highBodyTemperatureAlarm (was high_body_temperature_alarm) - health_record_dynamic_bp (the ~90-field ABPM record): the entire field mapper was routing every lookup through a camelCase->snake_case "snake()" conversion, which is now removed - every one of its ~90 fields was being queried under the wrong key. Also fixes extendData (was extend_data). Given the now 5-for-5 confirmed pattern across every health.record.* type actually checked (also daily_activity_summary, active_hours.statistics from earlier), also applies the same camelCase correction to the remaining health.record.* routes not covered by these specific docs (bradycardia/tachycardia's shared heart-rate-alert builder, menstrual_cycle, sleep) - high confidence by the established pattern, though not individually doc-verified like the three above. Removes the now-dead snake() helper (in both the main file and the test's dynamicBpFieldKeys()) since nothing converts case anymore. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
The official "Sleep Records" reference confirms com.huawei.health.record.sleep actually uses snake_case field keys (fall_asleep_time, wakeup_time, sleep_type, etc.), contradicting the "all health.record.* types use camelCase" pattern inferred from lowSpo2Alert/dynamic_bp/hyperthermia in the previous commit. That inference doesn't hold universally, so also reverts the other two changes that were extended from it without direct doc evidence (health_record_bradycardia/tachycardia's shared heart-rate-alert builder, health_record_menstrual_cycle) back to their original snake_case keys, pending actual documentation for those. The three fixes with direct doc confirmation (lowSpo2Alert/dynamic_bp/hyperthermia, and daily_activity_summary/ active_hours.statistics from earlier) are unaffected. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
Per the official "Menstrual Cycle Data" reference: recordDay, status, subStatus, timeZone, remarks - confirms and re-applies the camelCase fix reverted in the previous commit for lack of evidence at the time. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
- Use documented field keys: emotionStatus, heartRateVariabilityRMSSD, onOffBedState, eventName, voltage_datas, camelCase breathe-rate and resting-calories statistics, measure_count for stress statistics. - Query heart rate, blood pressure and blood glucose statistics via their instantaneous.* raw types; read body fat statistics from instantaneous.body_weight's *_body_fat_rate fields. - Disable continuous_calories_consumed by default (rejected live, not a documented data type). - FieldValues: accept fallback keys, serialize list/object values in getString, tolerate unknown typed-value keys and more map shapes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
sampleSet:polymerize responses wrap sample sets in group[] (like dailyPolymerize) and report sample point times in nanoseconds; the converter only read a top-level sampleSet[] in milliseconds, so it would have emitted nothing (or wrong times). Share one tolerant parser across both endpoints and infer s/ms/us/ns from each timestamp's magnitude in all converters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
- Keep querying the last 7 days until data arrives, since devices sync to the Huawei cloud late; advance offsets only past records received, drop records already emitted, and follow hasMoreData by continuing after the latest record. - Request daily statistics for whole, completed UTC days only, with an inclusive endDay, so consecutive requests neither overlap nor emit partial days. - Stop the remaining chunks of a route after a failure or rate limit, so offsets can't skip a failed window; honour the global 429 back-off. - Contain token/user-listing errors raised while building requests instead of letting them kill the source task; back off the user. - On HTTP 401, invalidate the cached access token and retry after 10 min. - poll(): single pass, no wait while catching up, wakes on stop(). - Store Kafka offsets just past the last record and tolerate a missing offset reader. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
- Only stream users assigned to the task (huawei.users), so users aren't polled by every task. - Give HuaweiLocalUser identity-based equals/hashCode so token refreshes don't trigger an hourly task reconfiguration. - Reload user files edited on disk; skip unreadable or duplicate files instead of dropping all users; write temp files next to the target. - Send client credentials as form parameters to Huawei's token endpoint and include the error body when a refresh token is rejected. - Support access-token invalidation; update config test for routes that are disabled by default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
Per the official ECG Measurement Details reference, continuous.ecg_detail has only ecg_type and voltage_datas; stop reading guessed keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
ECG measurement details are only open through health record queries, so the continuous_ecg_detail route (same key, topic and schema) now queries healthRecords for com.huawei.continuous.ecg_record with subDataType com.huawei.continuous.ecg_detail. Record fields map to the documented ecg_record keys, ecgRecordId comes from the record id, and voltageData from the associated detail points' voltage_datas. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
The official Body Temperature reference defines only body and skin temperature (detailed and statistics); there is no resting variant. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
- Fill ecgType, float averageHeartRate, long userSymptom, ecgAlgorithmVersion, ecgDataSources, ecgDataLength and packageName from the documented ecg_record fields (RADAR-Schemas #430 update). - FieldValues numeric getters now return null for non-numeric text and non-scalar values instead of Jackson's default 0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
Both query their raw detailed data types via sampleSet:polymerize and share the new HuaweiTemperature schema (RADAR-Schemas #430), reading the documented float "temperature" field. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
scripts/huawei-api-probe.sh sends the connector's exact request for every Huawei route (sampleSet:polymerize, dailyPolymerize, healthRecords, activityRecords) and saves each response for review. It can refresh the access token from a refresh token (argument or connector user file) and warns if Huawei issues a new refresh token. Responses are gitignored. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
From a live run of scripts/huawei-api-probe.sh: - Disable by default the routes Huawei rejects with "Invalid dataTypeName" (activity, calories bmr, exercise heart rate, power, speed, steps rate and stroke rate statistics), and back off 12h on that error. - Treat "no default dataCollector found" (the user has no data source for the type) as an empty response instead of an error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
The Huawei schemas (RADAR-Schemas #430) are released in 0.9.1, so drop the 0.9.0-SNAPSHOT pin and the extra snapshot/mavenLocal/GitHub Packages repositories; the Huawei modules now resolve from Maven Central like the other connectors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w6G3vGzKQ4ogcLgtWwzun
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Initial implementation of Huawei HealthKit, powered by claude