/dev/blah

things i want to remember, things i want to share

Développeur Python et adepte Linux depuis 2005, passionné par beaucoup trop de choses. Profil Github

Entries tagged “python”

Prepare for the Kivy contest #2

written by tshirtman, on 3/16/14 2:50 AM.

Developers, developers… rejoice!

kivy contest

The kivy project is organizing a second contest!

From the 15th of April, when the theme will be revealed, to the 15th of May, the final deadline, all the interested candidates will compete to produce the nicest application possible, once month to make the difference, and collect prices :).

Prizes will be announced before the beginning of the contest, the python software foundation kindly offered to sponsor the contest, if you are or know somebody interested in sponsoring the contest, please contact us at contest@kivy.org.

Tip me if you like this :)

Kpritz

written by tshirtman, on 3/3/14 2:23 AM.

Just because it seemed not to hard, i spent the evening (actually about 2 hours) making a Spritz clone using kivy.

Code is a bit crude and all, but here it is for your enjoyment:

https://gist.github.com/9316552

Here is an apk if you want to actually try it.

Only flat text files are supported, opening the option window is a bit slow (because of ColorPickers), but it’s functionnal, progression is saved, all the options i though were needed are there, and it seems usable.

You can find a lot of free book in the .txt format over at project gutenberg.

Tip me if you like this :)

Using tex_coords in kivy for fun and profit

written by tshirtman, on 2/19/14 1:45 AM.

Your gpu is an awesome thing, it can do all kind of calculations, very fast. For example, instead of telling it exactly how you want each pixels to be, you can throw textures and vertices at it, and have it correctly deduce how this things will have to look.

To do this, though, you must tell it what part of the texture will be stuck to each vertice. This is usually denoted using texture coordinates.

Texture coordinates, like usual coordinates, indicate the place of something. Instead of noting them x and y, they are often called u and v, which allows to clearly note what parts of an equation relate to each.

While kivy offers you high level canvas instructions, it gives you a pretty good access to lower level features, and you can actually manipulate the texture coordinates of your Rectangle instructions. This allow for cheap zooming, repeat-scrolling, and other image manipulations, since your gpu is doing all the actual work.

tex_coords = u, v, u + w, v, u + w, v + h, u, v + h

which is better understood as:

tex_coords = [
    u,     v,
    u + w, v,
    v + w, v + h,
    u,     v + h
]

considering a default tex_coords value, where u and v = 0, and w and h = 1 (all values are relatives, so usually between 0 and 1).

u, v + h-------u + w, v + h
|              |
u, v-----------u + w, v

which means the 4 angles of your texture, will be on the 4 angles of your rectangle, how dull!

One way to play with different values, is to create a little app showing you the effect of deformations.

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty, ListProperty
from kivy.core.image import Image as CoreImage

kv = '''
#:import chain itertools.chain
RootWidget:
    canvas:
        Color:
            rgba: 1, 1, 1, 1
        Rectangle:
            pos: root.pos
            size: root.size
            texture: app.texture
            # here is our usage of the calculated texture coordinates
            # we devide by 100 because we took the input with a 100x100
            # rectangle as default value
            tex_coords: [x / 100. for x in chain(*root.points)]

        PushMatrix
        # translate the rectangle to make it easier to play with
        Translate:
            xy: root.width / 2, root.height / 2

        Color:
            rgba: 1, 0, 0, .5
        Line:
            points: chain(*root.points + root.points[:1])
            width: 2
        PopMatrix
'''


def dist(point, pos):
    return ((point[0] - pos[0]) ** 2 + (point[1] - pos[1]) ** 2)
    # ** .5 # who cares about square root here? it doesn't change the order


