Pythonista 1.6 Beta
So, with Apple's new TestFlight, I can finally have a lot more beta testers, and won't have to worry about device limits so much.
If you'd like to play with the next version of Pythonista before it hits the App Store, please send me an email with your Apple ID, and I'll see what I can do. I don't plan to use up all the slots yet (I might need some later), but if you've been posting here, there's a very good chance that I can give you access to the beta.
As for what to expect, here's a list of the new features in the current beta:
- Improved support for the iPhone 6 and 6 Plus screen sizes
- Removing files from the library moves them to a trash folder instead of deleting them immediately.
- URLs in console output are 'linkified' automatically
remindersmodule for accessing the iOS Reminders database (read/write)
cbmodule for connecting to Bluetooth LE peripherals (experimental)
- New dialogs module as an easy-to-use alternative to the ui module when you just need simple data entry. This also contains functions to import files from the iCloud Drive document picker (iOS 8 only). – note: this is broken in the current beta, but will be fixed in the next few days
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?
@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.
I sent an email. Hope I can get te beta soon
I sent you an email. Can't wait to test the Beta!
I have just sent my name in, hope the line isn't long!
I would be highly interested in being a test guinea pig 😃. As a researcher who depends on Python for simulations, Pythonista is a blessing.
@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.
I found a bug with the autocompletion which causes Pythonista to crash.
Suppose you have a generator:
def fibonacci_gen(up_to): count = 0 var1 = 0 var2 = 1 while count <= up_to: yield var1 count += 1 var1,var2 = var2,var1+var2
If you now try to write
gen = fibonacci_gen(10) number = gen.next()
Pythonista crashes a few seconds after writing the dot behind the gen. If you write .next() somewhere else and copy it you can paste it in and it works fine.
@JonB - as always, you're correct. restart fixed it.
@Moe - a VERRY unreproducible bug that @OMZ is chasing. It's seems to always involve instance variable or methods, but not all objects and not every time. Best suggestion - mistype the object name intentionally to suppress the autocompletion and go back after the fact and correct.
However, yours fits the bill of reproducible and pretty small. Even this blows it up:
def gen(x): count = 0 while count < x: yield count count += 1 mygen = gen(10) print mygen.next()
FYI, just noticed there is a new beta posted, which supposedly fixes this problem.
All I see is a beta that expires in 7 days, which I would expect is the current one. @OMZ please clarify?
Testflight says the current beta expires Saturday May 30, a week from now. I haven't heard of any other updates.