-
A counter app
08/21/2015 at 04:26 • 0 commentsHello!
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 self.refresh() def decrement(self): self.counter -= 1 self.refresh() def reset(self): self.counter = 0 self.refresh()
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) self.refresh() while self.running: sleep(1)
Next, the usual routines:
counter = Counter(input, output) wcs.register_object(counter) wcs.start_daemon_thread() wcs.wm.activate_app(application.number) #WM-side - to remove wcs.run(counter.run, application.shutdown)
Here it is! (Sorry, no video as for now - got no camera =( ) -
System structure
08/17/2015 at 19:06 • 0 comments -
A simple app - MOCP music player control interface
08/17/2015 at 18:41 • 0 commentsHere, 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 menu.menu 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(): mocp_command("-f")
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:wcs.register_object(menu)
For wcs.run wrapper, we need a blocking call, and Menu.activate is just the thing. So - let's start the application!wcs.run(menu.activate, 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
08/17/2015 at 16:55 • 0 commentsNow 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.py 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: sleep(1)
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:
wcs.register_object(helloworld) wcs.start_daemon_thread()
Let's ask for a WM to switch context to our application - so that it immediately appears on the screen:
wcs.wm.activate_app(self.app.number)
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.
wcs.run(helloworld.run, application.shutdown)
So, here's our Hello, world!
-
The structure
08/17/2015 at 04:50 • 0 commentsFirst 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 it work without pulling too much of my hair, and it's clear it's here to stay. Once it's finished, and that is - in a couple of days, I'll be able to write apps. Lots of apps, controlling various aspects of my Linux system - that sure will be the best part to write =)
Oh, but menus... Somebody has to code them, right? So I plan on including every control there can be into an application creation framework. First, menus. That's the only UI design framework element I have by now - but I'll fix that. Menus I have now aren't as flexible as they could be but it's pretty easy to modify them by instantiating and overriding certain functions, you know, that's one of the things I know people and I personally will do. I can also add dialogs, text input fields using keyboards, keypads, T9 and whatever text input facilitation method I can invent or implement, [comboboxes, radiobuttons, buttons] and lists of them, confirmation dialogs, message boxes, warnings and stuff like that. Basically, I won't run out of ideas on what to implement as long as I have an access to any WYSIWYG-capable IDE, such as Visual Studio =)
So far, it's all I've got. There sure are more components, but they are seem more or less optional. The main thing is - it will all be about applications. See, I actually think there are many awesome Linux things, some of them just need an interface to be noticed. Some need an interface to be useable =) By making an interface like this, people are enabled to use their Linux boxes at full power - and what could be better?
-
How it all started
08/17/2015 at 02:59 • 0 commentsIt 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 a config interface because I was tired of SSH-ing every time there was a problem. I've decided on a web interface, and used web.py as a base. It's a truly beautiful framework, but I feel like I've put too much on it. I've managed to make a few apps, but then I started making a module system (it needed to be modular, see).e end, it became complicated enough to justify logging in through SSH - which I did anyway while I was developing the framework.
Another half a year forward - earned the money somewhere. I put it in the case I had made a year before - and it worked. I hacked things till I broke most of the peripherals I had embedded, but it was great while it lasted. My other failure was desoldering all the connectors. See, after all the Wastelands a Pip-boy-like device has become a necessity for me, so I desoldered all the big bulky connectors, put it all into a small and what could be roughly-described as wrist-wearable case and... Accidentally the whole customised SD card image. I was stuck with a stock Raspbian image, no Ethernet jack and a WiFi card which needed custom drivers that needed to be compiled against the kernel headers - which I were unable to install on my Pi, as well as any other package. Oh, and It was a mess of wires and headers with pinouts that I've myself had created but forgot to describe so had to reverse-engineer a couple of times. I have learned to etch Internet-downloaded PCBs but weren't yet able to do it properly. Oh, and one day I've got tea with sugar spilled over my laptop. It died immediately, and so did my programming advancements.
The winter that year was exhausting, but it wasn't about weather. I've been betrayed by a person who I've considered to be my best friend for about 8 years, and the betrayal was about a large sum of money - which I still owe to one organisation he convinced me to sign papers for. Unfortunately, while dealing with all that mess I had developed clinical depression and have spent a year trying to battle it. My projects and ideas had halted for more than a year, and I still didn't have money - which didn't help, basically, nothing did. It was only after a year that I started to recover, getting some hope that in the end it would all become. And it did.
I've got to know about Sci Fi Your Pi competition. Basically, it's one of the contests Element14 organises. You have an idea fitting their contest topic, you submit it, they send you some parts and you get on working, submitting your project logs. A PipBoy idea did fit the sci-fi topic, I mean, at least, a little bit? Regardless, from 2 years of thinking I had plenty to write in the application form, as well as was ready to be working even if I didn't get accepted... But I did. It was the first time I've seen a Raspberry Pi 2, not to say touch. It soon has proved itself a reliable desktop replacement, fitted with lightweight Raspbian install - it still is. Much more powerful than 1st generation but still consuming as much as a smartphone... Why can't it be a smartphone? And a wearable computer? And a workstation you could take with you? I'm pretty sure it can.
I've failed the Element14 competition. See... Depression comes back once in a while and it can be quite difficult to obtain motivation compared to what it was before. I also need social interaction, or else it becomes worse. My laptop I had for writing had developed a HDD failure, so I got cut out of being able to write posts in my spare time - which is mainly not at home. Regardless, I started hacking on a Raspberry Pi once in a while. I already knew what kind of interface I would need, having some 16x2 displays and USB numpads at hand. It was just a matter of time before first versions came to be, but it was a long road before it has become what it is.
My goals for this project:
- Finish the system. Make it usable and, possibly, useable by many people - I'm an avid Hackaday blog reader and I see an application for this system here and there, as well as have already received feedback from people that had gotten ideas about how they could use it. It can't be perfect, but it'll be close.
- Then, proceed on making my PipBoy. Basically, finish what I've started with that Element14 contest application - and what I feel like I owe them. It's no use anyway - until I make the control system there's no sense in building the custom hardware - you can't even test it the way it's intended to be used!
- Get rid of depression. Like, finally. It ain't gonna disappear completely, I know, but I also don't get depressed while coding an interesting thing and for some days afterwards, and this system is full of interesting things which are still to be written.
That's all for this post. - Now - onto describing the system!