Skip to content

Add therapy settings upload to Tidepool#975

Open
blalezarian wants to merge 16 commits intonightscout:devfrom
blalezarian:feat/tidepool-pump-settings
Open

Add therapy settings upload to Tidepool#975
blalezarian wants to merge 16 commits intonightscout:devfrom
blalezarian:feat/tidepool-pump-settings

Conversation

@blalezarian
Copy link
Copy Markdown

Addresses #915

Adds an opt-in "Include Therapy Settings" toggle to the Tidepool settings screen, enabling upload of therapy configuration alongside existing glucose, carb, and insulin data. Tidepool represents these as "Pump Settings" in their data model.

What's uploaded

  • Basal rate schedules
  • Carb ratios
  • Insulin sensitivity factors
  • Blood glucose targets
  • Suspend threshold
  • Insulin model
  • Max basal rate / max bolus
  • Active overrides and temp targets (as scheduleOverride)
  • Override and temp target presets

How it works

  • TrioSettingsAdapter converts Trio's JSON-based settings to LoopKit's StoredSettings format, which TidepoolService then converts to the Tidepool API format
  • Settings changes are detected via observer protocols (BasalProfileObserver, CarbRatiosObserver, InsulinSensitivitiesObserver, BGTargetsObserver, PreferencesObserver, PumpSettingsObserver, TempTargetsObserver) and a CoreData publisher for override changes
  • Upload requests are debounced (1.5s) to coalesce rapid successive changes
  • A content-based SHA-256 sync identifier enables Tidepool server-side deduplication — unchanged settings produce the same ID and are not re-uploaded
  • The toggle state is stored in TrioSettings (not in the TidepoolService submodule) to keep all Trio settings in one place

Known limitations

Host identifier: Trio currently identifies as com.loopkit.Loop to Tidepool because Tidepool doesn't have a mapping for org.nightscout.Trio and would display the device as "unknown". With the Loop identifier, Tidepool displays it as "DIY Loop". The proper fix is to request Tidepool add a mapping for Trio — this is noted as a TODO.

Override display: Override presets and active overrides are included in the upload payload and are present in the Tidepool API response, but Tidepool's web UI does not currently display them. This may require Trio to be registered as a recognized app with Tidepool. The user-facing description has been updated to not mention overrides to avoid confusion.

Testing

  • 855 lines of unit tests covering settings conversion, content-based sync IDs, override expiration logic, deterministic preset UUIDs, and Tidepool datum format validation
  • Tested on a real device connected to a Tidepool test account — verified therapy settings, active overrides, and override cancellations all trigger successful uploads with correct payloads

Adds an "Include Therapy Settings" toggle to the Tidepool settings
screen, allowing users to opt-in to uploading their therapy
configuration (basal schedules, carb ratios, insulin sensitivity factors,
BG targets, and override presets) alongside their diabetes data.
Tidepool represents these as "Pump Settings" in their data model.

Architecture decisions:

- The toggle state is stored in TrioSettings (persisted via FileStorage)
  rather than in the TidepoolService submodule. This keeps all Trio
  settings in one place and avoids modifying upstream dependencies.

- TrioSettingsAdapter converts Trio's JSON-based settings format to
  LoopKit's StoredSettings format, which TidepoolService then converts
  to the Tidepool API format. This two-step conversion maintains clean
  separation between Trio's data model and the upload mechanism.

- The toggle is disabled when Tidepool is not connected, preventing
  users from enabling a feature that would have no effect.

- TidepoolManager listens for therapy setting changes via the existing
  observer pattern (SettingsObserver, BasalProfileObserver, etc.) and
  triggers debounced uploads when the toggle is enabled.

- Upload requests are debounced (1.5s) to prevent redundant uploads
  when multiple settings observers fire in rapid succession.

- syncIdentifier uses a content-based SHA-256 hash of therapy values
  instead of a random UUID, enabling Tidepool server-side deduplication.

- Basal schedule conversion uses the numeric minutes field directly
  instead of parsing the start time string, avoiding format mismatches.

Note: CarbRatios and InsulinSensitivities observers are not yet wired
up to trigger uploads - this will be addressed in a follow-up commit.
Therapy setting changes for carb ratios and insulin sensitivity factors
were not triggering Tidepool uploads because the observer pattern was
incomplete. BGTargets and BasalProfile had observers, but CarbRatios
and InsulinSensitivities did not.

