Linux Control System

*moved to pyLCI project*An expandable control system for Linux with apps that lets you control your Linux devices using a screen and buttons

Similar projects worth following
This is a system that can use all kinds of input and output devices to create a simple control panel for your Linux PC. It's primarily designed for a Raspberry Pi (and on a Raspberry Pi!), but can be adapted to work on any Linux - the only requirement is capability to run Python, which includes even OpenWRT routers. It's designed for:
-Home servers
-Home automation systems
-Wearable computers
-DIY devices
-Desktop computers
..Anything goes. It doesn't need any other device for control. The minimum hardware is just a display and some GPIO/HID buttons - USB numpad/keyboard will do. As a result, it's also very cheap and easy to get.
It's all that you need to control your Linux device. It's a perfect interface for your embedded Linux-based project, no matter how big or small. It's well thought-out, powerful and extensible. I will develop a large number of apps, and it'll be easy to write your own - just focus on your project, the sys

This project moved to pyLCI - Python-based Linux Control Interface.

My system is a layer between input/output devices and applications that can make use of them. It will provide:

  • Drivers for various input and output devices, as well as guidelines and examples for writing your own drivers in case your device is not supported yet. For most devices, it'll mean just importing an existing library and making a Python wrapper. It will also include an input and output emulator - in case you ever need a debug facility.
  • An "app"-like mechanism, which is familiar and comfortable to many programmers and used in most interface frameworks, be it web-interfaces, desktop GUIs or mobile apps.
  • A window manager allowing simultaneous existence of many apps at once - that means no limitations on how many apps you can install, as well as ability to switch between them.
  • Security layers, isolating apps and making sure they don't break the system or steal each others' data, as well as preventing any unforeseen consequences from app crashes.
  • A powerful framework that will make app development very easy - with various tools for UI creation (menus, dialogs, lists etc.), many supported input ways (numeric, keyboard, touchscreen, custom etc.), many supported output ways (text-mode, graphical, network).
  • An application manager with package manager integration, allowing you to download and install any applications with ease.

