diff --git a/ChangeLog.md b/ChangeLog.md index 826d8a7..0b0347f 100755 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,14 @@ todos: * Start daemon on system reboot * Floating window on iOS12 +1.7版本更新日志: +* 增加对SBC的支持 +* App服务分离, 防止越狱环境下某些插件导致服务崩溃 + +1.7 ChangeLog: +* Support SBC +* Seperate Daemon from App, to make it compatible with some tweaks + 1.6.1版本更新日志: * 增加对有线充电宝的支持 * 增加阿拉伯语 @@ -14,6 +22,7 @@ todos: 1.6.1 ChangeLog: * Support wired power banks * Arabic language added +* Add UI support for iOS17.0 1.6版本更新日志: * 增加温度模拟(即Powercuff)及峰值性能状态管理功能 diff --git a/ChargeLimiter/Package/DEBIAN/control b/ChargeLimiter/Package/DEBIAN/control index a74f60e..9fd0496 100644 --- a/ChargeLimiter/Package/DEBIAN/control +++ b/ChargeLimiter/Package/DEBIAN/control @@ -1,6 +1,6 @@ Package: chaoge.ChargeLimiter Name: ChargeLimiter -Version: 1.6.1 +Version: 1.7 Description: ChargeLimiter Section: Applications Depends: firmware (>= 5.0) diff --git a/ChargeLimiter/Package/DEBIAN/postinst b/ChargeLimiter/Package/DEBIAN/postinst index 3b9c314..8b90bcf 100755 --- a/ChargeLimiter/Package/DEBIAN/postinst +++ b/ChargeLimiter/Package/DEBIAN/postinst @@ -1,9 +1,9 @@ #!/bin/bash chown -R root:wheel /Applications/ChargeLimiter.app -chmod +s /Applications/ChargeLimiter.app/ChargeLimiter +chmod +s /Applications/ChargeLimiter.app/ChargeLimiterDaemon chown root:wheel /Library/LaunchDaemons/chaoge.ChargeLimiter.plist -killall -9 ChargeLimiter +killall -9 ChargeLimiter ChargeLimiterDaemon launchctl load /Library/LaunchDaemons/chaoge.ChargeLimiter.plist uicache mobile; uicache --path /Applications/ChargeLimiter.app diff --git a/ChargeLimiter/Package/DEBIAN/prerm b/ChargeLimiter/Package/DEBIAN/prerm index d8799d0..a36a64e 100755 --- a/ChargeLimiter/Package/DEBIAN/prerm +++ b/ChargeLimiter/Package/DEBIAN/prerm @@ -1,8 +1,8 @@ #!/bin/bash -/Applications/ChargeLimiter.app/ChargeLimiter reset +/Applications/ChargeLimiter.app/ChargeLimiterDaemon reset launchctl unload /Library/LaunchDaemons/chaoge.ChargeLimiter.plist -killall -9 ChargeLimiter +killall -9 ChargeLimiter ChargeLimiterDaemon uicache mobile exit 0 diff --git a/ChargeLimiter/Package/Library/LaunchDaemons/chaoge.ChargeLimiter.plist b/ChargeLimiter/Package/Library/LaunchDaemons/chaoge.ChargeLimiter.plist index aecd395..72e6e8e 100755 --- a/ChargeLimiter/Package/Library/LaunchDaemons/chaoge.ChargeLimiter.plist +++ b/ChargeLimiter/Package/Library/LaunchDaemons/chaoge.ChargeLimiter.plist @@ -7,11 +7,10 @@ Label chaoge.ChargeLimiter Program - /Applications/ChargeLimiter.app/ChargeLimiter + /Applications/ChargeLimiter.app/ChargeLimiterDaemon ProgramArguments - /Applications/ChargeLimiter.app/ChargeLimiter - daemon + /Applications/ChargeLimiter.app/ChargeLimiterDaemon ExecuteAllowed diff --git a/ChargeLimiter/Package_rootless/DEBIAN/control b/ChargeLimiter/Package_rootless/DEBIAN/control index a74f60e..9fd0496 100755 --- a/ChargeLimiter/Package_rootless/DEBIAN/control +++ b/ChargeLimiter/Package_rootless/DEBIAN/control @@ -1,6 +1,6 @@ Package: chaoge.ChargeLimiter Name: ChargeLimiter -Version: 1.6.1 +Version: 1.7 Description: ChargeLimiter Section: Applications Depends: firmware (>= 5.0) diff --git a/ChargeLimiter/Package_rootless/DEBIAN/postinst b/ChargeLimiter/Package_rootless/DEBIAN/postinst index 2bf2816..f97f7c3 100755 --- a/ChargeLimiter/Package_rootless/DEBIAN/postinst +++ b/ChargeLimiter/Package_rootless/DEBIAN/postinst @@ -1,8 +1,8 @@ #!/bin/bash chown -R root:wheel /var/jb/Applications/ChargeLimiter.app -chmod +s /var/jb/Applications/ChargeLimiter.app/ChargeLimiter -killall -9 ChargeLimiter +chmod +s /var/jb/Applications/ChargeLimiter.app/ChargeLimiterDaemon +killall -9 ChargeLimiter ChargeLimiterDaemon chown root:wheel /var/jb/Library/LaunchDaemons/chaoge.ChargeLimiter.plist launchctl load /var/jb/Library/LaunchDaemons/chaoge.ChargeLimiter.plist diff --git a/ChargeLimiter/Package_rootless/DEBIAN/prerm b/ChargeLimiter/Package_rootless/DEBIAN/prerm index 26a7d53..56e2fa9 100755 --- a/ChargeLimiter/Package_rootless/DEBIAN/prerm +++ b/ChargeLimiter/Package_rootless/DEBIAN/prerm @@ -1,8 +1,8 @@ #!/bin/bash -/var/jb/Applications/ChargeLimiter.app/ChargeLimiter reset +/var/jb/Applications/ChargeLimiter.app/ChargeLimiterDaemon reset launchctl unload /var/jb/Library/LaunchDaemons/chaoge.ChargeLimiter.plist -killall -9 ChargeLimiter +killall -9 ChargeLimiter ChargeLimiterDaemon uicache mobile exit 0 diff --git a/ChargeLimiter/Package_rootless/Library/LaunchDaemons/chaoge.ChargeLimiter.plist b/ChargeLimiter/Package_rootless/Library/LaunchDaemons/chaoge.ChargeLimiter.plist index 6cd3087..dcea529 100755 --- a/ChargeLimiter/Package_rootless/Library/LaunchDaemons/chaoge.ChargeLimiter.plist +++ b/ChargeLimiter/Package_rootless/Library/LaunchDaemons/chaoge.ChargeLimiter.plist @@ -7,11 +7,10 @@ Label chaoge.ChargeLimiter Program - /var/jb/Applications/ChargeLimiter.app/ChargeLimiter + /var/jb/Applications/ChargeLimiter.app/ChargeLimiterDaemon ProgramArguments - /var/jb/Applications/ChargeLimiter.app/ChargeLimiter - daemon + /var/jb/Applications/ChargeLimiter.app/ChargeLimiterDaemon ExecuteAllowed diff --git a/ChargeLimiter/common.h b/ChargeLimiter/common.h index fe22b4a..9ca829a 100755 --- a/ChargeLimiter/common.h +++ b/ChargeLimiter/common.h @@ -5,14 +5,16 @@ #include #include #include +#include #include #include #include #include #include -#import #import +#import +#import #import #define NSLog2(FORMAT, ...) os_log(OS_LOG_DEFAULT,"%{public}@", [NSString stringWithFormat:FORMAT, ##__VA_ARGS__]) @@ -24,12 +26,11 @@ #define FLOAT_ORIGINY 100 #define FLOAT_WIDTH 80 #define FLOAT_HEIGHT 60 +#define log_prefix @"ChargeLimiterLogger" #define LOG_PATH "/var/root/aldente.log" #define CONF_PATH "/var/root/aldente.conf" #define DB_PATH "/var/root/aldente.db" -extern NSString* log_prefix; - #endif // common_h diff --git a/ChargeLimiter/main.mm b/ChargeLimiter/daemon.mm similarity index 62% rename from ChargeLimiter/main.mm rename to ChargeLimiter/daemon.mm index a477431..7f0f91f 100755 --- a/ChargeLimiter/main.mm +++ b/ChargeLimiter/daemon.mm @@ -1,24 +1,98 @@ +#include #import #import #import -#include "ui.h" #include "utils.h" -#include -NSString* log_prefix = @(PRODUCT "Logger"); + +#define kHIDPage_PowerDevice 0x84 +#define kHIDUsage_PD_PeripheralDevice 0x06 +#define kHIDPage_BatterySystem 0x85 +#define kHIDUsage_BS_PrimaryBattery 0x2e +#define kHIDPage_AppleVendor 0xFF00 +#define kHIDUsage_AppleVendor_AccessoryBattery 0x14 + +#define S_OK 0 +#define S_FALSE 1 + +#define kIOMessageServiceIsTerminated 0xE0000010 +#define kIOPMMessageBatteryStatusHasChanged 0xE0024100 + +typedef SInt32 HRESULT; +typedef UInt32 ULONG; +typedef void* LPVOID; +typedef CFUUIDBytes REFIID; + +typedef void (*IOUPSEventCallbackFunction)(void* target, IOReturn result, void* refcon, void* sender, CFDictionaryRef event); + +struct IOUPSPlugInInterface { + void* _reserved; + HRESULT (*QueryInterface)(void* thisPointer, REFIID iid, LPVOID* ppv); // IUNKNOWN_C_GUTS + ULONG (*AddRef)(void* thisPointer); // IUNKNOWN_C_GUTS + ULONG (*Release)(void* thisPointer); // IUNKNOWN_C_GUTS + IOReturn (*getProperties)(void* thisPointer, CFDictionaryRef* properties); + IOReturn (*getCapabilities)(void* thisPointer, CFSetRef* capabilities); + IOReturn (*getEvent)(void* thisPointer, CFDictionaryRef* event); + IOReturn (*setEventCallback)(void* thisPointer, IOUPSEventCallbackFunction callback, void* target, void* refcon); + IOReturn (*sendCommand)(void* thisPointer, CFDictionaryRef command); +}; + +struct IOUPSPlugInInterface_v140 { + void* _reserved; + HRESULT (*QueryInterface)(void* thisPointer, REFIID iid, LPVOID* ppv); // IUNKNOWN_C_GUTS + ULONG (*AddRef)(void* thisPointer); // IUNKNOWN_C_GUTS + ULONG (*Release)(void* thisPointer); // IUNKNOWN_C_GUTS + IOReturn (*getProperties)(void* thisPointer, CFDictionaryRef* properties); + IOReturn (*getCapabilities)(void* thisPointer, CFSetRef* capabilities); + IOReturn (*getEvent)(void* thisPointer, CFDictionaryRef* event); + IOReturn (*setEventCallback)(void* thisPointer, IOUPSEventCallbackFunction callback, void* target, void* refcon); + IOReturn (*sendCommand)(void* thisPointer, CFDictionaryRef command); + IOReturn (*createAsyncEventSource)(void* thisPointer, CFTypeRef* source); +}; + +struct IOCFPlugInInterface { + void* _reserved; + HRESULT (*QueryInterface)(void* thisPointer, REFIID iid, LPVOID* ppv); // IUNKNOWN_C_GUTS + ULONG (*AddRef)(void* thisPointer); // IUNKNOWN_C_GUTS + ULONG (*Release)(void* thisPointer); // IUNKNOWN_C_GUTS + UInt16 version; + UInt16 revision; + IOReturn (*Probe)(void* thisPointer, CFDictionaryRef propertyTable, io_service_t service, SInt32* order); + IOReturn (*Start)(void* thisPointer, CFDictionaryRef propertyTable, io_service_t service); + IOReturn (*Stop)(void* thisPointer); +}; + +extern "C" { +kern_return_t IOCreatePlugInInterfaceForService(io_service_t service, CFUUIDRef pluginType, CFUUIDRef interfaceType, IOCFPlugInInterface*** theInterface, SInt32* theScore); +} + +@interface UPSDataSlim: NSObject +@property IOUPSPlugInInterface_v140** interface; +@property io_object_t noti; +@property CFRunLoopSourceRef source; +@property CFRunLoopTimerRef timer; +@property(retain) NSMutableDictionary* props; +- (instancetype)init; +- (void)initDB; +- (void)updateProps:(NSDictionary*)props isEvent:(BOOL)event; +@end + + static NSDictionary* bat_info = nil; static BOOL g_enable = NO; static BOOL g_enable_floatwnd = NO; static BOOL g_use_smart = NO; static int g_jbtype = -1; static int g_serv_boot = 0; -int g_wind_type = 0; // 1: HUD + +static IONotificationPortRef gNotifyPort = NULL; +static io_object_t iopmpsNoti = IO_OBJECT_NULL; +static UPSDataSlim* gUPSPS = nil; NSDictionary* handleReq(NSDictionary* nsreq); -static void start_daemon(); -@interface Service: NSObject +@interface Service: NSObject + (instancetype)inst; - (instancetype)init; - (void)serve; @@ -26,27 +100,6 @@ - (void)initLocalPush; - (void)localPush:(NSString*)title msg:(NSString*)msg; @end -static NSMutableDictionary* cache_kv = nil; -id getlocalKV(NSString* key) { - if (cache_kv == nil) { - cache_kv = [NSMutableDictionary dictionaryWithContentsOfFile:@CONF_PATH]; - } - if (cache_kv == nil) { - return nil; - } - return cache_kv[key]; -} - -void setlocalKV(NSString* key, id val) { - if (cache_kv == nil) { - cache_kv = [NSMutableDictionary dictionaryWithContentsOfFile:@CONF_PATH]; - if (cache_kv == nil) { - cache_kv = [NSMutableDictionary new]; - } - } - cache_kv[key] = val; - [cache_kv writeToFile:@CONF_PATH atomically:YES]; -} static io_service_t getIOPMPSServ() { static io_service_t serv = IO_OBJECT_NULL; @@ -72,7 +125,7 @@ static io_service_t getIOPMPSServ() { NSArray* keep = @[ @"Amperage", @"AppleRawCurrentCapacity", @"BatteryInstalled", @"BootVoltage", @"CurrentCapacity", @"CycleCount", @"DesignCapacity", @"ExternalChargeCapable", @"ExternalConnected", @"InstantAmperage", @"IsCharging", @"NominalChargeCapacity", @"PostChargeWaitSeconds", @"PostDischargeWaitSeconds", @"Serial", @"Temperature", - @"UpdateTime", @"Voltage"]; + @"UpdateTime", @"VirtualTemperature", @"Voltage"]; for (NSString* key in info) { if ([keep containsObject:key]) { filtered_info[key] = info[key]; @@ -103,12 +156,12 @@ static io_service_t getIOPMPSServ() { } static int getBatInfoWithServ(io_service_t serv, NSDictionary* __strong* pinfo) { - CFMutableDictionaryRef prop = nil; - IORegistryEntryCreateCFProperties(serv, &prop, kCFAllocatorDefault, 0); - if (prop == nil) { + CFMutableDictionaryRef props = nil; + IORegistryEntryCreateCFProperties(serv, &props, kCFAllocatorDefault, 0); + if (props == nil) { return -2; } - NSMutableDictionary* info = (__bridge_transfer NSMutableDictionary*)prop; + NSMutableDictionary* info = (__bridge_transfer NSMutableDictionary*)props; *pinfo = getBatSlimInfo(info); return 0; } @@ -118,12 +171,12 @@ static int getBatInfo(NSDictionary* __strong* pinfo, BOOL slim=YES) { if (serv == IO_OBJECT_NULL) { return -1; } - CFMutableDictionaryRef prop = nil; - IORegistryEntryCreateCFProperties(serv, &prop, kCFAllocatorDefault, 0); - if (prop == nil) { + CFMutableDictionaryRef props = nil; + IORegistryEntryCreateCFProperties(serv, &props, kCFAllocatorDefault, 0); + if (props == nil) { return -2; } - NSMutableDictionary* info = (__bridge_transfer NSMutableDictionary*)prop; + NSMutableDictionary* info = (__bridge_transfer NSMutableDictionary*)props; if (slim) { *pinfo = getBatSlimInfo(info); } else { @@ -148,6 +201,14 @@ static int setInflowStatus(BOOL flag) { } static BOOL isAdaptorConnect(NSDictionary* info, NSNumber* disableInflow) { // 是否连接电源 + if (gUPSPS != nil) { // UPS电源 + // 使用SBC时ExternalConnected/ExternalChargeCapable一直为false + return YES; + } + NSNumber* InstantAmperage = info[@"InstantAmperage"]; + if (InstantAmperage.intValue >= 0) { // 有电流流入则有电源 + return YES; + } // 某些充电器ExternalConnected为false,而禁流时ExternalConnected/ExternalChargeCapable均为false if (disableInflow.boolValue) { // 禁流模式下只能通过电源信息判断, 某些时候系统会缓存该信息导致不准确 NSDictionary* AdapterDetails = info[@"AdapterDetails"]; @@ -245,7 +306,10 @@ static void performAcccharge(BOOL flag) { if (acc_charge_bright.boolValue) { float val = getBrightness(); cache_status[@"acc_charge_bright"] = @(val); - setAutoBrightEnable(NO); + if (isAutoBrightEnable()) { + setAutoBrightEnable(NO); + cache_status[@"acc_charge_bright_auto"] = @YES; + } setBrightness(0.0); } if (acc_charge_lpm.boolValue) { @@ -266,7 +330,9 @@ static void performAcccharge(BOOL flag) { NSNumber* acc_charge_bright = cache_status[@"acc_charge_bright"]; setBrightness(acc_charge_bright.floatValue); } - setAutoBrightEnable(YES); + if (cache_status[@"acc_charge_bright_auto"] != nil) { + setAutoBrightEnable(YES); + } } if (acc_charge_lpm.boolValue) { setLPMEnable(NO); @@ -279,7 +345,7 @@ static void performAcccharge(BOOL flag) { static NSString* getMsgForLang(NSString* msgid, NSString* lang) { static NSDictionary* messages = nil; if (messages == nil) { - NSString* bundlePath = [getAppEXEPath() stringByDeletingLastPathComponent]; + NSString* bundlePath = [getSelfExePath() stringByDeletingLastPathComponent]; NSString* langPath = [bundlePath stringByAppendingString:@"/www/lang.json"]; NSData* data = [NSData dataWithContentsOfFile:langPath]; messages = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; @@ -323,63 +389,25 @@ static void updateDBData(const char* tbl, int tid, NSDictionary* info) { } } -static void initDB() { +static void initDB(NSString* batId) { @autoreleasepool { - sqlite3* cdb = NULL; - if (sqlite3_open(DB_PATH, &cdb) != SQLITE_OK) { - return; - } - char* err; - const char* tbls[] = {"min5", "hour", "day", "month", NULL}; - for (int i = 0; tbls[i]; i++) { - char sql[256]; - sprintf(sql, "create table if not exists %s(id integer primary key, data text)", tbls[i]); - if (sqlite3_exec(cdb, sql, NULL, NULL, &err) != SQLITE_OK) { - sqlite3_close(cdb); + if (!db) { + sqlite3* cdb = NULL; + if (sqlite3_open(DB_PATH, &cdb) != SQLITE_OK) { return; } + db = cdb; } - db = cdb; - // 迁移老数据 - NSArray* arr = getlocalKV(@"stat_min5"); - if (arr != nil && arr.count > 0) { - for (NSDictionary* item in arr) { - NSMutableDictionary* mitem = item.mutableCopy; - NSString* key = mitem[@"key"]; - [mitem removeObjectForKey:@"key"]; - updateDBData("min5", key.intValue, mitem); - } - setlocalKV(@"stat_min5", nil); - } - arr = getlocalKV(@"stat_hour"); - if (arr != nil && arr.count > 0) { - for (NSDictionary* item in arr) { - NSMutableDictionary* mitem = item.mutableCopy; - NSString* key = mitem[@"key"]; - [mitem removeObjectForKey:@"key"]; - updateDBData("hour", key.intValue, mitem); - } - setlocalKV(@"stat_hour", nil); - } - arr = getlocalKV(@"stat_day"); - if (arr != nil && arr.count > 0) { - for (NSDictionary* item in getlocalKV(@"stat_day")) { - NSMutableDictionary* mitem = item.mutableCopy; - NSString* key = mitem[@"key"]; - [mitem removeObjectForKey:@"key"]; - updateDBData("day", key.intValue, mitem); - } - setlocalKV(@"stat_day", nil); - } - arr = getlocalKV(@"stat_month"); - if (arr != nil && arr.count > 0) { - for (NSDictionary* item in getlocalKV(@"stat_month")) { - NSMutableDictionary* mitem = item.mutableCopy; - NSString* key = mitem[@"key"]; - [mitem removeObjectForKey:@"key"]; - updateDBData("month", key.intValue, mitem); + if (db) { + for (NSString* rawTbl in @[@"min5", @"hour", @"day", @"month"]) { + NSString* tblName = rawTbl; + if (batId != nil) { + tblName = [NSString stringWithFormat:@"%@.%@", batId, rawTbl]; + } + NSString* sql = [NSString stringWithFormat:@"create table if not exists %@(id integer primary key, data text)", tblName]; + char* err; + sqlite3_exec(db, sql.UTF8String, NULL, NULL, &err); } - setlocalKV(@"stat_month", nil); } } } @@ -427,17 +455,36 @@ static void uninitDB() { static void updateStatistics() { int ts = (int)time(0); - NSDictionary* info_h = getFilteredMDic(bat_info, @[ + NSDictionary* info_h = nil; + NSDictionary* info_d = nil; + info_h = getFilteredMDic(bat_info, @[ @"Amperage", @"AppleRawCurrentCapacity", @"CurrentCapacity", @"ExternalChargeCapable", @"ExternalConnected", @"InstantAmperage", @"IsCharging", @"Temperature", @"UpdateTime", @"Voltage" ]); updateDBData("min5", ts / 300, info_h); updateDBData("hour", ts / 3600, info_h); - NSDictionary* info_d = getFilteredMDic(bat_info, @[ + info_d = getFilteredMDic(bat_info, @[ @"CycleCount", @"DesignCapacity", @"NominalChargeCapacity", @"UpdateTime" ]); updateDBData("day", ts / 86400, info_d); updateDBData("month", ts / 2592000, info_d); + if (gUPSPS != nil && gUPSPS.props[@"Serial"] != nil && gUPSPS.props[@"UpdateTime"] != nil) { + NSString* batId = gUPSPS.props[@"Serial"]; + NSString* tblMin5 = [batId stringByAppendingString:@".min5"]; + info_h = getFilteredMDic(gUPSPS.props, @[ + @"Amperage", @"AppleRawCurrentCapacity", @"CurrentCapacity", @"IncomingCurrent", @"IncomingVoltage", @"IsCharging", @"Temperature", @"UpdateTime", @"Voltage" + ]); + updateDBData(tblMin5.UTF8String, ts / 300, info_h); + NSString* tblHour = [batId stringByAppendingString:@".hour"]; + updateDBData(tblHour.UTF8String, ts / 3600, info_h); + info_d = getFilteredMDic(gUPSPS.props, @[ + @"CycleCount", @"MaxCapacity", @"NominalCapacity", @"UpdateTime" + ]); + NSString* tblDay = [batId stringByAppendingString:@".day"]; + updateDBData(tblDay.UTF8String, ts / 86400, info_d); + NSString* tblMonth = [batId stringByAppendingString:@".month"]; + updateDBData(tblMonth.UTF8String, ts / 2592000, info_d); + } } static void onBatteryEventEnd() { @@ -476,7 +523,7 @@ static void onBatteryEvent(io_service_t serv) { NSNumber* charge_above = getlocalKV(@"charge_above"); NSNumber* enable_temp = getlocalKV(@"enable_temp"); NSNumber* capacity = bat_info[@"CurrentCapacity"]; - NSNumber* is_charging = bat_info[@"IsCharging"]; + BOOL is_charging = [bat_info[@"IsCharging"] boolValue]; NSNumber* is_inflow_enabled = bat_info[@"ExternalConnected"]; NSNumber* adv_disable_inflow = getlocalKV(@"adv_disable_inflow"); BOOL is_adaptor_connected = isAdaptorConnect(bat_info, adv_disable_inflow); @@ -493,7 +540,7 @@ static void onBatteryEvent(io_service_t serv) { do { if (capacity.intValue <= 5) { // 电量极低,优先级=1 // 防止误用或意外造成无法充电 - if (is_adaptor_connected && !is_charging.boolValue) { + if (is_adaptor_connected && !is_charging) { NSFileLog(@"start charging for extremely low capacity %@", capacity); setInflowStatus(YES); setBatteryStatus(YES); @@ -502,7 +549,7 @@ static void onBatteryEvent(io_service_t serv) { break; } if (capacity.intValue >= charge_above.intValue) { // 停充-电量高,优先级=2 - if (is_charging.boolValue) { + if (is_charging) { NSFileLog(@"stop charging for high capacity %@ >= %@", capacity, charge_above); setBatteryStatus(NO); performAction(@"stop_charge"); @@ -515,7 +562,7 @@ static void onBatteryEvent(io_service_t serv) { break; } if (enable_temp.boolValue && temperature >= charge_temp_above) { // 停充-温度高,优先级=3 - if (is_charging.boolValue) { + if (is_charging) { NSFileLog(@"stop charging for high temperature %lf >= %lf", temperature, charge_temp_above); setBatteryStatus(NO); performAction(@"stop_charge"); @@ -534,7 +581,7 @@ static void onBatteryEvent(io_service_t serv) { NSFileLog(@"enable inflow for low capacity %@ <= %@", capacity, charge_below); setInflowStatus(YES); } - if (!is_charging.boolValue) { + if (!is_charging) { NSFileLog(@"start charging for low capacity %@ <= %@", capacity, charge_below); setBatteryStatus(YES); performAction(@"start_charge"); @@ -550,7 +597,7 @@ static void onBatteryEvent(io_service_t serv) { NSFileLog(@"enable inflow for low temperature %lf < %lf", temperature, charge_temp_below); setInflowStatus(YES); } - if (!is_charging.boolValue) { + if (!is_charging) { NSFileLog(@"start charging for low temperature %lf < %lf", temperature, charge_temp_below); setBatteryStatus(YES); performAction(@"start_charge"); @@ -564,7 +611,7 @@ static void onBatteryEvent(io_service_t serv) { NSFileLog(@"enable inflow for plug in"); setInflowStatus(YES); } - if (!is_charging.boolValue) { + if (!is_charging) { NSFileLog(@"start charging for plug in"); setBatteryStatus(YES); performAction(@"start_charge"); @@ -574,7 +621,7 @@ static void onBatteryEvent(io_service_t serv) { } } else if ([mode isEqualToString:@"edge_trigger"]) { if (isAdaptorNewConnect(old_bat_info, bat_info, adv_disable_inflow)) { - if (!is_charging.boolValue) { + if (!is_charging) { NSFileLog(@"stop charging for plug in"); setBatteryStatus(NO); } @@ -647,6 +694,7 @@ static void initConf(BOOL reset) { NSMutableDictionary* def_mdic = def_dic.mutableCopy; [def_mdic addEntriesFromDictionary:@{ @"enable": @NO, + @"disable_smart_charge": @YES, // Disable "Optimized Battery Charging" within Settings app @"mode": @"charge_on_plug", @"update_freq": @1, @"lang": @"en", @@ -670,7 +718,9 @@ static void showFloatwnd(BOOL flag) { NSDictionary* param = @{ @"close": getUnusedFds(), }; - spawn(@[getAppEXEPath(), @"floatwnd"], nil, nil, &floatwnd_pid, SPAWN_FLAG_NOWAIT, param); + NSString* bundlePath = [getSelfExePath() stringByDeletingLastPathComponent]; + NSString* appExePath = [bundlePath stringByAppendingPathComponent:@"ChargeLimiter"]; + spawn(@[appExePath, @"floatwnd"], nil, nil, &floatwnd_pid, SPAWN_FLAG_NOWAIT, param); } } else { // close if (floatwnd_pid != -1) { @@ -685,7 +735,7 @@ static void showFloatwnd(BOOL flag) { if ([api isEqualToString:@"get_conf"]) { NSString* key = nsreq[@"key"]; if (key == nil) { - NSMutableDictionary* kv = [cache_kv mutableCopy]; + NSMutableDictionary* kv = [getAllKV() mutableCopy]; kv[@"enable"] = @(g_enable); kv[@"floatwnd"] = @(g_enable_floatwnd); //kv[@"dark"] = @(isDarkMode()); daemon获取到的结果不随系统变化,需要从app获取 @@ -722,6 +772,13 @@ static void showFloatwnd(BOOL flag) { g_enable = [val boolValue]; if (!g_enable) { resetBatteryStatus(); + } else { // 启用时检查 + NSNumber* val = getlocalKV(@"disable_smart_charge"); + if (val.boolValue) { + if (isSmartChargeEnable()) { + setSmartChargeEnable(NO); + } + } } } else if ([key isEqualToString:@"action"]) { if ([val isEqualToString:@"noti"]) { @@ -753,8 +810,16 @@ static void showFloatwnd(BOOL flag) { @"status": @0, }; } else if ([api isEqualToString:@"get_bat_info"]) { + if (gUPSPS.props != nil) { + return @{ + @"status": @0, + @"data": bat_info, + @"data_ups": gUPSPS.props, + }; + } return @{ @"status": @0, + @"enable": @(g_enable), // for floatwnd @"data": bat_info, }; } else if ([api isEqualToString:@"get_statistics"]) { @@ -784,16 +849,222 @@ static void showFloatwnd(BOOL flag) { return @{ @"status": @(status) }; - } else if ([api isEqualToString:@"set_pb"]) { - NSString* val = nsreq[@"val"]; - UIPasteboard* pb = [UIPasteboard generalPasteboard]; - pb.string = val; } return @{ @"status": @-10 }; } +static void processUPSEventSource(UPSDataSlim* upsPS, CFTypeRef typeRef) { + CFRunLoopTimerRef timer = nil; + CFRunLoopSourceRef source = nil; + if (CFGetTypeID(typeRef) == CFArrayGetTypeID()) { + NSArray* arrayRef = (__bridge_transfer NSArray*)typeRef; + for (CFIndex i = 0; i < arrayRef.count; i++) { + CFTypeRef typeRefI = (__bridge CFTypeRef)arrayRef[i]; + if (CFGetTypeID(typeRefI) == CFRunLoopTimerGetTypeID()) { + timer = (CFRunLoopTimerRef)typeRefI; + } else if (CFGetTypeID(typeRefI) == CFRunLoopSourceGetTypeID()) { + source = (CFRunLoopSourceRef)typeRefI; + } + } + } else if (CFGetTypeID(typeRef) == CFRunLoopTimerGetTypeID()) { + timer = (CFRunLoopTimerRef)typeRef; + } else if (CFGetTypeID(typeRef) == CFRunLoopSourceGetTypeID()) { + source = (CFRunLoopSourceRef)typeRef; + } + if (timer != nil) { + upsPS.timer = timer; + CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode); + } + if (source != nil) { + upsPS.source = source; + CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode); + } +} + +static void releaseUPSBattery(UPSDataSlim* upsPS) { + if (upsPS == nil) { + return; + } + if (upsPS.interface != NULL) { + (*upsPS.interface)->Release(upsPS.interface); + } + if (upsPS.source) { + CFRunLoopRemoveSource(CFRunLoopGetCurrent(), upsPS.source, kCFRunLoopDefaultMode); + CFRelease(upsPS.source); + } + if (upsPS.timer) { + CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), upsPS.timer, kCFRunLoopDefaultMode); + CFRelease(upsPS.timer); + } + if (upsPS.noti != MACH_PORT_NULL) { + IOObjectRelease(upsPS.noti); + } +} + +static void addUPSBattery(void* refCon, io_iterator_t iterator) { + @autoreleasepool { + static CFUUIDRef kIOUPSPlugInTypeID = CFUUIDCreateFromString(NULL, CFSTR("40A57A4E-26A0-11D8-9295-000A958A2C78")); + static CFUUIDRef kIOUPSPlugInInterfaceID = CFUUIDCreateFromString(NULL, CFSTR("63F8BFC4-26A0-11D8-88B4-000A958A2C78")); + static CFUUIDRef kIOUPSPlugInInterfaceID_v140 = CFUUIDCreateFromString(NULL, CFSTR("E60E0799-9AA6-49DF-B55B-A5C94BA07A4A")); + static CFUUIDRef kIOCFPlugInInterfaceID = CFUUIDCreateFromString(NULL, CFSTR("C244E858-109C-11D4-91D4-0050E4C6426F")); + io_object_t upsDevice = MACH_PORT_NULL; + while ((upsDevice = IOIteratorNext(iterator))) { + IOReturn kr = 0; + HRESULT result = S_FALSE; + IOCFPlugInInterface** plugInInterface = NULL; + IOUPSPlugInInterface_v140** upsPlugInInterface = NULL; + SInt32 score; + kr = IOCreatePlugInInterfaceForService(upsDevice, kIOUPSPlugInTypeID, kIOCFPlugInInterfaceID, &plugInInterface, &score); + if (kr == kIOReturnSuccess && plugInInterface != NULL) { + UPSDataSlim* upsPS = [UPSDataSlim new]; + result = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOUPSPlugInInterfaceID_v140), (LPVOID*)&upsPlugInInterface); + if (result == S_OK && upsPlugInInterface != nil) { + CFTypeRef typeRef = nil; + (*upsPlugInInterface)->createAsyncEventSource(upsPlugInInterface, &typeRef); + if (typeRef != nil) { + processUPSEventSource(upsPS, typeRef); + } + } else { + result = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOUPSPlugInInterfaceID), (LPVOID*)&upsPlugInInterface); + } + if (result == S_OK && upsPlugInInterface != NULL) { + gUPSPS = upsPS; + gUPSPS.interface = upsPlugInInterface; + CFMutableDictionaryRef props = nil; + IORegistryEntryCreateCFProperties(upsDevice, &props, kCFAllocatorDefault, 0); + if (props != nil) { + [gUPSPS updateProps:(__bridge NSDictionary*)props isEvent:NO]; + } + [gUPSPS initDB]; + CFDictionaryRef upsEvent = nil; + kr = (*upsPlugInInterface)->getEvent(upsPlugInInterface, &upsEvent); + if (kr == kIOReturnSuccess && upsEvent != nil) { + [gUPSPS updateProps:(__bridge NSDictionary*)upsEvent isEvent:NO]; + } + (*upsPlugInInterface)->setEventCallback(upsPlugInInterface, [](void* target, IOReturn kr, void* refcon, void* sender, CFDictionaryRef event) { + @autoreleasepool { + if (gUPSPS != nil && event != nil) { + [gUPSPS updateProps:(__bridge NSDictionary*)event isEvent:NO]; + } + } + }, NULL, NULL); + io_object_t noti = IO_OBJECT_NULL; + IOServiceAddInterestNotification(gNotifyPort, upsDevice, "IOGeneralInterest", [](void* refcon, io_service_t service, uint32_t type, void* args) { + @autoreleasepool { + if (type == kIOMessageServiceIsTerminated) { + NSFileLog(@"detect ups battery unplug"); + releaseUPSBattery(gUPSPS); + gUPSPS = nil; + } + } + }, nil, ¬i); + gUPSPS.noti = noti; + NSFileLog(@"detect ups battery plug in"); + } + (*plugInInterface)->Release(plugInInterface); + } + IOObjectRelease(upsDevice); + if (gUPSPS != nil) { + break; + } + } + } +} + +void detectUPSBattery() { + @autoreleasepool { + if (gUPSPS != nil) { // 存在电池则忽略 + return; + } + NSDictionary* dic = @{ + @"IOProviderClass": @"IOHIDDevice", + @"DeviceUsagePairs": @[ + @{ // kDeviceTypeAccessoryBattery + @"DeviceUsagePage": @kHIDPage_AppleVendor, + @"DeviceUsage": @kHIDUsage_AppleVendor_AccessoryBattery, + }, @{ // kDeviceTypeAccessoryBattery + @"DeviceUsagePage": @kHIDPage_PowerDevice, + @"DeviceUsage": @kHIDUsage_PD_PeripheralDevice, + }, @{ // kDeviceTypeBatteryCase + @"DeviceUsagePage": @kHIDPage_BatterySystem, + @"DeviceUsage": @kHIDUsage_BS_PrimaryBattery, + }, + ] + }; + io_iterator_t gAddedIter = MACH_PORT_NULL; + kern_return_t kr = IOServiceAddMatchingNotification(gNotifyPort, kIOMatchedNotification, (__bridge_retained CFDictionaryRef)dic, addUPSBattery, NULL, &gAddedIter); + if (kr == kIOReturnSuccess) { + if (gAddedIter != MACH_PORT_NULL) { + addUPSBattery(NULL, gAddedIter); + IOObjectRelease(gAddedIter); + } + } + } +} + +@implementation UPSDataSlim +- (instancetype)init { + self = [super init]; + self.noti = IO_OBJECT_NULL; + self.source = nil; + self.timer = nil; + self.props = [NSMutableDictionary dictionary]; + return self; +} +- (void)initDB { + NSString* serial = self.props[@"Serial"]; + if (serial != nil) { + initDB(serial); + } +} +- (void)updateProps:(NSDictionary*)propsSrc isEvent:(BOOL)event { + NSDictionary* keep = @{ + @"Authenticated": @"Authenticated", + @"Manufacturer": @"Manufacturer", + @"ModelNumber": @"ModelNumber", + @"PrimaryUsagePage": @"UsagePage", + @"PrimaryUsage": @"Usage", + @"Product": @"Name", + @"ProductID": @"ProductID", + @"ReportInterval": @"ReportInterval", + @"SerialNumber": @"Serial", + @"Transport": @"Transport", + @"VendorID": @"VendorID", + @"VersionNumber": @"VersionNumber", + @"AppleRawCurrentCapacity": @"AppleRawCurrentCapacity", + @"BatteryCaseChargingVoltage": @"BatteryCaseChargingVoltage", + @"Cell0Voltage": @"Cell0Voltage", + @"Cell1Voltage": @"Cell1Voltage", + @"Current": @"Amperage", + @"CurrentCapacity": @"CurrentCapacity", + @"CycleCount": @"CycleCount", + @"IncomingCurrent": @"IncomingCurrent", + @"IncomingVoltage": @"IncomingVoltage", + @"IsCharging": @"IsCharging", + @"MaxCapacity": @"MaxCapacity", + @"NominalCapacity": @"NominalCapacity", + @"PowerSourceState": @"PowerSourceState", + @"Temperature": @"Temperature", + @"Voltage": @"Voltage", + }; + for (NSString* rawkey in propsSrc) { + NSString* key = [rawkey stringByReplacingOccurrencesOfString:@" " withString:@""]; + if (keep[key] == nil) { + continue; + } else { + key = keep[key]; + } + id val = propsSrc[rawkey]; + self.props[key] = val; + } + if (event) { + self.props[@"UpdateTime"] = @(time(0)); + } +} +@end + @implementation Service { NSString* bid; } @@ -848,7 +1119,7 @@ - (void)localPush:(NSString*)title msg:(NSString*)msg { } - (void)serve { initConf(NO); - initDB(); + initDB(nil); static GCDWebServer* _webServer = nil; if (_webServer == nil) { if (localPortOpen(GSERV_PORT)) { @@ -874,79 +1145,62 @@ - (void)serve { exit(0); } getBatInfo(&bat_info); - IONotificationPortRef port = IONotificationPortCreate(kIOMasterPortDefault); - CFRunLoopSourceRef runSrc = IONotificationPortGetRunLoopSource(port); + gNotifyPort = IONotificationPortCreate(kIOMasterPortDefault); + CFRunLoopSourceRef runSrc = IONotificationPortGetRunLoopSource(gNotifyPort); CFRunLoopAddSource(CFRunLoopGetCurrent(), runSrc, kCFRunLoopDefaultMode); io_service_t serv = getIOPMPSServ(); if (serv != IO_OBJECT_NULL) { - io_object_t noti = IO_OBJECT_NULL; - IOServiceAddInterestNotification(port, serv, "IOGeneralInterest", [](void* refcon, io_service_t service, uint32_t type, void* args) { + IOServiceAddInterestNotification(gNotifyPort, serv, "IOGeneralInterest", [](void* refcon, io_service_t service, uint32_t type, void* args) { // type == kIOPMMessageBatteryStatusHasChanged @synchronized (Service.inst) { + detectUPSBattery(); // 在USB插拔事件中更新 onBatteryEvent(service); } - }, nil, ¬i); + }, nil, &iopmpsNoti); + detectUPSBattery(); } [LSApplicationWorkspace.defaultWorkspace addObserver:self]; isBlueEnable(); // init isLPMEnable(); + isSmartChargeEnable(); } } @end -static void start_daemon() { - @autoreleasepool { - if (g_jbtype == JBTYPE_TROLLSTORE) { - NSTimer* start_daemon_timer = [NSTimer timerWithTimeInterval:10 repeats:YES block:^(NSTimer* timer) { - @autoreleasepool { - if (!localPortOpen(GSERV_PORT)) { - spawn(@[getAppEXEPath(), @"daemon"], nil, nil, 0, SPAWN_FLAG_ROOT | SPAWN_FLAG_NOWAIT); - } - } - }]; - [start_daemon_timer fire]; - [NSRunLoop.currentRunLoop addTimer:start_daemon_timer forMode:NSDefaultRunLoopMode]; - } - } -} -int main(int argc, char** argv) { +int main(int argc, char** argv) { // daemon_main @autoreleasepool { g_jbtype = getJBType(); if (argc == 1) { - start_daemon(); - return UIApplicationMain(argc, argv, nil, @"AppDelegate"); - } else if (argc > 1) { - if (0 == strcmp(argv[1], "daemon")) { - g_serv_boot = (int)time(0); - if (g_jbtype == JBTYPE_TROLLSTORE) { - signal(SIGHUP, SIG_IGN); - signal(SIGTERM, SIG_IGN); // 防止App被Kill以后daemon退出 - } else { - platformize_me(); // for jailbreak - set_mem_limit(getpid(), 80); + NSFileLog(@"CLv%@ start pid=%d", NSBundle.mainBundle.infoDictionary[@"CFBundleShortVersionString"], getpid()); + g_serv_boot = (int)time(0); + if (g_jbtype == JBTYPE_TROLLSTORE) { + signal(SIGHUP, SIG_IGN); + signal(SIGTERM, SIG_IGN); // 防止App被Kill以后daemon退出 + } else { + platformize_me(); // for jailbreak + set_mem_limit(getpid(), 80); + } + [Service.inst serve]; + atexit_b(^{ + resetBatteryStatus(); + if (iopmpsNoti != IO_OBJECT_NULL) { + IOObjectRelease(iopmpsNoti); + iopmpsNoti = IO_OBJECT_NULL; } - [Service.inst serve]; - atexit_b(^{ - resetBatteryStatus(); - showFloatwnd(NO); - uninitDB(); - [LSApplicationWorkspace.defaultWorkspace removeObserver:Service.inst]; - }); - NSFileLog(@"CLv%@ start", NSBundle.mainBundle.infoDictionary[@"CFBundleShortVersionString"]); - [NSRunLoop.mainRunLoop run]; - NSFileLog(@"daemon unexpected"); - return 0; - } else if (0 == strcmp(argv[1], "floatwnd")) { - start_daemon(); - g_wind_type = 1; - static id appDelegate = [AppDelegate new]; - UIApplicationInstantiateSingleton(HUDMainApplication.class); - static UIApplication* app = [UIApplication sharedApplication]; - [app setDelegate:appDelegate]; - [app __completeAndRunAsPlugin]; - CFRunLoopRun(); - return 0; - } else if (0 == strcmp(argv[1], "reset")) { // 越狱下卸载前重置 + releaseUPSBattery(gUPSPS); + if (gNotifyPort != 0) { + IONotificationPortDestroy(gNotifyPort); + gNotifyPort = 0; + } + showFloatwnd(NO); + uninitDB(); + [LSApplicationWorkspace.defaultWorkspace removeObserver:Service.inst]; + }); + [NSRunLoop.mainRunLoop run]; + NSFileLog(@"daemon unexpected"); + return 0; + } else if (argc > 1) { + if (0 == strcmp(argv[1], "reset")) { // 越狱下卸载前重置 resetBatteryStatus(); return 0; } else if (0 == strcmp(argv[1], "watch_bat_info")) { @@ -957,6 +1211,15 @@ int main(int argc, char** argv) { [NSThread sleepForTimeInterval:1.0]; spawn(@[@"clear"], nil, nil, nil, 0, nil); } + return 0; + } else if (0 == strcmp(argv[1], "set_charge")) { + bool flag = argv[2][0] - '0'; + setChargeStatus(flag); + return 0; + } else if (0 == strcmp(argv[1], "set_inflow")) { + bool flag = argv[2][0] - '0'; + setInflowStatus(flag); + return 0; } } return -1; diff --git a/ChargeLimiter/ui.h b/ChargeLimiter/ui.h index ab98402..608be19 100755 --- a/ChargeLimiter/ui.h +++ b/ChargeLimiter/ui.h @@ -2,8 +2,8 @@ #define UI_H #include "common.h" -#import -#include + +#import extern "C" { void BKSDisplayServicesStart(); diff --git a/ChargeLimiter/ui.mm b/ChargeLimiter/ui.mm index 20ac7e6..1c788a1 100755 --- a/ChargeLimiter/ui.mm +++ b/ChargeLimiter/ui.mm @@ -1,9 +1,19 @@ #include "ui.h" #include "utils.h" -extern int g_wind_type; -NSDictionary* handleReq(NSDictionary* nsreq); -id getlocalKV(NSString* key); +static int g_jbtype = -1; +static int g_wind_type = 0; // 1: HUD + +static void daemonRun(NSArray* nsreq); + +static BOOL isDarkMode() { + if (@available(iOS 13, *)) { + if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) { + return YES; + } + } + return NO; +} @implementation HUDMainWindow - (instancetype)initWithFrame:(CGRect)frame { @@ -33,15 +43,9 @@ - (void)scene:(UIScene*)scene openURLContexts:(NSSet*)URLContexts API_AVAILABLE( NSURL* url = urlContext.URL; // cl:///(charge|nocharge)(/exit) for (NSString* cmd in url.pathComponents) { if ([cmd isEqualToString:@"charge"]) { - handleReq(@{ - @"api": @"set_charge_status", - @"flag": @YES, - }); + daemonRun(@[@"set_charge", @"1"]); } else if ([cmd isEqualToString:@"nocharge"]) { - handleReq(@{ - @"api": @"set_charge_status", - @"flag": @NO, - }); + daemonRun(@[@"set_charge", @"0"]); } else if ([cmd hasPrefix:@"exit"]) { int n = 1; if (cmd.length > 4) { @@ -244,19 +248,30 @@ - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coor - (void)webViewDidFinishLoad:(UIWebView*)webview { [_mainWnd addSubview:webview]; [_mainWnd bringSubviewToFront:webview]; - static BOOL isFirstLoad = YES; - if (isFirstLoad) { - [webview stringByEvaluatingJavaScriptFromString:@"window.location.reload()"]; - isFirstLoad = NO; - } else { - [self speedUpWebView: webview]; - if (isDarkMode()) { - [webview stringByEvaluatingJavaScriptFromString:@"window.app.switch_dark(true)"]; - } else { - [webview stringByEvaluatingJavaScriptFromString:@"window.app.switch_dark(false)"]; + if (@available(iOS 17.0, *)) { + static BOOL ios17plusInit = NO; + if (!ios17plusInit) { // 修复iOS17 UIWebView无法滑动 + ios17plusInit = YES; + [webview stringByEvaluatingJavaScriptFromString:@"window.location.reload()"]; + return; } - [webview stringByEvaluatingJavaScriptFromString:@"window.source='CL'"]; } + [self speedUpWebView:webview]; + if (isDarkMode()) { + [webview stringByEvaluatingJavaScriptFromString:@"window.app.switch_dark(true)"]; + } else { + [webview stringByEvaluatingJavaScriptFromString:@"window.app.switch_dark(false)"]; + } + [webview stringByEvaluatingJavaScriptFromString:@"window.source='CL'"]; + JSContext* context = [webview valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; + context[@"set_pb"] = ^{ + @autoreleasepool { + NSArray* args = [JSContext currentArguments]; + JSValue* val = args[0]; + UIPasteboard* pb = [UIPasteboard generalPasteboard]; + pb.string = val.toString; + } + }; } - (void)webView:(UIWebView*)webview didFailLoadWithError:(NSError*)error { NSString* surl = webview.request.URL.absoluteString; @@ -362,3 +377,54 @@ - (void)handlePan:(UIPanGestureRecognizer*)recognizer { } @end + +void daemonRun(NSArray* argv) { + NSString* bundlePath = [getSelfExePath() stringByDeletingLastPathComponent]; + NSString* daemonPath = [bundlePath stringByAppendingPathComponent:@"ChargeLimiterDaemon"]; + NSMutableArray* mArgv = [NSMutableArray array]; + [mArgv addObject:daemonPath]; + if (argv != nil) { + [mArgv addObjectsFromArray:argv]; + } + spawn(mArgv, nil, nil, 0, SPAWN_FLAG_ROOT | SPAWN_FLAG_NOWAIT); +} + +static void start_daemon() { + @autoreleasepool { + if (g_jbtype == JBTYPE_TROLLSTORE) { + NSTimer* start_daemon_timer = [NSTimer timerWithTimeInterval:10 repeats:YES block:^(NSTimer* timer) { + @autoreleasepool { + if (!localPortOpen(GSERV_PORT)) { + daemonRun(nil); + } + } + }]; + [start_daemon_timer fire]; + [NSRunLoop.currentRunLoop addTimer:start_daemon_timer forMode:NSDefaultRunLoopMode]; + } + } +} + +int main(int argc, char** argv) { // ChargeLimiter + @autoreleasepool { + g_jbtype = getJBType(); + if (argc == 1) { + start_daemon(); + return UIApplicationMain(argc, argv, nil, @"AppDelegate"); + } else if (argc > 1) { + if (0 == strcmp(argv[1], "floatwnd")) { + start_daemon(); + g_wind_type = 1; + static id appDelegate = [AppDelegate new]; + UIApplicationInstantiateSingleton(HUDMainApplication.class); + static UIApplication* app = [UIApplication sharedApplication]; + [app setDelegate:appDelegate]; + [app __completeAndRunAsPlugin]; + CFRunLoopRun(); + return 0; + } + } + return -1; + } +} + diff --git a/ChargeLimiter/utils.h b/ChargeLimiter/utils.h index 3a69227..1bc19b7 100755 --- a/ChargeLimiter/utils.h +++ b/ChargeLimiter/utils.h @@ -27,7 +27,7 @@ int platformize_me(); int32_t get_mem_limit(int pid); int set_mem_limit(int pid, int mb); BOOL localPortOpen(int port); -NSString* getAppEXEPath(); +NSString* getSelfExePath(); NSArray* getUnusedFds(); NSArray* getFrontMostBid(); @@ -47,7 +47,6 @@ enum { }; int getJBType(); void NSFileLog(NSString* fmt, ...); -BOOL isDarkMode(); NSString* getAppVer(); NSString* getSysVer(); NSOperatingSystemVersion getSysVerInt(); @@ -66,6 +65,7 @@ BOOL isLocEnable(); void setLocEnable(BOOL flag); float getBrightness(); void setBrightness(float val); +BOOL isAutoBrightEnable(); void setAutoBrightEnable(BOOL flag); NSDictionary* getThermalData(); @@ -75,6 +75,14 @@ NSString* getThermalSimulationMode(); void setThermalSimulationMode(NSString* mode); NSString* getPPMSimulationMode(); void setPPMSimulationMode(NSString* mode); +BOOL isSmartChargeEnable(); // 系统自带电池优化 +void setSmartChargeEnable(BOOL flag); + +/* ---------------- App ---------------- */ +id getlocalKV(NSString* key); +void setlocalKV(NSString* key, id val); +NSDictionary* getAllKV(); +/* ---------------- App ---------------- */ #endif // UTILS_H diff --git a/ChargeLimiter/utils.mm b/ChargeLimiter/utils.mm index c813d46..d2bef91 100755 --- a/ChargeLimiter/utils.mm +++ b/ChargeLimiter/utils.mm @@ -3,6 +3,10 @@ #include #include +extern "C" { +CFTypeRef MGCopyAnswer(CFStringRef str); +} + int platformize_me() { int ret = 0; #define FLAG_PLATFORMIZE (1 << 1) @@ -333,7 +337,7 @@ BOOL localPortOpen(int port) { } extern "C" int _NSGetExecutablePath(char* buf, uint32_t* bufsize); -NSString* getAppEXEPath() { +NSString* getSelfExePath() { char exe[256]; uint32_t bufsize = sizeof(exe); _NSGetExecutablePath(exe, &bufsize); @@ -366,9 +370,6 @@ int getJBType() { return JBTYPE_ROOTLESS; } if ([path containsString:@".app/"]) { // for App - if ([path hasPrefix:@"/Applications"]) { - return JBTYPE_ROOT; - } NSArray* parts = [path componentsSeparatedByString:@"/"]; if (parts.count < 4) { return JBTYPE_UNKNOWN; @@ -382,10 +383,11 @@ int getJBType() { return JBTYPE_ROOTHIDE; } return JBTYPE_UNKNOWN; - } else { // for Tweak/Daemon + } else if ([path containsString:@"LaunchDaemons/"]) { // for Daemon return JBTYPE_ROOT; - // todo } + return JBTYPE_ROOT; + // todo } void NSFileLog(NSString* fmt, ...) { @@ -406,23 +408,14 @@ void NSFileLog(NSString* fmt, ...) { [handle closeFile]; } -BOOL isDarkMode() { - if (@available(iOS 13, *)) { - if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) { - return YES; - } - } - return NO; -} - NSString* getAppVer() { static NSString* ver = [NSBundle.mainBundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; return ver; } NSString* getSysVer() { - static NSString* ver = UIDevice.currentDevice.systemVersion; - return ver; + CFTypeRef val = MGCopyAnswer(CFSTR("ProductVersion")); + return (__bridge_transfer NSString*)val; } NSOperatingSystemVersion getSysVerInt() { @@ -541,7 +534,8 @@ - (NSDictionary*)applicationInfoForPID:(int)pid; // com.apple.CarPlaySplashScreen // com.apple.CarPlayTemplateUIHost // com.apple.ScreenshotServicesService?? - if (bid != nil && ![bid isEqualToString:@"com.apple.springboard"] && ![bid hasPrefix:@"com.apple.Accessibility"] && ![bid hasPrefix:@"com.apple.CarPlay"]) { + if (bid != nil && ![bid isEqualToString:@"com.apple.springboard"] && ![bid hasPrefix:@"com.apple.Accessibility"] && + ![bid hasPrefix:@"com.apple.CarPlay"]) { [allFrontMostBid addObject:bid]; } } @@ -695,7 +689,7 @@ - (BOOL)locationServicesEnabled; static id getLocMan() { static Class man = nil; if (man == nil) { - NSBundle* b = [NSBundle bundleWithPath:@"/System/Library/Frameworks/CoreLocation.framework/CoreLocation"]; + NSBundle* b = [NSBundle bundleWithPath:@"/System/Library/Frameworks/CoreLocation.framework"]; [b load]; man = objc_getClass("CLLocationManager"); } @@ -743,6 +737,13 @@ void setBrightness(float val) { BrightnessSet(val, 1); } +BOOL isAutoBrightEnable() { + // This seems not work: CFPreferencesGetAppBooleanValue(CFSTR("BKEnableALS"), CFSTR("com.apple.backboardd"), &val); + NSDictionary* backboardPref = [NSDictionary dictionaryWithContentsOfFile:@"/private/var/mobile/Library/Preferences/com.apple.backboardd.plist"]; + NSNumber* nsVal = backboardPref[@"BKEnableALS"]; + return nsVal != nil && nsVal.boolValue; +} + void setAutoBrightEnable(BOOL flag) { BKSDisplayBrightnessSetAutoBrightnessEnabled(flag); } @@ -837,3 +838,74 @@ void setPPMSimulationMode(NSString* mode) { } } +@interface PowerUISmartChargeClient +- (instancetype)initWithClientName:(NSString*)name; +- (int)isSmartChargingCurrentlyEnabled:(NSError**)err; +- (BOOL)disableSmartCharging:(NSError**)err; +- (BOOL)enableSmartCharging:(NSError**)err; +- (BOOL)temporarilyDisableSmartCharging:(NSError**)err; +@end + +static PowerUISmartChargeClient* getSmartChargeClient() { + static PowerUISmartChargeClient* client = nil; + if (client == nil) { + NSBundle* b = [NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/PowerUI.framework"]; + [b load]; + client = [[objc_getClass("PowerUISmartChargeClient") alloc] initWithClientName:@"Settings"]; + } + return client; +} + +BOOL isSmartChargeEnable() { + PowerUISmartChargeClient* client = getSmartChargeClient(); + NSError* err = nil; + int status = [client isSmartChargingCurrentlyEnabled:&err]; + NSLog(@"status=%d %@", status, client); + if (err != nil) { + NSLog(@"err=%@", err); + return NO; + } + return status != 0; // 0:disable 1:enable 2:fullcharge 3:temporarily_disable +} + +void setSmartChargeEnable(BOOL flag) { + PowerUISmartChargeClient* client = getSmartChargeClient(); + BOOL status = isSmartChargeEnable(); + if (status == flag) { + return; + } + NSError* err = nil; + if (flag) { + [client enableSmartCharging:&err]; + } else { + [client disableSmartCharging:&err]; + } +} + +/* ---------------- App ---------------- */ +static NSMutableDictionary* cache_kv = nil; +id getlocalKV(NSString* key) { + if (cache_kv == nil) { + cache_kv = [NSMutableDictionary dictionaryWithContentsOfFile:@CONF_PATH]; + } + if (cache_kv == nil) { + return nil; + } + return cache_kv[key]; +} + +void setlocalKV(NSString* key, id val) { + if (cache_kv == nil) { + cache_kv = [NSMutableDictionary dictionaryWithContentsOfFile:@CONF_PATH]; + if (cache_kv == nil) { + cache_kv = [NSMutableDictionary new]; + } + } + cache_kv[key] = val; + [cache_kv writeToFile:@CONF_PATH atomically:YES]; +} +NSDictionary* getAllKV() { + return cache_kv; +} +/* ---------------- App ---------------- */ + diff --git a/Readme.md b/Readme.md index 34381f2..9fa09e3 100755 --- a/Readme.md +++ b/Readme.md @@ -5,8 +5,10 @@   ChargeLimiter(CL)是针对iOS开发的AlDente替代工具,适用于长时间过充情况下保护电池健康度.   支持有根越狱(???-arm.deb)/无根越狱(???-arm64.deb )/巨魔(???.tipa),目前支持iOS12-17.0(注意: 巨魔环境下安装新版之前请先卸载旧版)   测试过的环境: iPhone6/7+iOS12/13 Checkra1n/Unc0ver/Odyssey; iPhone7/X/11+iOS15/16 Palera1n/Dopamine/TrollStore. -  v1.4.1功能可以满足大多数用户需求,v1.5兼容不支持停充的电池,v1.6兼容充电电流过大的电池 -  CL是开放式项目,如果有兴趣参与或者对CL有建议的的欢迎参提交代码.CL纯属偶然兴趣而开发,最开始是作者自己玩的,后来觉得其他人会需要才开源分享.CL承诺永久免费且无广告,但因为使用CL导致系统或硬件方面的影响(或认为会有影响的)作者不负任何责任,用户使用CL即为默认同意本条款. +  v1.4.1功能可以满足大多数用户需求,v1.5兼容不支持停充的电池,v1.6兼容充电电流过大的电池, v1.7兼容SBC +  CL是开放式项目,如果有兴趣参与或者对CL有建议的的欢迎参提交代码. +  CL纯属偶然兴趣而开发,CL的完美运行强依赖软硬件环境, 作者没有义务保证每个人都完美使用. 如果你的软硬件环境不兼容则不建议使用. +  CL承诺永久免费且无广告,但因为使用CL导致系统或硬件方面的影响(或认为会有影响的)作者不负任何责任,用户使用CL即为默认同意本条款. ![](https://raw.githubusercontent.com/lich4/ChargeLimiter/main/banner.jpg) @@ -15,6 +17,7 @@ * 由于缺少开发环境和设备, CL可能不兼容iOS<=11.x. * 悬浮窗暂不支持iOS<=12.x. * DEB版本CL可能会由于某些tweak导致启动卡屏,这并非CL本身的bug,这些tweak注入到com.apple.UIKit,可以在此目录寻找:/Library/MobileSubstrate/DynamicLibraries(有根),或/var/jb/Library/MobileSubstrate/DynamicLibraries(无根). +* 因为系统自身机制原因, 高温模拟在锁屏时失效, 这一点作者认为可行只是时间成本大, 未来有待增强. 另外高温模拟的参数定制未来也有待加入. ## 常见问题 @@ -30,10 +33,10 @@ CL支持第三方电池吗? * CL支持正版电池也支持大部分第三方品牌电池 使用CL后能增加健康度吗? -* 个人认为健康度递减是自然过程,软件更不可能直接修复硬件.不过有些用户使用CL一个月后确实健康度涨了. +* 个人认为健康度递减是自然过程,软件更不可能直接修复硬件,有些用户使用CL前期健康度会涨. 几个月的实测发现涨跌与使用程度有关. * 大部分使用者会明显延缓电池健康度下降速度. * 个别用户在使用CL后出现健康度下降更快的情况,请立即停用并卸载. -* 停充状态下一直连电源的情况下(非禁流),正常情况下电池电流为0,健康度永久不掉. +* 停充且一直连电源的情况下(非禁流),理想情况下电池电流为0,健康度永久不掉. 为什么手机无法停充或恢复充电?(小白经常遇到的问题) * CL并非傻瓜式工具,如果开启了温控请根据实际情况调整温度上下限,否则到达上限会停止充电,下限又无法达到自然无法充电. @@ -42,6 +45,7 @@ CL支持第三方电池吗? * 电池由于过热导致硬件停充功能失效,导致CL无法生效. 而电池冷却后硬件停充功能会恢复. 请注意大部分处于高温的的电池仍然可以正常使用CL. * 新电池未激活则有几率导致硬件停充失效.常见品牌激活方法见本页 * 如果是iPad,如果确定充电状态正常但电量不增加,且电源显示为pd charger,可以尝试重新插拔或更换质量较好的充电线和充电头,直到电源处显示usb brick +* 如果你的巨魔环境足够稳定, CL服务可以正常运行数月以上, 但如果iCloud半夜同步强杀进程, 或半越狱不稳定导致用户空间重启, 或其他可能导致CL服务失效的情况, 表现为一夜充满, 这并非软件Bug, CL为此提供了尽力而为的保活能力, 包括scheme/悬浮窗拉起服务. 如果你的巨魔不稳定可以使用越狱版本CL. 我的电池&小板硬件是否支持CL停充? * 唯一测试方式就是在关闭全局开关的情况下手动控制(开或关)"正在充电"按钮,若120秒内不变(关或开),就是电池或小板存在问题而非软件BUG,以下将硬件无法支持简称"失控" @@ -57,11 +61,17 @@ CL(及类似功能的软件)可以不依赖越狱或巨魔类工具吗? * 充电状态下,使用低功率充电头充电 * 购买手机散热器 +怎样使用电池最好? +* 见官方文档 +* 避免极端高温和低温使用 +* 避免长时间过充 +* 避免电量耗尽 + 遇到问题时如何自行诊断? * 可以参照5分钟图和日志(/var/root/aldente.log)。例如:发现5分钟图存在1小时以上的数据缺失,就可能是掉后台了。 如何找到耗电应用? -* 可以借助观察5分钟图或者Helium的实时电流数据,来检测开启某项系统功能或运行某App会增加多少电流。 +* 可以借助观察5分钟图或者Helium的实时电流数据,来检测开启某项系统功能或运行某App会增加多少电流 ## 测试电池兼容性 @@ -92,7 +102,7 @@ CL(及类似功能的软件)可以不依赖越狱或巨魔类工具吗? ## 充电宝兼容性   CL可以和充电宝配合使用,停充模式下充电宝优先为手机供电,充电宝电量耗尽后再由手机电池供电,对长途旅行的用户更为有意义,充电宝的容量性价比也远高于手机电池。注意: -* 无线充电时,如果充电功率不够有可能消耗电池电量,所以如果手机自身耗电较大就不适合这种充电方式 +* 无线充电时,如果充电功率不够有可能消耗电池电量,不推荐CL搭配无线充电使用 * 大部分有线充电宝支持"休眠模式",在电流低于某个阈值一段时间后,会自动关闭自身电源。这种模式下使用CL,充电宝可能在手机锁屏后由于电流过小导致充电宝自动关闭电源, 造成无CL无法正常工作 * 大部分有线充电宝支持"小电流模式",双击或长按电源键后进入小电流模式,这种模式在低电流时不会自动关闭电源,这种模式下CL在手机锁屏后也可以正常工作。注意有的充电宝几小时后会自动退出小电流模式 @@ -100,10 +110,10 @@ CL(及类似功能的软件)可以不依赖越狱或巨魔类工具吗? * iPhone8+存在120秒设定充电状态延迟. iPad可能也存在. * 停充模式不会更新系统状态栏的充电标志,实际充电状态可以在看爱思助手或者CL查看.禁流模式会改变系统状态栏的充电标志(iPhone8+), 禁流模式在"高级-停充时启用禁流"中设定。 -* 对于巨魔环境,因任何原因导致的服务被杀(比如重启系统/重启用户空间/...),将导致CL失效. +* 对于巨魔环境,因任何原因导致的服务被杀(比如重启系统/重启用户空间/半越狱不稳定/iCloud夜间同步/其他巨魔App...),将导致CL失效. * CL的设计思路就是减少充电次数,因此不会连着usb就自发充电,也不会无故自动恢复充电,充电/停充都有触发条件,请仔细查看本页说明. -* 系统自带电池优化会导致CL失效,CL会自动关闭自带优化(但系统设置里不会显示).如果不使用CL需在系统设置中手动重置电池优化开关(先关后开). -* 停充过久会导致小板统计有误和其他奇奇怪怪的问题, 建议一个月至少满充满放一次 +* 系统自带电池优化会导致CL失效,建议关闭.如果不使用CL需在系统设置中手动重置电池优化开关(先关后开). +* 新电池请先激活再使用CL, 否则有几率出现硬件停充失效问题。停充过久会出现小板统计有误和硬件停充失效问题, 建议一个月至少满充满放一次. ## 使用说明 @@ -150,7 +160,7 @@ CL(及类似功能的软件)可以不依赖越狱或巨魔类工具吗? ### 阈值设定 -* 有研究表明电量在20%-80%之间,温度在10°C-35°C之间,对电池寿命影响最小.因此CL阈值默认设定为20/80/10/35,长期过充/电量耗尽/高温对电池会产生不良影响. +* CL阈值默认设定为20/80/10/35, 你需要根据实际情况设置否则可能无法正常工作. * 温度阈值的设定,可根据"历史统计-小时数据"的温度数据设置合适的阈值. * 设定阈值和实际触发值不一定完全相同,例如设定80%上限结果到81%停充,大部分手机差距在0-1%,极少数3-5%,差异值与120秒延迟有关,与充电速度有关,也与电池质量有关.停充后如果存在微弱电流可能造成差值;另外健康度的突然变化也会影响电量;新电池未激活直接使用CL也会导致停充后有较大电流. * 电量上限阈值的设定,如果是短期停充,此上限可以根据自己需要设置;如果是iPad长年连电停充,则此上限可以设置为最佳停充电量,最佳停充电确定方法如下:将电量充满,关闭所有耗电App和功能然后静置,让电量自然降低,等待一天后此电量就是最佳停充电量. @@ -173,7 +183,7 @@ CL(及类似功能的软件)可以不依赖越狱或巨魔类工具吗? ### 电池信息 -* 健康度,与爱思助手保持一致,CL健康度是根据最大实际容量计算的.通常新电池健康度都会超过100%,而系统设置显示的健康度不会超过100%,但设置里只显示100%,所以健康度100%到99%的过程较长.注意健康度在长期停充的状态下可能有突然大幅度下降和统计不准确的情况,此时禁用CL并正常充电几次即可恢复. +* 健康度,与爱思助手保持一致,CL健康度是根据最大实际容量计算的.通常新电池健康度都会超过100%,而系统设置显示的健康度不会超过100%,,所以健康度100%到99%的过程较长.注意健康度在长期停充的状态下可能有突然大幅度下降和统计不准确的情况,此时禁用CL并正常充电几次即可恢复. * 硬件电量,一般情况下和系统电量接近,如果差值过大则可能是未校准或质量问题导致.硬件电量比系统电量更准确,硬件电量可能会大于100%也可能为负值,硬件电量大于100%时为过充此时系统电量为100%,为负时为电池亏电,此时系统电量为0%. * 电流以"瞬时电流"为准,电池电流为正说明从充电器流入电池,电池电流为负说明电池为设备供电.使用CL且停充状态下电池电流一般为0,此时电流流经电池为设备供电,电池起到闭合电路作用(可以理解为导线),此时对电池的损耗应小于仅使用电池为设备供电.禁流状态下电池电流一般为负. @@ -197,6 +207,7 @@ CL(及类似功能的软件)可以不依赖越狱或巨魔类工具吗? 注意: * iPhone8+存在至多120秒延迟 * 可以在个人自动化中的电量事件使用上述指令实现指定电量开始/停止充电,也可以和其他模式结合实现开机自启(比如打开某App时触发) +* 快捷指令拉起服务需要在非锁屏状态下生效 集成快捷指令(iOS16+): @@ -381,7 +392,7 @@ Will the battery health percentage increase after using CL for a period of time? * Keep connecting to a power source and enable ChargeInhibit(without DisableInflow) as long as possible, the normal amperage should be 0mA, and the health of battery will never drop. Why does my iDevice unable to charge or discharge(Most questions from freshman)? -* CL is not a fully-automatic tool, please set the threshhold carefully according to the actual temperature if temperature control is enabled, or CL will surely stop charging and won't re-charge any more. +* CL is not a fully-automatic tool, please set the threshold carefully according to the actual temperature if temperature control is enabled, or CL will surely stop charging and won't re-charge any more. * CL is designed to minimize the charging count, so it won't start charging or recover charging for connecting to a power source in "Plug and charge" mode, but will start charging for re-connecting to a power source. * A battery with low health may cause the failure of ChargeInhibit/DisableInflow. In this case, ChargeInhibit/DisableInflow works fine after a system reboot, but would fail after tens of minutes. CL is unavailable for this kind of battery. Please notice there are only a few cases about the failure among all the batteries with low health. * An overheated battery may cause the failure of ChargeInhibit. In this case, it will resume as normal as the battery get colder. Please notice there are only a few cases about the failure among all the batteries overheated. @@ -397,6 +408,12 @@ How to cool down the battery in summer? * Use a charger of lower Watt to charge. * Use a heat dissipation or sth. +What is the best way to keep the battery healthy? +* Refer to +* Avoid use in extremely high or low temperature +* Avoid long time overcharged +* Avoid out of power + How can I debug CL myself when sth. goes wrong? * View the data in 5min chart and the log(/var/root/aldente.log) to verify the history data of charge/discharge. @@ -419,10 +436,10 @@ Official document: ## Compatibility with battery banks -CL can be used with a power bank. iDevice will be powered by the power bank in the first place in ChargeInhibit mode, and the battery of iDevice will supply power after the power bank run out of power. This is meaningful for users who plans to make a long journey, and power bank have more capacity and lower price than battery. Notice: +CL can be used together with a power bank. iDevice will be powered by the power bank in the first place in ChargeInhibit mode, and the battery of iDevice will supply power after the power bank running out of power. This is meaningful for users who plans to make a long journey, and power bank have more capacity and lower price than battery. Notice: * If the wattage is insufficient in wireless charging, then battery may supply power simultaneously. If the phone itself consumes a lot of power than the charger can supply, then it is not suitable to use a wireless charger. * Most wired power banks support "sleep mode", in which the power bank will automatically turns off power after the current falls below a certain threshold for a period of time. When using CL in this mode, the power bank may turn off the power due to low amperage after the phone is locked, and CL will not able to re-charge any more due to power source disconnection in this case. -* Most wired power banks support "small current mode" by Double-click or long press the power button, in which the powwr bank will not automatically turn off the power when the current is low. CL will work perfect in this mode after the screen locked. Please notice that some power banks will automatically exit the "small current mode" after a few hours. +* Most wired power banks support "small current mode" by Double-click or long press the power button, in which the power bank will not automatically turn off the power when the current is low. CL will work perfect in this mode after the screen locked. Please notice that some power banks will automatically exit the "small current mode" after a few hours. ## Notice @@ -430,7 +447,7 @@ CL can be used with a power bank. iDevice will be powered by the power bank in t * In ChargeInhibit mode, The lightning icon of system status bar will not be updated, and the actual charging status can be found in 3utools(and similar) or CL, while it will be updated in DisableInflow mode(iPhone8+), this mode is enabled in "Advanced-Control inflow". * For TrollStore, if the daemon(of CL) get killed in any condition(such as system-reboot/userspace-reboot/...), CL will become invalid for not being able to restart daemon itself automatically. * CL is designed to minimize the charging count, so it won't start charging or recover charging for connecting to a power source in "Plug and charge" mode, it will only start/stop charging under certain conditions as show behind. -* CL is not compatible with "Optimized Battery Charging" of Settings.app. sCL will disable it automatically(won't shown in Settings.app). Please re-enable in Settings.app after disabling CL if necessary. It's not recommend to use CL on newest iDevice, "Optimized Battery Charging" is already perfect from iPhone15. +* CL is not compatible with "Optimized Battery Charging" of Settings.app. you'd better disable it. Please re-enable in Settings.app after disabling CL if necessary. It's not recommend to use CL on newest iDevice, "Optimized Battery Charging" is already perfect from iPhone15. * If the iDevice stay in ChargeInhibit mode for too long time, the hardware statistics may be incorrect. It's recommend to charge/discharge the battery once a month at least. ## Instruction @@ -476,10 +493,10 @@ triggers precedence from high to low: * Update frequency is data updating speed of UI, both App UI and floating window. * Lower frequency, more power maybe saved. Insensitive to most users with 1sec, it's up to you. -### The threshhold +### The thresholds -* Some studies shown that capacity between 20%-80%, and temperature is between 10°C-35°C, is better for battery. Therefore, the default threshold is set to 20/80/10/35. Long-time-overcharged/Out of power/High temperature will do harm to the battery. -* Please set temperature threshhold according to "History-Hourly Data". +* The default threshold is set to 20/80/10/35, you need to adjust them yourself to get CL work as expected. +* Please set temperature threshold according to "History-Hourly Data". * The real value stop on trigger is not necessarily equal to the target value, the differ is 0-1% in most situations, a few users got 3-5% , the differ has sth. to do with the "120 seconds delay", charging speed, and battery hardware itself. If weak amperage occurs after stopping charging, the differ maybe higher than 3%. besides, A suddenly change of the battery health may cause this situation; A new battery without activation may cause this situation. ### Action @@ -538,11 +555,11 @@ curl http://localhost:1230 -d '{"api":"get_conf","key":"enable"}' -H "content-ty |floatwnd |boolean |Floating window | |floatwnd_auto |boolean |Floating window auto hide | |mode |string |Mode,"charge_on_plug" or "edge_trigger" | -|charge_below |integer |Capacity threshhold | -|charge_above |integer |Capacity threshhold | +|charge_below |integer |Capacity threshold | +|charge_above |integer |Capacity threshold | |enable_temp |boolean |Temperature control | -|charge_temp_above |integer |Temperature threshhold | -|charge_temp_below |integer |Temperature threshhold | +|charge_temp_above |integer |Temperature threshold | +|charge_temp_below |integer |Temperature threshold | |acc_charge |boolean |Speedup charging | |acc_charge_airmode |boolean |Airplane mode | |acc_charge_wifi |boolean |WiFi | diff --git a/md-trollstore b/md-trollstore index 7ea0f26..83d7edb 100755 --- a/md-trollstore +++ b/md-trollstore @@ -10,7 +10,7 @@ function run_at { popd } -codesign --remove "${APP}" +codesign --remove-signature "${APP}" if [ -e "${APP}/_CodeSignature" ]; then rm -rf "${APP}/_CodeSignature" fi diff --git a/www/help_en.md b/www/help_en.md index 2f0f290..f965a29 100755 --- a/www/help_en.md +++ b/www/help_en.md @@ -8,13 +8,14 @@ Tested on iPhone6/7+iOS12/13 Checkra1n/Unc0ver/Odyssey; iPhone7/X/11+iOS15/16 Pa v1.4.1 is for most users; v1.5 is for batteries not support ChargeInhibit; v1.6 is for batteries with too large amperage during charging. -This project is opensourced, any better ideas, submit code directly; any suggestions, submit to issue region. This software will be opensourced, free, without ads forever. Author is not responsible for any impact on iOS system or hardware caused by this software. +This project is opensourced, any better ideas, submit code directly; any suggestions, submit to issue region. This software will be opensourced, free, without ads forever. Author is not responsible for any impact on iOS system or hardware caused by this software. ## Known issues * Due to the lack of devices to test with, CL may not supported on iOS<=11. * Floating window is not supported on iOS<=12. * For deb version, some tweaks will cause CL to stuck at SplashScreen, it's not a bug of CL itself. these tweaks, injected into com.apple.UIKit, can be found in /Library/MobileSubstrate/DynamicLibraries(rootful), and /var/jb/Library/MobileSubstrate/DynamicLibraries(rootless). +* Due to the limitation of system, thermal simulation will be unavailable when the screen locked. ## FAQ @@ -32,13 +33,16 @@ Does CL support 3rd party battery? Will the battery health percentage increase after using CL for a period of time? * I don't think it's possible, especially for a software, but there are indeed some users have their battery health increased after using CL for a month. * CL will slow down dropping speed of battery health for most users. -* Health percentage may fluctuate in certain range. There are indeed little users keep dropping health after using CL, please stop using CL in this case. +* Health percentage may fluctuate in certain range. There are indeed few users keep dropping health after using CL, please stop using CL in this case. * Keep connecting to a power source and enable ChargeInhibit(without DisableInflow) as long as possible, the normal amperage should be 0mA, and the health of battery will never drop. -Why does my iPhone won't charge any more after using for a while(Most questions from freshman)? -* CL is not a fully-automatic tool, please set the threshhold carefully according to the actual temperature if temperature control is enabled, or CL will surely stop charging and won't re-charge any more. +Why does my iDevice unable to charge or discharge(Most questions from freshman)? +* CL is not a fully-automatic tool, please set the threshold carefully according to the actual temperature if temperature control is enabled, or CL will surely stop charging and won't re-charge any more. * CL is designed to minimize the charging count, so it won't start charging or recover charging for connecting to a power source in "Plug and charge" mode, but will start charging for re-connecting to a power source. -* There are a few cases of battery with low health cause this problem. In this case, CL can control charge/discharge normally after a system reboot, but will fail to control after tens of minutes. CL is unavailable for this kind of battery. +* A battery with low health may cause the failure of ChargeInhibit/DisableInflow. In this case, ChargeInhibit/DisableInflow works fine after a system reboot, but would fail after tens of minutes. CL is unavailable for this kind of battery. Please notice there are only a few cases about the failure among all the batteries with low health. +* An overheated battery may cause the failure of ChargeInhibit. In this case, it will resume as normal as the battery get colder. Please notice there are only a few cases about the failure among all the batteries overheated. +* A new battery without activation may cause the amperage far higher than 5mA, which will break a perfect ChargeInhibit. +* For iPad, if the battery is charging normally without increasing capacity, and with the description of power source shown as "pd charger", then try to replug the cable or change the cable and charger with better ones, until it shown as "usb brick" Is it possible to install CL without Jailbreak or TrollStore(-like) environment? * Private api is used in CL, so it is impossible to be published to Appstore. @@ -49,6 +53,12 @@ How to cool down the battery in summer? * Use a charger of lower Watt to charge. * Use a heat dissipation or sth. +What is the best way to keep the battery healthy? +* Refer to +* Avoid use in extremely high or low temperature +* Avoid long time overcharged +* Avoid out of power + How can I debug CL myself when sth. goes wrong? * View the data in 5min chart and the log(/var/root/aldente.log) to verify the history data of charge/discharge. @@ -71,10 +81,10 @@ Official document: ## Compatibility with battery banks -CL can be used with a power bank. iDevice will be powered by the power bank in the first place in ChargeInhibit mode, and the battery of iDevice will supply power after the power bank run out of power. This is meaningful for users who plans to make a long journey, and power bank have more capacity and lower price than battery. Notice: +CL can be used together with a power bank. iDevice will be powered by the power bank in the first place in ChargeInhibit mode, and the battery of iDevice will supply power after the power bank running out of power. This is meaningful for users who plans to make a long journey, and power bank have more capacity and lower price than battery. Notice: * If the wattage is insufficient in wireless charging, then battery may supply power simultaneously. If the phone itself consumes a lot of power than the charger can supply, then it is not suitable to use a wireless charger. * Most wired power banks support "sleep mode", in which the power bank will automatically turns off power after the current falls below a certain threshold for a period of time. When using CL in this mode, the power bank may turn off the power due to low amperage after the phone is locked, and CL will not able to re-charge any more due to power source disconnection in this case. -* Most wired power banks support "small current mode" by Double-click or long press the power button, in which the powwr bank will not automatically turn off the power when the current is low. CL will work perfect in this mode after the screen locked. Please notice that some power banks will automatically exit the "small current mode" after a few hours. +* Most wired power banks support "small current mode" by Double-click or long press the power button, in which the power bank will not automatically turn off the power when the current is low. CL will work perfect in this mode after the screen locked. Please notice that some power banks will automatically exit the "small current mode" after a few hours. ## Notice @@ -82,7 +92,8 @@ CL can be used with a power bank. iDevice will be powered by the power bank in t * In ChargeInhibit mode, The lightning icon of system status bar will not be updated, and the actual charging status can be found in 3utools(and similar) or CL, while it will be updated in DisableInflow mode(iPhone8+), this mode is enabled in "Advanced-Control inflow". * For TrollStore, if the daemon(of CL) get killed in any condition(such as system-reboot/userspace-reboot/...), CL will become invalid for not being able to restart daemon itself automatically. * CL is designed to minimize the charging count, so it won't start charging or recover charging for connecting to a power source in "Plug and charge" mode, it will only start/stop charging under certain conditions as show behind. -* CL is not compatible with "Optimized Battery Charging" of Settings.app. sCL will disable it automatically(won't shown in Settings.app). Please re-enable in Settings.app after disabling CL if necessary. It's not recommend to use CL on newest iDevice, "Optimized Battery Charging" is already perfect from iPhone15. +* CL is not compatible with "Optimized Battery Charging" of Settings.app. you'd better disable it. Please re-enable in Settings.app after disabling CL if necessary. It's not recommend to use CL on newest iDevice, "Optimized Battery Charging" is already perfect from iPhone15. +* If the iDevice stay in ChargeInhibit mode for too long time, the hardware statistics may be incorrect. It's recommend to charge/discharge the battery once a month at least. ## Instruction @@ -127,11 +138,11 @@ triggers precedence from high to low: * Update frequency is data updating speed of UI, both App UI and floating window. * Lower frequency, more power maybe saved. Insensitive to most users with 1sec, it's up to you. -### The threshhold +### The thresholds -* Some studies shown that capacity between 20%-80%, and temperature is between 10°C-35°C, is better for battery. Therefore, the default threshold is set to 20/80/10/35. Long-time-overcharged/Out of power/High temperature will do harm to the battery. -* Please set temperature threshhold according to "History-Hourly Data". -* The real value stop on trigger is not necessarily equal to the target value, the differ is 0-1% in most situations, a little users got 3-5% , the differ has sth. to do with the "120 seconds delay", charging speed, and battery hardware itself. If weak amperage occurs after stopping charging, the differ maybe higher than 3%. besides, A suddenly change of the battery health will cause this situation too. +* The default threshold is set to 20/80/10/35, you need to adjust them yourself to get CL work as expected. +* Please set temperature threshold according to "History-Hourly Data". +* The real value stop on trigger is not necessarily equal to the target value, the differ is 0-1% in most situations, a few users got 3-5% , the differ has sth. to do with the "120 seconds delay", charging speed, and battery hardware itself. If weak amperage occurs after stopping charging, the differ maybe higher than 3%. besides, A suddenly change of the battery health may cause this situation; A new battery without activation may cause this situation. ### Action @@ -148,7 +159,7 @@ Action on trigger start/stop charging. Please reset it after reinstalling/updati ### Battery Information * Health of battery is calculated with NominalChargeCapacity. In general the health of a new battery is higher than 100%, even though it shows always 100% in system status bar. Please be aware of that the health maybe drop largely suddenly due to long term ChargeInhibit, in this case use should disable CL temporarily and have the battery fully charged several times to recover the health. -* Hardware capacity is close to the system capacity and is more accurate in most cases, too much difference maybe show the battery is not calibrated or of poor quality. Hardware capacity chould be higher than 100%(100% in system status bar) if overcharged, and could be negative(0 in system status bar) if undercharged, +* Hardware capacity is close to the system capacity and is more accurate in most cases, too much difference may indicate the battery is not calibrated or of poor quality. Hardware capacity chould be higher than 100%(100% in system status bar) if overcharged, and could be negative(0 in system status bar) if undercharged, * InstantAmperage with positive value means the current flow into battery from the power source, negative means the current flow into iDevice from battery without any power source. InstantAmperage should be 0mA normally in ChargeInhibit mode, in this case the current will flow through battery and feed iDevice, it will cause less damage to battery than use battery to supply power directly. (*In fact, keep connecting to any power source and stop charging, the health may never drop*). InstantAmperage should be negative in DisableInflow mode. ### History diff --git a/www/help_zh_CN.md b/www/help_zh_CN.md index 4b00ff1..6c4ed06 100755 --- a/www/help_zh_CN.md +++ b/www/help_zh_CN.md @@ -1,16 +1,19 @@ ## ChargeLimiter   ChargeLimiter(CL)是针对iOS开发的AlDente替代工具,适用于长时间过充情况下保护电池健康度. -  支持有根越狱(???-arm.deb)/无根越狱(???-arm64.deb )/巨魔(???.tipa),目前支持iOS12-17.0(注意: TrollStore环境下安装新版之前请先卸载旧版) -  测试过的环境: iPhone6/7+iOS12/13 Checkra1n/Unc0ver/Odyssey; iPhone7/X/11+iOS15/16 Palera1n/Dopamine/TrollStore. -  v1.4.1功能可以满足大多数用户需求,v1.5兼容不支持停充的电池,v1.6兼容充电电流过大的电池 -  CL是开放式项目,如果有兴趣参与或者对CL有建议的的欢迎参提交代码.CL纯属偶然兴趣而开发,最开始是作者自己玩的,后来觉得其他人会需要才开源分享.CL承诺永久免费且无广告,但因为使用CL导致系统或硬件方面的影响(或认为会有影响的)作者不负任何责任,用户使用CL即为默认同意本条款. +  支持有根越狱(???-arm.deb)/无根越狱(???-arm64.deb )/巨魔(???.tipa),目前支持iOS12-17.0(注意: 巨魔环境下安装新版之前请先卸载旧版) +  测试过的环境: iPhone6/7+iOS12/13 Checkra1n/Unc0ver/Odyssey; iPhone7/X/11+iOS15/16 Palera1n/Dopamine/TrollStore. +  v1.4.1功能可以满足大多数用户需求,v1.5兼容不支持停充的电池,v1.6兼容充电电流过大的电池, v1.7兼容SBC +  CL是开放式项目,如果有兴趣参与或者对CL有建议的的欢迎参提交代码. +  CL纯属偶然兴趣而开发,CL的完美运行强依赖软硬件环境, 作者没有义务保证每个人都完美使用. 如果你的软硬件环境不兼容则不建议使用. +  CL承诺永久免费且无广告,但因为使用CL导致系统或硬件方面的影响(或认为会有影响的)作者不负任何责任,用户使用CL即为默认同意本条款. ## 已知BUG * 由于缺少开发环境和设备, CL可能不兼容iOS<=11.x. * 悬浮窗暂不支持iOS<=12.x. * DEB版本CL可能会由于某些tweak导致启动卡屏,这并非CL本身的bug,这些tweak注入到com.apple.UIKit,可以在此目录寻找:/Library/MobileSubstrate/DynamicLibraries(有根),或/var/jb/Library/MobileSubstrate/DynamicLibraries(无根). +* 因为系统自身机制原因, 高温模拟在锁屏时失效, 这一点作者认为可行只是时间成本大, 未来有待增强. 另外高温模拟的参数定制未来也有待加入. ## 常见问题 @@ -26,31 +29,48 @@ CL支持第三方电池吗? * CL支持正版电池也支持大部分第三方品牌电池 使用CL后能增加健康度吗? -* 个人认为健康度递减是自然过程,软件更不可能直接修复硬件.不过有些用户使用CL一个月后确实健康度涨了. +* 个人认为健康度递减是自然过程,软件更不可能直接修复硬件,有些用户使用CL前期健康度会涨. 几个月的实测发现涨跌与使用程度有关. * 大部分使用者会明显延缓电池健康度下降速度. * 个别用户在使用CL后出现健康度下降更快的情况,请立即停用并卸载. -* 停充状态下一直连电源的情况下(非禁流),正常情况下电池电流为0,健康度永久不掉. +* 停充且一直连电源的情况下(非禁流),理想情况下电池电流为0,健康度永久不掉. -为什么手机用一会不充电了?(小白经常遇到的问题) +为什么手机无法停充或恢复充电?(小白经常遇到的问题) * CL并非傻瓜式工具,如果开启了温控请根据实际情况调整温度上下限,否则到达上限会停止充电,下限又无法达到自然无法充电. * CL的设计思路就是减少充电次数,因此不会连着usb就充电,充电/停充都有触发条件,请仔细查看本页说明. -* 电池由于老化严重健康度低,刚启动系统时可以使用CL,一段时间后CL再也无法控制充电/停充.此种情况无法使用CL. 已有三例健康度在80%的反馈出现该问题. -* 电池由于过热导致CL无法生效. - -CL可以不依赖越狱或巨魔类工具吗? +* 电池由于健康度低,刚启动系统时可以使用CL,一段时间后CL再也无法控制充电/停充.此种情况无法使用CL. 请注意大部分低健康度的电池仍然可以正常使用CL. +* 电池由于过热导致硬件停充功能失效,导致CL无法生效. 而电池冷却后硬件停充功能会恢复. 请注意大部分处于高温的的电池仍然可以正常使用CL. +* 新电池未激活则有几率导致硬件停充失效.常见品牌激活方法见本页 +* 如果是iPad,如果确定充电状态正常但电量不增加,且电源显示为pd charger,可以尝试重新插拔或更换质量较好的充电线和充电头,直到电源处显示usb brick +* 巨魔环境下如果系统足够稳定, CL服务可以正常运行数月以上, 但如果iCloud半夜同步强杀进程, 或半越狱不稳定导致用户空间重启, 或其他可能导致CL服务失效的情况, 表现为一夜充满, 这并非软件Bug, CL为此提供了尽力而为的保活能力, 包括scheme/悬浮窗拉起服务. 如果你的巨魔不稳定可以使用越狱版本CL. + +我的电池&小板硬件是否支持CL停充? +* 唯一测试方式就是在关闭全局开关的情况下手动控制(开或关)"正在充电"按钮,若120秒内不变(关或开),就是电池或小板存在问题而非软件BUG,以下将硬件无法支持简称"失控" +* 从近几个月反馈来看少数电池/小板可能因为包括但不限于以下几种原因导致失控: 温度升高(温度高时失控,温度恢复时又可控)/健康度低(一直失控,或系统开机后一段时间内可控,之后失控)/电池未激活(新买电池未做激活导致有概率失控) +* 以上失控问题由于是硬件层面有问题, 如果是因为不明原因或健康度低导致的失控, 不推荐使用CL及类似工具, 可以尝试更换电池/小板解决; 电池未激活的,确定电池品牌(非软件中显示)并从官方客服获取正确激活方法激活即可;如果因为温度失控的想办法控温即可. + +CL(及类似功能的软件)可以不依赖越狱或巨魔类工具吗? * CL需要用到私有API所以无法上架. -* CL需要用到特殊签名因此无法以常规IPA方式来安装. +* CL需要用到特殊签名因此无法以常规IPA方式来安装, 也无法用自签方式使用. 夏天怎样降低电池温度? * 使用CL的Powercuff功能减少硬件用电量,充电状态下会同时降低充电功率 * 充电状态下,使用低功率充电头充电 * 购买手机散热器 +怎样使用电池最好? +* 见官方文档 +* 避免极端高温和低温使用 +* 避免长时间过充 +* 避免电量耗尽 + +如何安全的卸载CL? +* 少数用户使用常规方式卸载CL后出现无法充电的情况,需要在CL中确保"正在充电"按钮处于打开状态重新卸载 + 遇到问题时如何自行诊断? * 可以参照5分钟图和日志(/var/root/aldente.log)。例如:发现5分钟图存在1小时以上的数据缺失,就可能是掉后台了。 如何找到耗电应用? -* 可以借助观察5分钟图或者Helium的实时电流数据,来检测开启某项系统功能或运行某App会增加多少电流。 +* 可以借助观察5分钟图或者Helium的实时电流数据,来检测开启某项系统功能或运行某App会增加多少电流 ## 测试电池兼容性 @@ -58,13 +78,13 @@ CL可以不依赖越狱或巨魔类工具吗? * 1.测试电池是否支持停充.关闭CL全局开关,关闭"高级"中所有选项,在“正在充电”按钮开启的状态下,手动关闭之,若120秒内按钮有反应则电池支持停充,但如果停充后有较大持续电池电流(>=5mA)则无法支持停充(有些电池返回电池电流值有误,此时以实际电量变化为准). * 2.测试电池是否支持智能停充.开启"高级-智能停充",其余同1. * 3.测试电池是否支持禁流.关闭CL全局开关,在“电源已连接”按钮开启的状态下,手动关闭之,若120秒内按钮有反应则电池支持禁流,但如果禁流后有较大持续电流(>=5mA)则无法支持禁流(有些电池返回电池电流值有误,此时以实际电量变化为准). -* 有的电池因为老化而健康度过低,会出现刚重启系统时可以使用上述方式停充但过一段时间就再也无法停充,这种电池也无法被CL所兼容. +* 注意: 有的电池因为老化而健康度过低,会出现刚重启系统时可以使用上述方式停充但过一段时间就再也无法停充,这种电池也无法被CL所兼容;有的电池在温度过高时因为硬件原因无法停充,温度正常后又能正常停充. * 若电池既不支持停充也不支持禁流则永远不被CL支持. * 如果使用CL过程中,健康度以不正常的方式下降,请自行调整高级菜单中的选项或卸载CL. ## 品牌新电池激活 -  电池保养官方文档: 。电池激活是指电池刚出厂后需要采取正确方式,排除虚电并激发电池中全部的锂离子活性。 +  电池保养官方文档: 。电池激活是指电池刚出厂后需要采取正确方式,排除虚电并激发电池中全部的锂离子活性。建议咨询电池卖家或电池厂商获取正确激活方式,否则可能导致CL无法正常工作,常见品牌已整理在此 * 诺希: 不管开始有多少电量 ,使用到手机关机,然后进行充电,充满之后再充半小时!循环5~8天(如已充电没关系,下次再重复以上步骤即可)建议使用小电流充电器(即5V1A充电器)进行激活,因为慢充可以确保把电池容量充满,所以效果更佳。快充中间损耗的容量会较大,对激活效果有一定影响 * Dseven: 用到10%左右再开始充电,不要用到关机,充到100再多充1-2小时,如此循环充电使用5-7次之后,就会完全激发电池里面的电量 @@ -81,7 +101,7 @@ CL可以不依赖越狱或巨魔类工具吗? ## 充电宝兼容性   CL可以和充电宝配合使用,停充模式下充电宝优先为手机供电,充电宝电量耗尽后再由手机电池供电,对长途旅行的用户更为有意义,充电宝的容量性价比也远高于手机电池。注意: -* 无线充电时,如果充电功率不够有可能消耗电池电量,所以如果手机自身耗电较大就不适合这种充电方式 +* 无线充电时,如果充电功率不够有可能消耗电池电量,不推荐CL搭配无线充电使用 * 大部分有线充电宝支持"休眠模式",在电流低于某个阈值一段时间后,会自动关闭自身电源。这种模式下使用CL,充电宝可能在手机锁屏后由于电流过小导致充电宝自动关闭电源, 造成无CL无法正常工作 * 大部分有线充电宝支持"小电流模式",双击或长按电源键后进入小电流模式,这种模式在低电流时不会自动关闭电源,这种模式下CL在手机锁屏后也可以正常工作。注意有的充电宝几小时后会自动退出小电流模式 @@ -89,13 +109,15 @@ CL可以不依赖越狱或巨魔类工具吗? * iPhone8+存在120秒设定充电状态延迟. iPad可能也存在. * 停充模式不会更新系统状态栏的充电标志,实际充电状态可以在看爱思助手或者CL查看.禁流模式会改变系统状态栏的充电标志(iPhone8+), 禁流模式在"高级-停充时启用禁流"中设定。 -* 对于巨魔环境,因任何原因导致的服务被杀(比如重启系统/重启用户空间/...),将导致CL失效. +* 对于巨魔环境,因任何原因导致的服务被杀(比如重启系统/重启用户空间/半越狱不稳定/iCloud夜间同步/其他巨魔App...),将导致CL失效. * CL的设计思路就是减少充电次数,因此不会连着usb就自发充电,也不会无故自动恢复充电,充电/停充都有触发条件,请仔细查看本页说明. -* 系统自带电池优化会导致CL失效,CL会自动关闭自带优化(但系统设置里不会显示).如果不使用CL需在系统设置中手动重置电池优化开关(先关后开). +* 系统自带电池优化会导致CL失效,建议关闭.如果不使用CL需在系统设置中手动重置电池优化开关(先关后开). +* 新电池请先激活再使用CL, 否则有几率出现硬件停充失效问题。停充过久会出现小板统计有误和硬件停充失效问题, 建议一个月至少满充满放一次. ## 使用说明 ### 名词解释 + * 停充: 禁止电流流入电池, 此时电池不充电也不放电, 电源直接为设备硬件供电. 用户应优先使用此模式 * 禁流: 禁止电流流入设备, 此时电池处于放电状态, 电池为设备硬件供电. 电池若不支持停充, 则应使用此模式 * 限流: 通过模拟高温的方式限制充电电流. 电池在充电时如果电流过大导致异常发热, 则应选择此模式 @@ -137,9 +159,9 @@ CL可以不依赖越狱或巨魔类工具吗? ### 阈值设定 -* 有研究表明电量在20%-80%之间,温度在10°C-35°C之间,对电池寿命影响最小.因此CL阈值默认设定为20/80/10/35,长期过充/电量耗尽/高温对电池会产生不良影响. +* CL阈值默认设定为20/80/10/35, 你需要根据实际情况设置否则可能无法正常工作. * 温度阈值的设定,可根据"历史统计-小时数据"的温度数据设置合适的阈值. -* 设定阈值和实际触发值不一定完全相同,例如设定80%上限结果到81%停充,大部分手机差距在0-1%,极少数3-5%,差异值与120秒延迟有关,与充电速度有关,也与电池质量有关.停充后如果存在微弱电流可能造成差值;另外健康度的突然变化也会影响电量. +* 设定阈值和实际触发值不一定完全相同,例如设定80%上限结果到81%停充,大部分手机差距在0-1%,极少数3-5%,差异值与120秒延迟有关,与充电速度有关,也与电池质量有关.停充后如果存在微弱电流可能造成差值;另外健康度的突然变化也会影响电量;新电池未激活直接使用CL也会导致停充后有较大电流. * 电量上限阈值的设定,如果是短期停充,此上限可以根据自己需要设置;如果是iPad长年连电停充,则此上限可以设置为最佳停充电量,最佳停充电确定方法如下:将电量充满,关闭所有耗电App和功能然后静置,让电量自然降低,等待一天后此电量就是最佳停充电量. ### 行为 @@ -160,7 +182,7 @@ CL可以不依赖越狱或巨魔类工具吗? ### 电池信息 -* 健康度,与爱思助手保持一致,CL健康度是根据最大实际容量计算的.通常新电池健康度都会超过100%,而系统设置显示的健康度不会超过100%,但设置里只显示100%,所以健康度100%到99%的过程较长.注意健康度在长期停充的状态下可能有突然大幅度下降和统计不准确的情况,此时禁用CL并正常充电几次即可恢复. +* 健康度,与爱思助手保持一致,CL健康度是根据最大实际容量计算的.通常新电池健康度都会超过100%,而系统设置显示的健康度不会超过100%,,所以健康度100%到99%的过程较长.注意健康度在长期停充的状态下可能有突然大幅度下降和统计不准确的情况,此时禁用CL并正常充电几次即可恢复. * 硬件电量,一般情况下和系统电量接近,如果差值过大则可能是未校准或质量问题导致.硬件电量比系统电量更准确,硬件电量可能会大于100%也可能为负值,硬件电量大于100%时为过充此时系统电量为100%,为负时为电池亏电,此时系统电量为0%. * 电流以"瞬时电流"为准,电池电流为正说明从充电器流入电池,电池电流为负说明电池为设备供电.使用CL且停充状态下电池电流一般为0,此时电流流经电池为设备供电,电池起到闭合电路作用(可以理解为导线),此时对电池的损耗应小于仅使用电池为设备供电.禁流状态下电池电流一般为负. @@ -184,6 +206,7 @@ CL可以不依赖越狱或巨魔类工具吗? 注意: * iPhone8+存在至多120秒延迟 * 可以在个人自动化中的电量事件使用上述指令实现指定电量开始/停止充电,也可以和其他模式结合实现开机自启(比如打开某App时触发) +* 快捷指令拉起服务需要在非锁屏状态下生效 集成快捷指令(iOS16+): diff --git a/www/index.html b/www/index.html index 2c84b73..c1ba38a 100755 --- a/www/index.html +++ b/www/index.html @@ -241,13 +241,18 @@ {{$t("enable")}} + + + {{$t("disable_smart_charge")}} + +
{{$t("floatwnd")}} - + @@ -525,7 +530,7 @@ {{$t("IsCharging")}} - + {{$t('charge_btn_desc')}} @@ -552,6 +557,11 @@ {{bat_info.NominalChargeCapacity}} + + {{$t("AppleRawCurrentCapacity")}} + {{bat_info.AppleRawCurrentCapacity}} + + {{$t("HardwareCapacity")}} {{get_hardware_capacity()}} @@ -582,6 +592,132 @@ {{bat_info.Serial}} + {{$t("adaptorinfo")}} @@ -616,7 +752,7 @@ {{$t("ExternalConnected")}} - + {{$t('inflow_btn_desc')}} diff --git a/www/js/app.js b/www/js/app.js index 46e3669..ce1b95b 100755 --- a/www/js/app.js +++ b/www/js/app.js @@ -163,11 +163,13 @@ const App = { lang: get_local_val("conf", "lang", "en"), sysver: "", devmodel: "", + disable_smart_charge: true, floatwnd: false, floatwnd_auto: false, mode: "charge_on_plug", msg_list: [], bat_info: {}, + upsbat_info: {}, adaptor_info: {}, charge_below: 20, charge_above: 80, @@ -207,6 +209,7 @@ const App = { actions: null, cuffmods: null, show_tips: { + enable: false, setting: true, floatwnd_auto: false, lang: false, @@ -304,6 +307,7 @@ const App = { this.daemon_alive = true; if (jdata.status == 0) { this.bat_info = jdata.data; + this.upsbat_info = jdata.data_ups; this.adaptor_info = jdata.data.AdapterDetails; } else { this.msg_list.push({ @@ -344,6 +348,14 @@ const App = { this.enable = v; } }, + set_disable_smart_charge: function(v) { + this.ipc_send_wrapper({ + api: "set_conf", + key: "disable_smart_charge", + val: v, + }); + this.disable_smart_charge = v; + }, set_floatwnd: function(v) { this.ipc_send_wrapper({ api: "set_conf", @@ -548,13 +560,45 @@ const App = { this.wait_daemon_alive(); } }, + is_charge: function() { + if (this.bat_info.IsCharging) { + return true; + } + if (this.upsbat_info) { + if (this.bat_info.InstantAmperage > 10) { // -:放电 0-5mA:停充 +:充电 小板可能控流不精确因此取10mA + return true; + } + } + return false; + }, + is_inflow: function() { + if (this.bat_info.ExternalConnected) { + return true; + } + if (this.upsbat_info) { + if (this.bat_info.InstantAmperage >= 0) { // -:放电 0-5mA:停充 +:充电 停充时由于硬件供电因此也有电流流入 + return true; + } + } + return false; + }, get_health: function(item) { - return (item["NominalChargeCapacity"] / item["DesignCapacity"] * 100).toFixed(2); + return (item.NominalChargeCapacity / item.DesignCapacity * 100).toFixed(2); }, get_hardware_capacity: function() { var v = (this.bat_info.AppleRawCurrentCapacity / this.bat_info.NominalChargeCapacity * 100).toFixed(2); return v + "%"; }, + get_ups_cur_capcity: function(item) { + return (item.CurrentCapacity / item.NominalCapacity * 100).toFixed(2); + }, + get_ups_health: function(item) { + return (item.NominalCapacity / item.MaxCapacity * 100).toFixed(2); + }, + get_ups_hardware_capacity: function() { + var v = (this.upsbat_info.AppleRawCurrentCapacity / this.upsbat_info.NominalCapacity * 100).toFixed(2); + return v + "%"; + }, get_adaptor_desc: function() { var s = ""; if (this.adaptor_info.Description) { @@ -593,6 +637,17 @@ const App = { that.get_conf(); }, v * 1000); }, + get_ups_temp_desc: function() { + var centigrade = this.upsbat_info.Temperature; + if (centigrade == null) { + return; + } + if (this.temp_mode == 0) { + return centigrade.toFixed(1) + "°C"; + } else if (this.temp_mode == 1) { + return t_c_to_f(centigrade).toFixed(1) + "°F"; + } + }, get_temp_desc: function() { var centigrade = this.bat_info.Temperature / 100; if (this.temp_mode == 0) { @@ -687,16 +742,14 @@ const App = { delete copy_bat_info["Serial"]; var data = JSON.stringify({ "Battery": copy_bat_info, + "UPSBattery": this.upsbat_info, "System": { sysver: this.sysver, devmodel: this.devmodel, }, "Config": this.conf, }, null, 2); - this.ipc_send_wrapper({ - api: "set_pb", - val: data, - }); + set_pb(data); this.msg_list.push({ "id": get_id(), "title": this.$t("suc"), diff --git a/www/js/float.js b/www/js/float.js index 78cf18d..73eac7d 100755 --- a/www/js/float.js +++ b/www/js/float.js @@ -34,6 +34,7 @@ const App = { }, get_bat_info_cb: function(jdata) { if (jdata.status == 0) { + this.enable = jdata.enable; this.bat_info = jdata.data; } }, diff --git a/www/lang.json b/www/lang.json index c3397a9..f2b9b40 100644 --- a/www/lang.json +++ b/www/lang.json @@ -6,6 +6,7 @@ "fail": "Failed", "setting": "Settings", "batinfo": "Battery information", + "upsbatinfo": "UPS Battery information", "adaptorinfo": "Adaptor information", "sysinfo": "System information", "update_freq": "Update frequency", @@ -19,12 +20,13 @@ "user_disable": "UserDisable", "unknown": "Unknown", "floatwnd": "Floating window", + "disable_smart_charge": "Disable system optimized charging", "autohide": "Auto hide", "mode": "mode", "charge_on_plug": "Plug and charge", - "charge_on_plug_desc": "iDevice will start charging when replug the USB cable, and stop charging when capacity increase to max threshhold specified", + "charge_on_plug_desc": "iDevice will start charging when replug the USB cable, and stop charging when capacity increase to max threshold specified", "edge_trigger": "Edge trigger", - "edge_trigger_desc": "iDevice will stop charging when capacity increase to max threshhold specified, and start charging only when capacity drop to min threshhold specified", + "edge_trigger_desc": "iDevice will stop charging when capacity increase to max threshold specified, and start charging only when capacity drop to min threshold specified", "temp_ctrl": "Temperature control", "acc_charge": "Speedup charging", "acc_charge_desc": "Enable Airplane Mode, disable WiFi, enable LPM, disable Bluetooth, and minimize brightness automatically during charging", @@ -61,13 +63,19 @@ "Serial": "Serial", "BootVoltage": "Boot voltage(V)", "Voltage": "Voltage(V)", + "ChargeVoltage": "Charge voltage(V)", "AdapterVoltage": "Voltage(V)", + "CellVoltage": "Cell voltage(V)", "Current": "Current(mA)", "Watts": "Watts(W)", "DesignCapacity": "Design capacity(mAh)", + "MaxCapacity": "Max capacity(mAh)", + "AppleRawCurrentCapacity": "Current capacity(mAh)", "NominalChargeCapacity": "Nominal charge capacity(mAh)", "Amperage": "Amperage(mA)", "InstantAmperage": "Instant amperage(mA)", + "IncomingCurrent": "Incoming current(mA)", + "IncomingVoltage": "Incoming voltage(V)", "CurrentCapacity": "Current capacity", "Capacity": "Capacity", "HardwareCapacity": "Hardware capacity", @@ -83,6 +91,8 @@ "WirelessCharge": "Wireless charge", "UpdateAt": "Updated at", "Health": "Health", + "ModelNumber": "Model", + "PowerSourceState": "Power source state", "conn_adaptor": "Please connect to a power source first", "wait_update": "Please wait {0} sec to take effect", "conn_daemon_error": "Service connect failed", @@ -127,6 +137,7 @@ "fail": "失败", "setting": "设置", "batinfo": "电池信息", + "upsbatinfo": "UPS电池信息", "adaptorinfo": "电源信息", "sysinfo": "系统信息", "update_freq": "更新频率", @@ -140,6 +151,7 @@ "user_disable": "用户禁用", "unknown": "未知", "floatwnd": "悬浮窗", + "disable_smart_charge": "禁用系统充电优化", "autohide": "自动隐藏", "mode": "模式", "charge_on_plug": "插电即充", @@ -182,13 +194,19 @@ "Serial": "序列号", "BootVoltage": "开机电压(V)", "Voltage": "电压(V)", + "ChargeVoltage": "充电电压(V)", "AdapterVoltage": "电压(V)", + "CellVoltage": "单元电压(V)", "Current": "电流(mA)", "Watts": "功率(W)", "DesignCapacity": "设计容量(mAh)", + "MaxCapacity": "最大容量(mAh)", + "AppleRawCurrentCapacity": "当前电量(mAh)", "NominalChargeCapacity": "实际容量(mAh)", "Amperage": "电流(mA)", "InstantAmperage": "瞬时电流(mA)", + "IncomingCurrent": "输入电流(mA)", + "IncomingVoltage": "输入电压(V)", "CurrentCapacity": "当前电量", "Capacity": "电量", "HardwareCapacity": "硬件电量", @@ -204,6 +222,8 @@ "WirelessCharge": "无线充电", "UpdateAt": "更新于", "Health": "健康度", + "ModelNumber": "型号", + "PowerSourceState": "电源状态", "conn_adaptor": "请先连接电源", "wait_update": "请等待{0}秒生效", "conn_daemon_error": "服务连接失败", @@ -248,6 +268,7 @@ "fail": "失敗", "setting": "設定", "batinfo": "電池資訊", + "upsbatinfo": "UPS電池資訊", "adaptorinfo": "充電器資訊", "sysinfo": "系統資訊", "update_freq": "更新頻率", @@ -261,6 +282,7 @@ "user_disable": "使用者停用", "unknown": "未知", "floatwnd": "懸浮視窗", + "disable_smart_charge": "停用系統充電優化", "autohide": "自動隱藏", "mode": "模式", "charge_on_plug": "插電即充", @@ -303,12 +325,19 @@ "Serial": "序號", "BootVoltage": "開機電壓(V)", "Voltage": "電壓(V)", + "ChargeVoltage": "充電電壓(V)", "AdapterVoltage": "電壓(V)", + "CellVoltage": "單元電壓(V)", "Current": "電流(mA)", "Watts": "功率(W)", "DesignCapacity": "額定容量(mAh)", + "MaxCapacity": "最大容量(mAh)", + "AppleRawCurrentCapacity": "目前電量(mAh)", + "NominalChargeCapacity": "實際電量(mAh)", "Amperage": "電流(mA)", "InstantAmperage": "瞬時電流(mA)", + "IncomingCurrent": "輸入電流(mA)", + "IncomingVoltage": "輸入電壓(V)", "CurrentCapacity": "目前電量", "Capacity": "電量", "HardwareCapacity": "硬體電量", @@ -324,6 +353,8 @@ "WirelessCharge": "無線充電", "UpdateAt": "更新於", "Health": "電池健康度", + "ModelNumber": "型號", + "PowerSourceState": "充電器狀態", "conn_adaptor": "請連接電源", "wait_update": "請等待{0}秒生效", "conn_daemon_error": "常駐程式連線失敗", @@ -368,6 +399,7 @@ "fail": "فشل", "setting": "إعدادات", "batinfo": "معلومات البطارية", + "upsbatinfo": "معلومات بطارية UPS", "adaptorinfo": "معلومات الكيبل", "sysinfo": "معلومات النظام", "update_freq": "تحديث التردد", @@ -381,6 +413,7 @@ "user_disable": "تعطيل المستخدم", "unknown": "غير معروف", "floatwnd": "نافذة عائمة", + "disable_smart_charge": "تعطيل تحسين شحن النظام", "autohide": "إخفاء تلقائي", "mode": "أسلوب", "charge_on_plug": "التوصيل والشحن", @@ -422,14 +455,19 @@ "min": "دقيقة", "Serial": "الرقم التسلسلي", "BootVoltage": "جهد التمهيد(V)", - "Voltage": "الجهد(V)", + "Voltage": "شحن الجهد(V)", + "ChargeVoltage": "(V)", "AdapterVoltage": "جهد الكيبل(V)", + "CellVoltage": "جهد الخلية(V)", "Current": "الحالي(mA)", "Watts": "الواط(W)", "DesignCapacity": "قدرة التصميم(mAh)", + "AppleRawCurrentCapacity": "مستوى البطارية الحالي(mAh)", "NominalChargeCapacity": "سعة الشحن العشوائية(mAh)", "Amperage": "التيار الكهربائي(mA)", "InstantAmperage": "تيار فوري(mA)", + "IncomingCurrent": "المدخلات الحالية(mA)", + "IncomingVoltage": "جهد الإدخال(V)", "CurrentCapacity": "السعة الحالية", "Capacity": "السعة", "HardwareCapacity": "سعة الجهاز", @@ -445,6 +483,8 @@ "WirelessCharge": "الشحن اللاسلكي", "UpdateAt": "تم التحديث في", "Health": "الصحة", + "ModelNumber": "نموذج", + "PowerSourceState": "حالة الطاقة", "conn_adaptor": "يرجى الاتصال بمصدر طاقة أولا", "wait_update": " فضلاً انتظر {0} ثانية ليتم التنفيذ", "conn_daemon_error": "فشل توصيل الخدمة",