class RootWidget(Widget):
    # the default values, a 100x100 square, displayed around the middle of the screen
    points = ListProperty([[0, 0], [100, 0], [100, 100], [0, 100]])

    def on_touch_down(self, touch, *args):
        # compensate the translation done in canvas
        pos = touch.pos[0] - self.width / 2, touch.pos[1] - self.height / 2

        touch.ud['point'] = min(
            range(4), key=lambda x: dist(self.points[x], pos))

    def on_touch_move(self, touch, *args):
        # compensate the translation done in canvas
        pos = touch.pos[0] - self.width / 2, touch.pos[1] - self.height / 2
        # update the point
        self.points[touch.ud['point']] = pos


class TexCoordsApp(App):
    texture = ObjectProperty(None)

    def build(self):
        self.root = Builder.load_string(kv)
        self.texture = CoreImage.load(
            'GrassGreenTexture0002.jpg').texture
        self.texture.wrap = 'repeat'
        return self.root


if __name__ == '__main__':
    TexCoordsApp().run()

The texture have been scrapped on the web and is not very interesting, but you can find it here.

Of course, this is much more a learning device (helful because these transformation are not quite straighforward for our brains) than a practical application, but a lot more can be done.

Here is, for example, a little infinite scroll app uting this concept.

edit: Ben Rousch kindly created apks out of the two examples, if you want to try them on android: TexCoordsExample ScrollExample

Tip me if you like this :)

Using jinja2 outside of the web.

written by tshirtman, on 2/10/14 12:56 AM.

I may be the last person out there to do that, but i hadn’t actually gotten to that yet.

So here is the thing, sometime you want to produce a text file, with some complex structure, and string.format is not really up to the task, so you scratch your head and wonder what could be useful to… template an output.

Then a nice lib like jinja2 totally makes sense.

For my use case, i want to generate java source code, using info parsed from other java source code (thanks to the awesome plyj lib), so it’s easy to give the general form of the output file, and to pass the content after that.

{% if package %}package {{ package }};{% endif %}
import {{ module.package_declaration.name.value }}.*;
{{ imports }}\

public class {{ cls.name }}{{ suffix }} extends {{ cls.name }} {
    public interface I{{ cls.name }} {\
    {% for method in methods %}
    {{ method.return_type | get_type }} {{ method.name }}({{ method.parameters | render_params }});\
    {% endfor %}
    }

    private I{{ cls.name }} implem = null;

    public void setImplem(I{{ cls.name }} implem) {
    this.implem = implem;
    }

    {% for method in methods %}
    {{ method.return_type | get_type }} {{ method.name }}({{ method.parameters | render_params }}) {
    if (this.implem)
        return this.implem.{{ method.name }}({{ method.parameters | render_params_values }});
    }{% endfor %}
}

One problem i had, though, was when it turned out it would be convenient to use custom filters inside my template, it took me some time to find the relevant documentation about how to register functions as filters in this situation (since people usually use jinja2 in a framework, they document how to do so using the environment used by this framework).

So the answer was not to use jinja2.Template directly, but to build an environment before.

env = jinja2.Environment()

then to setup my filters in this environment:

env.filters['render_params'] = render_params
env.filters['render_params_values'] = render_params_values
env.filters['get_type'] = get_type

and to use env.template_from_string(template_string) to create a Template object from my string.

Tip me if you like this :)

Magnet: a widget to easily make your interface more lively

written by tshirtman, on 2/6/14 12:38 AM.

Kivy has a very nice Animation class, that allows you to move move widgets around (or other things), by updating any numeric property to a target value, just by setting a time and a transition function. Still, when you are building a complex interface, it can still be a bit cumbersome to manually trigger animations for all the elements, and to keep track of them.

After being frustrated about this issue for some time, i tried my luck some time ago, at doing a nicer, “90% use cases” interface, and the Magnet garden widget was born.

Its usage is simple, you simply use it to wrap your target widget, and give it rules about how to transition when the magnet is moved. As the magnet is moved or resized by the usual kivy logic, instead of making the wrapped widget immediately follow such constraints, it’ll create and keep track of animations to achieve a smooth transition to the new values for you.

As any garden “flower”, to install it, you need to install and use the garden project.

