Monday, 12 November 2012

Python 3 Clipboard and File Manipulation in Windows


This script reads the contents of the clipboard and renames the most recently downloaded file accordingly (if it's a particular type). It also moves the file to a new directory and opens it.

Programmers are funny sometimes. We'd rather spend 2x longer developing a program/script to do our repetitive tasks for us than to just do them! That's exactly what this is. I used this while marking assignments because I found I wasted a lot of time on this. I know there are other ways (like downloading assignments in bulk) but it wasn't even a question in my mind, I had to do it this way :)

import os
import sys
from time import sleep
import clipboard_code as win_clipboard # should be in same DIR

##########################
########## Main ##########
##########################

modified = ""
append_string = " - Assignment 2 MARKED"
source_dir = "C:\\Users\\me\\Downloads"
old_listdir = os.listdir(source_dir)
destination_dir = "C:\\Users\\me\\Dropbox\\ADS\\Assignment 2\\marked"

while True:
    ##### Clipboard Phase #####
    s = ""
    try:
        s = win_clipboard.Get()
    except (TypeError):
        # the contents of the clipboard isn't text
        s = ""
        modified = ""
    
    # print(s)
    if (s + append_string) != modified and s != "":
        modified = s + append_string
        print(s, " -> ", modified)
        s = modified
    
    ##### File Moving & Renaming Phase #####
    listdir = os.listdir(source_dir)
    if old_listdir != listdir:
        if not modified:
            print("ERROR: found a new file but no text from the clipboard")
            # print("resetting old_listdir")
            old_listdir = listdir
        else:
            newfileslist = [file for file in listdir if file not in old_listdir]
            newfile = ""
            if newfileslist == []:
                print("Error figuring out which file was new")
            else:
                newfile = os.path.join(source_dir, newfileslist[0])
                print("\tNEWFILE:", newfile)
            
            
            if newfile and newfile.count(".docx") >= 1:
                newfilename = modified + ".docx"
                newpath = os.path.join(destination_dir, newfilename)
                print("Moving\n", newfile, "\nto\n", newpath)
                os.rename(newfile, newpath)
                sleep(0.2)
                os.system("call \"" + newpath + "\"")
            else:
                print("File is not a docx. User must handle it manually")
                
    sleep(0.75)

So what's in clipboard_code.py? It's code taken from a gentleman named kapace that I found somewhere off Google. It's not the neatest code but it works well.

import ctypes

#Get required functions, strcpy..
strcpy = ctypes.cdll.msvcrt.strcpy
ocb = ctypes.windll.user32.OpenClipboard        #Basic Clipboard functions
ecb = ctypes.windll.user32.EmptyClipboard
gcd = ctypes.windll.user32.GetClipboardData
scd = ctypes.windll.user32.SetClipboardData
ccb = ctypes.windll.user32.CloseClipboard
ga = ctypes.windll.kernel32.GlobalAlloc     # Global Memory allocation
gl = ctypes.windll.kernel32.GlobalLock       # Global Memory Locking
gul = ctypes.windll.kernel32.GlobalUnlock
GMEM_DDESHARE = 0x2000 

def Get( ):
    ocb(None) # Open Clip, Default task
    pcontents = gcd(1) # 1 means CF_TEXT.. too lazy to get the token thingy ... 
    data = ctypes.c_char_p(pcontents).value
    #gul(pcontents) ?
    ccb()
    return bytes.decode(data)

def Paste( data ):
    ocb(None) # Open Clip, Default task
    ecb()
    hCd = ga( GMEM_DDESHARE, len( str.encode(data) )+1 )
    pchData = gl(hCd)
    strcpy(ctypes.c_char_p(pchData),str.encode(data))
    gul(hCd)
    scd(1,hCd)
    ccb()

I've gone with the Windows-only version of messing with the clipboard because I couldn't get it to work through Tkinter. I got Tkinter to open and read the clipboard but pasting or reading a second time didn't work. Also if I put text into the clipboard with Tkinter then any program I pasted it into would freeze until the python script closed. Weird! I don't recommend using Tkinter for clipboard stuff.

Thursday, 12 January 2012

Arduino 1.0 Ethernet Shield & Nintendo DS Touch Panel

Interfacing with a DS touch panel is surprizingly easy and since they're so cheap (you probably know someone with a broken DS/DS Lite/DSi) it just makes sense.
The Arduino doesn't have enough horsepower to drive the screen but you can use the touch panel pretty easily. My idea was to stick paper behind the touch panel and draw an interface on it, and eventually have several "screens" on a single paper that moves behind the touch panel. Well at least I thought it was a clever idea...

My sketches are based on info from this Arduino + DS touch page.


const int xLow = 17;
const int xHigh = 15;
const int yLow = 16;
const int yHigh = 14;

int touchX;
int touchY;

void getTouchValues() {
pinMode(xLow,OUTPUT);
pinMode(xHigh,OUTPUT);
digitalWrite(xLow,LOW);
digitalWrite(xHigh,HIGH);
digitalWrite(yLow,LOW);
digitalWrite(yHigh,LOW);
pinMode(yLow,INPUT);
pinMode(yHigh,INPUT);
delay(10);

//analog pins are 0-5
touchX = analogRead(yLow -14);

pinMode(yLow,OUTPUT);
pinMode(yHigh,OUTPUT);
digitalWrite(yLow,LOW);
digitalWrite(yHigh,HIGH);
digitalWrite(xLow,LOW);
digitalWrite(xHigh,LOW);
pinMode(xLow,INPUT);
pinMode(xHigh,INPUT);
delay(10);

//analog pins are 0-5
touchY = analogRead(xLow - 14);
}


/*******************************/
void setup(){
Serial.begin(9600);
}

/*******************************/
void loop(){
getTouchValues();
Serial.print(touchX,DEC);   
Serial.print(",");     
Serial.println(touchY,DEC); 
}


Pretty simple right? Ok, getting the values is a little complicated. Nonetheless, it runs pretty quick and leaves plenty of time to do whatever you want with the results.

I took the touch input and used it to send a packet using the Arduino's ethernet shield. For this I had to move analog pins 0 and 1 because the ethernet shield used them for the SD card (even if you're not using the SD card it will change the values you read from the touch panel). I moved them to analog pins 4 and 5, respectively.


