Pythonista 1.6 Beta
I believe that there are issues with threads but have not been able to pin anything down enough to give omz anything to work with. I spent a great deal of time working on a way of logging that did not interfere with the cb modules natural functionality. I kept having the issue that a crashing problem (crash or weird behavior) would go away when I got the logging going. I can say for sure that each callback is run in a new thread each time and it looks a lot like IOS is using some kind of thread pool. The thread names seem to count up and then start over. I have been able to get dozens of different threads going using several peripherals. What all this means is that any code you write in a callback needs to be thread safe and some elements of the python libraries may not be thread safe in these callbacks. What omz needs is a sample app that clearly shows if one of the callbacks can be running simultaneously in multiple threads. Perhaps your app is just doing something that takes a long time in a callback and somehow gets python to context switch and allow the callback to get re-enterred? I am drawing at straws here. I would be willing to get a WowWee MIP in order to help repo this. This would be a good one to use as the sample app for cb in the future IMHO. Anything with a fairly high data transfer rate would be the best. I have been using a BLE MIDI interface and these tend to generate lots of data.
So, I gather the history is handled through some builtin that we cannot access? (Builtins can't be disassembled, right?)
I was hoping for the ability to add to history programatically, oh well. That leads to a feature request: @omz Add console.history command to get / set history. And/or some way to enable completion on raw_input ( which we could finesse into our own history system :)
ccc last edited by
Reported elsewhere but repeated here for completeness:
import photos # When you tap 'Cancel' in the image picker... x = photos.pick_image() # x = None which is the correct behavior. y = photos.pick_image(include_metadata=True) # Exception thrown which is a bug.
No, I haven't tried using the device orientation to control the MiP. I first need to be able to update its speed continuously. Also, in my experience, virtual joysticks works better to control physical things, mainly because you easily can let go of them. So, like the original app by default, I use two of them.
I don't get any crashes if I just send the speed to the robot whenever the joystick changes (in the action function). But, unless I constantly move my finger, the robot stutters. And even if I do this, the robot turns slightly counter-clockwise every now and then. (I only look at the joystick y position and always sends straight forward or backward speeds.) This never happens with the original app. So my current theory is that I send data too fast in this case. That is, that the value of the characteristic changes before it has been fully parsed within the robot. This turning also didn't seem to occur during the short periods I was able to drive while limiting the rate using ui.delay().
So, I don't think the crashing issue is related specifically to the cb module. (But possibly to ui.delay(), since it tends to crash Pythonista also if I try to update the UI in a posted function.) I will try different approaches, but it might take a few days.
@mteep - one big question I have about CoreBluetooth is whether it buffers the data being transferred to and from a characteristic. I believe it does and you can get it to crash be overrunning its buffering. This is just a theory. I have read quite of posts on stackoverflow having to do with people not getting enough speed and what to expect for speed of transfer. Some of this is confusion about Bluetooth LE (Smart) being both for low power and high speed. It is NOT for high speed and it seems difficult to actually make it low power.
Mteep, do you call ui.cancel_delays at the start of the function that was calling ui.delay? That might not do exactly what you want, but it would keep you from calling the cb function simultaneously.
One pattern for dealing with this issue is that you've got one thread only that talks to the resource, and other threads send messages. You might consider using a
gets commands from a
queue.Queue. Your ui would
putvalues into the queue on touch moved, or whatever your interface is.
The Thread would manage how frequently it sends cb commands, for instance maybe you send with a 10msec delay if the queue.get returned a value, but if you get Queue.Empty, send the last command then wait for 50 msec. You'd probably want to handle the Queue getting too full, in which case you'd be falling behind.. Like check qsize, and if more than some value, you could read multiple values from the queue, but only send the latest.
I got my MiP driving app to work like the original app once I set up a dedicated thread to send the speed every 50 ms.
The ui.delay() seems to be more or less the same as creating a threading.Timer, which is a new thread. I had hoped/guessed that it would instead schedule the function to be run in the UI thread, as this is something most UI frameworks I am familiar with supports and often requires. (As a side note, that erroneous approach worked for a longer time when I tried it on a newer (A8X class) device.)
The turning seems to have been caused by a misreading of the MiP protocol description. You send it a command byte followed by a number of argument bytes that depend on the command. The problem is that the robot often accept commands with fewer arguments, if there is sufficient delay afterwards. So I sent two too short driving commands after each other, and the second one was interpreted the turning argument of the first one.
So, as long as you observe thread safety and send commands of the correct length, controlling the MiP using the cb module seems entirely deterministic.
I will see if I can come up with a test of your buffering theory, but in the MiP case, there is really no point in trying to push data faster than the robot physically can act. So throttling on the host side seems reasonable. (A possible exception is if you open up the robot and use the hacker port UART to communicate with some other micro-controller.)
@mteep - can you post your script as an example. I would like to see how you setup the dedicated thread and did the timing and communication between the callback and the thread.
@omz - I found a real bug (probably in 1.5 as well). The animation function curve_bounce_in_out calls two non-existent functions.
def curve_bounce_in_out(x): t = x d = 1.0 if t < (d / 2.0): return curve_ease_in_bounce(x * 2) * 0.5 return curve_ease_out_bounce(x * 2 - d) * 0.5 + 0.5
Should be curve_bounce_in and curve_bounce_out - right?
0942v8653 last edited by
@wradcliffe: confirmed in 1.5.
Sorry about the late reply. The forum was down last time I tried answering. My script is a little too long for this thread, so I have removed almost everything except the sending thread. (I will post the whole thing to GitHub once it's a little more complete.) The MipManager starts the SpeedUpdater thread once it gets the write characteristic in a callback. My UI calls action methods in MipManager (not shown), which either sets the current speed or turn in the SpeedUpdater, or occasionally queues a special command. Currently, I only send other commands when the robot has fallen over in order to attempt to get up (something the original app doesn't do at all), so the simple timing works. Finally, when my UI is closed, I call the shutdown method of SpeedUpdater via the MipManager.
Note that I'm not sure of the thread safety of this code or of Queue.Queue. I just noticed that the threading support was closely modeled after Java and hoped the memory model would be too.
import cb import threading import Queue class SpeedUpdater(threading.Thread): def __init__(self, peripheral, characteristic): threading.Thread.__init__(self) self.peripheral = peripheral self.characteristic = characteristic self.state_lock = threading.Condition() self.keep_alive = True self.speed_code = 0 self.turn_code = 0 self.queue = Queue.Queue() def run(self): keep_alive = True while keep_alive: try: msg = self.queue.get_nowait() self.peripheral.write_characteristic_value(self.characteristic, msg, False) except Queue.Empty: with self.state_lock: self.state_lock.wait(0.05) speed_code = self.speed_code turn_code = self.turn_code if (speed_code != 0) or (turn_code != 0): msg = chr(0x78)+chr(speed_code) + chr(turn_code) self.peripheral.write_characteristic_value(self.characteristic, msg, False) with self.state_lock: keep_alive = self.keep_alive def set_speed(self, speed_code): with self.state_lock: self.speed_code = speed_code def set_turn(self, turn_code): with self.state_lock: self.turn_code = turn_code def queue_cmd(self, cmd): self.queue.put(cmd) def shutdown(self): with self.state_lock: self.keep_alive = False self.state_lock.notifyAll() self.join() class MiPManager (object): def __init__(self): self.state_lock = threading.RLock() self.peripheral = None self.speed_updater = None def did_discover_characteristics(self, s, error): log('Did discover characteristics...') for c in s.characteristics: if c.uuid == 'FFE9': with self.state_lock: self.speed_updater = SpeedUpdater(self.peripheral, c) self.speed_updater.start()
@mteep - did you determine that the RLock used in the MiPManager object was necessary to prevent crashes?
Not really, I just synchronized everything to be safe. (Absence of a crash doesn't make it safe.) I use the RLock for all potentially concurrently accessed state in MipManager, which is more than I included above. Also, the self.speed_updater state is accessed by my code from two threads (UI and main). I used RLock instead of Lock since it has nice properties that I am well used to.
reefboy1 last edited by
I sent an email. Hope I can get te beta soon
tdamdouni last edited by
I sent you an email. Can't wait to test the Beta!
S1029514 last edited by
I have just sent my name in, hope the line isn't long!
ebewo last edited by
I would be highly interested in being a test guinea pig 😃. As a researcher who depends on Python for simulations, Pythonista is a blessing.
ccc last edited by
polymerchm last edited by
@OMZ: There is a "glitch" in numpy. I get the following trying to do an import:
Loading NumPy... Traceback (most recent call last): File "/var/mobile/Containers/Data/Application/762ACFDE-3824-4115-AF2E-EDE94F8CB4AF/Documents/Examples/Function Plot.py", line 7, in <module> import numpy File "/private/var/mobile/Containers/Bundle/Application/400D5B78-8EA1-4AD3-AEE4-EE1AE9DBF78E/Pythonista.app/pylib_ext/numpy/__init__.py", line 154, in <module> from . import add_newdocs ImportError: cannot import name add_newdocs
@poly -- not sure if this is the same problem, but I've found similar import problems can happen if I cancel a script that imports numpy without letting it complete.
I think numpy gets partially imported, and entries start getting created in sys.modules, but it gets cancelled and therefore is just totally confused. restarting pythonista can help. I think I also had luck with a script that crawled through sys.modules, and
del'd any entries starting with numpy.
I have imported numpy in the beta without problems -- you just have to make sure the first time is a clean import.