Forum Archive

Accessing Barometer Measurements

tszheichoi

I'm wondering whether it is possible to access the barometer (pressure) measurements on a supported iPhone from within a pythoninsta script? I've tried to use ObjCClass with the corresponding objective C class without much luck.

Webmaster4o

I wasn't even aware that iPhones had barometer measurements. What code have you tried, maybe someone can help debug?

tszheichoi

Models since the iPhone 6 has a built in barometer. I believe it's primarily for measuring how many flights of stairs climbed. But the raw pressure measurements should be accessible:

https://developer.apple.com/library/ios/documentation/CoreMotion/Reference/CMAltitudeData_class/index.html#//apple_ref/occ/cl/CMAltitudeData

To be honest, I'm fairly new to Pythoninsta, and am not entirely sure whether objCClass is for this purpose.

What I'm trying to do is to write a simple utility to map out a pressure and magnetic field variations in underground caves. The latter is possible with the motion module. I'm a caving hobbyist, and want to see whether sensors in phones can aid underground spatial orientation. (GPS obviously doesn't work underground!)

omz

It's a little tricky because of the block-based API, but here's a working example that simply prints altitude/pressure data to the console (until the script is stopped). I've only tested this on an iPad Air 2 (which also has a barometer), but it should work the same on an iPhone 6.

from objc_util import ObjCInstance, ObjCClass, ObjCBlock, c_void_p

def handler(_cmd, _data, _error):
    print(ObjCInstance(_data))

handler_block = ObjCBlock(handler, restype=None, argtypes=[c_void_p, c_void_p, c_void_p])

def main():
    CMAltimeter = ObjCClass('CMAltimeter')
    NSOperationQueue = ObjCClass('NSOperationQueue')
    if not CMAltimeter.isRelativeAltitudeAvailable():
        print('This device has no barometer.')
        return
    altimeter = CMAltimeter.new()
    main_q = NSOperationQueue.mainQueue()
    altimeter.startRelativeAltitudeUpdatesToQueue_withHandler_(main_q, handler_block)
    print('Started altitude updates.')
    try:
        while True:
            pass
    finally:
        altimeter.stopRelativeAltitudeUpdates()
        print('Updates stopped.')

if __name__ == '__main__':
    main()
omz

I'm a caver hobbyist myself, and want to see whether sensors in phones can aid underground spatial orientation. (GPS obviously doesn't work underground!)

That's a pretty awesome use case btw!

Phuket2

@omz , is this a dangerous example. I mean for battery life. Eg is it like the motion module. That if you don't actually call the stop update method, it will use extra battery. I am not sure what context that is in. No pun intended. But can that example be used with a context manager, or maybe it does not matter. I am not sure. Just going off what I read before from the Pythonista modules for hardware access. Maybe it's different when doing it this way

omz

@Phuket2 Well, you'd need to stop the updates at some point (and I'm doing that in the example). I'm not sure how much altimeter updates drain the battery; I suspect that it's actually not very much with the M8/M9 motion co-processor all these devices have.

But even if you forget to stop the updates, iOS will suspend the app after a couple of minutes anyway, if it's not in the foreground, so you can't really drain the battery very much while you're not actively using the app.

Phuket2

@omz , ok perfect. I wasn't sure how global the settings were. Btw, if I stop the script, I don't get the 'Updates stopped" print.

omz

Btw, if I stop the script, I don't get the 'Updates stopped" print.

@Phuket2 Hmm, that's weird. Are you using Pythonista 2 or 3?

Phuket2

@omz , sorry I was wrong it does print the stop msg. Not sure what I did wrong. I recopied the script from here and it worked as you described . I thought I was being attentive , but apparently not 😱
It was Pythonista 2. Again sorry, hate to waste your time

ccc

I am sure that @tszheichoi takes an extra flashlight when spelunking just in case the iPhone batteries are drained!

victordomingos

I am not used to ObjC stuff. Is there an easy way to get a single barometer reading from the sensor?

mikael

@victordomingos, this Apple API only gives you relative readings, not one absolute value. Thus it seems that one single call would not be very useful?

mikael

... but of course you have the pressure value there. Here’s a modification that gives you a simple get_pressure function (a bit of a hack because of the global variable use).

from objc_util import ObjCInstance, ObjCClass, ObjCBlock, c_void_p

pressure = None

def get_pressure():

  def handler(_cmd, _data, _error):
    global pressure
    pressure = ObjCInstance(_data).pressure()

  handler_block = ObjCBlock(handler, restype=None, argtypes=[c_void_p, c_void_p, c_void_p])

  CMAltimeter = ObjCClass('CMAltimeter')
  NSOperationQueue = ObjCClass('NSOperationQueue')
  if not CMAltimeter.isRelativeAltitudeAvailable():
    print('This device has no barometer.')
    return
  altimeter = CMAltimeter.new()
  main_q = NSOperationQueue.mainQueue()
  altimeter.startRelativeAltitudeUpdatesToQueue_withHandler_(main_q, handler_block)
  #print('Started altitude updates.')
  try:
    while pressure is None:
      pass
  finally:
    altimeter.stopRelativeAltitudeUpdates()
    #print('Updates stopped.')
    return pressure

if __name__ == '__main__':
  print(get_pressure())
victordomingos

What kind of value (units) is this last version of the script printing out?

JonB

timestamp (an NSTimeInterval in seconds since the device has been booted)
relativeAltitude (an NSNumber in meters)
pressure (an NSNumber in kilopascal)

DaveClark

I want to use the NSNumber for pressure in a calculation. How do I convert it to something I can use with mathematical functions?

mikael

@DaveClark, change return pressure on line 28 to return pressure.CGFloatValue().

DaveClark

That did it and now I know I need to read the documents more. Thanks you for your help

DaveClark

It was working after I change line 28 to your suggestion. But today without changing anything(I think)

line 28 was return pressure

line 28 now return pressure.CGFloatValue()

Now it is showing an error message complaining about line 28. It says:

AttributeError
no method found for
CGFloatValue

again, I am at a loss

DaveClark

OK. I changed the

return pressure.CGFloatValue()

to

return pressure.floatValue()

and it works again. I still don't understand how it worked earlier.

Thanks for pointing me in the right direction

mikael

@DaveClark, did you update to 11.2.5?

zrzka

Side note, just something to learn about double, float, NSNumber, ...

CGFloat is kind of unfortunate type name. In the C / ObjC world, it's double (C) on 64-bit platforms and float (C) on the rest.

On the other side, Python doesn't have double. It has "just" float and almost all platforms map Python float to IEEE-754 double precision -> double (C).

What I would like to say, you should get your value via [NSNumber doubleValue] instead of floatValue these days.

Not saying that it affects your case, but it's good to know. For example 1.3 is actually stored as 1.2999999523162841796875 (float) and as 1.30000000000000004440892098501 (double), etc.

You can learn more at Floating Point Arithmetic: Issues and Limitations or if you'd like to explore how is float (C) represented just check this nice converter. You can see what is actually stored, what's the error b/o conversion, ... Or just Google for "IEE 754 converter" to find more of them.

DaveClark

@mikael said:

@DaveClark, did you update to 11.2.5?

Yes I did update to 11.2.5

@zrzka Thanks for the good info

mikael

As a general note, I just updated as well. CGFloatValue worked before the update, and works no longer.

Lesson learned: Only use stuff mentioned in the docs, not something that you discover in REPL.