python setup.py install

(either in a virtualenv or system-wide)

then do:

garden install magnet

you can now import it a kivy application:

from kivy.garden.magnet import Magnet

Garden Magnet video

Tip me if you like this :)

Kivy image manipulations with Mesh and Textures

written by tshirtman, on 1/29/14 1:05 AM.

If you want to give a little life to interactive (or not) elements, it’s always nice to have more tricks to manipulate images for nifty effects.

One of such ways is mapping a Texture on a special canvas instruction, that will distort your texture based on the position of its points.

[kivy.graphics.Mesh](http://kivy.org/docs/api-kivy.graphics.html#kivy.graphics.Mesh) is such an instruction, and it has a simple interface, if not 100% straightforward, you basically call it this way.

Mesh:
vertices: (x1, y1, s1, v1, x2, y2, s2, v2, x3, y3, s3, v3...)
indices: (1, 2, 3...)
texture: some_texture
mode: some_mode

where all: x mean the horizontal coordinate of a point y mean the vertical coordinate of a point s mean the horizontal position of the texture attached to this point (between 0 and 1) v mean the vertical position of the texture attached to this point (between 0 and 1)

indices is useful if, for some (very good) reason, you don’t want to give the points in the order you’ll use them, or if you want to refer to a point multiple time (this won’t be demonstrated in this blog post, but try to think about a grid mapping, for example), now we will use it in the most simple way, just give the range of your number of points.

the texture needs to be a texture object, you can get such an object by getting the texture property of a [CoreImage](http://kivy.org/docs/api-kivy.core.image.html#kivy.core.image.Image) or an Image (or even a Label, if you want to).

Let’s say we have an image, for example, kivy logo.

from kivy.core.image import Image as CoreImage

texture = CoreImage('data/logo/kivy-icon-512.png').texture

(this will work wherever you are, because kivy look at his images anyway :))

This image is a circle, lets use that to cut it into a virtual pie and assign a part of the texture to each part, we’ll be using the triangle_fan mode of Mesh, that is a perfect fit for such an operation.

# the central point
points = [Window.width / 2, Window.height / 2, .5, .5]

# now go around
    i = 0
    while i < 2 * pi:
        i += 0.01 * pi
        points.extend([
    Window.width / 2 + cos(i) * 100,
    Window.height / 2 + sin(i) * 100,
    .5 + sin(i),
    .5 + cos(i)])

put that in a more complete program, and you get this:

from kivy.app import App
from kivy.lang import Builder
from kivy.core.image import Image as CoreImage
from kivy.properties import ListProperty, ObjectProperty
from kivy.clock import Clock
from kivy.core.window import Window
from math import sin, cos, pi


kv = '''
Widget:
    canvas:
        Color:
            rgba: 1, 1, 1, 1
        Mesh:
            vertices: app.mesh_points
            indices: range(len(app.mesh_points) / 4)
            texture: app.mesh_texture
            mode: 'triangle_fan'
'''


class MeshBallApp(App):
    mesh_points = ListProperty([])
    mesh_texture = ObjectProperty(None)

    def build(self):
        self.mesh_texture = CoreImage('data/logo/kivy-icon-512.png').texture
        Clock.schedule_interval(self.update_points, 0)
        return Builder.load_string(kv)

    def update_points(self, *args):
        points = [Window.width / 2, Window.height / 2, .5, .5]
        i = 0
        while i < 2 * pi:
            i += 0.01 * pi
            points.extend([
                Window.width / 2 + cos(i) * 100,
                Window.height / 2 + sin(i) * 100,
                .5 + cos(i),
                .5 + sin(i)])

        self.mesh_points = points

if __name__ == '__main__':
    MeshBallApp().run()

Now, this is not very impressive, we just have an image, there were much simpler ways to do that!

But now, we can tweek the rendering a lot, lets add some offseting of the texture, some radius change, and some wobbling, with sliders to control all this.

from kivy.app import App
from kivy.lang import Builder
from kivy.core.image import Image as CoreImage
from kivy.properties import ListProperty, ObjectProperty, NumericProperty
from kivy.clock import Clock
from kivy.core.window import Window
from math import sin, cos, pi


kv = '''
BoxLayout:
    Widget:
        canvas:
            Color:
                rgba: 1, 1, 1, 1
            Mesh:
                vertices: app.mesh_points
                indices: range(len(app.mesh_points) / 4)
                texture: app.mesh_texture
                mode: 'triangle_fan'
    BoxLayout:
        orientation: 'vertical'
        size_hint_x: None
        width: 100
        Slider:
            value: app.offset_x
            on_value: app.offset_x = args[1]
            min: -1
            max: 1
        Slider:
            value: app.offset_y
            on_value: app.offset_y = args[1]
            min: -1
            max: 1
        Slider:
            value: app.radius
            on_value: app.radius = args[1]
            min: 10
            max: 1000
        Slider:
            value: app.sin_wobble
            on_value: app.sin_wobble = args[1]
            min: -50
            max: 50
        Slider:
            value: app.sin_wobble_speed
            on_value: app.sin_wobble_speed = args[1]
            min: 0
            max: 50
            step: 1
'''


class MeshBallApp(App):
    mesh_points = ListProperty([])
    mesh_texture = ObjectProperty(None)
    radius = NumericProperty(500)
    offset_x = NumericProperty(.5)
    offset_y = NumericProperty(.5)
    sin_wobble = NumericProperty(0)
    sin_wobble_speed = NumericProperty(0)

    def build(self):
        self.mesh_texture = CoreImage('data/logo/kivy-icon-512.png').texture
        Clock.schedule_interval(self.update_points, 0)
        return Builder.load_string(kv)

    def update_points(self, *args):
        points = [Window.width / 2, Window.height / 2, .5, .5]
        i = 0
        while i < 2 * pi:
            i += 0.01 * pi
            points.extend([
                Window.width / 2 + cos(i) * (self.radius + self.sin_wobble * sin(i * self.sin_wobble_speed)),
                Window.height / 2 + sin(i) * (self.radius + self.sin_wobble * sin(i * self.sin_wobble_speed)),
                self.offset_x + sin(i),
                self.offset_y + cos(i)])

        self.mesh_points = points

if __name__ == '__main__':
    MeshBallApp().run()

which gives us:

Kivy image manipulations with Mesh and Text

Now we are talking!

Tip me if you like this :)

