Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
19d13e1
using VirtualPumpPlugin, pumpType = CELLNOVO, extendedBolus there is …
kot1202 Jun 3, 2026
3af4fbd
3.4.2.3-dev
MilosKozak Jun 4, 2026
7737f81
Merge pull request #4878 from kot1202/dev3_fix_extendbolus_min_and_init
MilosKozak Jun 4, 2026
2e1640b
Cleanup Workflow AAPS CI: keep_runs=20, keep_threshold=5. Uses cleanu…
Jun 11, 2026
9565b25
Merge pull request #4895 from vanelsberg/dev3
MilosKozak Jun 16, 2026
4a7f89d
Equil: do not drop commands after connect
MilosKozak Jun 23, 2026
39a9d2f
Equil: max basal fix, tbr result fix
MilosKozak Jul 6, 2026
52056eb
Profile sync fix
MilosKozak Jul 20, 2026
aa82551
3.4.2.4
MilosKozak Jul 22, 2026
40f9633
aaps-ci
MilosKozak Jul 22, 2026
bcbfd4b
3.4.2.4
MilosKozak Jul 22, 2026
f93982b
3.4.2.4-dev
MilosKozak Jul 22, 2026
27ffd44
fix CI Cleanup permissions
Angus-repo Jul 23, 2026
195f218
Merge pull request #5000 from Angus-repo/fix/aaps-ci-cleanup-permissions
MilosKozak Jul 23, 2026
a4c85ab
fix CI Cleanup permissions
Angus-repo Jul 23, 2026
9516bb9
Equil: fix race conditions
MilosKozak Jul 30, 2026
515489a
3.4.2.5
MilosKozak Jul 30, 2026
1896162
Add version to CI
MilosKozak Jul 30, 2026
b6b88d8
Merge pull request #5036 from nightscout/dev3
MilosKozak Jul 30, 2026
af65f8b
update default ci
MilosKozak Jul 30, 2026
73eec05
Merge branch 'dev3'
MilosKozak Jul 30, 2026
39b229e
3.4.2.5-dev
MilosKozak Jul 30, 2026
c28448d
Fix ci
MilosKozak Jul 31, 2026
8456c9d
Fix ci
MilosKozak Jul 31, 2026
c0ca46a
Equil: SCAN_MODE_LOW_LATENCY
MilosKozak Aug 1, 2026
d762bdf
3.4.2.5-dev
MilosKozak Aug 1, 2026
1dd66e0
Equil: direct connect without scan
MilosKozak Aug 1, 2026
03ea9f7
Equil: fix prime
MilosKozak Aug 1, 2026
335465c
3.4.2.6
MilosKozak Aug 2, 2026
598e2eb
Merge pull request #5046 from nightscout/dev3
MilosKozak Aug 2, 2026
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
22 changes: 22 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,28 @@ jobs:
emulator -avd citest -delay-adb -verbose -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim
background: true

- run:
name: Wait for emulator boot
command: |
export ANDROID_SDK_ROOT=/usr/lib/android-sdk
export ANDROID_HOME=/usr/lib/android-sdk
# Boot gate: the emulator above is launched in the background, so without this the test task
# can start before the device is online and fail with the misleading
# "com.android.builder.testing.api.DeviceException: No online devices found".
# Block until adb sees the device AND the system has fully booted, with a hard timeout so a
# stuck emulator fails loudly HERE (clear message) instead of later.
adb wait-for-device
timeout 420 bash -c 'until [ "$(adb shell getprop sys.boot_completed 2>/dev/null | tr -d "\r")" = "1" ]; do sleep 2; done'
adb shell getprop sys.boot_completed
# Settle, dismiss keyguard and disable animations so ComposeMainActivity launches cleanly:
# a heavy UI launch on an unsettled emulator can ANR system_server and drop the device offline
# mid-run (the failure mode this hardening targets).
adb shell input keyevent 82 || true
adb shell settings put global window_animation_scale 0 || true
adb shell settings put global transition_animation_scale 0 || true
adb shell settings put global animator_duration_scale 0 || true
adb devices