Current status - debugging and refactoring the window manager, as well as starting to write proof-of-concept apps.

  • 1 × Linux PC with a Python interpreter, USB/GPIO connectivity with Python wrappers available (All this applies to most PCs and SBCs)
  • 1 × A HD44780 display Only type supported by now, 20x4 displays can be used but not fully supported by 'menu' component yet
  • 1 × A HID device Such as an USB numpad or a keyboard. Alternatively, a device for which you could write a HID Python driver, which is simply injecting key events in Linux input system

  • A counter app

    Arya08/21/2015 at 04:26 0 comments


    First of all, I finally have solved most of issues that had troubled co-existence of multiple apps at once. Apps may now live in peace together. One issue I know that still isn't solved is the one where setting some callbacks or outputting data on display while the app isn't activated (t.i. on the screen right now) will not do anything - except for the first time where app has been created but hasn't been activated yet, and that's not even confirmed. I just need to rethink some functions.

    The next system usage example is the counter app. You know these counters with one or two buttons and a screen, where one button increases a number on the screen and the other one resets the counter? Well, why not re-implement it with an Arduino^W^W a Raspberry Pi? That way, when it's next time you'll need a counter, you can just launch an app =)

    Overall, it's a simple example of how to deal with changing data.

    from time import sleep
    import wcs
    application = wcs.wm.create_new_application("Counter app")
    window = application.get_window("Counter window")
    input = window.input_interface
    output = window.output_interface
    class Counter():
        running = False
    So far, nothing unusual.
        prompt = "Counter value is:"
        counter = 0

    I added a prompt variable just for usability. The counter variable is the one we will be changing all the time.

        def increment(self):
            self.counter += 1
        def decrement(self):
            self.counter -= 1
        def reset(self):
            self.counter = 0
    Those are the functions we will use to change the counter. Notice the refresh() at the end? We need to output the new data on the screen each time we change the variable, otherwise nothing will be visible to the user =) The refresh() is as follows:
        def refresh(self):
            self.output.display_data(self.prompt, str(self.counter))
    Again, display_data() positional arguments are printed each one on a new row of the display. As for now, there's no line-wrapping or scrolling - but I plan to change that =)
    Let's finish it with a run() function, now with more callbacks:
    def run(self):
            self.running = True
            input.set_callback('KEY_KPPLUS', 'increment', self)
            input.set_callback('KEY_KPMINUS', 'decrement', self)
            input.set_callback('KEY_BACKSPACE', 'reset', self)
            input.set_callback('KEY_ENTER', 'stop', self)
            input.set_callback('KEY_KPENTER', 'stop', self)
            while self.running:

    Next, the usual routines:

    counter = Counter(input, output)
    wcs.wm.activate_app(application.number) #WM-side - to remove, application.shutdown)

    Here it is! (Sorry, no video as for now - got no camera =( )

  • System structure

    Arya08/17/2015 at 19:06 0 comments

  • A simple app - MOCP music player control interface

    Arya08/17/2015 at 18:41 0 comments

    Here, I will show you how to make a script that does actual work. Let's say I want to control a music player that is Ncurses-only. Fortunately, by passing some parameters to its executable it is possible to control its playback. So, we'll be executing commands:

    from subprocess import call

    We'll also need a menu. For now, it's imported like that:

    from import Menu
    As soon as I package my framework, it'll be something like:
    from wcs.interfaces import Menu

    But, as for now, it's not yet properly packaged so every script using menus is to be run from the working tree. I'm going to fix this soon.

    The setup part is mainly the same. We import the wcs framework, create an application with a self-descriptive name, then a window and get interfaces from it.

    application = wm.create_new_application("MOCP control")
    Now is the fun part - getting working Python commands to change things. First, I have made two wrappers. One is for calling mocp executable:
    def mocp_command(*command):
        return call(['mocp'] + list(command))
    Then, all the other commands can be described like:
    def mocp_next():
    The second wrapper is for calling amixer program. It's called to change volume. MOCP can do it by itself, it's just that I don't have some component on my system so the control from inside the application is not really working. Fortunately, it doesn't matter if we use one more executable - it's just another wrapper:
    def amixer_command(*command):
        return call(['amixer'] + list(command))
    From which we can make commands like this:
    def plus_volume():
        return amixer_command("-c", "0", "--", "sset", "PCM", "400+")

    Now we've got the commands, all that is left is making a menu. We'll start with the contents:

    main_menu_contents = [ 
    ["Toggle play/pause", mocp_toggle_play],
    ["Next song", mocp_next],
    ["Previous song", mocp_prev],
    ["Increase volume", plus_volume],
    ["Decrease volume", minus_volume],
    ["Toggle mute", toggle_mute]
    It's as simple as that. A list of lists representing menu items, where first element is the menu item name and second element is a function to be called when said element is chosen.
    Let's initialise this:

    menu = Menu(main_menu_contents, output, input, "Main menu", daemon = wcs._daemon)
    Now we're registering the menu object with the daemon, same as we did with our HelloWorld object:
    For wrapper, we need a blocking call, and Menu.activate is just the thing. So - let's start the application!, application.shutdown)

    The result is on the video above. The full code for the application can be seen here.

    What do you think about it? Do you have an idea of how you'll be using that project?

  • A simple hello_world application example

    Arya08/17/2015 at 16:55 0 comments

    Now it's time to present you a simple "Hello world" app. It's supposed to be short and easy to understand, and as for now it's as simple as a web framework "Hello world". The app is located here and, well, works for me ;-)

    First, you import the wcs module:

    import wcs

    Then, you send an application request to a window manager. WM returns an application object which has information about your application.

    application = wcs.wm.create_new_application("Hello world")

    This object contains a Window object, which stores your input and output interfaces you can use for controlling your screen and input device.

    window = application.get_window("Hello window")

    Now it's time to make an object class for your application. It needs to have a function with a blocking loop that can be interrupted on a call - a simple:

    while self.running:

    will do. It also needs to have a function to interrupt the loop (setting the self.running variable to False). This is our stop() method.

    But, of course, it needs to have output functions:

    self.output.display_data("Hello world", "ENTER to exit")

    output.display_data takes an arbitrary number of strings and display them line by line on the screen provided. It also needs a function that'd end the loop on a keypress:

    self.input.set_callback('KEY_ENTER', 'stop', self)

    This function takes a key ecode, an object and a object's method name which to call on a keypress. An object passing is necessary because Pyro, an IPC solution that's used, cannot pass functions around but can only pass objects and built-in types. There's also a 'KEY_KPENTER' callback which does the same - it's just that Enter key ecodes for a numpad depend on whether Numlock is on or off.

    helloworld = HelloWorld(input, output)

    That's it, we're done with the class! we'll just make an instance of it and register the object with Pyro, a function that's conveniently hidden by a wcs module, which also takes care of the concurrency - the loop that runs the Pyro daemon is blocking:


    Let's ask for a WM to switch context to our application - so that it immediately appears on the screen:


    The last thing is running the blocking loop. WCS has a wrapper for this that gives a graceful shutdown feature. First argument to this wrapper is the function to be run - our run() method, second is the application.destroy function - it lets the WM know about the shutdown and cleans it all up nicely., application.shutdown)

    So, here's our Hello, world!

  • The structure

    Arya08/17/2015 at 04:50 0 comments

    First of all, there are input devices. That's what I started with - when I was working on my Raspberry Pi case, it had an IR receiver. It was connected using lirc and emulated keyboard media key presses using Python and its uinput bindings. I hoped media keys would control mocp, a nice console music player I was accustomed to use, or omxplayer, which is a RPi-specific player making use of hardware acceleration available there. I was wrong but it was a great learning experience about how input devices work. No wonder first thing I did was looking at the existing input devices and incorporating them into my project which desperately needed some input (pun intended). Most input devices I have at home are HID (I've written HID-enabling drivers for those which aren't =) ) This, of course, means that by using HID devices as input device base it is possible to cover most input devices, and make a room for the others by using a standardised interface - therefore the choice. I've got a global input driver taking care of this, it uses evdev - a Linux method of grabbing input device events, and it has nice Python bindings. I've written a wrapper around it which is asynchronous, therefore, everything that connects to this drier is callback-driven. You provide key names and callback functions, then it takes care of the rest. Callback functions are what drives various applications and parts of them such as menus.

    Of course, menus have to output themselves somewhere. So every time menu's callback such as move_up, move_down or select_element is called, it has to do something on the screen. That's solved - you guessed it right - by a callback which the driver provides. Now, output drives are more numerous. There are much more HD44780 screen connection ways than there are input device types - just remember about all the ways one can connect an IO expander outputs to those 8 data lines. It's 8!, and sometimes I feel like those people developing cheap I2C HD44780 backpacks have an inside joke of some kind:

    -"Let's swap D0 and D1 so that they need to rewrite their Arduino libraries MWAHAHAHA"

    -"You thought this library was okay NOW YOUR BACKLIGHT FLASHES AT 20Hz LOL"

    -"Oh look garbled characters with that custom library, why don't you use our library IT COMPILES ONLY ON ARDUINO IDE 15 THOUGH WHAT A SHAME"

    Anyway, I was saying... There's plenty of ways to output data on a character display. However, I've got a plan on how to write drivers to support most of them, and it's limited only by how much different displays I can get access to - chances are it's got an Arduino library and those are simple to rewrite into Python. I plan to develop wrappers for most popular display ICs, which would contain commands needed to be sent to ICs to display anything, so you needn't redefine all the commands to write a new driver.

    Once we've got input and output, the situation is good. As long as we don't need to run more than one thing using it. You could just make one big script which would contain every import there is and give them access directly... And every time there is an unlucky exception, it crashes the whole system. I'm not even talking about how big the file would be. It also would need to run as root, since once the monolith system like this becomes big enough, it is run as root - nobody wants to mess with permissions when you can just type "sudo". Oh, writing your own apps? You'd need to incorporate them into that file. Debugging sure would be hard. It'd be an equivalent of compiling your web browser together with your window manager.

    So, we need a window manager of some sort. After a week of hard work, it's finally there, even though very basic - but mostly working. It's all thanks to Pyro4 - an IPC solution for Python objects, really powerful and worth trying if you ever need inter-process communication capable of also providing security features, which IMO is a must in my system. By some clever (and sometimes bruteforcish) principles I've managed to make...

    Read more »

  • How it all started

    Arya08/17/2015 at 02:59 0 comments

    It all started 4 years before, when I just got to know about the Raspberry Pi. It was still in the prototyping stage, a HDMI stick-like device back then, just like numerous Linux-capable HDMI dongles we can see now. It occured to me that it's wonderful just how little a computer can be, and how much power it can pack. I had many thoughts about it, but not so much real project ideas - being just an inexperienced guy repairing computers for pocket money. I didn't even know it was just a start of all the failures that'd occur.

    3 years before, I asked my parents to gift me money. See, Raspberry Pi just appeared and i wanted to try it out, since I've always wanted to build a robot or something, or maybe a portable computer. I hardly had any money back then, and it hardly changed since. I got the money, put it into a nice envelope, but went on a trip where my phone got broken and I had to fix it, and had no possibility to get more money after this. Envelope went empty, but I still did something. I made a case out of an old CD-ROM drive enclosure. The case would house Raspberry Pi, break out all the connectors, add an USB hub or two and house some peripherals, such as WiFi, BT and other modules, oh, and be insertable where the CD drive at the PC. I did manage to make the basics, specifically, the case and breakout connectors, as well as the power supply part. It was super ghetto and as much as I could have made, being broke 99% of the time. I knew I needed an interface, so planned to include 3 Nokia 3310 screens on the front, have menus on them and control them using an IR remote. I also did some programming - you know, writing lines of code is free =)

    2 and a half years before today, I've got a laptop. An i3 laptop with a nice graphics card. My parents paid for it, thankfully. Almost as soon as I got it, I decided to make some projects using a new programming language - and chose Python. It was awesome. Compared to the other languages I have tried, it enabled me to do awesome things exactly the way I wanted to do them - mind you, even on Windows ;-) I wrote a program that'd parse local public transportation provider schedules and make a menu where I could input transport number and get its arrival times for a certain bus stops. It even worked offline, and had a nice CLI interface for selection - as nice as it could be, given that cmd.exe doesn't seem to support certain Unicode characters, which were all over the place in bus stop names =) Still, it was lacking in terms on interface. The very next month, I had chosen to write an interface for it for my mobile phone. Mobile phone and Python, you ask? Well, Symbian is almost dead, but it sure left some nice things behind, including a Python SDK for developing apps. Even though debugging by ["compiling", ""uploading a package on a SD card", "installing it on the phone", "reading print statements", "trying to understand thrown exceptions"] was a pain, I still got it working and even managed to submit it as my high school programming project and get a nice mark. Though mainly I was exploring the Capital and the Mojave Wastelands - you didn't miss the part about "nice graphics card", right?

    In half a year, I got myself an EEE Pc 701 with a broken screen - for, like, 10 bucks. It immediately became my Linux hacking machine, running webservers, Python applications and all kinds of stuff I'll never ever use. Mainly though, it was my portable hotspot - it accepted a 3G modem or an Ethernet cable link and worked as a WiFi gateway. It also hosted various Left4Dead contests we had with my soon-to-be wife =) The problem was controlling various aspects of it. I've had SSH clients installed on every machine I'd use with that thing, and even this wouldn't help when the network lagged - requiring WiFi or DHCP service restart, which is 'kinda' hard to do over local network when those services are providing the said local network. A reboot by button was a fix, but not always. Besides that, I wanted to make...

    Read more »

View all 6 project logs

  • 1
    Step 1

    Connect a character display to your Linux PC

  • 2
    Step 2

    Connect a HID input device to your Linux PC

  • 3
    Step 3

    Install the software and configure the input/output parameters

View all 4 instructions

Enjoy this project?



Craig Hissett wrote 11/28/2015 at 17:28 point

This could be simply amazing on the new Pi Zero - you'd be able to have a control device no bigger than the screen!

Have you seen @Stefan Lochbrunner's #Pro Trinket USB Keyboard? It's a great little device, using either a Pro Trinket or Arduino Pro Micro to act as a HID device.

One of these, a Pi Zero and a screen would rock!

  Are you sure? yes | no

Arya wrote 12/21/2015 at 11:43 point

Hey! Yes, I'm dreaming of playing with one =) Say, you've got a Pi0-based FM radio, hardware done and APIs written, now just needing a simple UI. You'd usually write your own interface for that, and it woud still be a pain to manage everything because any little function would need to be included. With my system, you could take most existing apps for managing your Linux system and write your own that just does the radio control thing - and in the end you'd have a system that:

-Manages your radio, and in much less SLOC than you'd have done it
-Lets you connect to wireless networks and see your network information
-Lets you see system stats, update the system etc.
-Lets you shutdown the thing
-Does not fail completely when a single part of the system fails
-And does everything without networking, SSH, serial console - to sum up, cables and other computers.

You know, that kind of interface would be amazing for many things - including Pi0, but not limited ;-) I'm currently refactoring my room's electronic structure, reinstalling and remaking things to work better, then working on a shared project for two weeks, then y'all getting an announcement - better late than never, and this thing has lots of potential. I just feel it's not a one man job, after having written the core I already know where it needs to be rewritten =D

My real pain, as for now, is making the app writing process as simple as possible. Unless I'm not designing it (specifically, the API) as good as it can be done architecturally, it's gonna have problems and everybody's gonna have problems and then more problems as the APIs will be re-structured =D

  Are you sure? yes | no

Craig Hissett wrote 12/21/2015 at 18:58 point

I wish I had some skills to be able to contribute to this mate, I really do.

I hope I can help test it when you get into that point :)

Definitely want to put a display and Stefan's board together for this, with the Zero and a battery behind it into a sweet controller :)

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates