Creating a ChargeInhibit Assertion (OS X 10.6.8)

2019-04-13 10:51发布

问题:

Platform (OS X 10.6.8) - [Macbook Pro - this is important as I want to deal with the battery handling - not applicable for a desktop]

Forgive me if I've made a basic mistake that I don't see, as I haven't written any C/C++ in five years or more, and I'm not fully following the way apple APIs want you to deal with them, so this is what my question regards. Basically, I want to be able to inhibit charging from the AC adaptor at my command, so that I can choose whether to charge my laptop or not while it's in use and plugged in. I hadn't been able to find any utilities that do this, so I had at first written it off as something that was on the hardware level and couldn't be changed from software, but then I ran across something encouraging:

If you open a Terminal window and run

pmset -g assertionslog

you get a list of assertions, and a 0 or 1 for each, indicating if some process has asserted that assertion. The first of them is titled ChargeInhibit, and I discovered after some digging that this does exactly what I want, at the software level. I just have to figure out how to assert it.

I copied some code from a file in the apple source archive called SetActive.c (link) I copied the function sendSmartBatteryCommand into my XCode project, and spent time linking other headers and copying over definitions until I could get it to compile correctly. Here is the copied function:

// The copied function, 
// modified very slightly:
// to return a success/fail value instead of void

kern_return_t sendSmartBatteryCommand(uint32_t which, uint32_t level)
{

    io_service_t    sbmanager = MACH_PORT_NULL;
    io_connect_t    sbconnection = MACH_PORT_NULL;
    kern_return_t   kret = 99;
    uint32_t        output_count = 1;
    uint64_t        uc_return = kIOReturnError;
    uint64_t        level_64 = level;

    // Find SmartBattery manager
    sbmanager = IOServiceGetMatchingService(MACH_PORT_NULL,
                                            IOServiceMatching("AppleSmartBatteryManager"));

    if(MACH_PORT_NULL == sbmanager) {

        goto bail;
    }

    kret = IOServiceOpen( sbmanager, mach_task_self(), 0, &sbconnection);
    if(kIOReturnSuccess != kret) {
        goto bail;
    }

    kret = IOConnectCallMethod(
                               sbconnection, // connection
                               which,      // selector
                               &level_64,  // uint64_t *input
                               1,          // input Count
                               NULL,       // input struct count
                               0,          // input struct count
                               &uc_return, // output
                               &output_count,  // output count
                               NULL,       // output struct
                               0);         // output struct count

bail:

    if (MACH_PORT_NULL != sbconnection) {
        IOServiceClose(sbconnection);
    }

    if (MACH_PORT_NULL != sbmanager) {
        IOObjectRelease(sbmanager);
    }

    return kret;
}

I get success values back when I try to use it to send assertions for charge inhibiting and inflow disabling, but the log from pmset doesn't show any changes, and neither does the actual state of my battery charging / not-charging.

I've also tried modifying the name of the service to look for from "AppleSmartBatteryManager" to a nonsense word just to see if the function returns failure, and it does, so that indicates I'm connecting to a real service.

Any tips on how I can achieve this as simply as possible?

By the way, I've tried to recompile the AppleSmartBatteryManager in the PowerManagement project from the source package on the apple source site, but I way more errors in XCode than I can deal with. I'm looking to try to interact with the existing service in some way that does not make me recompile the AppleSmartBatteryManager source alongside my own project.

EDIT: By the way, this is an example of me calling the function:

int CInh()
{
    kern_return_t kret = sendSmartBatteryCommand( kSBUCChargeInhibit, 255);  // zero:uninhibit, non-zero:inhibit

    if(kret == KERN_SUCCESS)
        return 1;
    else
        return 0;
}

Where options for the "which" parameter are defined in an enum in my header file (also copied from SetActive.c):

enum {
    kSBUCInflowDisable = 0,
    kSBUCChargeInhibit = 1
};

回答1:

I first tried the same thing with no luck, then i found "pmset noidle." (pmset.c) This raises an assertion, just not the right one. Look at the function prevent_idle_sleep.

Change this:

IOPMAssertionCreateWithName(kIOPMAssertionTypeNoIdleSleep,
                    kIOPMAssertionLevelOn,
                    CFSTR("pmset prevent sleep"),
                    &neverSleep))

to:

IOPMAssertionCreateWithName(kIOPMAssertionTypeInhibitCharging,
                    kIOPMAssertionLevelOn,
                    CFSTR("prevent charging..."),
                    &neverSleep))

and add these defines:

// Disables battery charging (requires root to initiate)
#define kIOPMAssertionTypeInhibitCharging       CFSTR("ChargeInhibit")
#define kIOPMChargeInhibitAssertion             kIOPMAssertionTypeInhibitCharging