Kivy - Mouse cursor on Rapsberry Pi

written by tshirtman, on 1/23/14 12:40 AM.

It’s no news that kivy have been ported to Rapsberry Pi, it happened nearly a year ago, and works quite well, especially if you start the app without an X server, since kivy draw directly to EGL (don’t ask me :)), it doesn’t need it.

However, there is a little issue about it if you don’t have a touch screen, you can use your mouse, but you don’t see any cursor, whever you are in X or not, because in X, kivy display is actually on top of the X cursor, and without X, nothing even try to draw a mouse cursor.

A few months ago, i wrote a quick hack, adding a few graphic instructions to the egl window provider, and made the probsys driver used on rpi directly update the position of this instruction. It worked, but it wasn’t really a general solution.

So after procrastinating a proper solution for a long time, i finally got my act together and added the cursor display as an option to the touchring module of kivy, along with options of what image to use, offsets and so on, and added the code that made the mouse driver dispatch pos to window’s mouse_pos property.

So here it is, you can find it here until the PR is merged.

Tip me if you like this :)

Position/Size of widgets in kivy.

written by tshirtman, on 1/16/14 12:55 AM.

I see it’s a common struggle for people to understand how to manage size and positions of the widgets in kivy.

There is not much to it, really, just a few principles to understand, but they are better explained by example, i believe.

dumb positionning

Let’s create a root widget, and put two buttons in it.

root = Widget()
b1 = Button()
b2 = Button()
root.add_widget(b1)
root.add_widget(b2)