Changes:
- Add CarbRatiosObserver protocol to CarbRatios.swift
- Add InsulinSensitivitiesObserver protocol to InsulinSensitivities.swift
- Broadcast changes from CarbRatioEditorStateModel.save()
- Broadcast changes from ISFEditorStateModel.save()
- Register TidepoolManager for both new observers
- Implement observer methods to trigger uploadSettings()

Now all four therapy settings (BG targets, basal profile, carb ratios,
and insulin sensitivities) properly trigger Tidepool uploads when the
"Include Therapy Settings" toggle is enabled.
Converts currently active Profile Overrides and Temp Targets to
Tidepool's scheduleOverride format for upload. Profile Overrides
include insulin needs scaling (percentage), while Temp Targets
only modify the glucose target.

Note: Override presets and active overrides are included in the
upload payload, but Tidepool's web UI does not currently display
them. The data is present in the API response and may be surfaced
in the future once Trio is registered as a recognized app with
Tidepool. The UI description has been updated to not mention
overrides to avoid user confusion.

Also adds TempTargetsObserver registration to trigger uploads when
temp targets change, and documents the hostIdentifier mapping issue.
@blalezarian
Copy link
Copy Markdown
Author

blalezarian commented Feb 14, 2026

Screenshot of new toggle in Settings -> Services -> Tidepool:
image

Screenshot of Tidepool -> Devices in Tidepool Web UI:
image

@dnzxy
Copy link
Copy Markdown
Contributor

dnzxy commented Feb 15, 2026

General question: why was this put behind a setting + toggle? What was your idea behind making this optional? Is there any user benefit or (keeping it deactivated) potential downside ?

@blalezarian
Copy link
Copy Markdown
Author

@dnzxy good question - I wasn't sure how existing users that rebuild an update know about all the new additions, so the thinking was this makes it visible, explicit and allows them to turn it on if they want. But I agree, in most cases it makes sense to just have it on by default.

Want me to remove the toggle / state and just leave the language in the information tooltip to mention that therapy settings will also be included? That would make it simpler. Only downside I can think of is larger payload sent via API call to Tidepool but it's minimal data and not really a real issue.

@dnzxy
Copy link
Copy Markdown
Contributor

dnzxy commented Feb 15, 2026

Thanks for adding some background to that @blalezarian 😊

I‘d suggest that we just add it. There’s no real downside to this being sent to TP.

I‘d suggest just extending the help hint text content (when you tap (?) below the TP connectivity section) and listing there what we actually upload and adding pump settings to that.

Maybe adding a little nugget like "Tap more for a full list of all uploaded data" or something along those lines.

I‘ll try and review + have this PR tested over the next few days. Thanks for this contribution and a definite +1 for adding unit testing to it.

Therapy settings (basal schedules, carb ratios, insulin sensitivities,
and BG targets) are now uploaded automatically whenever connected to
Tidepool, removing the opt-in "Include Therapy Settings" toggle per
PR review feedback.
@blalezarian
Copy link
Copy Markdown
Author

Updated to always include therapy settings with Tidepool enabled. Updated the UI text to reflect this.

image image

Ready for review!

@blalezarian
Copy link
Copy Markdown
Author

