Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
Getting the battery level and battery state of the device
-
Will
ctype
or other allow a Pythonista app to determine battery level or signal strength? -
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 singlewith
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 thatset_battery_monitoring_enabled(False)
will always get called even if an exception is thrown in the code underneath thewith
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 simplifiesget_current_device()
allowsget_battery_level()
andget_battery_state()
to be one liners. If parameter passing was working (fixed), thenset_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 makesset_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)