What will happen when i return root to be used as my root widget? As a root widget, it will have all the space for itself, the two buttons, however, will only use their default size, which is (100, 100), and will use their default position, which is (0, 0), the bottom-left corner of the screen.

We can change b1 and b2 positions to absolute values, let’s change line 2 and 3 to:

b1 = Button(pos=(100, 100))
b2 = Button(pos=(200, 200))

something a little smarter

Now, that doesn’t make for a very flexible UI, if you ask me, it would be better to use the parent size to decide where to place the buttons, no?

b1 = Button(pos=(root.x, root.height / 2))
b2 = Button(pos=(root.width - 100, root.height / 2))

No, that’s not much smarter, it’s still quite dumb actually, it doesn’t even work, why? because we just instanciated root, it’s not even the root widget yet, it doesn’t have its final size, so our widgets will use the default value of (100, 100), for their calculations, no - no.

Let’s fix this with bindings.

b1 = Button()
root.bind(on_size=reposition_b1, on_pos=reposition_b1)
b2 = Button()
root.bind(on_size=reposition_b2, on_pos=reposition_b2)

Where these two functions will be along the lines of:

def reposition_b1(root, *args):
    b1.pos = root.x, root.height / 2 - b1.height / 2

Also, widget have nice alias properties for things like height / 2, x + width, x + width / 2, here is a quick table:

right = x + width
top = y + height
center_x = x + width / 2
center_y = y + height / 2
center = (center_x, center_y)

and actually, pos is just an alias property for (x, y). Alias Properties works both ways, so we can use them to set our positions in a simpler way.

def reposition_b1(root, *args):
    b1.x = root.x
    b1.center_y = root.center_y

A lot of work for not so much, right? That’s because we are not using the right tools!

Let’s jump on a layout, specifically FloatLayout.

FloatLayout to the rescue

Layouts are here to make our life easier when constructing an UI.

FloatLayout is very flexible, it lets you set rules for positions, or do things in the absolute way.

root = FloatLayout()
b1 = Button(pos_hint={'x': 0, 'center_y': .5})
b2 = Button(pos_hint={'right': 1, 'center_y': .5})

pos_hint will make the values relative to the size/position of the parent. So here, for b1 x will be the x of the parent, and center_y will be at the middle between y and top.

Now, if you run this, you may get a surprise, because FloatLayout also made b1 and b2 sizes relative to root.size, and the default value being (1, 1), they both have the same size as the root widget, sometime you want to keep that relative, maybe with a different value, sometime you want to have them keep the value passed in size (or default size value), if so, you can pass (None, None) to size_hint.

b1 = Button(pos_hint={'x': 0, 'center_y': .5}, size_hint=(None, None))

Conclusion

I’ll probably do other posts on the subject, because there is much more to it, but the basic principles are here. I anyway really encourage you to look at this section of the documentation, which go further with the various kind of layouts you have access to.

Tip me if you like this :)

Publisher/Consumer model in Kivy

written by tshirtman, on 1/14/14 1:25 AM.

Few things are worse to an user than an unresponsive UI, well i can think of a crashing UI, of course, but not much more. So, in an event driven environment, it’s important to avoid blocking the UI for too long.

But sometime you have a task that will take an unacceptable time for such constraint, if the task can’t really be chunked, a Thread is likely to be the acceptable solution, but threads have constraints and in Kivy, you can’t update the UI from one, you have to schedule something to happen on the main thread, and update things from here. If the task is chunkable, it’s even easier, but the following idea can apply to both situation.

So, a solution that i find convenient, is to use a publisher/consumer model.

The idea is simple, have a scheduled action each frame do a small part of your task, until a timeout is triggered, and then wait for next frame to continue.
To trigger work, just put (publish) it in a list of tasks to be treated py the consumer.

So let’s start by setting a consumer for adding elements to a list, we don’t want to add 100 elements in the same frame, because that would take too much time.

