Will ctype or other allow a Pythonista app to determine battery level or signal strength?
Forum Archive
Getting the battery level and battery state of the device
here's battery level. this requires the beginning matter from
https://gist.github.com/omz/9882a00abf59c6009fa4
def get_battery_level():
'''[[UIDevice currentDevice] setBatteryMonitoringEnabled:YES]]
[[UIDevice currentDevice] batteryLevel];
[[UIDevice currentDevice] batteryState];
'''
UIDevice = cls('UIDevice')
currentDevice = msg(UIDevice, c_void_p, 'currentDevice')
msg(currentDevice,None,'setBatteryMonitoringEnabled:',[c_bool],True)
b=msg(currentDevice,c_float,'batteryLevel')
return b
apparently Apple doesn't let apps access the wifi signal strength meter.
You beat me to it. Anyway, here's a "standalone" version, but it does pretty much the same as @JonB's script:
# coding: utf-8
from ctypes import cdll, c_void_p, c_char_p, c_int, c_bool, c_float
c = cdll.LoadLibrary(None)
def get_current_device():
c.sel_registerName.restype = c_void_p
c.sel_registerName.argtypes = [c_char_p]
c.objc_getClass.restype = c_void_p
c.objc_getClass.argtypes = [c_char_p]
UIDevice = c.objc_getClass('UIDevice')
c.objc_msgSend.argtypes = [c_void_p, c_void_p]
c.objc_msgSend.restype = c_void_p
device = c.objc_msgSend(UIDevice, c.sel_registerName('currentDevice'))
return device
def set_battery_monitoring_enabled(flag):
device = get_current_device()
c.objc_msgSend.argtypes = [c_void_p, c_void_p, c_bool]
c.objc_msgSend.restype = None
c.objc_msgSend(device, c.sel_registerName('setBatteryMonitoringEnabled:'), c_bool(flag))
def get_battery_level():
device = get_current_device()
c.objc_msgSend.argtypes = [c_void_p, c_void_p]
c.objc_msgSend.restype = c_float
battery_level = c.objc_msgSend(device, c.sel_registerName('batteryLevel'))
return battery_level
def get_battery_state():
device = get_current_device()
c.objc_msgSend.argtypes = [c_void_p, c_void_p]
c.objc_msgSend.restype = c_int
battery_state = c.objc_msgSend(device, c.sel_registerName('batteryState'))
return battery_state
def main():
set_battery_monitoring_enabled(True)
battery_level = get_battery_level()
print 'Battery Level: %0.1f%%' % (battery_level * 100.0,)
battery_state = get_battery_state()
states = {0: 'unknown', 1: 'unplugged', 2: 'charging', 3: 'full'}
print 'Battery State: %i (%s)' % (battery_state, states.get(battery_state))
set_battery_monitoring_enabled(False)
if __name__ == '__main__':
main()
I don't think getting the signal level will be possible, at least it would be quite difficult as there's no public API for that.
Thanks!! You guys are awesome. I doubt that I will ever be able to write ctype code (my mind does not work that way) but I am impressed with what it can do.
I did make one change to @omz's code to create a context manager:
import contextlib
@contextlib.contextmanager
def battery_monitoring_enabled():
set_battery_monitoring_enabled(True)
yield
set_battery_monitoring_enabled(False)
Then we can remove the first and last lines of main() and replace them with a single with statement.
def main():
with battery_monitoring_enabled():
battery_level = get_battery_level()
print 'Battery Level: %0.1f%%' % (battery_level * 100.0,)
battery_state = get_battery_state()
states = {0: 'unknown', 1: 'unplugged', 2: 'charging', 3: 'full'}
print 'Battery State: %i (%s)' % (battery_state, states.get(battery_state))
The other nice feature of the with statement context manager is that set_battery_monitoring_enabled(False) will always get called even if an exception is thrown in the code underneath the with statement.
Thanks again!
@ccc, I am not being a smart a**. In both above cases are they just not preparing the c data types to be used in a call the native API call, then also preparing the return data types to be recieved from the called function (msg) Maybe I am wrong, but that's what I make of it. Keep in mind I have only ever open Xcode accidentally viewing a plist file.
I only mention, because you say you think you can not think like that. I find that hard to believe. If it is what I think it is, I think you would have your head around it in a very short period of time
My humble opinion
You are right... I just needed an easier to understand approach so I defined objc_call(return_type, obj, sel_name, *args) which simplifies get_current_device() allows get_battery_level() and get_battery_state() to be one liners. If parameter passing was working (fixed), then set_battery_monitoring_enabled() would also be a one liner.
EDIT: The code below has been edited to add the missing : as pointed out by @omz in the following post. This also makes set_battery_monitoring_enabled() a one liner.
# coding: utf-8
#See: http://omz-forums.appspot.com/pythonista/post/5776493279969280
import contextlib
from ctypes import cdll, c_void_p, c_char_p, c_int, c_bool, c_float
c = cdll.LoadLibrary(None)
objc_types = cdll, c_void_p, c_char_p, c_int, c_bool, c_float
return_types = list(objc_types) + [None]
def objc_call(return_type, obj, sel_name, *args):
assert return_type in return_types, '{} is an invalid return type.'.format(return_type)
assert isinstance(sel_name, basestring), '{} is an invalid sel_name.'.format(sel_name)
arg_types = [c_void_p, c_void_p]
if args:
fmt = 'arg[{}] has an invalid arg_type: {}.'
for i, arg in enumerate(args):
arg_type = type(arg)
assert arg_type in objc_types, fmt.format(i, arg_type)
arg_types.append(arg_type)
c.objc_msgSend.argtypes = arg_types
c.objc_msgSend.restype = return_type
return c.objc_msgSend(obj, c.sel_registerName(sel_name), *args)
def get_current_device():
c.sel_registerName.restype = c_void_p
c.sel_registerName.argtypes = [c_char_p]
c.objc_getClass.restype = c_void_p
c.objc_getClass.argtypes = [c_char_p]
UIDevice = c.objc_getClass('UIDevice')
return objc_call(c_void_p, UIDevice, 'currentDevice')
def set_battery_monitoring_enabled(flag):
objc_call(None, get_current_device(), 'setBatteryMonitoringEnabled:', c_bool(flag))
import contextlib
@contextlib.contextmanager
def battery_monitoring_enabled():
set_battery_monitoring_enabled(True)
yield
set_battery_monitoring_enabled(False)
def get_battery_level():
return objc_call(c_float, get_current_device(), 'batteryLevel')
def get_battery_state():
return objc_call(c_int, get_current_device(), 'batteryState')
def main():
with battery_monitoring_enabled():
battery_level = get_battery_level()
print 'Battery Level: %0.1f%%' % (battery_level * 100.0,)
battery_state = get_battery_state()
states = {0: 'unknown', 1: 'unplugged', 2: 'charging', 3: 'full'}
print 'Battery State: %i (%s)' % (battery_state, states.get(battery_state))
if __name__ == '__main__':
main()
@ccc Your selector name is wrong, it should be 'setBatteryMonitoringEnabled:' (note the trailing colon).
Perfect. Edited in the code above.
@ccc, nice. I can not run your code yet, still on 1.5. But I knew I would take you minutes to figure it out. I don't understand all the code you wrote, just the concept.
I was trying to think what this reminded me of. But, I remember now. In a way reminds me of Apple Events. As far as I remember, a lot of work with types. Also back then had worries about big and little endian. From memory, 80xxx series Vrs Motorola MC64xxx series processors supported opposing standards. I am not sure but I think byte order, byte boundaries are a thing of the past. Or at least if not, only in assembly language.
@ccc: No, the code will not run with an exception. I've been caught by that before. This is the correct way to use the context manager if you want to be exception safe. (See context manager's docs)
import contextlib
@contextlib.contextmanager
def battery_monitoring_enabled():
set_battery_monitoring_enabled(True)
try:
yield
finally:
set_battery_monitoring_enabled(False)
With the latest beta, the code becomes much simpler:
from objc_util import *
device = ObjCClass('UIDevice').currentDevice()
device.setBatteryMonitoringEnabled_(True)
print 'Battery level:', device.batteryLevel()
device.setBatteryMonitoringEnabled_(False)