forked from NG-SD-Plugins/PowerTools
Add charge mode setting for SD, move static battery readings into driver
This commit is contained in:
parent
1466c4647b
commit
2b2a6eaf9d
9 changed files with 201 additions and 56 deletions
|
@ -1,27 +1,101 @@
|
||||||
use std::sync::mpsc::{Sender, self};
|
use std::sync::mpsc::{Sender, self};
|
||||||
use std::sync::Mutex;
|
use std::sync::{Arc, Mutex};
|
||||||
use usdpl_back::core::serdes::Primitive;
|
use usdpl_back::core::serdes::Primitive;
|
||||||
|
use usdpl_back::AsyncCallable;
|
||||||
|
|
||||||
use super::handler::{ApiMessage, BatteryMessage};
|
use super::handler::{ApiMessage, BatteryMessage};
|
||||||
|
|
||||||
/// Current current (ha!) web method
|
/// Current current (ha!) web method
|
||||||
pub fn current_now(_: super::ApiParameterType) -> super::ApiParameterType {
|
pub fn current_now(
|
||||||
super::utility::map_optional_result(crate::settings::driver::read_current_now())
|
sender: Sender<ApiMessage>,
|
||||||
|
) -> impl AsyncCallable {
|
||||||
|
let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety
|
||||||
|
let getter = move || {
|
||||||
|
let sender2 = sender.clone();
|
||||||
|
move || {
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
let callback = move |val: Option<f64>| tx.send(val).expect("current_now callback send failed");
|
||||||
|
sender2.lock().unwrap().send(ApiMessage::Battery(BatteryMessage::ReadCurrentNow(Box::new(callback)))).expect("current_now send failed");
|
||||||
|
rx.recv().expect("current_now callback recv failed")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
super::async_utils::AsyncIshGetter {
|
||||||
|
set_get: getter,
|
||||||
|
trans_getter: |result| {
|
||||||
|
super::utility::map_optional_result(Ok(result))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Current current (ha!) web method
|
||||||
|
/*pub fn current_now(_: super::ApiParameterType) -> super::ApiParameterType {
|
||||||
|
super::utility::map_optional_result(crate::settings::driver::read_current_now())
|
||||||
|
}*/
|
||||||
|
|
||||||
/// Charge now web method
|
/// Charge now web method
|
||||||
pub fn charge_now(_: super::ApiParameterType) -> super::ApiParameterType {
|
pub fn charge_now(
|
||||||
super::utility::map_optional_result(crate::settings::driver::read_charge_now())
|
sender: Sender<ApiMessage>,
|
||||||
|
) -> impl AsyncCallable {
|
||||||
|
let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety
|
||||||
|
let getter = move || {
|
||||||
|
let sender2 = sender.clone();
|
||||||
|
move || {
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
let callback = move |val: Option<f64>| tx.send(val).expect("charge_now callback send failed");
|
||||||
|
sender2.lock().unwrap().send(ApiMessage::Battery(BatteryMessage::ReadChargeNow(Box::new(callback)))).expect("charge_now send failed");
|
||||||
|
rx.recv().expect("charge_now callback recv failed")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
super::async_utils::AsyncIshGetter {
|
||||||
|
set_get: getter,
|
||||||
|
trans_getter: |result| {
|
||||||
|
super::utility::map_optional_result(Ok(result))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Charge full web method
|
/// Charge full web method
|
||||||
pub fn charge_full(_: super::ApiParameterType) -> super::ApiParameterType {
|
pub fn charge_full(
|
||||||
super::utility::map_optional_result(crate::settings::driver::read_charge_full())
|
sender: Sender<ApiMessage>,
|
||||||
|
) -> impl AsyncCallable {
|
||||||
|
let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety
|
||||||
|
let getter = move || {
|
||||||
|
let sender2 = sender.clone();
|
||||||
|
move || {
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
let callback = move |val: Option<f64>| tx.send(val).expect("charge_full callback send failed");
|
||||||
|
sender2.lock().unwrap().send(ApiMessage::Battery(BatteryMessage::ReadChargeFull(Box::new(callback)))).expect("charge_full send failed");
|
||||||
|
rx.recv().expect("charge_full callback recv failed")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
super::async_utils::AsyncIshGetter {
|
||||||
|
set_get: getter,
|
||||||
|
trans_getter: |result| {
|
||||||
|
super::utility::map_optional_result(Ok(result))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Charge design web method
|
/// Charge design web method
|
||||||
pub fn charge_design(_: super::ApiParameterType) -> super::ApiParameterType {
|
pub fn charge_design(
|
||||||
super::utility::map_optional_result(crate::settings::driver::read_charge_design())
|
sender: Sender<ApiMessage>,
|
||||||
|
) -> impl AsyncCallable {
|
||||||
|
let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety
|
||||||
|
let getter = move || {
|
||||||
|
let sender2 = sender.clone();
|
||||||
|
move || {
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
let callback = move |val: Option<f64>| tx.send(val).expect("charge_design callback send failed");
|
||||||
|
sender2.lock().unwrap().send(ApiMessage::Battery(BatteryMessage::ReadChargeDesign(Box::new(callback)))).expect("charge_design send failed");
|
||||||
|
rx.recv().expect("charge_design callback recv failed")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
super::async_utils::AsyncIshGetter {
|
||||||
|
set_get: getter,
|
||||||
|
trans_getter: |result| {
|
||||||
|
super::utility::map_optional_result(Ok(result))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate set battery charge rate web method
|
/// Generate set battery charge rate web method
|
||||||
|
|
|
@ -24,6 +24,10 @@ pub enum BatteryMessage {
|
||||||
GetChargeRate(Callback<Option<u64>>),
|
GetChargeRate(Callback<Option<u64>>),
|
||||||
SetChargeMode(Option<String>),
|
SetChargeMode(Option<String>),
|
||||||
GetChargeMode(Callback<Option<String>>),
|
GetChargeMode(Callback<Option<String>>),
|
||||||
|
ReadChargeFull(Callback<Option<f64>>),
|
||||||
|
ReadChargeNow(Callback<Option<f64>>),
|
||||||
|
ReadChargeDesign(Callback<Option<f64>>),
|
||||||
|
ReadCurrentNow(Callback<Option<f64>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BatteryMessage {
|
impl BatteryMessage {
|
||||||
|
@ -33,6 +37,10 @@ impl BatteryMessage {
|
||||||
Self::GetChargeRate(cb) => cb(settings.get_charge_rate()),
|
Self::GetChargeRate(cb) => cb(settings.get_charge_rate()),
|
||||||
Self::SetChargeMode(mode) => settings.charge_mode(mode),
|
Self::SetChargeMode(mode) => settings.charge_mode(mode),
|
||||||
Self::GetChargeMode(cb) => cb(settings.get_charge_mode()),
|
Self::GetChargeMode(cb) => cb(settings.get_charge_mode()),
|
||||||
|
Self::ReadChargeFull(cb) => cb(settings.read_charge_full()),
|
||||||
|
Self::ReadChargeNow(cb) => cb(settings.read_charge_now()),
|
||||||
|
Self::ReadChargeDesign(cb) => cb(settings.read_charge_design()),
|
||||||
|
Self::ReadCurrentNow(cb) => cb(settings.read_current_now()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,10 +66,10 @@ fn main() -> Result<(), ()> {
|
||||||
vec![format!("{} v{}", PACKAGE_NAME, PACKAGE_VERSION).into()]
|
vec![format!("{} v{}", PACKAGE_NAME, PACKAGE_VERSION).into()]
|
||||||
})
|
})
|
||||||
// battery API functions
|
// battery API functions
|
||||||
.register("BATTERY_current_now", api::battery::current_now)
|
.register_async("BATTERY_current_now", api::battery::current_now(api_sender.clone()))
|
||||||
.register("BATTERY_charge_now", api::battery::charge_now)
|
.register_async("BATTERY_charge_now", api::battery::charge_now(api_sender.clone()))
|
||||||
.register("BATTERY_charge_full", api::battery::charge_full)
|
.register_async("BATTERY_charge_full", api::battery::charge_full(api_sender.clone()))
|
||||||
.register("BATTERY_charge_design", api::battery::charge_design)
|
.register_async("BATTERY_charge_design", api::battery::charge_design(api_sender.clone()))
|
||||||
.register(
|
.register(
|
||||||
"BATTERY_set_charge_rate",
|
"BATTERY_set_charge_rate",
|
||||||
api::battery::set_charge_rate(api_sender.clone()),
|
api::battery::set_charge_rate(api_sender.clone()),
|
||||||
|
|
|
@ -142,44 +142,7 @@ impl Driver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// static battery calls
|
// sshhhh, this function isn't here ;)
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn read_current_now() -> Result<Option<u64>, SettingError> {
|
|
||||||
match auto_detect() {
|
|
||||||
DriverJson::SteamDeck => super::steam_deck::Battery::read_current_now().map(|x| Some(x)),
|
|
||||||
DriverJson::SteamDeckAdvance => super::steam_deck::Battery::read_current_now().map(|x| Some(x)),
|
|
||||||
DriverJson::Unknown => Ok(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn read_charge_now() -> Result<Option<f64>, SettingError> {
|
|
||||||
match auto_detect() {
|
|
||||||
DriverJson::SteamDeck => super::steam_deck::Battery::read_charge_now().map(|x| Some(x)),
|
|
||||||
DriverJson::SteamDeckAdvance => super::steam_deck::Battery::read_charge_now().map(|x| Some(x)),
|
|
||||||
DriverJson::Unknown => Ok(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn read_charge_full() -> Result<Option<f64>, SettingError> {
|
|
||||||
match auto_detect() {
|
|
||||||
DriverJson::SteamDeck => super::steam_deck::Battery::read_charge_full().map(|x| Some(x)),
|
|
||||||
DriverJson::SteamDeckAdvance => super::steam_deck::Battery::read_charge_full().map(|x| Some(x)),
|
|
||||||
DriverJson::Unknown => Ok(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn read_charge_design() -> Result<Option<f64>, SettingError> {
|
|
||||||
match auto_detect() {
|
|
||||||
DriverJson::SteamDeck => super::steam_deck::Battery::read_charge_design().map(|x| Some(x)),
|
|
||||||
DriverJson::SteamDeckAdvance => super::steam_deck::Battery::read_charge_design().map(|x| Some(x)),
|
|
||||||
DriverJson::Unknown => Ok(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn maybe_do_button() {
|
pub fn maybe_do_button() {
|
||||||
match auto_detect() {
|
match auto_detect() {
|
||||||
|
|
|
@ -242,7 +242,7 @@ impl TBattery for Battery {
|
||||||
max: max.charge_rate.unwrap(),
|
max: max.charge_rate.unwrap(),
|
||||||
}),
|
}),
|
||||||
charge_current_step: 50,
|
charge_current_step: 50,
|
||||||
charge_modes: vec!["discharge".to_owned(), "idle".to_owned(), "normal".to_owned()],
|
charge_modes: vec!["normal".to_owned(), "discharge".to_owned(), "idle".to_owned()],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,4 +265,44 @@ impl TBattery for Battery {
|
||||||
fn get_charge_mode(&self) -> Option<String> {
|
fn get_charge_mode(&self) -> Option<String> {
|
||||||
self.charge_mode.map(Self::charge_mode_to_str)
|
self.charge_mode.map(Self::charge_mode_to_str)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_charge_full(&self) -> Option<f64> {
|
||||||
|
match Self::read_charge_full() {
|
||||||
|
Ok(x) => Some(x),
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("read_charge_full err: {}", e.msg);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_charge_now(&self) -> Option<f64> {
|
||||||
|
match Self::read_charge_now() {
|
||||||
|
Ok(x) => Some(x),
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("read_charge_now err: {}", e.msg);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_charge_design(&self) -> Option<f64> {
|
||||||
|
match Self::read_charge_design() {
|
||||||
|
Ok(x) => Some(x),
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("read_charge_design err: {}", e.msg);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_current_now(&self) -> Option<f64> {
|
||||||
|
match Self::read_current_now() {
|
||||||
|
Ok(x) => Some(x as f64),
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("read_current_now err: {}", e.msg);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,4 +83,12 @@ pub trait TBattery: OnResume + OnSet + Debug + Send {
|
||||||
fn charge_mode(&mut self, mode: Option<String>);
|
fn charge_mode(&mut self, mode: Option<String>);
|
||||||
|
|
||||||
fn get_charge_mode(&self) -> Option<String>;
|
fn get_charge_mode(&self) -> Option<String>;
|
||||||
|
|
||||||
|
fn read_charge_full(&self) -> Option<f64>;
|
||||||
|
|
||||||
|
fn read_charge_now(&self) -> Option<f64>;
|
||||||
|
|
||||||
|
fn read_charge_design(&self) -> Option<f64>;
|
||||||
|
|
||||||
|
fn read_current_now(&self) -> Option<f64>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,4 +55,12 @@ impl TBattery for Battery {
|
||||||
fn get_charge_mode(&self) -> Option<String> {
|
fn get_charge_mode(&self) -> Option<String> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_charge_full(&self) -> Option<f64> { None }
|
||||||
|
|
||||||
|
fn read_charge_now(&self) -> Option<f64> { None }
|
||||||
|
|
||||||
|
fn read_charge_design(&self) -> Option<f64> { None }
|
||||||
|
|
||||||
|
fn read_current_now(&self) -> Option<f64> { None }
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ export type SettingsLimits = {
|
||||||
export type BatteryLimits = {
|
export type BatteryLimits = {
|
||||||
charge_current: RangeLimit | null;
|
charge_current: RangeLimit | null;
|
||||||
charge_current_step: number;
|
charge_current_step: number;
|
||||||
charge_modes: number;
|
charge_modes: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CpuLimits = {
|
export type CpuLimits = {
|
||||||
|
@ -105,11 +105,11 @@ export async function unsetBatteryChargeRate(): Promise<any[]> {
|
||||||
return await call_backend("BATTERY_unset_charge_rate", []);
|
return await call_backend("BATTERY_unset_charge_rate", []);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getBatteryChargeMode(): Promise<number> {
|
export async function getBatteryChargeMode(): Promise<string> {
|
||||||
return (await call_backend("BATTERY_get_charge_mode", []))[0];
|
return (await call_backend("BATTERY_get_charge_mode", []))[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setBatteryChargeMode(val: number): Promise<number> {
|
export async function setBatteryChargeMode(val: string): Promise<string> {
|
||||||
return (await call_backend("BATTERY_set_charge_mode", [val]))[0];
|
return (await call_backend("BATTERY_set_charge_mode", [val]))[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,7 @@ const LIMITS_INFO = "LIMITS_all";
|
||||||
|
|
||||||
const CURRENT_BATT = "BATTERY_current_now";
|
const CURRENT_BATT = "BATTERY_current_now";
|
||||||
const CHARGE_RATE_BATT = "BATTERY_charge_rate";
|
const CHARGE_RATE_BATT = "BATTERY_charge_rate";
|
||||||
|
const CHARGE_MODE_BATT = "BATTERY_charge_mode";
|
||||||
const CHARGE_NOW_BATT = "BATTERY_charge_now";
|
const CHARGE_NOW_BATT = "BATTERY_charge_now";
|
||||||
const CHARGE_FULL_BATT = "BATTERY_charge_full";
|
const CHARGE_FULL_BATT = "BATTERY_charge_full";
|
||||||
const CHARGE_DESIGN_BATT = "BATTERY_charge_design"
|
const CHARGE_DESIGN_BATT = "BATTERY_charge_design"
|
||||||
|
@ -107,6 +108,7 @@ const reload = function() {
|
||||||
|
|
||||||
backend.resolve(backend.getBatteryCurrent(), (rate: number) => { set_value(CURRENT_BATT, rate) });
|
backend.resolve(backend.getBatteryCurrent(), (rate: number) => { set_value(CURRENT_BATT, rate) });
|
||||||
backend.resolve(backend.getBatteryChargeRate(), (rate: number) => { set_value(CHARGE_RATE_BATT, rate) });
|
backend.resolve(backend.getBatteryChargeRate(), (rate: number) => { set_value(CHARGE_RATE_BATT, rate) });
|
||||||
|
backend.resolve(backend.getBatteryChargeMode(), (mode: string) => { set_value(CHARGE_MODE_BATT, mode) });
|
||||||
backend.resolve(backend.getBatteryChargeNow(), (rate: number) => { set_value(CHARGE_NOW_BATT, rate) });
|
backend.resolve(backend.getBatteryChargeNow(), (rate: number) => { set_value(CHARGE_NOW_BATT, rate) });
|
||||||
backend.resolve(backend.getBatteryChargeFull(), (rate: number) => { set_value(CHARGE_FULL_BATT, rate) });
|
backend.resolve(backend.getBatteryChargeFull(), (rate: number) => { set_value(CHARGE_FULL_BATT, rate) });
|
||||||
backend.resolve(backend.getBatteryChargeDesign(), (rate: number) => { set_value(CHARGE_DESIGN_BATT, rate) });
|
backend.resolve(backend.getBatteryChargeDesign(), (rate: number) => { set_value(CHARGE_DESIGN_BATT, rate) });
|
||||||
|
@ -218,6 +220,11 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
|
||||||
label: <span>{elem}</span>,
|
label: <span>{elem}</span>,
|
||||||
};});
|
};});
|
||||||
|
|
||||||
|
const chargeModeOptions: SingleDropdownOption[] = (get_value(LIMITS_INFO) as backend.SettingsLimits).battery.charge_modes.map((elem) => {return {
|
||||||
|
data: elem,
|
||||||
|
label: <span>{elem}</span>,
|
||||||
|
};});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelSection>
|
<PanelSection>
|
||||||
{/* CPU */}
|
{/* CPU */}
|
||||||
|
@ -494,7 +501,7 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
|
||||||
}}
|
}}
|
||||||
/>}
|
/>}
|
||||||
</PanelSectionRow>}
|
</PanelSectionRow>}
|
||||||
{advancedMode && (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].governors.length != 0 && <PanelSectionRow>
|
{advancedMode && governorOptions.length != 0 && <PanelSectionRow>
|
||||||
<Field
|
<Field
|
||||||
label="Governor"
|
label="Governor"
|
||||||
>
|
>
|
||||||
|
@ -739,6 +746,43 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
|
||||||
}}
|
}}
|
||||||
/>}
|
/>}
|
||||||
</PanelSectionRow>}
|
</PanelSectionRow>}
|
||||||
|
{chargeModeOptions.length != 0 && <PanelSectionRow>
|
||||||
|
<ToggleField
|
||||||
|
checked={get_value(CHARGE_MODE_BATT) != null}
|
||||||
|
label="Charge Mode"
|
||||||
|
description="Force battery charge mode"
|
||||||
|
onChange={(value: boolean) => {
|
||||||
|
if (value) {
|
||||||
|
set_value(CHARGE_MODE_BATT, chargeModeOptions[0].data as string);
|
||||||
|
reloadGUI("BATTChargeModeToggle");
|
||||||
|
} else {
|
||||||
|
set_value(CHARGE_MODE_BATT, null);
|
||||||
|
backend.resolve(backend.unsetBatteryChargeMode(), (_: any[]) => {
|
||||||
|
reloadGUI("BATTUnsetChargeMode");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{get_value(CHARGE_MODE_BATT) != null && <Field
|
||||||
|
label="Mode"
|
||||||
|
>
|
||||||
|
<Dropdown
|
||||||
|
menuLabel="Charge Mode"
|
||||||
|
rgOptions={chargeModeOptions}
|
||||||
|
selectedOption={chargeModeOptions.find((val: SingleDropdownOption, _index, _arr) => {
|
||||||
|
return val.data == get_value(CHARGE_MODE_BATT);
|
||||||
|
})}
|
||||||
|
strDefaultLabel={get_value(CHARGE_MODE_BATT)}
|
||||||
|
onChange={(elem: SingleDropdownOption) => {
|
||||||
|
console.debug("Charge mode dropdown selected", elem);
|
||||||
|
backend.resolve(backend.setBatteryChargeMode(elem.data as string), (mode: string) => {
|
||||||
|
set_value(CHARGE_MODE_BATT, mode);
|
||||||
|
reloadGUI("BATTChargeMode");
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Field>}
|
||||||
|
</PanelSectionRow>}
|
||||||
<PanelSectionRow>
|
<PanelSectionRow>
|
||||||
<Field
|
<Field
|
||||||
label="Current"
|
label="Current"
|
||||||
|
|
Loading…
Reference in a new issue