Page 1 of 2
Suggestion - how to make micropython much simpler
Posted: Thu May 14, 2015 4:45 pm
by thorwald
One thing that is still a bit undeveloped with embedded platforms for beginners(arduino/mbed/micropython) is concurrency and non-blocking libraries - and it makes it harder for beginners to write code, because of timing issues. This is especially true when you want to mix 2 projects from 2 different sources.
At the moment there all kinds of ideas how to solve these issues ,and they surely improve usability a great deal , for example the asynchrouns library by pfalcon. But they're rarely used by beginners(at least in the arduino).
So, what could be the simplest concurrency abstraction for beginners ?
What about software "chips"/"Shields" ? You basically just instantiate them(power them up), send them commands, and check if they have new data to read, and if so - read the data ?
This way requires almost zero learning(unlike all common current common concurrency methods) , because as part of learning to use embedded platform users already learn the concept behind chips/shields.
So user code should look something like this:
c = KeyboardScannerChip(scan_inputs, scan_outputs, speed) ; \\key scanner is a concurrent module
c.change_speed(3);
c.key_available(){
data = read_key();
}
Simple example, i know , but this may as well be a complex machine with accurate timing abstracted by this block.
This i believe is where micropython can really shine. Why ?
1. Currently many choose arduino over micropython because of more libraries. by requiring almost zero learning, it might have a decent chance to be preferred by the beginners , and significantly increase the level of abstraction and code reuse in the community - way beyond the levels of the arduino - And encourage faster library creation.
2. Python has better and more usable abstraction tools as a language(for example introspection and decorators), i believe those could apply well to making the tasks of writing such libraries much easier.
3. Having more memory on the platform could surely help.
Of course the hard part is how to implement this - which i don't have good ideas to how .
So what do you think ?
Re: Suggestion - how to make micropython much simpler
Posted: Thu May 14, 2015 5:43 pm
by cloudformdesign
I would think you could just implement your "chips" library using the asyncio library and it could work like this.
If the uasyncio library is anything like the stanard one, you just add it to the event loop and wallah! It will do the "asynchronous io" for you.
Also, why is the example code written in C?
Re: Suggestion - how to make micropython much simpler
Posted: Sat May 16, 2015 7:05 am
by pythoncoder
@ thorwald Many of the Micropython device drivers already work in the way you suggest and are designed to enable the user to opt for blocking or non-blocking I/O. Look at the drivers for the nRF24L01 radio or the MPU9150 inertial navigation unit just to give examples I've recently used. What you propose is standard practice.
Re: Suggestion - how to make micropython much simpler
Posted: Sat May 16, 2015 12:13 pm
by thorwald
Thanks pythoncoder - i'll have a look.
Re: Suggestion - how to make micropython much simpler
Posted: Sat May 16, 2015 2:26 pm
by Turbinenreiter
I battled this problem in my BMP180 library. Maybe that sensor and library are a good practical starting point to figure out how to do this well. The way I implemented it works, but I think there is a lot of room to grow. I.e. there should be blocking functions alongside the non-blocking, for when time of read is critical.
Re: Suggestion - how to make micropython much simpler
Posted: Sat May 16, 2015 5:45 pm
by thorwald
Turbinenreiter , Maybe the ideal way , if possible , is when writing a module, you just write something according to some specifications , and mostly automatically you'll get blocking functions, and non-blocking functions , both arranged in the optimal form for the user ?
So for example :
class KeypadDriver(Driver):
def __init__ (... ):
@input
def get_scan_speed(speed):
@output
def return_key():
# do something
return last_read_key
And than micropython will automatically generate both blocking and non-blocking interfaces ? For example , things returned from return_key will be automatically put inside a queue. If you use a non-blocking function you'll have a way to to check the queue and read items - while if you use the blocking function you'll wait until something in queue and read it ?
Like:
k = KeypadDriver(..)
#blocking
data = k.return_key_blocking()
#non-blocking
if k.return_key_availble():
data = k.return_key_read()
When return_key_blocking and return_key_read are automatically generated?
This way , writing drivers could be very easy - just follow a few easy rules , while driver users can get the optimal driver ?
BTW editor doesn't give tabs, so code is a bit messy.
Re: Suggestion - how to make micropython much simpler
Posted: Sat May 16, 2015 7:32 pm
by Turbinenreiter
Code: Select all
'''
example-sensor.py
A generic sample class to figure out best approach for specification to get
librarys with good support for non-blocking and standard interface.
Sebastian Plamauer
'''
import time
delay = 2 # Sensor read delay time
class Sensor():
'''
Generic sample class do find a good specification to write librarys with
good support for non-blocking functions.
'''
def __init__(self):
'''
A measurement_ready_at is defined for every type of measurement with
known delay. If delay is unknown, there has to be a data_ready-register
on the Sensor.
'''
self.measurement_ready_at = time.time()
def _start_measurement(self):
'''
starts the measurement, i.e. by writing to registers of the sensor.
'''
print('start')
self.measurement_ready_at = time.time()+delay
def _read_measurement(self):
'''
reads the measurement, i.e. by reading the registers.
There is no protection against reading to early.
'''
return 125
def _measurement_ready(self):
'''
checks if the measurement is ready, either by checking ready_at or
using data_ready-register of Sensor.
'''
if time.time() > self.measurement_ready_at:
return True
else:
return False
def get_measurement_blocking(self):
'''
simply start measurement and wait for it to finish.
'''
self._start_measurement()
while not self._measurement_ready():
pass
return self._read_measurement()
def get_measurement_non_blocking(self):
'''
generator yielding None until finished, then yields measurement.
'''
self._start_measurement()
while not self._measurement_ready():
yield None
yield self._read_measurement()
return
if __name__=='__main__':
# create Sensor object
s = Sensor()
# print measurement blocking
print(s.get_measurement_blocking())
# print measurement non-blocking
g = s.get_measurement_non_blocking() # start generator
while True: # start loop
# do stuff
try: # try getting generator respond
r = next(g) # save to r
except StopIteration: # when StopIterator (return)
print(r) # print last r - holds measuremnt
break # stop loop
This could be a base class to derive from. You then would have to rewrite all _functions, fitting to your device, i.E:
Code: Select all
import time
import sensor
delay = 3
class testSensor(sensor.Sensor):
def __init__(self):
'''
A measurement_ready_at is defined for every type of measurement with
known delay. If delay is unknown, there has to be a data_ready-register
on the Sensor.
'''
self.measurement_ready_at = time.time()
def _start_measurement(self):
'''
starts the measurement, i.e. by writing to registers of the sensor.
'''
print('start')
self.measurement_ready_at = time.time()+delay
def _read_measurement(self):
'''
reads the measurement, i.e. by reading the registers.
There is no protection against reading to early.
'''
return 225
def _measurement_ready(self):
'''
checks if the measurement is ready, either by checking ready_at or
using data_ready-register of Sensor.
'''
if time.time() > self.measurement_ready_at:
return True
else:
return False
Re: Suggestion - how to make micropython much simpler
Posted: Sun May 17, 2015 12:56 am
by thorwald
Turbinenreiter, that's a well written example and it can work well for the sensor.
But what about the more general case ? Let's say our goal is to enable people to combine full systems.
So for example somebody built a hex-robot system that can also sense when it's going outside the house which isn't recommended. That's one system(which need real-time operation).
Another guy built some sensor that can sense if there's a cat before it(don't ask me how) , it was a part of system to feeding cats.
It would be fun to easily combine them into one system - a cat chasing hex-robot that is smart enough not to go outside - while not messing up the real-time performance of them.
Made up example, but the principle holds - mixing systems is very useful and powerful.
And for that we need something more generic than sensors - and i think "chips"(as i put in the beginning of the thread) and the rough idea of how to build them , might be a useful starting point.
Re: Suggestion - how to make micropython much simpler
Posted: Sun May 17, 2015 7:23 am
by stijn
Let's say our goal is to enable people to combine full systems.
No matter how generic you make your interfaces, you cannot simply combine two full systems by adding their corresponding software together (at least I have the impression that's what you're after) or in another simple way. Say you have code for chasing cats and code for not leaving the house: in the end both of these pieces are going to need access to the motion system of the robot to make it do whatever it needs to do, so you cannot just combine that because part A would be saying 'go X' while part B would be saying 'go Y'. Instead you'd use a single control loop in which you read from both sensors and then decide what to do based on that input. The more sensors you get, the more complicated thats get and a typical pattern to cope with that is to use a state machine.
Re: Suggestion - how to make micropython much simpler
Posted: Sun May 17, 2015 9:17 am
by pythoncoder
In general if you write a nonblocking driver, providing a blocking interface is trivial. The nonblocking driver provides two interfaces: a device_ready() test and device_use(). device_ready() returns immediately, device_use() returns immediately if it's ready. device_ready() may provide some initialisation to initiate the reading (if it's a sensor). In principle the blocking interface is just
Code: Select all
def device_use_blocking():
while not device_ready():
pass
device_use()
I'm not saying the nonblocking case is necessarily simple but, having developed it, the blocking interface is surely easy to implement.