Noting that currently in Tidepool > Devices this data will show under the device "DIY Loop". When we later change the hostIdentifier in TidepoolManager to be "org.nightscout.Trio", the pump settings will show up under a new device (e.g. "DIY Trio" or "Trio") so the user will show two separate device settings in Tidepool (original "DIY Loop" and new "Trio). It's not a deal breaker, it won't break the code, it's just not the cleanest. So options are:

  1. Push as is, users will have a remnant DIY Loop device with stale settings when we change to Trio (not end of the world), or,
  2. Have Tidepool update their device mapping to recognize Trio, then push this so users will have clean device settings history.

dnzxy
dnzxy previously requested changes Feb 16, 2026
Copy link
Copy Markdown
Contributor

@dnzxy dnzxy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for tackling this @blalezarian
I have reviewed the code and have a few comments, some questions, and some change request.
I won't get to test running this code, but either someone else will add dedicated "end user" test feedback, or I will have to find the time :)

Comment thread Trio/Sources/Modules/Settings/View/TidepoolStartView.swift Outdated
Comment thread Trio/Sources/Services/Network/TrioSettingsAdapter.swift Outdated
Comment thread Trio/Sources/Services/Network/TrioSettingsAdapter.swift Outdated
Comment thread Trio/Sources/Services/Network/TrioSettingsAdapter.swift Outdated
Comment thread Trio/Sources/Services/Network/TrioSettingsAdapter.swift Outdated
Comment thread Trio/Sources/Services/Network/TrioSettingsAdapter.swift Outdated
Comment thread TrioTests/TidepoolTherapySettingsTests.swift Outdated
Comment thread Trio/Sources/Services/Network/TidepoolManager.swift
Comment thread Trio/Sources/Services/Network/TrioSettingsAdapter.swift Outdated
Overrides are buggy and temp targets shouldn't be mixed with them.
Remove all override/temp target conversion code, Core Data queries,
related observers, and corresponding tests.
threshold_setting in Preferences is always stored in mg/dL, but was
being labeled with the user's display unit (bgUnit). If the user
displays in mmol/L, a value like 70 (mg/dL) would be incorrectly
labeled as 70 mmol/L.

Always create the GlucoseThreshold in milligramsPerDeciliter since
that matches the stored value. TidepoolServiceKit converts all glucose
values to mg/dL before uploading (via convertTo(unit:)), and the
Tidepool API always stores data in mg/dL. The mmol/L option in the
Tidepool web UI is a display preference only.
- Read DIA from pumpSettings.insulinActionCurve (5-10h) instead of
  hard-coded 6h
- Use ExponentialInsulinModelPreset defaults for peak activity (75min
  for rapidActing, 55min for fiasp/lyumjev) instead of hard-coded
  2.5/3 hours which were incorrect
- Respect useCustomPeakTime toggle — when enabled, use the user's
  insulinPeakTime setting (35-120min)
- Distinguish Fiasp vs Lyumjev via pumpManager's configured insulin
  type instead of mapping both to .fiasp

Note: TidepoolServiceKit currently maps .lyumjev model type to .other
in its Tidepool datum conversion. When TidepoolServiceKit adds native
.lyumjev support, the correct model type will flow through
automatically.
@blalezarian
Copy link
Copy Markdown
Author

I'll work on these, I'll tackle them in logical groups with 1 commit each so it's easier to follow changes. If you prefer to squash in 1 commit let me know

…loads

Move all therapy settings conversion logic from the standalone
TrioSettingsAdapter into a BaseTidepoolManager extension, eliminating
the separate injectable class.

Replace observer-based upload pattern (CarbRatios, InsulinSensitivities,
BasalProfile, BGTargets, PumpSettings observers) with direct
Task.detached upload calls in each state model's save(), matching the
existing Nightscout upload pattern. Keep SettingsObserver and
PreferencesObserver for closedLoop/units/algorithm preference changes.
Replace XCTest with Swift Testing to match Trio's test conventions:
- XCTestCase classes → @suite structs
- func testXxx() → @test("description") func xxx()
- XCTAssert* → #expect()
Clarify what data Trio uploads: glucose, carb entries, insulin (bolus
and basal), pump settings, and therapy settings. List therapy settings
explicitly: basal schedules, carb ratios, insulin sensitivities, and
glucose targets.
@blalezarian
Copy link
Copy Markdown
Author

@dnzxy changes are updated and ready for review. I'm new to the codebase but tried to follow patterns based on your feedback and tried not to duplicate anything that may already exist elsewhere. If I'm getting anything wrong, just let me know

@dnzxy
Copy link
Copy Markdown
Contributor

dnzxy commented Feb 18, 2026

Added to my TODO to review your updates. Thanks for tackling this so quickly @blalezarian 🙏

@marionbarker
Copy link
Copy Markdown
Contributor

Test

Summary - test was successful.

  • ✅ Trio settings were uploaded as soon as I built this version of code.
  • ✅ Previously, only the Loop Device settings were visible

Configuration

SE 2nd gen phone running iOS 18.7

  • Ensure that all Loop test phones are not using my test Tidepool account (fake account that I use for testing)
  • Start uploading to Tidepool from Trio on 15 Feb 2026
  • original build configuration was Trio (various builds) without this PR (which adds uploading the settings to Tidepool)
    • all daily data was filled out correctly but the Devices screen showed the last values uploaded when this same Tidepool username was used as a Loop Service
  • modify build around noon on 18 Feb 2026
    • confirm that the settings were modifed (although it is still labeled DIY Loop)

Graphic from Tidepool Web

trio-tidepool-pr975

Ensure no effect on Loop

Disconnect from Trio from the Tidepool test account
Connect a different test phone running Loop to the Tidepool test account

  • the new Loop Device values were present
    • not the same as the original Loop values because this phone has modified therapy values
    • all the new values match the phone
  • modified one value in Therapy settings (max basal rate from 6 to 4), today's display updated
  • previous days used the older value

Because Loop uploads the last 7 days worth of data when first attached to Tidepool, all the Trio Device values were wiped out and the daily plots have data from both Trio and Loop.

The other Loop instance previously connected had different settings. Those settings were similarly replaced for the 7-day period.

Tidepool indicates they save only the most recent set of settings for a given day.

Copy link
Copy Markdown
Contributor

@marionbarker marionbarker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve from Test:

  • The test was successful.

No code review performed

@marionbarker
Copy link
Copy Markdown
Contributor

Test

Configure a Trio instance and upload information to a tidepool account to which Tidepool engineers were granted sharing access.

Start with blalezarian:feat/tidepool-pump-settings, commit d46c691

  • Make a new branch dev_plus_pr975 from that branch.
  • Merge the current version of dev (0.6.0.52) into dev_plus_pr975 and build

Hardware

  • iPhone SE 2nd gen, iOS 18.7 phone
  • Nightscout as a CGM
    • Upload a glucose pattern with low events and missed meal events
  • DASH rPi simulator

Therapy Settings

  • read settings from Nightscout
  • CR 15 g/U
  • ISF 60 mg/dL/U
  • Basal 1.2 U/hr
  • target 90 mg/dL

Services

  • Configure Nightscout service so treatments are uploaded
    • Readable Link shared with Tidepool team
  • Configure Tidepool using test account to which Tidepool engineers has share access.

Active as of 2026-03-12 just before 16:00 PDT.

@dnzxy
Copy link
Copy Markdown
Contributor

dnzxy commented Mar 20, 2026

There has been back and fourth with the Tidepool engineering team, so I have made two small changes and pushed them, plus a sync w/ dev.

@marionbarker
Copy link
Copy Markdown
Contributor

Test

Repeat test with latest version of blalezarian:feat/tidepool-pump-settings on 20 Mar 2026, around noon PDT.
❌ Test was not successful.

Configuration Loop

My test Tidepool site is connected to a Loop phone.
Last time I checked devices with Loop, they worked fine.
❌ Today, the end date for the Therapy setting is way in the future.
❓ has there been a modification on the Tidepool side that would explain this?

Screenshot 2026-03-20 at 12 05 55 PM

Configuration Trio

Log out of Tidepool with the Loop phone

  • iPhone SE 2nd gen is currently running Trio v0.6.0.57, branch fix-negative-iob-after-onboarding
    • Pump: Medtronic 515
    • CGM Nightscout as a CGM
  • switch to blalezarian:feat/tidepool-pump-settings, commit 975ecd4
  • build onto iPhone SE 2nd gen
  • Attempt to log in to Tidepool and get Missing authentication configuration warning

Quit out of Trio

  • Clean build folder
  • build again
  • Attempt to log in to Tidepool and get warning again

screenshot

IMG_7046

@marionbarker
Copy link
Copy Markdown
Contributor

Test earlier commit

Configuration

Continue from prior comment.
Checkout cc5f711 (this commit not included)

Clean build folder
build onto same phone.
Note - there is some linting to clean up with this commit.

modified: Trio/Resources/InfoPlist.xcstrings
modified: Trio/Sources/Localizations/Main/Localizable.xcstrings
modified: Trio/Sources/Services/Network/TidepoolManager.swift

Login works this time.

On Trio phone, modify basal rates from 1.2 U/hr to 1.3 U/hr
See TidepoolManager.swift - uploadSettings() - 716 DEV: Settings uploaded to Tidepool (syncId: CBE5EDD9-392A-2B4A-0535-9CD97947F6CB) in xcode log.

Device upload did not work as expected.

Screenshot 2026-03-20 at 12 40 42 PM

@marionbarker
Copy link
Copy Markdown
Contributor

Test earlier commit

Back up another commit to caf7d54.

  • clean build folder, close workspace, open workspace

Modify basal rates back to 1.2 U/hr from 1.3 U/hr

Refresh Tidepool display. Now the Device values shown are the Trio upload with the Loop label.

Screenshot 2026-03-20 at 12 50 38 PM

@dnzxy
Copy link
Copy Markdown
Contributor

dnzxy commented Mar 20, 2026

Thanks for testing :)

