Implement getJSONStatus() and shortStatus()
This commit is contained in:
parent
082b365520
commit
996ef8e5ef
7 changed files with 234 additions and 69 deletions
|
@ -5,6 +5,7 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.os.IBinder
|
||||
import android.text.format.DateFormat
|
||||
import dagger.android.HasAndroidInjector
|
||||
import info.nightscout.core.ui.toast.ToastUtils
|
||||
import info.nightscout.core.utils.fabric.FabricPrivacy
|
||||
|
@ -31,6 +32,7 @@ import info.nightscout.interfaces.pump.defs.PumpType
|
|||
import info.nightscout.interfaces.queue.CommandQueue
|
||||
import info.nightscout.interfaces.queue.CustomCommand
|
||||
import info.nightscout.interfaces.ui.UiInteraction
|
||||
import info.nightscout.interfaces.utils.DecimalFormatter
|
||||
import info.nightscout.interfaces.utils.TimeChangeType
|
||||
import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState
|
||||
import info.nightscout.pump.medtrum.ui.MedtrumOverviewFragment
|
||||
|
@ -222,7 +224,7 @@ import kotlin.math.round
|
|||
get() = medtrumPump.reservoir
|
||||
|
||||
override val batteryLevel: Int
|
||||
get() = 0 // TODO
|
||||
get() = 0 // We cannot determine battery level (yet)
|
||||
|
||||
@Synchronized
|
||||
override fun deliverTreatment(detailedBolusInfo: DetailedBolusInfo): PumpEnactResult {
|
||||
|
@ -317,7 +319,43 @@ import kotlin.math.round
|
|||
}
|
||||
|
||||
override fun getJSONStatus(profile: Profile, profileName: String, version: String): JSONObject {
|
||||
return JSONObject() // TODO
|
||||
val now = System.currentTimeMillis()
|
||||
if (medtrumPump.lastConnection + 60 * 60 * 1000L < System.currentTimeMillis()) {
|
||||
return JSONObject()
|
||||
}
|
||||
val pumpJson = JSONObject()
|
||||
val status = JSONObject()
|
||||
val extended = JSONObject()
|
||||
try {
|
||||
status.put(
|
||||
"status", if (!isSuspended()) "normal"
|
||||
else if (isInitialized() && isSuspended()) "suspended"
|
||||
else "no active patch"
|
||||
)
|
||||
status.put("timestamp", dateUtil.toISOString(medtrumPump.lastConnection))
|
||||
if (medtrumPump.lastBolusTime != 0L) {
|
||||
extended.put("lastBolus", dateUtil.dateAndTimeString(medtrumPump.lastBolusTime))
|
||||
extended.put("lastBolusAmount", medtrumPump.lastBolusAmount)
|
||||
}
|
||||
val tb = pumpSync.expectedPumpState().temporaryBasal
|
||||
if (tb != null) {
|
||||
extended.put("TempBasalAbsoluteRate", tb.convertedToAbsolute(now, profile))
|
||||
extended.put("TempBasalStart", dateUtil.dateAndTimeString(tb.timestamp))
|
||||
extended.put("TempBasalRemaining", tb.plannedRemainingMinutes)
|
||||
}
|
||||
extended.put("BaseBasalRate", baseBasalRate)
|
||||
try {
|
||||
extended.put("ActiveProfile", profileName)
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
pumpJson.put("status", status)
|
||||
pumpJson.put("extended", extended)
|
||||
pumpJson.put("reservoir", medtrumPump.reservoir.toInt())
|
||||
pumpJson.put("clock", dateUtil.toISOString(now))
|
||||
} catch (e: JSONException) {
|
||||
aapsLogger.error(LTag.PUMP, "Unhandled exception: $e")
|
||||
}
|
||||
return pumpJson
|
||||
}
|
||||
|
||||
override fun manufacturer(): ManufacturerType {
|
||||
|
@ -336,7 +374,20 @@ import kotlin.math.round
|
|||
get() = PumpDescription(medtrumPump.pumpType)
|
||||
|
||||
override fun shortStatus(veryShort: Boolean): String {
|
||||
return ""// TODO
|
||||
var ret = ""
|
||||
if (medtrumPump.lastConnection != 0L) {
|
||||
val agoMillis = System.currentTimeMillis() - medtrumPump.lastConnection
|
||||
val agoMin = (agoMillis / 60.0 / 1000.0).toInt()
|
||||
ret += "LastConn: $agoMin minAgo\n"
|
||||
}
|
||||
if (medtrumPump.lastBolusTime != 0L)
|
||||
ret += "LastBolus: ${DecimalFormatter.to2Decimal(medtrumPump.lastBolusAmount)}U @${DateFormat.format("HH:mm", medtrumPump.lastBolusTime)}\n"
|
||||
|
||||
if (medtrumPump.tempBasalInProgress)
|
||||
ret += "Temp: ${medtrumPump.temporaryBasalToString()}\n"
|
||||
|
||||
ret += "Res: ${DecimalFormatter.to0Decimal(medtrumPump.reservoir)}U\n"
|
||||
return ret
|
||||
}
|
||||
|
||||
override val isFakingTempsByExtendedBoluses: Boolean = false
|
||||
|
@ -375,7 +426,7 @@ import kotlin.math.round
|
|||
}
|
||||
|
||||
override fun setUserOptions(): PumpEnactResult {
|
||||
if (!isInitialized()) {
|
||||
if (!isInitialized()) {
|
||||
val result = PumpEnactResult(injector).success(false)
|
||||
result.comment = "pump not initialized"
|
||||
return result
|
||||
|
|
|
@ -68,19 +68,13 @@ class MedtrumPump @Inject constructor(
|
|||
|
||||
private var _lastBasalType: MutableStateFlow<BasalType> = MutableStateFlow(BasalType.NONE)
|
||||
val lastBasalTypeFlow: StateFlow<BasalType> = _lastBasalType
|
||||
var lastBasalType: BasalType
|
||||
val lastBasalType: BasalType
|
||||
get() = _lastBasalType.value
|
||||
set(value) {
|
||||
_lastBasalType.value = value
|
||||
}
|
||||
|
||||
private val _lastBasalRate = MutableStateFlow(0.0)
|
||||
val lastBasalRateFlow: StateFlow<Double> = _lastBasalRate
|
||||
var lastBasalRate: Double
|
||||
val lastBasalRate: Double
|
||||
get() = _lastBasalRate.value
|
||||
set(value) {
|
||||
_lastBasalRate.value = value
|
||||
}
|
||||
|
||||
private val _reservoir = MutableStateFlow(0.0)
|
||||
val reservoirFlow: StateFlow<Double> = _reservoir
|
||||
|
@ -141,6 +135,22 @@ class MedtrumPump @Inject constructor(
|
|||
sp.putString(R.string.key_actual_basal_profile, encodedString ?: "")
|
||||
}
|
||||
|
||||
private var _lastBolusTime = 0L // Time in ms!
|
||||
var lastBolusTime: Long
|
||||
get() = _lastBolusTime
|
||||
set(value) {
|
||||
_lastBolusTime = value
|
||||
sp.putLong(R.string.key_last_bolus_time, value)
|
||||
}
|
||||
|
||||
private var _lastBolusAmount = 0.0
|
||||
var lastBolusAmount: Double
|
||||
get() = _lastBolusAmount
|
||||
set(value) {
|
||||
_lastBolusAmount = value
|
||||
sp.putDouble(R.string.key_last_bolus_amount, value)
|
||||
}
|
||||
|
||||
private var _lastConnection = 0L // Time in ms!
|
||||
var lastConnection: Long
|
||||
get() = _lastConnection
|
||||
|
@ -181,7 +191,6 @@ class MedtrumPump @Inject constructor(
|
|||
|
||||
var lastTimeReceivedFromPump = 0L // Time in ms! // TODO: Consider removing as is not used?
|
||||
var suspendTime = 0L // Time in ms!
|
||||
|
||||
var patchAge = 0L // Time in seconds?! // TODO: Not used
|
||||
|
||||
|
||||
|
@ -196,11 +205,18 @@ class MedtrumPump @Inject constructor(
|
|||
var bolusStopForced = false // bolus forced to stop by user
|
||||
var bolusDone = false // success end
|
||||
|
||||
// Last basal status update
|
||||
// TODO maybe make basal parameters private? So we are forced to update trough handleBasalStatusUpdate
|
||||
var lastBasalSequence = 0
|
||||
var lastBasalPatchId = 0L
|
||||
var lastBasalStartTime = 0L
|
||||
// Last basal status update (from pump)
|
||||
private var _lastBasalSequence = 0
|
||||
val lastBasalSequence: Int
|
||||
get() = _lastBasalSequence
|
||||
|
||||
private var _lastBasalPatchId = 0L
|
||||
val lastBasalPatchId: Long
|
||||
get() = _lastBasalPatchId
|
||||
|
||||
private var _lastBasalStartTime = 0L
|
||||
val lastBasalStartTime: Long
|
||||
get() = _lastBasalStartTime
|
||||
|
||||
val baseBasalRate: Double
|
||||
get() = getHourlyBasalFromMedtrumProfileArray(actualBasalProfile, dateUtil.now())
|
||||
|
@ -225,6 +241,8 @@ class MedtrumPump @Inject constructor(
|
|||
// Load stuff from SP
|
||||
_patchSessionToken = sp.getLong(R.string.key_session_token, 0L)
|
||||
_lastConnection = sp.getLong(R.string.key_last_connection, 0L)
|
||||
_lastBolusTime = sp.getLong(R.string.key_last_bolus_time, 0L)
|
||||
_lastBolusAmount = sp.getDouble(R.string.key_last_bolus_amount, 0.0)
|
||||
_currentSequenceNumber = sp.getInt(R.string.key_current_sequence_number, 0)
|
||||
_patchId = sp.getLong(R.string.key_patch_id, 0L)
|
||||
_syncedSequenceNumber = sp.getInt(R.string.key_synced_sequence_number, 0)
|
||||
|
@ -243,7 +261,7 @@ class MedtrumPump @Inject constructor(
|
|||
|
||||
fun loadUserSettingsFromSP() {
|
||||
desiredPatchExpiration = sp.getBoolean(info.nightscout.pump.medtrum.R.string.key_patch_expiration, false)
|
||||
val alarmSettingCode = sp.getString(info.nightscout.pump.medtrum.R.string.key_alarm_setting, AlarmSetting.LIGHT_VIBRATE_AND_BEEP.code.toString())?.toByte()
|
||||
val alarmSettingCode = sp.getString(info.nightscout.pump.medtrum.R.string.key_alarm_setting, AlarmSetting.LIGHT_VIBRATE_AND_BEEP.code.toString()).toByte()
|
||||
desiredAlarmSetting = AlarmSetting.values().firstOrNull { it.code == alarmSettingCode } ?: AlarmSetting.LIGHT_VIBRATE_AND_BEEP
|
||||
desiredHourlyMaxInsulin = sp.getInt(info.nightscout.pump.medtrum.R.string.key_hourly_max_insulin, 40)
|
||||
desiredDailyMaxInsulin = sp.getInt(info.nightscout.pump.medtrum.R.string.key_daily_max_insulin, 180)
|
||||
|
@ -370,17 +388,17 @@ class MedtrumPump @Inject constructor(
|
|||
}
|
||||
|
||||
// Update medtrum pump state
|
||||
lastBasalType = basalType
|
||||
lastBasalRate = basalRate
|
||||
lastBasalSequence = basalSequence
|
||||
_lastBasalType.value = basalType
|
||||
_lastBasalRate.value = basalRate
|
||||
_lastBasalSequence = basalSequence
|
||||
if (basalSequence > currentSequenceNumber) {
|
||||
currentSequenceNumber = basalSequence
|
||||
}
|
||||
lastBasalPatchId = basalPatchId
|
||||
_lastBasalPatchId = basalPatchId
|
||||
if (basalPatchId != patchId) {
|
||||
aapsLogger.error(LTag.PUMP, "handleBasalStatusUpdate: WTF? PatchId in status update does not match current patchId!")
|
||||
}
|
||||
lastBasalStartTime = basalStartTime
|
||||
_lastBasalStartTime = basalStartTime
|
||||
}
|
||||
|
||||
fun handleStopStatusUpdate(stopSequence: Int, stopPatchId: Long) {
|
||||
|
@ -418,4 +436,8 @@ class MedtrumPump @Inject constructor(
|
|||
"handleBasalStatusUpdate: ${if (newRecord) "**NEW** " else ""}EVENT TEMP_START (FAKE)"
|
||||
)
|
||||
}
|
||||
fun temporaryBasalToString(): String {
|
||||
if (!tempBasalInProgress) return ""
|
||||
return tempBasalAbsoluteRate.toString() + "U/h"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -125,7 +125,12 @@ class GetRecordPacket(injector: HasAndroidInjector, private val recordIndex: Int
|
|||
// detailedInfo can be from another similar record. Reinsert
|
||||
detailedBolusInfoStorage.add(detailedBolusInfo)
|
||||
}
|
||||
if (bolusStartTime > medtrumPump.lastBolusTime) {
|
||||
medtrumPump.lastBolusTime = bolusStartTime
|
||||
medtrumPump.lastBolusAmount = bolusNormalDelivered
|
||||
}
|
||||
} else {
|
||||
// TODO: at least record the bolus
|
||||
aapsLogger.error(
|
||||
LTag.PUMPCOMM,
|
||||
"from record: EVENT BOLUS ${dateUtil.dateAndTimeString(bolusStartTime)} ($bolusStartTime) " + "Bolus type: $bolusType not supported"
|
||||
|
|
|
@ -53,6 +53,10 @@ class MedtrumOverviewViewModel @Inject constructor(
|
|||
val lastConnectionMinAgo: LiveData<String>
|
||||
get() = _lastConnectionMinAgo
|
||||
|
||||
private val _lastBolus = SingleLiveEvent<String>()
|
||||
val lastBolus: LiveData<String>
|
||||
get() = _lastBolus
|
||||
|
||||
private val _activeAlarms = SingleLiveEvent<String>()
|
||||
val activeAlarms: LiveData<String>
|
||||
get() = _activeAlarms
|
||||
|
@ -64,7 +68,7 @@ class MedtrumOverviewViewModel @Inject constructor(
|
|||
private val _fwVersion = SingleLiveEvent<String>()
|
||||
val fwVersion: LiveData<String>
|
||||
get() = _fwVersion
|
||||
|
||||
|
||||
private val _patchNo = SingleLiveEvent<String>()
|
||||
val patchNo: LiveData<String>
|
||||
get() = _patchNo
|
||||
|
@ -148,6 +152,21 @@ class MedtrumOverviewViewModel @Inject constructor(
|
|||
val agoMilliseconds = System.currentTimeMillis() - medtrumPump.lastConnection
|
||||
val agoMinutes = agoMilliseconds / 1000 / 60
|
||||
_lastConnectionMinAgo.postValue(rh.gs(info.nightscout.shared.R.string.minago, agoMinutes))
|
||||
if (medtrumPump.lastBolusTime != 0L) {
|
||||
val agoMilliseconds = System.currentTimeMillis() - medtrumPump.lastBolusTime
|
||||
val agoHours = agoMilliseconds.toDouble() / 60.0 / 60.0 / 1000.0
|
||||
if (agoHours < 6)
|
||||
// max 6h back
|
||||
_lastBolus.postValue(
|
||||
dateUtil.timeString(medtrumPump.lastBolusTime) + " " + dateUtil.sinceString(medtrumPump.lastBolusTime, rh) + " " + rh.gs(
|
||||
info.nightscout.interfaces.R.string
|
||||
.format_insulin_units, medtrumPump
|
||||
.lastBolusAmount
|
||||
)
|
||||
)
|
||||
else
|
||||
_lastBolus.postValue("")
|
||||
}
|
||||
|
||||
// TODO: Update these values
|
||||
// _activeAlarms.postValue(rh.gs(R.string.active_alarms, pump.activeAlarms))
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="3dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
|
@ -68,10 +69,20 @@
|
|||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="2dip"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:background="?android:attr/dividerHorizontal" />
|
||||
|
||||
<!-- Last Connected -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="3dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
|
@ -236,6 +247,104 @@
|
|||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Last bolus -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="3dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.5"
|
||||
android:gravity="end"
|
||||
android:paddingStart="5dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:text="@string/last_bolus_label"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="5dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0"
|
||||
android:gravity="center_horizontal"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingEnd="2dp"
|
||||
android:text=":"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/last_bolus"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="start"
|
||||
android:paddingStart="5dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:text='@{viewmodel.lastBolus}'
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="2dip"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:background="?android:attr/dividerHorizontal" />
|
||||
|
||||
<!-- Active Alarms -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="3dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.5"
|
||||
android:gravity="end"
|
||||
android:paddingStart="5dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:text="@string/active_alarms_label"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="5dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0"
|
||||
android:gravity="center_horizontal"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingEnd="2dp"
|
||||
android:text=":"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/active_alarms"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="start"
|
||||
android:paddingStart="5dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:text="@{viewmodel.activeAlarms}"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="2dip"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:background="?android:attr/dividerHorizontal" />
|
||||
|
||||
<!-- Reservoir -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
@ -316,47 +425,6 @@
|
|||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Active Alarms -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="3dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.5"
|
||||
android:gravity="end"
|
||||
android:paddingStart="5dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:text="@string/active_alarms_label"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="5dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0"
|
||||
android:gravity="center_horizontal"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingEnd="2dp"
|
||||
android:text=":"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/active_alarms"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="start"
|
||||
android:paddingStart="5dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:text="@{viewmodel.activeAlarms}"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<View
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="2dip"
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
<string name="key_medtrumpump_settings" translatable="false">medtrumpump_settings</string>
|
||||
<string name="key_pump_state" translatable="false">pump_state</string>
|
||||
<string name="key_last_connection" translatable="false">last_connection</string>
|
||||
<string name="key_last_bolus_time" translatable="false">last_bolus_time</string>
|
||||
<string name="key_last_bolus_amount" translatable="false">last_bolus_amount</string>
|
||||
<string name="key_session_token" translatable="false">medtrum_session_token</string>
|
||||
<string name="key_patch_id" translatable="false">patch_id</string>
|
||||
<string name="key_device_type" translatable="false">device_type</string>
|
||||
|
@ -35,9 +37,7 @@
|
|||
<string name="last_connection_label">Last connected</string>
|
||||
<string name="pump_state_label">Pump state</string>
|
||||
<string name="active_alarms_label">Active alarms</string>
|
||||
<string name="reservoir_label"> Reservoir</string>
|
||||
<string name="reservoir_level"> %.2f U</string>
|
||||
<string name="battery_label">Battery</string>
|
||||
<string name="battery_voltage"> %.2f V</string>
|
||||
<string name="basal_type_label">Basal type</string>
|
||||
<string name="basal_rate_label">Basal rate</string>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
<EditTextPreference
|
||||
android:key="@string/key_sn_input"
|
||||
android:summary="@string/sn_input_summary"
|
||||
android:dialogMessage="@string/sn_input_summary"
|
||||
android:singleLine="true"
|
||||
android:title="@string/sn_input_title" />
|
||||
|
||||
|
|
Loading…
Reference in a new issue