#include <SPI.h>

#include <Ethernet.h>
#include <EthernetClient.h>
#include <EthernetServer.h>
#include <EthernetUdp.h>
#include <util.h>

/*
** WARNING: Ethernet Sheild uses analog pins 0 and 1 for the SD card
**          Even if you're not using the SD card, there are 10k pull-ups
**          So readings will be inaccurate
*/

/*******************************/

// Touch
const int xLow = 17;
const int xHigh = 19; // 15
const int yLow = 16;
const int yHigh = 18; // 14
int touchX;
int touchY;

// Ethernet
int eth_mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };  
int eth_ip[] = { 192,168,1,3 };
int eth_theserver[] = { 192,168,1,100 };
int eth_port = 12345;
EthernetClient client;

/*******************************/

void getTouchValues() {
//This function is the same, see top of this blog post
}

boolean process_touch() {
//Do whatever you want in here
}

/*******************************/
void setup(){
Serial.begin(9600);
Ethernet.begin(eth_mac, eth_ip);

Serial.println("connecting...");
if (client.connect(eth_theserver, eth_port)) {
Serial.println("connected");
} else {
Serial.println("connecting failed");
}
}

/*******************************/
void loop(){

getTouchValues();

Serial.print(touchX,DEC);   
Serial.print(",");     
Serial.println(touchY,DEC); 

process_touch();
}


Not too crazy. Calling getTouchValues() gets the touch position from the panel, then process_touch() handles it. You'll also notice I don't like to be consistent with the naming of my functions.

Here's the final script. You'll see I ignore the first few inputs. Probably more than I need to. It's because as the person is putting their full pressure on the touch panel it can read values that are lower than expected, registering a touch lower on the panel than expected. I also ignore incomplete inputs (X but no Y, or vice versa). I also want to ignore all touch until the user lifts their finger (so we don't get 999x inputs for one "button press"). For this, I ignore all touch inputs until the Arduino reads (0,0) from the touch panel again (which means nobody is touching it).

#include <SPI.h>

#include <Ethernet.h>
#include <EthernetClient.h>
#include <EthernetServer.h>
#include <EthernetUdp.h>
#include <util.h>

/*
** WARNING: Ethernet Sheild uses analog pins 0 and 1 for the SD card
**          Even if you're not using the SD card, there are 10k pull-ups
**          So readings will be inaccurate
*/

/*
**
** X values range from 80-750
** Lower value as low as 50 at extreme edge
** Upper value slightly dependant on Y coord, but much more affected by pressure
**
** Y values range from 150-720
** Lower value 180 with finger
** Upper value slightly dependant on X coord (range from 680-720)
*/

/*******************************/

// Touch
const int xLow = 17;
const int xHigh = 19; // 15
const int yLow = 16;
const int yHigh = 18; // 14
int touchX;
int touchY;
boolean ignore_touch = false;
const int TOUCHES_TO_IGNORE = 2; // ignore first 2 touches (filters bad inputs)
int ignored_touches = 0;