from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.properties import ListProperty
from kivy.uix.label import Label

kv = '''
BoxLayout:
    ScrollView:
        GridLayout:
            cols: 1
            id: target
            size_hint: 1, None
            height: self.minimum_height
     Button:
        text: 'add 100'
        on_press: app.consumables.extend(range(100))
'''

class PubConApp(App):
    consumables = ListProperty([])

    def build(self):
        Clock.schedule_interval(self.consume, 0)
        return Builder.load_string(kv)'

    def consume(self, *args):
        if self.consumables:
            item = self.consumables.pop(0)
            label = Label(text='%s' % item)
            self.root.ids.target.add_widget(label)

if __name__ == '__main__':
    PubConApp().run()

Now, i’m only taking one item each frame, it’s probably good enough, but if we have a lot of item, we still may want to take as much is possible, considering kivy loop is frame-limited to 60fps, 100 items will take more than a second, why wait if we have the power? Let’s use a slightly smarter version.

add this import near of the top

from kivy.clock import _default_time as time

then change consume definition to be:

def consume(self, *args):
    limit = Clock.get_time() + 1 / 60.
    while self.consumables and time() < limit:
        item = self.consumables.pop(0)
        label = Label(text='%s' % item)
        self.root.ids.target.add_widget(label)

Of course, this only work because we know creating and adding one widget takes considerably less time than one frame, so it’s not like one of such operation will make our loop hang too long.

Now, filling the consumable list here is done from main UI, but it could totally be done from a Thread, assuming locking is correctly handled (or that extend/pop are atomic, which seems to be the case), so if your filling of work to be displayed is slow, doing the exact same thing as a background task will allow you to do heavy lifting, while keeping your app snappy.

A slightly more demonstrative version of this example can be found here

Tip me if you like this :)

Building a background application on android with Kivy.

written by tshirtman, on 1/12/14 12:36 AM.

Kivy runs on android using the python-for-android project, which support android services. This works in a simple way, you basically bundle two main.py in your application, one for the UI, one for the service. The UI can start the services on start. From that point though, things may be a little uneasy. Why? Because you have two applications, and now you have to make them talk to each over if you want to do anything useful.

Android’s way of having an Activity and a Service talk to each other is Broadcast signals, which can be limited to part of your applications, howether, it’s not straightforward to use them with pyjnius, which is the magical interface we use to use java code from python.

Another way is to use network, i’ve been doing it with twisted in the past, setting a twisted server in the Service, and using twisted as a client in the Activity, howether, i find this to be heavy lifting for the trivial task of communicating between two programs on the same device. And including Twisted in your app, certainly add some weight to it.

Howether, in order to support TUIO, kivy ships with a simple OSC implementation, OSC is a simple connectionless network protocol, that allow you to pack messages, and send them to an ip/port/api URI, turns out we don’t need anything more, and a connectionless protocol avoid us dealing with disconnections (like the UI being closed, or the service not being started yet) that could give us some headaches (and certainly gave me some). We just need to have both part of the program listen for OSC messages, and have them send data to each other, if confirmation is needed, it’s possible to have a messages been sent back on a specific api.

So let’s get started.

getting a minimal kivy app

The usual things, let’s put a simple UI with a Button.

from kivy.app import App
from kivy.lang import Builder

kv = '''
Button:
    text: 'push me!'
'''

class ServiceApp(App):
    def build(self):
        return Builder.load_string(kv)

if __name__ == '__main__':
    ServiceApp().run()

here we just load a kv string that defines a button, and return the result, nothing fancy.

getting a minimal service

from time import sleep

if __name__ == '__main__':
    while True:
        sleep(.1)

Yeah, nothing much needed, actually, the sleep isn’t even needed, but the program have to run, and we’ll need this loop anyway.

starting the service

For your service to run, you need to tell your UI to start it.

from kivy.app import App
from kivy.lang import Builder
from kivy.utils import platform

