PyoConnect

PyoConnect is a linux alternative to what MyoConnect scripting does under Windows. The pun here is that while MyoConnect uses Lua scripting, PyoConnect uses Python. In fact, it is entirely written in Python, based on the work of dzhu posted on thalmiclabs forum (https://github.com/dzhu/myo-raw).

A great part of the credit goes to dzhu and the folk who wrote PyKeyboard and PyMouse (merged into PyUserInput) and PySerial.



License

Use at will, modify at will. Always keep my name in the library as original author. And that's it.


How to Install

The following steps are required in order to use PyoConnect. A clean debian installation with python is enough as long as the kernel supports bluetooth low energy (developed and tested with python 2.7). These steps work on desktop debian based distros as well as embedded debians such as Raspbian (on Raspberry Pi) and Cubian (on Cubieboard) - both tested!



That's it, dependencies met. Now just download and unzip the core libraries below and start writing your PyoConnect scripts!

The most recent version is 2.0. However, version 1.0 is still available for legacy reasons.

Version 2.0 (new) Version 1.0 (old)  

PyoConnect v2.0: download


How to use

Place the libraries in a folder of your choice. You will notice there is a subfolder named "scripts":


That is where you place your scripts, with a .py extension. There are a few examples there, already.

In a terminal, navigate to the folder and use python to run PyoManager.pyc:
$ python PyoManager.pyc

This will bring up the manager and menu:



The "Connect Myo" and "Disconnect" buttons allow you to connect to your armband. Unlike previous versions, in PyoConnect 2.0 the manager connects to the armband only once and holds the connection, while your scripts will have only your own code.

For each script found in the Scripts folder, there will be a line in the menu, with a button to enable/disable that particular script, just like MyoConnect for windows. More than one script can be active at the same time. In fact, all scripts can be active at the same time.

You can run PyoManager from anywhere in the system. So, if you placed the library and your scripts in:
/home/YourName/PyoConnect_v2.0/
you can create a shortcut in your desktop to:
"python /home/YourName/PyoConnect_v2.0/PyoManager.pyc"
and keep it running minimized.


The script files

Unlike version 1.0, in version 2.0 your scripts will contain only the code you are interested in.

What code?

The purpose of this library is to be as close as possible to the original Lua scripting in MyoConnect. 100% compatibility is not possible because of language differences (this is python afterall) and OS differences, but the resources available are very similar.

Below is the complete list of all methods and events. In the end of this page you can find some example scripts to give a quick start, but they are very simple.

The library documentation below is the same for both 1.0 and 2.0. The difference when writing your scripts is that when using the older versions you had to place a header and footer so it could run as standalone scripts.

If you want to use the examples in the end of this page, just copy the content in the boxes, paste in a new file and save as a .py script in the Scripts folder. Restart the manager.

Example: this code will print pose names if you run PyoManager from a terminal console

Version 2.0 is focused on having a manager with a constant connection to Myo.
If you are interested in standalone scripts, go back to the top of this page and take a look in 1.0.



Library Documentation


- Relationship between original MyoConnect and my PyoConnect -

There are four cases: things not implemented (some because they are useless here), things working just like in the original, things working in a modified way, and new things the original does not have.

- The Lua Environment:

PyoConnect is written in plain Python, so whatever python can do, Myo can do through PyoConnect.

From this point of view, PyoConnect is a lot better since Python environment does not have the limitations imposed by MyoConnect's Lua scripting environment. However, from a security point of view, things are a bit complicated. You should have in mind this is experimental and never run scripts without reading and understanding them first.

- Global Variables:

In 2.0, your scripts can have four variables:
scriptId - useless so far, reserved for future implementation
scriptTitle - title shown in menu
scriptURL - useless so far, reserved for future implementation
scriptDescription - useless so far, reserved for future implementation

Not implemented in 1.0 since there is no MyoConnect manager and we are always manually running the script from linux.


* Things working just like in the original: *
(except they obviously return python variables)

Refer to the original MyoConnect scripting documentation in order to use these.

- Callback functions:

onUnlock()
onLock()
onPoseEdge(pose, edge)
onPeriodic()


- API functions

myo.getArm()
myo.getXDirection()
myo.getTimeMilliseconds()
myo.getRoll()
myo.getPitch()
myo.getYaw()
myo.getGyro()
myo.getAccel()
myo.centerMousePosition()
myo.setLockingPolicy(lockingPolicy)
myo.unlock(unlockType)
myo.lock()
myo.isUnlocked()
myo.notifyUserAction()


* Things working in a different way *

- Callback functions:

onForegroundWindowChange(app, title)

Instead of using this callback, you should poll the OS whenever a user action has occurred.
In fact, the script is always active regardless of current application, but it will only take action when the code says so.

Example: whenever onPoseEdge is called, start with an "if current application is..."
There is a new function for this, explained in another section below.


- API Functions:

myo.vibrate(vibrationType)

instead of textual types, this function takes numeric input in the range 1-4.
Example: myo.vibrate(2)

myo.debug(output)

not available. We are in plain python. Just use print() instead.

myo.keyboard(key, edge, modifiers)

very similar to the original, except for the key argument.
This function is implemented using PyKeyboard from PyUserInput, and therefore the key argument should follow SavinaRoja's PyKeyboard structure (https://github.com/SavinaRoja/PyUserInput/)

The PyKeyboard object is myo.pkeyboard
A list can be found here(lines 124-214):
https://github.com/SavinaRoja/PyUserInput/blob/master/pykeyboard/x11.py

myo.mouse(button, edge, modifiers)

very similar to the original, except modifiers are ignored, supply "" for it.
Use myo.keyboard to control modifiers instead. Example, for shifted left click:
myo.keyboard(myo.pkeyboard.shift_key,"down","")
myo.mouse("left","click","")
myo.keyboard(myo.pkeyboard.shift_key,"up","")
This function is implemented using PyMouse from PyUserInput, and the PyMouse object is myo.pmouse

myo.controlMouse(enabled)

the particular function is not implemented, but can be easily implemented in user script using two lines of code:

Inside onUnlock, put:
myo.rotSetCenter()

Inside onPeriodic, put:
myo.mouseMove(600+myo.rotYaw()*2000, 500-myo.rotPitch()*2000)

These functions will be explained in another section below.


* New things the original MyoConnect lua scripting does not have: *

- Callback Functions

myo.onEMG

EMG handler as written by dzhu
Usage: myo.onEMG = (your function name, written in code above this line)

myo.onWear(arm, xdirection)

called whenever the sync movement has been performed.
Arm is "left" or "right", xdirection is "towardWrist" or "towardElbow"
Usage: myo.onWear = (your function name, written in code above this line)

myo.onUnwear()

called whenever myo has been removed from arm (or for some reason has gone out of sync)
Usage: myo.onUnwear = (your function name, written in code above this line)

myo.onBoxChange(boxNumber, state)

called whenever the arm is pointing to a new box (read myo.getBox() below).
boxNumber is the box number (integer in range 0-8), and state is "on"/"off".
The on/off logic is the same as on onPoseEdge (old box "off" followed by new box "on").
Usage: myo.onBoxChange = (your function name, written in code above this line)

- API Functions

myo.getPose()

return the current pose the user is performing. Unlike onPoseEdge(), this is asynchronous, can be called anytime from anywhere.

myo.getPoseSide()

the same as myo.getPose, except it will automatically check which arm myo is worn and return "waveLeft" or "waveRight" instead of "waveIn" or "waveOut".
This function exists to avoid creating a conditionallySwapWave() or similar.
Can be used inside onPoseEdge, such as:
if (pose == "waveIn") or (pose == "waveOut"):
    if myo.getPoseSide() == "waveRight": do right stuff (such as slide forward)
    if myo.getPoseSide() == "waveLeft": do left stuff (such as slide backward)
You can actually omit the first IF since for all other poses myo.getPoseSide() == myo.getPose() == pose.

myo.isLocked()

the same as (myo.isUnlocked() == False)

myo.mouseMove(x, y)

Moves the cursor to screen position specified (x = column starting at left=0, y = row starting at top = 0)

myo.title_contains(textToCheck)

returns True if the current active window contains textToCheck in it's title.
Example: if myo.title_contains("Iceweasel"): do stuff if Iceweasel is the current active window
The equivalent in the original MyoConnect would be to check in onForegroundWindowChange if title contains "Iceweasel", however in PyoConnect you must check this whenever an action has occurred.

myo.class_contains(textToCheck)

similar to the above but checks in application's class instead of title.
Google chrome, as example, does not keep the words "Google Chrome" in it's title, but it's class (at least in my pc) is always "Google-chrome-stable" regardless of current website.
Console class is always "gnome-terminal" regardless of user, etc.

myo.get_active_window_title()

This is a function (by Alex Spurling) to get the title for the active window.
It is the same as the title variable in original MyoConnect's onForegroundWindowChange callback function

myo.get_active_window_class()

Similar to above but for class instead of title.

myo.rotSetCenter()
myo.rotYaw()
myo.rotPitch()
myo.rotRoll()

These functions are similar to getYaw/getPitch/getRoll, except they return a difference between the current rotation and the rotation at the time myo.rotSetCenter() was called.
The prefix stands for "rotated". So they are rotated Yaw, rotated Pitch, rotated Roll.
In other words, it automatically handles deltas, so you only have to worry about relative rotations.
Call myo.rotSetCenter() whenever convenient (such as in onUnlock()) and work with the other 3 functions in radians. If the user has not moved the arm since last myo.rotSetCenter() call, all functions will return 0 (that is, no rotation from the new reference).

* The box pointing/movement and sequencing system:
The whole system below is particularly an improvement of my own.

myo.getBox()

(Before using this function you must calibrate a center using myo.rotSetCenter().)
Returns an integer number in the range 0-8, indicating a direction box the arm is pointing to (rotYaw and rotPitch), in a clockwise pattern:
812
703
654

If the arm is pointing to the center, returns 0. If it's pointing to the right, returns 3. If it's pointing left-down, returns 6. The center box is a square 0.5 radians wide, 0.25 to each side (around 30 degrees, half to each side; 30 degrees is the clock angle of one hour)
If you want to change this, overwrite myo.box_factor writing the angle in radians from the center to the box border (default: 0.25).

myo.getHBox()

(Before using this function you must calibrate a center using myo.rotSetCenter().)
Following the same 0.25 radians rule as above, returns -1 if the arm is pointing to the left, 0 if it's pointing to the center, 1 if it's pointing to the right.
The H stands for horizontal.

myo.getVBox()

(Before using this function you must calibrate a center using myo.rotSetCenter().)
Following the same 0.25 radians rule as above, returns -1 if the arm is pointing down, 0 if it's pointing to the center, 1 if it's pointing up.
The V stands for vertical.

myo.getLastMovements(numberOfMovements)

returns a string with the specified number of boxes the arm has been pointing to, using myo.getBox().
Example: if the arm has described a T path, pointing to center, up, up-left, up, up-right, up, center, down, calling myo.getLastMovements(8) will return "01812105".
Calling myo.getLastMovements(3) will return "105"
(that is, the last 3 boxes pointed to: up, center, down)
Notice the boxes are recorded regardless of locking state or policy.

myo.getLastGestures(numberOfGestures)

returns a string with the specified number of gestures in sequence.
Example, if the user was resting and performed: fist, rest, fist, rest, waveOut, rest, waveIn, rest, fingersSpread, rest, calling myo.getLastGestures(10) will return "FRFRORIRSR"
Calling myo.getLastGestures(3) will return "RSR"
(that is, the last 3 gestures: rest, fingersSpread, rest)
The characters for each gesture are: R=Rest, F=Fist, I=waveIn, O=waveOut, S=fingersSpread, D=doubleTap
Notice the gestures are recorded regardless of locking state or policy.

myo.getLastActions(numberOfActions)

returns the specified number of the combination of boxes and gestures, in sequence.
Example, if the user was resting, then pointed up, made a fist, while holding the fist pointed center and down, rest while pointing down, and while resting pointed down-right, calling myo.getLastActions(6) will return "1F05R4"
Calling myo.getLastActions(3) will return "5R4"
(that is, the last 3 actions: pointing down, resting, pointing down-right)
Notice the actions are recorded regardless of locking state or policy.

myo.clearHistory()

clears the history for getLastMovements, getLastGestures and getLastActions.
The maximum history is 100 for each (100 movements for getLastMovements, 100 gestures for getLastGestures, 100 actions for getLastActions)


* Things not implemented: *

- Callback Functions

activeAppName()

Not implemented yet in 2.0. Pointless in 1.0.

onActiveChange(isActive)

Not implemented yet in 2.0. Pointless in 1.0, the script is always active, but just deciding to interfere or not.

- API Functions

myo.getOrientationWorld()
myo.getAccelWorld()

I do not understand the orientation of Myo enough to implement a compatible version of these functions.

myo.mediaKey(key)

I do not know how to implement this in linux.
Most likely possible using myo.keyboard and providing the suitable key code, but I do not know.

myo.mouseControlEnabled()

pointless since myo.controlMouse(enabled) is not implemented and this is achieved in another way

myo.midi(channel, type, data1, data2)
myo.midiMachineControl(channel, command)

Didn't spend time on this since it's very application-specific.




Examples


A very simple script that only prints out the pose events:


A script to control youtube when it's not fullscreen (and therefore the active window is the browser). Making a fist pauses and resumes the video (by sending spacebar key). WaveOut moves the video cursor forward (by sending right arrow key), and WaveIn moves back (left arrow key).
Notice this is not a perfect script, since it uses "press" for left and right keys. An ideal logic would be to use "down" and "up" keys when the edge is "on" or "off".


Now the same script but with the better logic:


A simple script to control LibreOffice Impress presentations, using automatic left/right side check:


A script to control mouse, using Fist to click and fingersSpread to right-click. Use DoubleTap to enable after script startup.


A script I used to play SuperTuxKart by just pointing left/right to steer, and up/down to speed forward/reverse. It uses my new box system through HBox and VBox methods.


A new unlock policy based on a secret movement: make a fist, move up (box 1), right (box 2), back to up (box 1), back to center (box 0), rest.
Notice the particular sequence is relevant only before unlocking. While unlocked the code is ignored and the same sequence can have another meaning (or nothing at all).
(This script does not have a way to set the center, it is just a piece of code to demonstrate the concept. But it works.)


If I come up with new examples, I'll post them here.


PyoConnect by Fernando Cosentino - http://www.fernandocosentino.net/pyoconnect