- run:
name: Run connectedFullDebugAndroidTest
command: |
Expand Down
18 changes: 17 additions & 1 deletion .github/workflows/aaps-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ on:
tagName:
description: 'Select AAPS Version'
required: true
default: '3.4.2.3'
default: '3.4.2.6'
type: choice
options:
# ── 3.4.x (JDK 21) ──────────────────────────────
- 3.4.2.6
- 3.4.2.4
- 3.4.2.3
- 3.4.2.2
- 3.4.2.1
Expand Down Expand Up @@ -354,3 +356,17 @@ jobs:
upload_to_gdrive "aaps-wear-${VERSION}${VERSION_SUFFIX}.apk" "aaps-wear-${VERSION}${VERSION_SUFFIX}.apk"

echo "🎉 APKs successfully uploaded to Google Drive!"

# Configuration: Threshold-based cleanup
# Clean up only occurs when total runs exceed: keep_runs + keep_threshold (e.g., 20 + 5 = 25)
cleanup:
name: Cleanup Old Workflow Runs
needs: build
if: always()
permissions:
actions: write
uses: ./.github/workflows/cleanup-workflow-runs.yml
with:
workflow_name: 'AAPS CI'
keep_runs: 20
keep_threshold: 5
2 changes: 2 additions & 0 deletions .github/workflows/branch-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,8 @@ jobs:
name: Cleanup Old Workflow Runs
needs: build
if: always()
permissions:
actions: write
uses: ./.github/workflows/cleanup-workflow-runs.yml
with:
workflow_name: 'Branch CI'
Expand Down
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/Versions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
object Versions {

// On change edit aaps-ci.yml
const val appVersion = "3.4.2.3"
const val appVersion = "3.4.2.6"
const val versionCode = 1500

const val compileSdk = 36
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class PumpDescription {
var isPatchPump = false
var maxResorvoirReading = 50
var useHardwareLink = false

var extendedBolusMinAmount = 0.0
fun resetSettings() {
isBolusCapable = true
bolusStep = 0.1
Expand Down Expand Up @@ -64,6 +64,7 @@ class PumpDescription {
needsManualTDDLoad = true
hasCustomUnreachableAlertCheck = false
useHardwareLink = false
extendedBolusMinAmount = extendedBolusStep
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ fun PumpDescription.fillFor(pumpType: PumpType): PumpDescription {
isPatchPump = pumpType.isPatchPump()
maxResorvoirReading = pumpType.maxReservoirReading()
useHardwareLink = pumpType.useHardwareLink
pumpType.extendedBolusSettings()?.minDose?.let { extendedBolusMinAmount = it }

return this
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ fun NSProfileSwitch.toProfileSwitch(activePlugin: ActivePlugin, dateUtil: DateUt
profileName = originalProfileName ?: profile,
timeshift = timeShift ?: 0,
percentage = percentage ?: 100,
duration = duration ?: 0L,
duration = originalDuration ?: duration ?: 0L,
iCfg = profileSealed.iCfg,
ids = IDs(nightscoutId = identifier, pumpId = pumpId, pumpType = PumpType.fromString(pumpType), pumpSerial = pumpSerial, endId = endId)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import app.aaps.core.data.model.IDs
import app.aaps.core.data.model.PS
import app.aaps.core.data.pump.defs.PumpType
import app.aaps.core.interfaces.insulin.Insulin
import app.aaps.core.nssdk.localmodel.treatment.EventType
import app.aaps.core.nssdk.localmodel.treatment.NSProfileSwitch
import app.aaps.core.nssdk.mapper.convertToRemoteAndBack
import app.aaps.plugins.sync.extensions.contentEqualsTo
Expand Down Expand Up @@ -83,4 +84,45 @@ internal class ProfileSwitchExtensionKtTest : TestBaseWithProfile() {
assertThat(profileSwitch.contentEqualsTo(profileSwitch2)).isTrue()
assertThat(profileSwitch.ids.contentEqualsTo(profileSwitch2.ids)).isTrue()
}

private fun nsProfileSwitch(duration: Long?, originalDuration: Long?) = NSProfileSwitch(
date = 10000,
identifier = "nightscoutId",
utcOffset = 0,
isValid = true,
eventType = EventType.PROFILE_SWITCH,
pumpId = 11000,
endId = null,
pumpType = PumpType.DANA_I.name,
pumpSerial = "bbbb",
profileJson = validProfile.toPureNsJson(dateUtil),
profile = "SomeProfile",
originalProfileName = "SomeProfile",
timeShift = 0,
percentage = 100,
duration = duration,
originalDuration = originalDuration
)

/**
* Documents uploaded before 9b90eea774 carry a duration inflated by 60000 and no
* durationInMilliseconds, so TreatmentMapper inflates it by 60000 a second time.
* originalDuration is written in milliseconds by every AAPS version and must win.
*/
@Test
fun inflatedLegacyDurationIsIgnoredInFavourOfOriginalDuration() {
val nineHours = 9 * 3600000L
val parsed = nsProfileSwitch(duration = nineHours * 60000 * 60000, originalDuration = nineHours)
.toProfileSwitch(activePlugin, dateUtil)!!
assertThat(parsed.duration).isEqualTo(nineHours)
}

/** A writer that supplies no originalDuration must still fall back to the mapped duration. */
@Test
fun durationIsUsedWhenOriginalDurationIsMissing() {
val nineHours = 9 * 3600000L
val parsed = nsProfileSwitch(duration = nineHours, originalDuration = null)
.toProfileSwitch(activePlugin, dateUtil)!!
assertThat(parsed.duration).isEqualTo(nineHours)
}
}
69 changes: 45 additions & 24 deletions pump/equil/src/main/kotlin/app/aaps/pump/equil/EquilPumpPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import app.aaps.pump.equil.manager.command.PumpEvent
import app.aaps.pump.equil.manager.customCommands.CmdModeAndHistoryGet
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import kotlin.math.max
import org.joda.time.DateTime
import org.joda.time.Duration
import javax.inject.Inject
Expand Down Expand Up @@ -115,20 +116,19 @@ class EquilPumpPlugin @Inject constructor(
.toObservable(EventEquilAlarm::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ eventEquilError ->
commandQueue.performing()?.let {
if (it.commandType == Command.CommandType.BOLUS) {
aapsLogger.info(
LTag.PUMPCOMM,
"eventEquilError.tips====${eventEquilError.tips}"
)
rxBus.send(EventDismissNotification(Notification.EQUIL_ALARM))
equilManager.showNotification(
Notification.EQUIL_ALARM,
eventEquilError.tips,
Notification.URGENT, app.aaps.core.ui.R.raw.alarm
)
stopBolusDelivering()
}
aapsLogger.info(LTag.PUMPCOMM, "eventEquilError.tips====${eventEquilError.tips}")
// Always surface the pump alarm - it is no longer gated on a bolus being in
// progress (alarms now come from the GATT history read on every connection, not
// just from an advertisement scan caught mid-bolus). See #5040.
rxBus.send(EventDismissNotification(Notification.EQUIL_ALARM))
equilManager.showNotification(
Notification.EQUIL_ALARM,
eventEquilError.tips,
Notification.URGENT, app.aaps.core.ui.R.raw.alarm
)
// But only halt bolus tracking if a bolus is actually delivering.
if (commandQueue.performing()?.commandType == Command.CommandType.BOLUS) {
stopBolusDelivering()
}
}, fabricPrivacy::logException)

Expand All @@ -146,20 +146,30 @@ class EquilPumpPlugin @Inject constructor(
else ToastUtils.infoToast(context, rh.gs(R.string.equil_error))
}
})
} else if (event.isChanged(DoubleKey.SafetyMaxBolus.key)) {
val profile = pumpSync.expectedPumpState().profile ?: return@subscribe
commandQueue.customCommand(
CmdSettingSet(constraintsChecker.getMaxBolusAllowed().value(), constraintsChecker.getMaxBasalAllowed(profile).value(), aapsLogger, preferences, equilManager),
object : Callback() {
override fun run() {
if (result.success) ToastUtils.infoToast(context, rh.gs(R.string.equil_pump_updated))
else ToastUtils.infoToast(context, rh.gs(R.string.equil_error))
}
})
} else if (event.isChanged(DoubleKey.SafetyMaxBolus.key) || event.isChanged(DoubleKey.ApsMaxBasal.key)) {
resendPumpSettings()
}
}, fabricPrivacy::logException)
}