kv = '''
Button:
    text: 'push me!'
'''

class ServiceApp(App):
    def build(self):
        if platform == 'android':
            from android import AndroidService
            service = AndroidService('my pong service', 'running')
            service.start('service started')
            self.service = service

        return Builder.load_string(kv)

if __name__ == '__main__':
    ServiceApp().run()

We make the test for android, so you can still test your app on desktop, by starting manually both parts.

Packaging them for android

both files must be named main.py, the UI one is at the root of the project, the other one is in a service directory directly under the root of the project.

├── main.py
└── service
    └── main.py

To package, i’ll be using buildozer.

As we are using network don’t forget to add the NETWORK permission when editing buildozer.spec

buildozer init
editor buildozer.spec
buildozer android debug deploy run logcat

After some time, you should see the (not very exciting) app start on your plugged android device.

setting up OSC

Whatever the side, OSC needs a port to listen on, and to have functions to call when things happen. The basic setup is simple.

from kivy.lib import osc

def some_api_callback(message, *args):
   print("got a message! %s" % message)

osc.init()
oscid = osc.listen(ipAddr='0.0.0.0', port=someport)
osc.bind(oscid, some_api_callback, '/some_api')

and then

osc.readQueue(oscid)

needs to be called regularly.

for the service, we’ll just put this call in the loop:

from time import sleep
from kivy.lib import osc

service = 3000

def some_api_callback(message, *args):
   print("got a message! %s" % message)

if __name__ == '__main__':
    osc.init()
    oscid = osc.listen(ipAddr='127.0.0.1', port=service)
    osc.bind(oscid, some_api_callback, '/some_api')

    while True:
        osc.readQueue(oscid)
        sleep(.1)

And for UI, we’ll use kivy.clock.Clock’s schedule_interval method.

from kivy.app import App
from kivy.lang import Builder
from kivy.lib import osc
from kivy.utils import platform
from kivy.clock import Clock

activityport = 3001

def some_api_callback(message, *args):
   print("got a message! %s" % message)

kv = '''
Button:
    text: 'push me!'
'''

class ServiceApp(App):
    def build(self):
        if platform == 'android':
            from android import AndroidService
            service = AndroidService('my pong service', 'running')
            service.start('service started')
            self.service = service

        osc.init()
        oscid = osc.listen(ipAddr='127.0.0.1', port=activityport)
        osc.bind(oscid, some_api_callback, '/some_api')
        Clock.schedule_interval(lambda *x: osc.readQueue(oscid), 0)

        return Builder.load_string(kv)

if __name__ == '__main__':
    ServiceApp().run()

Now, both sides can receive messages, that’s a good first step, but nothing will really happen, since none of them send any message.

sending messagse

The osc api to send message is very simple:

osc.sendMsg(api, data_list, port=someport, ipAddr=someaddress)

now, ipAddr is by default localhost, which is fine for us, so we only need to find the api we want to hit, the data list, and the port, which will be the one the other side listens on.

Let’s make our button send a message to the Service.

from kivy.app import App
from kivy.lang import Builder
from kivy.lib import osc
from kivy.clock import Clock

activityport = 3001
serviceport = 3000

def some_api_callback(message, *args):
   print("got a message! %s" % message)

kv = '''
Button:
    text: 'push me!'
    on_press: app.ping()
'''

class ServiceApp(App):
    def build(self):
        if platform == 'android':
            from android import AndroidService
            service = AndroidService('my pong service', 'running')
            service.start('service started')
            self.service = service

        osc.init()
        oscid = osc.listen(ipAddr='127.0.0.1', port=activityport)
        osc.bind(oscid, some_api_callback, '/some_api')
        Clock.schedule_interval(lambda *x: osc.readQueue(oscid), 0)

        return Builder.load_string(kv)

    def ping(self):
        osc.sendMsg('/some_api', ['ping', ], port=someotherport)


if __name__ == '__main__':
    ServiceApp().run()

