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.
Correct way to call Pythonista script from within a Shortcuts workflow?
-
@mikeno said
I found also a way to do some tasks every hour in shortcuts
But you could start a Pythonista script every hour?
-
Yes, it works but only if the iPad is awake
-
@mikeno did you try to run an automation at specified time, which runs a Pythonista short script logging the pressure in a file and put your IPad asleep some time before the specified time?
-
@cvp I’m not sure to understand what you mean, but calling a pythonista script from a shortcuts or an automation requires the iPad to be awake
-
@mikeno ok, I thought that calling the script from the shortcut did launch Pythonista even if iPad not awake. If you have Pyto, you could test because it runs really in background like a music player
-
I don’t know PyTo but I will try, thx in any case
-
@mikeno not Py To but Pyto, I think there is a free test version
-
I just downloaded it but my trial period already expired because I probably already tried it some times ago and since I don’t know if it works, I don’t want to buy it. If you’ve it, could you try if it runs when iPad is asleep?
-
@mikeno I'll do it and let it know
-
@mikeno I have tried a script which prints the time each second and closed my iPad cover during 200 seconds and when I have reopened it, the script was still running
-
Thx, the question is now if Pyto can read the barometer sensor value, below a short code which runs fine under Pythonista:
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) try: while pressure is None: pass finally: altimeter.stopRelativeAltitudeUpdates() #print('Updates stopped.') return pressure.floatValue()*10 pressure = get_pressure() print(pressure)
-
@mikeno I know this code but I'm new in Pyto and surely not (yet?) a specialist in ObjectiveC of Pyto.
I don't not yet know how to define an ObjcBlock in Pyto but I'll try.
But, obviously, I'll need some time -
@mikeno Sorry, no idea how to define an ObjcBlock in rubicon (ObjectiveC in Pyto).
Hoping that @JonB will read this and be able to help, as usual.# coding: utf-8 from rubicon.objc import * from ctypes import * def handler(_cmd, _data, _error): print(ObjCInstance(_data)) handler_block = ObjCBlock(handler, None, [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()
Gives
Traceback (most recent call last): File "iCloud/barometer.py", line 8, in <module> handler_block = ObjCBlock(handler, None, [c_void_p, c_void_p, c_void_p]) File "Pyto.app/Lib/rubicon/objc/api.py", line 1834, in __init__ self.struct = cast(self.pointer, POINTER(ObjCBlockStruct)) File "Pyto.app/site-packages/python3.10/ctypes/__init__.py", line 510, in cast return _cast(obj, obj, typ) ctypes.ArgumentError: argument 1: <class 'TypeError'>: wrong type
-
Thx for trying, I’ll wait!
-
@cvp I believe in Rubicon, the preferred usage is via type annotations and decorators. Also
ObjCBlock
wraps ObjC blocks so they can be called in python, whileBlock
wraps python so it is calls me in objc-- so you want plain old Block.I think the way you'd do it in Rubicon is:
(Edited)
@Block def handler(altitudeData: ObjCInstance, err:NSError) -> None: print(altitudeData)
Or, I think you can skip the annotation on ObjCInstances:
@Block def handler(altitudeData, err:NSError) -> None: print(altitudeData)
-
@JonB problems
1)@Block def handler(altitudeData , err:NSError) -> None: print(altitudeData) handler_block = ObjCBlock(handler, None, [c_void_p, c_void_p])
Gives
Traceback (most recent call last): File "iCloud/barometer.py", line 6, in <module> def handler(altitudeData, err:NSError) -> None: NameError: name 'NSError' is not defined. Did you mean 'OSError'?
@Block def handler(altitudeData, err) -> None: print(altitudeData) handler_block = ObjCBlock(handler, None, [c_void_p, c_void_p])
Gives
Traceback (most recent call last): File "iCloud/barometer.py", line 6, in <module> def handler(altitudeData, err) -> None: File "Pyto.app/Lib/rubicon/objc/api.py", line 1939, in __init__ raise ValueError( ValueError: Function has no argument type annotation for parameter 'altitudeData' - please add one, or pass return and argument types directly into Block
@Block def handler(altitudeData:ObjCInstance, err:ObjCInstance) -> None: print(altitudeData) handler_block = ObjCBlock(handler, None, [c_void_p, c_void_p])
Gives
Traceback (most recent call last): File "iCloud/barometer.py", line 9, in <module> handler_block = ObjCBlock(handler, None, [c_void_p, c_void_p]) File "Pyto.app/Lib/rubicon/objc/api.py", line 1846, in __init__ self.struct.contents.invoke.argtypes = (objc_id, ) + tuple(ctype_for_type(arg_type) for arg_type i n argtypes) File "Pyto.app/Lib/rubicon/objc/api.py", line 1846, in <genexpr> self.struct.contents.invoke.argtypes = (objc_id, ) + tuple(ctype_for_type(arg_type) for arg_type i n argtypes) File "Pyto.app/Lib/rubicon/objc/types.py", line 103, in ctype_for_type return _ctype_for_type_map.get(tp, tp) TypeError: unhashable type: 'list'
@Block def handler(altitudeData:ObjCInstance, err:ObjCInstance) :#-> None: print(altitudeData) handler_block = ObjCBlock(handler, None)#, [c_void_p, c_void_p])
Gives
Traceback (most recent call last): File "iCloud/barometer.py", line 6, in <module> def handler(altitudeData:ObjCInstance, err:ObjCInstance) :#-> None: File "Pyto.app/Lib/rubicon/objc/api.py", line 1930, in __init__ raise ValueError( ValueError: Function has no return type annotation - please add one, or pass return and argument types directly into Block Traceback (most recent call last): File "iCloud/barometer.py", line 6, in <module> def handler(altitudeData:ObjCInstance, err:ObjCInstance) :#-> None: File "Pyto.app/Lib/rubicon/objc/api.py", line 1930, in __init__ raise ValueError( ValueError: Function has no return type annotation - please add one, or pass return and argument types directly into Block
@Block def handler(altitudeData:ObjCInstance, err:ObjCInstance) -> None: print(altitudeData) handler_block = ObjCBlock(handler, None, (c_void_p, c_void_p))
Gives
Traceback (most recent call last): File "iCloud/barometer.py", line 10, in <module> handler_block = ObjCBlock(handler, None, (c_void_p, c_void_p)) File "Pyto.app/Lib/rubicon/objc/api.py", line 1846, in __init__ self.struct.contents.invoke.argtypes = (objc_id, ) + tuple(ctype_for_type(arg_type) for arg_type i n argtypes) TypeError: item 2 in _argtypes_ has no from_param method
-
@mikeno This code works for me with the latest version of pyto.
# coding: utf-8 from rubicon.objc import Block, ObjCClass, ObjCInstance, py_from_ns from rubicon.objc.runtime import objc_id pressure = None def handler(_data) -> None: nspressure = ObjCInstance(_data).pressure global pressure pressure = py_from_ns(nspressure) handler_block = Block(handler, None, (objc_id)) def get_pressure(): 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__': result = get_pressure() print(result) del pressure
-
@bosco Thanks for him, and for me, so I don't have to test anymore.
Do you know why the handler does not have a 2nd parameter (error) like described in Apple doc? -
@cvp, I think this one was correct:
@Block def handler(altitudeData:ObjCInstance, err:ObjCInstance) -> None: print(altitudeData)
But then pass
handler
directly to ObjC -- don't call ObjCBlock on it. ObjCBlock makes an Objc block callable by python, which isn't needed here.Or, to make no other changes:
@Block def handler_block(altitudeData:ObjCInstance, err:ObjCInstance) -> None: print(altitudeData)
-
@mikeno With the barometer module of @bosco, where print lines are commented, this script works and continues to log the pressure even if I close the iPad cover. Not tested during a day.
import background as bg import barometer with bg.BackgroundTask() as b: while True: result = barometer.get_pressure() l= f"{b.execution_time()}:{result}\n" with open("/private/var/mobile/Library/Mobile Documents/iCloud~is~workflow~my~workflows/Documents/bg.txt", mode='at') as fil: fil.write(l) #print(b.execution_time(), result) b.wait(5)