// Ethernet
uint8_t eth_mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };  
uint8_t eth_ip[] = { 192,168,1,3 };
uint8_t eth_theserver[] = { 192,168,1,100 };
int eth_port = 12345;
boolean eth_connected = false;
EthernetClient client;


/*******************************/

void getTouchValues(int delay_before_read = 10) {
pinMode(xLow,OUTPUT);
pinMode(xHigh,OUTPUT);
digitalWrite(xLow,LOW);
digitalWrite(xHigh,HIGH);
digitalWrite(yLow,LOW);
digitalWrite(yHigh,LOW);
pinMode(yLow,INPUT);
pinMode(yHigh,INPUT);
delay(delay_before_read);

//analog pins are 0-5
touchX = analogRead(yLow -14);

pinMode(yLow,OUTPUT);
pinMode(yHigh,OUTPUT);
digitalWrite(yLow,LOW);
digitalWrite(yHigh,HIGH);
digitalWrite(xLow,LOW);
digitalWrite(xHigh,LOW);
pinMode(xLow,INPUT);
pinMode(xHigh,INPUT);
delay(delay_before_read);

//analog pins are 0-5
touchY = analogRead(xLow - 14);
}

void music_play_pause() {
Serial.println("Play/Pause");
client.print('1');
}

void music_stop() {
Serial.println("Stop");
client.print('4');
}

void music_next() {
Serial.println("Next");
client.print('2');
}

void music_prev() {
Serial.println("Previous");
client.print('3');
}

void music_playlist_local() {
Serial.println("Switching to Local Libray");
client.print('a');
}

void music_playlist_dm() {
Serial.println("Switching to DJ Mixes");
client.print('c');
}

void music_playlist_eh() {
Serial.println("Switching to Electro House");
client.print('b');
}

boolean process_touch() {
if ((touchX == 0) && (touchY == 0)) {
ignore_touch = false;
ignored_touches = 0;
return false;
} else if ((touchX == 0) || (touchY == 0)) {
// Ignore partial touch inputs
return false;
} else if (ignore_touch) {
return false;
} else {
if (ignored_touches < TOUCHES_TO_IGNORE) {
ignored_touches++;
return false;
}
}
ignore_touch = true;

// Process the touch
if (touchX <= 200) { // Next or Prev
if (touchY <= 400)
music_prev();
else
music_next();
} 
else if ((touchX >= 250) && (touchX <= 400)) {
if ((touchY >= 280) && (touchY <= 520)) {
music_stop();
}
} 
else if ((touchX >= 440) && (touchX <= 670)) {
if ((touchY >= 200) && (touchY <= 660)) {
music_play_pause();
}
} 
else if ((touchX >= 700)) { // Playlists
if ((touchY < 250)) {
music_playlist_eh();
} 
else if ((touchY >= 350) && (touchY <= 510)) {
music_playlist_dm();
}
else {
music_playlist_local();
}
}

return true;
}

/*******************************/
void setup(){
Serial.begin(9600);
Ethernet.begin(eth_mac, eth_ip);

pinMode(relay_pin, OUTPUT);

Serial.println("connecting...");
if (client.connect(eth_theserver, eth_port)) {
Serial.println("connected");
} else {
Serial.println("connection failed");
}
}

/*******************************/
void loop(){

getTouchValues();
//  Serial.print(touchX,DEC);   
//  Serial.print(",");     
//  Serial.println(touchY,DEC); 

process_touch();
}

Wednesday, 11 January 2012

Python TCP Server & Keyboard Emulation

Using Python 3.2.2 to receive commands over TCP, emulate keypresses, and run commands. Sounds complex but the script is short & not too crazy. I did this on Windows 7 but it should work fine on other versions of Windows or python.

First lets emulate some key presses. This uses a wrapper[ish] for user32.dll, which is a really handy windows library!

import ctypes
user32 = ctypes.windll.user32

KEY_LEFT_WIN = 0x5B

user32.keybd_event(KEY_LEFT_WIN, 0,0,0) # press
user32.keybd_event(KEY_LEFT_WIN, 0,2,0) # release


No sweat! Full key (and mouse) code list at the bottom.

I also wanted keys to be pressed as a result of some incoming network data, so I needed to listen on a TCP socket:

#!/usr/bin/env python

import socket

LISTEN_ADDRESS = ''
LISTEN_PORT = 12345
BUFFER_SIZE = 512

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((LISTEN_ADDRESS, LISTEN_PORT))
print("Starting server on port", LISTEN_PORT)
s.listen(1)