Yes, at that point, you can run it, and see that when you press the button, your adb logcat on android, yay!

Now, let’s make our service answer, and our UI display the answer!

from time import sleep
from kivy.lib import osc

serviceport = 3000
activityport = 3001

def some_api_callback(message, *args):
    print("got a message! %s" % message)
    answer_message()

def answer_message():
    osc.sendMsg('/some_api', [asctime(localtime()), ], port=activityport)

if __name__ == '__main__':
    osc.init()
    oscid = osc.listen(ipAddr='127.0.0.1', port=serviceport)
    osc.bind(oscid, some_api_callback, '/some_api')

    while True:
        osc.readQueue(oscid)
        sleep(.1)

and for the UI to answer:

from kivy.app import App
from kivy.lang import Builder
from kivy.lib import osc
from kivy.utils import platform
from kivy.clock import Clock

activityport = 3001
serviceport = 3000

kv = '''
Button:
    text: 'push me!'
    on_press: app.ping()
'''

class ServiceApp(App):
    def build(self):
        if platform == 'android':
            from android import AndroidService
            service = AndroidService('my pong service', 'running')
            service.start('service started')
            self.service = service

        osc.init()
        oscid = osc.listen(ipAddr='127.0.0.1', port=activityport)
        osc.bind(oscid, some_api_callback, '/some_api')
        Clock.schedule_interval(lambda *x: osc.readQueue(oscid), 0)

        return Builder.load_string(kv)

    def ping(self):
        osc.sendMsg('/some_api', ['ping', ], port=someotherport)

    def some_api_callback(self, message, *args):
        print("got a message! %s" % message)
        self.root.text += '\n%s' % message[2]

if __name__ == '__main__':
    ServiceApp().run()

The only thing a bit confusing here is that the real message is in message[2], don’t ask me why, it’s probably explained in some documenation i didn’t care enough to search for :).

conclusion

That’s not much code! And it should be quite easy to extend to allow for more complex patterns, detecting if your service is running or not can be done by making it send pings at regular intervals, you can also make your service fetch/work on data from somewhere else in the background, and pass it to the UI when it’s ready.

A slightly more complex demo based on this can be found here.

Tip me if you like this :)

Tags

#FIXME 3G absurd ad_sense alterway aléatoire android animation anonymity atheism bach backlog bash bitcoins blog blogging boldness book books boulot bricolage bépo C canvas captcha captures carte SD censure christianity chroot CLI cli cloudwatt code colors comfort zone command line community company life conferences contest copwatch copwatchnord-idf core-devs cours ct705 culture deb debian debug deformation dehors dessin dev distribute distribution débutant déploiement développement ebooks eeepad eeepc effect ego empty en escher event firefly flappy bird flask fosdem foss fr fun game garden gdb geek culture git github goals graphics grrr gödel hack hackathon hacked hacking hooks i3 images IMAP inspirational isync java jeu jeu video jinja2 jni keyboard keynav kivy kv lame learning lib libre life linux lol macosx magnet mail mailing-list mails maths mbsync meetings memory leak mesh meta mint mirroir MIT module motivational mouse museomix mutt nexus7 no-mouse notmuch nottoomuch offlineimap onycroit opencourseware osc packaging paris passphrase password patch pentacatyl people perte de données ping pip planning plugin positioning pr procrastination programmation progress project projet property proudhon proxy psf publisher/consumer pull-down pygame pyjnius pypi python pythonar qtile rapsberry pi reading recorder references release religion responsive review reviews réseau réseaux sociaux résurection salon screenshots script self service shows shutil shyness sizing solib sortie sousous!!! spam spritz stash status systeme système templating terminal texture texture coordinates Thomas Paine thread thème tiling time time management. tip tips tools transformer tutorial tv twitter typematrix typing ubuntu ubuntu-fr ultimate-smash-friends unity upload images useless usf utils value VDM video vie/mort vim virtualenv visite widget windows wm wmii work workflow workflow. zine études