0

In my project i need to use maybe less common architecture: KivyMD + SocketIO as server and JS client (Flask).

Bellow is a code for minimal reproducible example (two apps): Server App (KivyMD) and Client App:

Structure of MRE:

- main.py
- flask_client.py
- templates/index.html.j2

KivyApp:

This App is based on these two sources: How to run kivy and flask apps together? and https://python-socketio.readthedocs.io/en/latest/intro.html#server-examples (part AioHttp)

# Filename: main.py
# Start Kivy app with Socket IO server: python main.py

from kivymd.app import MDApp
from kivymd.uix.screen import MDScreen

from kivy.lang import Builder

from aiohttp import web
import socketio

# common modules
import signal
from multiprocessing import Process


sio = socketio.AsyncServer(cors_allowed_origins='*')
webapp = web.Application()
sio.attach(webapp)


KV = '''
MainScreen:
    MDRaisedButton:
        pos_hint: {"center_x": .5, "center_y": .5}
        font_size: "18sp"
        text: "Push data"
        on_release:
            root.push_data()
'''


async def index(request):
    """Serve the client-side application."""
    with open('index.html') as f:
        return web.Response(text=f.read(), content_type='text/html')


@sio.event
def connect(sid, environ):
    print('SocketIo Connect connect ', sid)


@sio.on('my_message')
def message(sid, data):
    print("Server received message!", data)


@sio.event
def disconnect(sid):
    print('disconnect ', sid)


webapp.router.add_static('/', '')
webapp.router.add_get('/', index)


def start_websocket_server():
    print("Starting WebSocket server...")
    web.run_app(webapp)


def signal_handler(signal, frame):
    # for fetching CTRL+C and relatives
    print("CTRL + C detected, exiting ... ")
    exit(1)


class MainScreen(MDScreen):
    def __init__(self, **kwargs):
        super(MainScreen, self).__init__(**kwargs)

    # ## Unknown part - how to emit data for socketio client? ###
    def push_data(self):
        print('>> EMIT DATA')
        sio.emit('emitted_data', 'test data')
    # ## Unknown part - how to emit data for socketio client? ###

class SocketIOApp(MDApp):

    def exit(self):
        print("Exiting...")

        # terminate Flask by pressing on cancel
        ws_server_process.terminate()
        exit(1)

    def build(self):
        return Builder.load_string(KV)


if __name__ == '__main__':
    # #CTRL+C signal handler
    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)

    global ws_server_process
    ws_server_process = Process(target=start_websocket_server)

    # run WS as process
    ws_server_process.start()

    SocketIOApp().run()

The 2nd App: Minimal Flask Client

# Filename: flask_client.py
# Start Flask client: export FLASK_DEBUG=1 && export FLASK_APP=flask_client.py && flask run

from flask import Flask, render_template
app = Flask(__name__)


@app.route('/')
def hello_world():
    return render_template("index.html.j2")


if __name__ == "__main__":
    app.run()

Second file (must be in folder "templates")

Filename: index.html.j2

<!doctype html>
<head>
    <!-- Core JS files -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js" integrity="sha512-bLT0Qm9VnAYZDflyKcBaQ2gg0hSYNQrJ8RilYldYQ1FxQYoCLtUjuuRuZo+fjqhx/qtq/1itJ0C2ejDxltZVFg==" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/3.0.4/socket.io.js" integrity="sha512-aMGMvNYu8Ue4G+fHa359jcPb1u+ytAF+P2SCb+PxrjCdO3n3ZTxJ30zuH39rimUggmTwmh2u7wvQsDTHESnmfQ==" crossorigin="anonymous"></script>
</head>

<body>
    <div class="container">
        <div class="starter-template">
            <h1>Bootstrap starter template</h1>

            <form id="emit" method="POST" action='#'>
                <button type="button" id="socketio-click" class="btn btn-primary">
                        Send data to server
                </button>
            </form>

        </div>
    </div>

    <script>
        $(document).ready(function() {
            console.log('JS works');

            var socket = io('http://127.0.0.1:8080');
            socket.on('connect', function() {
                socket.emit('my_message', {data: 'I\'m connected!'});
            });

            socket.on('disconnect', function() {
                socket.emit('my_message', {data: 'I\'m disconnected!'});
            });
            
            // Show recieved data in console
            socket.on('emitted_data', function(msg, cb) {
                console.log('Recieved data:', msg);
                if (cb)
                    cb();
            });

            document.getElementById('socketio-click').onclick = function(event) {
                console.log('my_message');
                socket.emit('my_message', {data: 'Data from WebClient'});
                return false;
                };
        })

    </script>
</body>

Apps requirements: pip install kivymd flask python-socketio aiohttp


In this example it works: When i click on the button in client Flask app, data are emitted and server print recieved data.

But I don't know how to implement socketio for a reverse way: I need to click on button Push Data (KivyMD app) and i need to emit data and show them on client app (console).

When i click on "Push data" button in my current implementation is shown error:

>> EMIT DATA
 main.py:75: RuntimeWarning: coroutine 'AsyncServer.emit' was never awaited
   sio.emit('emitted_data', 'test data')
 RuntimeWarning: Enable tracemalloc to get the object allocation traceback

Please, can you help me with this implementation and calling emit data on Kivy App?

Thank you.

lukassliacky
  • 364
  • 7
  • 25
  • maybe it needs `await` like `await sio.emit(...)`? – furas Nov 29 '21 at 12:30
  • @furas thanks, but when i add only `await` before `sio.emit(...)` i see `File "main.py", line 76 await sio.emit('emitted_data', 'test data') ^ SyntaxError: 'await' outside async function` and when i add `async` before def: `async def push_data(self):` respnose is `:8: RuntimeWarning: coroutine 'MainScreen.push_data' was never awaited RuntimeWarning: Enable tracemalloc to get the object allocation traceback` – lukassliacky Nov 29 '21 at 12:52
  • if you use `AsyncServer` then you have to learn how to use `async` and `await` because it works diffrernt then normal functions. And it needs more work in Python. (in JavaScript all works with `async` and they don't even need to use `async`, `await`). It need to define function as `async def push_data(self):` and it may need to add it to some `event_loop` - and it may need more work. If it is possible then start with normal `Server` – furas Nov 29 '21 at 12:59
  • 1
    I see other problem - you have to run socketio server in separated process so main process may not have access to `sio` – furas Nov 29 '21 at 13:03
  • "I see other problem - you have to run socketio server in separated process..." ou, you are absolutely right... Please, is possible to use some comunication between Kivy and SocketIO server (maybe some message broker) or i need to change approach to start server? – lukassliacky Nov 29 '21 at 13:09
  • 1
    I would rather use normal `requests.get()` in Kivy to send data to `Flask` and `Flask` could use [Flask-SocketIO](https://flask-socketio.readthedocs.io/en/latest/). It can be also useful because `socket` in JavaScript needs IP address to `SocketIO` on Kivy so Kivy has to run always with the same IP. But if SocketIO works in Flask then Kivy can run on any computer or phone. – furas Nov 29 '21 at 17:29
  • Okay, after really long struggle i am going back to my primary solution: Independent `Kivy` + `Flask (with Socketi.IO)` + `Redis Server (as communication layer)` and JS Client... I tried to optimize this architecture but it was really pain and i think this will be maybe not best but relatively simple and transparent architecture. @furas Really thank you for your hints and time. – lukassliacky Nov 30 '21 at 20:55

0 Answers0