I think we have two options here now:

  1. wait for Tidepool to ship the changes on their end so the changes as of the last 2 commits work
  2. ship this PR without those two commits and add them at a later point in time

I'm personally fine with either. What do you think @blalezarian @marionbarker ?

@dnzxy
Copy link
Copy Markdown
Contributor

dnzxy commented Mar 24, 2026

So, after quick conferral with the guys at Tidepool two quick updates:

  1. There was a typo in the TidepoolServiceRedirectURL that is fixed now. The backend changes have been shipped. Authentication should work now.
  2. The frontend changes to display Trio are not yet live in Tidepool's production environment. However, the upload works correctly. So, when @marionbarker tested with org.nightscout.Trio instead of com.loopkit.Loopas hostIdentifier it broke showing the settings because now it is indeed an "unknown" device and the default fallback for that view in Tidepool's dashboard is to only show settings for devices Tidepool has defined for. The uploads do work though, it was confirmed by Tidepool.

This PR should be good to go now, because we can ship this and as soon as the Tidepool frontend changes go live, pump settings will show up and for as long as it hasn't been shipped, there's no difference for Trio users vs. what Tidepool currently shows – nothing.

@dnzxy dnzxy dismissed their stale review March 24, 2026 14:52

Discarded due to newer changes.

@loudestnoise
Copy link
Copy Markdown