// Re-program the pod's bolus/basal thresholds. The pod enforces the basal threshold (see CmdSettingSet)
// as a hard limit; if it goes stale-low, temp basals in the gap are silently rejected (the pump drops
// BLE with status 19). Use the STABLE max(ApsMaxBasal, maxDailyBasal) for basal — the same ceiling
// OpenAPS caps temp basals to — not the time-of-day-dependent getMaxBasalAllowed. Kept in sync on
// max-bolus / max-basal pref changes and on profile set (maxDailyBasal is profile-dependent).
private fun resendPumpSettings() {
val profile = pumpSync.expectedPumpState().profile ?: return
val maxBasal = max(preferences.get(DoubleKey.ApsMaxBasal), profile.getMaxDailyBasal())
commandQueue.customCommand(
CmdSettingSet(constraintsChecker.getMaxBolusAllowed().value(), maxBasal, aapsLogger, preferences, equilManager),
object : Callback() {
override fun run() {
if (result.success) ToastUtils.infoToast(context, rh.gs(R.string.equil_pump_updated))
else ToastUtils.infoToast(context, rh.gs(R.string.equil_error))
}
})
}

var tempActivationProgress = ActivationProgress.NONE
var indexEquilReadStatus = 5

Expand Down Expand Up @@ -210,6 +220,17 @@ class EquilPumpPlugin @Inject constructor(
val mode = equilManager.equilState?.runMode
if (mode === RunMode.RUN || mode === RunMode.SUSPEND) {
val basalSchedule = BasalSchedule.mapProfileToBasalSchedule(profile)
// Raise the pod's max-basal threshold BEFORE programming the schedule, so the pod accepts both
// the base schedule and later temp basals. Use the STABLE max(ApsMaxBasal, maxDailyBasal) — the
// same ceiling OpenAPS caps temp basals to — NOT getMaxBasalAllowed, which is time-of-day
// dependent (4x current basal) and can dip below the profile's own peak, wrongly starving the
// threshold. Sent directly (we're already inside a queued command — don't re-queue), gated on
// success (don't program the schedule on a stale threshold), and paced by EQUIL_BLE_NEXT_CMD
// like every other chained-command site so the second BLE write isn't dropped.
val maxBasal = max(preferences.get(DoubleKey.ApsMaxBasal), profile.getMaxDailyBasal())
val settingResult = equilManager.executeCmd(CmdSettingSet(constraintsChecker.getMaxBolusAllowed().value(), maxBasal, aapsLogger, preferences, equilManager))
if (!settingResult.success) return settingResult
SystemClock.sleep(EquilConst.EQUIL_BLE_NEXT_CMD)
val pumpEnactResult = equilManager.executeCmd(CmdBasalSet(basalSchedule, profile, aapsLogger, preferences, equilManager))
if (pumpEnactResult.success) equilManager.equilState?.basalSchedule = basalSchedule
return pumpEnactResult
Expand Down
Loading
Loading