while True:
conn, addr = s.accept()
print('Connection address:', addr)
while True:
data = conn.recv(BUFFER_SIZE)
if not data: break
if data == b'\x03': break # control+C
print("received data:", data)
conn.send(data) # echo
print("Client disconnected")
conn.close()
print("Exit")


Yes I agree, string formatting for python 3 is weird. One note here, the data comming in for me was ASCII text, which python 3.0+ considers to be data (not a string). To make it a string use the function bytes.decode(my_data).

Why use port 23? All of you old-timers reading this may remember a protocol called Telnet, which happens to run on port 23. All Telnet does is create a TCP connection and then send to/from the other host, displaying the data. What this means is you can connect to this python server using Telnet and just type commands! Of course, you could still write your own client if you wanted to.

That's pretty much it though. The script also runs system commands in a new thread (so they don't block) but that part was a one-liner. Here's the script in all it's glory:


#!/usr/bin/env python

import os
import sys
import socket
import ctypes
from threading import Thread # we'll run commands in a new thread, just in case they block

# Used to listen for connections
LISTEN_ADDRESS = ''
LISTEN_PORT = 12345
BUFFER_SIZE = 512

# Used for emulating key presses.
user32 = ctypes.windll.user32
# Virtual keys
VK_VOLUME_MUTE = 0xAD
VK_VOLUME_DOWN = 0xAE
VK_VOLUME_UP = 0xAF
VK_MEDIA_NEXT_TRACK = 0xB0
VK_MEDIA_PREV_TRACK = 0xB1
VK_MEDIA_STOP = 0xB2
VK_MEDIA_PLAY_PAUSE = 0xB3

# NOTE: bug in python, if using os.system(my_str) where my_str
# contains at least 2 sets of quotes, your must prefix "call" (windows only)
# Playlists
playlist_local = "call \"C:\\program files\\Windows Media Player\\wmplayer.exe\" /playlist \"Party - Jan2012\""
playlist_eh = "\"C:\\program files\\Windows Media Player\\wmplayer.exe\" http://scfire-ntc-aa04.stream.aol.com:80/stream/1025"
playlist_dm = "\"C:\\program files\\Windows Media Player\\wmplayer.exe\" http://80.94.69.106:6104/"

# Mapping expected input to actions
bytes_to_keys = {}
bytes_to_keys["1"] = VK_MEDIA_PLAY_PAUSE
bytes_to_keys["2"] = VK_MEDIA_NEXT_TRACK
bytes_to_keys["3"] = VK_MEDIA_PREV_TRACK
bytes_to_keys["4"] = VK_MEDIA_STOP
bytes_to_commands = {}
bytes_to_commands["a"] = playlist_local
bytes_to_commands["b"] = playlist_eh
bytes_to_commands["c"] = playlist_dm



def run_command(cmd):
os.system(cmd)

def do_action(char):
'''Takes a 1-character code and executes the action assoliated with it'''
if char in bytes_to_keys:
key_code = bytes_to_keys[char]
user32.keybd_event(key_code, 0, 0, 0) # press
user32.keybd_event(key_code, 0, 2, 0) # release
elif char in bytes_to_commands:
command = bytes_to_commands[char]
# print("running command:", command)
Thread(target=run_command, args=(command,)).start()
else:
print("unknown instruction:", char)


##########################################
################## Main ##################
##########################################

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((LISTEN_ADDRESS, LISTEN_PORT))
print("Starting server on port", LISTEN_PORT)
s.listen(1)


while True:
conn, addr = s.accept()
print('Connection address:', addr)
try:
while True:
data = conn.recv(BUFFER_SIZE)
if not data: break
str_data = bytes.decode(data) # data is non-unicode, but all python strings need to be
print("received data: \"{0}\"".format(str_data))
for char in str_data: # highly unlikely, but crazier things have happened
do_action(char)
print("Client disconnected")
except socket.error as e:
print("Socket error:", e)
except socket.timeout:
print("Connection timed-out")
except Exception as e:
print("Exception:", e)
finally:
conn.close()
print("Exit")





Windows Virtual Key List - http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
Windows Media Player Command Line Arguments - http://msdn.microsoft.com/en-us/library/windows/desktop/dd562624(v=vs.85).aspx
Python strings vs data - http://python.about.com/od/python30/ss/30_strings_3.htm

Saturday, 7 January 2012

Figured I'd start a blog

Lately I've been playing around with building little for-fun electronics, mostly using an Arduino and whatever I've taken apart. I figured why not document it, at the very least for myself. I'll probably add my old projects to here too, one of these days.