Thanks, all, for working on this. As a Trio user and someone who works at Tidepool, I'm excited to give this option to PwDs who want to share their Tidepool data with their healthcare team. I'll come back here and give an update when this ships on the Tidepool side to production.

@dnzxy dnzxy linked an issue Mar 26, 2026 that may be closed by this pull request
@marionbarker
Copy link
Copy Markdown
Contributor

marionbarker commented Apr 2, 2026

Test

✅ error when trying to upload, which is expected until Tidepool fixes their end

Narrative and Configuration

Step 1: build latest dev to test phone that has Medtrum pump added, choose the SE Phone, 3rd gen, iOS 26.4

Step 2: Check all my test phones to make sure Loop is not uploading to Tidepool

  • open Tidepool website and review status
  • last upload was 21 March 2026 at 9:29 am for the glucose trace
  • last update for Devices was 20 March 2026

Step 3: Login to Tidepool with the Trio phone using dev 0.6.0.75

  • Check CGM - there isn't one because this test phone last used Eversense and this build doesn't have that option
  • Add Glucose simulator, 120 mg/dL, +/- 20, noise of 1
  • Observe glucose pattern uploaded to Nightscout site
  • Capture last Devices view from Tidepool (when Loop was connected)
  • Add Tidepool as a service to Trio
  • Glucose appears on Tidepool display
  • Devices view unchanged
  • Modify a therapy setting for Trio, change basal rate from 1.2 U/hr to 1.25 U/hr
    • cannot do this with Medtrum without a patch connected. Need to write up an issue for this
    • for now, switch to OmniBLE - this allows therapy change without a pod being paired
  • No change to Tidepool devices display

Step 4: Pull in PR 975 code (blalezarian:feat/tidepool-pump-settings) and rebuild onto the test phone

  • examine Tidepool web display
  • still shows the March 20 Loop upload as most recent
  • modify Therapy setting, basal rate change from 1.25 U/hr to 1.3 U/hr
  • error seen in Xcode, snippet here with full log below.

TidepoolManager.swift - uploadSettings() - 718 DEV: Failed to upload settings to Tidepool: responseMalformedJSON(<NSHTTPURLResponse: 0x13f3a1900> { URL: https://auth.tidepool.org/realms/tidepool/protocol/openid-connect/token } {

Xcode debug log

Xcode-pr975-failed-to-upload-to-tidepool.txt

@marionbarker
Copy link
Copy Markdown
Contributor

Test for carb and dosing uploads

❌ this failed
I recommend this not be merged at this time

For completeness:

  • enter carbs (45 g)
  • pair an rPi DASH pod
  • observe a increased temp basal rate and SMB
  • Note to @kingst: No pump attached for > 24 hour, ✅ IOB remains 0 U as expected
  • ❌ I do NOT observe expected entries on the Tidepool Details plot

This used to work.

Test that dev uploads carbs and dosing

Restore to dev and test upload to Tidepool

  • enter carbs ( 30 g)
  • confirm 30 g on tidepool display
  • next loop, TBR to 0 U/hr, SMB of 0.55 U
  • confirm dosing changes show up in tidepool display

In Xcode log I see:

TidepoolManager.swift - uploadCarbs(_:) - 212 DEV: Success synchronizing carbs data. Upload to Tidepool complete.

TidepoolManager.swift - uploadDose(_:) - 377 DEV: Success synchronizing dose data. Upload to Tidepool complete.

Copy link
Copy Markdown
Contributor

@marionbarker marionbarker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I revoke my approval.

The current version breaks what used to work.

  • ❌ carbs and dosing changes are no longer uploaded

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Upload Pump Settings to Tidepool

4 participants