bitty.py - billroy/bitlash GitHub Wiki

bitty.py

The python program known as bitty.py is a serial port proxy. It runs on an Arduino-connected PC and makes the Arduino available over the network for connection via telnet, nc, or your favorite telnet client.

With Bitty you can make your Arduino available on the Internet with no extra hardware.

There are some nice features for Arduino users. You can unplug one Arduino and plug in another and bitty will find it. And the network port and baud rate are adjustable.

Requirements

Usage

To use: first, start the serial-to-net proxy and leave it running:

$ python bitty.py [options]

...and then, in another terminal window, connect to it with your favorite telnet or nc program:

$ nc localhost 8080
$ telnet localhost 8080
$ telnet arduino.bitlash.net 8080

Plug in Arduino via the USB-to-serial cable and you should connect up automatically.

Note: Type 'logout' when done for a clean disconnect.

Options

Run Bitty with the "--help" option to see a list of options:

Arduino.app/.../library/bitlash/extras bill$ python bitty.py --help
Usage: bitty.py [options]

Options:
-h, --help            show this help message and exit
-p PORT, --port=PORT  network connection port [8080]
-u USBDEVICE, --usbdevice=USBDEVICE
name of USB serial port device for serial connection
[/dev/tty.usbserial*]
-b BAUD, --baud=BAUD  baud rate for port specified by -u [57600]
-k, --keyboard-passthru
-d, --debug

-p, --port: set network port

Bitty normally listens for incoming connections on port 8080. If you want to use a different port, specify it like this:

$ python bitty.py -p 1234

-u, --usbdevice: set usb device name

To specify a particular usb device you can identify it like this:

$ python bitty.py -u TTYUSB0

-b, --baud: set serial port baud rate

Bitlash, by default, listens at 57600, so Bitty tries to connect at that rate. You can use a different rate by specifying it like this:

$ python bitty.py -b 115200

-k, --keyboard-passthru

This enables local keyboard input from the console running Bitty (in addition to the normal input from a remote network-connected user). You can use this option to turn Bitty into a terminal emulator. You can also use it for a "two steering wheel" setup: the remote user can type, and so can you.

$ python bitty.py -k

Source Code for bitty.py

#! /usr/bin/python
#
#	bitty.py: serial-to-network multiplexer for Arduino
#
#	This is a serial port proxy.  It makes a usbserial port available over the network
#	for connection via telnet, nc, or your favorite telnet client.
#
#	There are some nice features for Arduino users.  You can unplug one Arduino and
#	plug in another and bitty will find it.  And the network port and baud rate are
#	adjustable.
#
#	Requires:
#		python 2.4 or 2.5
#		pyserial from http://pyserial.wiki.sourceforge.net/pySerial
#			tested with pyserial 2.4 on OS X and Fedora 4
#
#	To use: first, start the serial-to-net proxy and leave it running:
#		$ python bitty.py [options]
#
#	and then, in another terminal window, connect to it with your favorite telnet or nc program:
#		$ nc localhost 8080
#		$ telnet localhost 8080
#		$ telnet arduino.bitlash.net 8080
#
#	plug in Arduino and you should connect up automatically.
#	Type 'logout' when done for a clean disconnect.
#
#
#	LICENSE
#
#	Copyright 2010 by Bill Roy
#
#	Permission is hereby granted, free of charge, to any person
#	obtaining a copy of this software and associated documentation
#	files (the "Software"), to deal in the Software without
#	restriction, including without limitation the rights to use,
#	copy, modify, merge, publish, distribute, sublicense, and/or sell
#	copies of the Software, and to permit persons to whom the
#	Software is furnished to do so, subject to the following
#	conditions:
#	
#	The above copyright notice and this permission notice shall be
#	included in all copies or substantial portions of the Software.
#	
#	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
#	EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
#	OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
#	NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
#	HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
#	WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
#	FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
#	OTHER DEALINGS IN THE SOFTWARE.
#
###############################################################################

__version__ = '1.0'
__copyright__ = 'Copyright 2010 by Bill Roy'

import serial, time, socket, commands, traceback, os, sys

# serial port config
usbdevice = None
baud = 57600

# network config
port = 8080

# options
kbdpassthru = False
showflow = False


# change below here at your own risk
serialport = None
netsocket = None
netclient = None
clientaddress = None

# Net pump thread, and queued interface
import Queue
netq = Queue.Queue()
serialq = Queue.Queue()


def closeSerialPort():
	global serialport
	if serialport and serialport.isOpen(): 
		print "Closing: ", serialport.portstr
		try:
			if netclient: 
				netclient.send("Closing ")
				netclient.send(serialport.portstr)
				netclient.send("\r\n");
		except: pass
		serialport.close()


def openSerialPort():
	global serialport
	closeSerialPort()


	# serial port autoconfig
	device = usbdevice					 # command line overrides auto select
	if not device:
		devicelist = commands.getoutput("ls /dev/tty.usbserial*")
		#devicelist = commands.getoutput("ls /dev/ttyUSB*")		# this works on the XO/Fedora
	
		if devicelist[0] == '/': device = devicelist
	if not device: 
		print "Waiting for device..."
		return False

	print "Connecting to", device, baud, "..."
	if netclient:
		netclient.send('Connecting to ');
		netclient.send(device);
		netclient.send('... ');

	try:
		# two stop bits helps paste-to-terminal not lose characters
		serialport = serial.Serial(device, baud, timeout=0, stopbits=serial.STOPBITS_TWO)
		print 'Opened port: ', serialport.portstr
		if netclient:
			netclient.send("connected.\r\n")
	except:
		print 'Failed to open port'
		if netclient:
			netclient.send("failed.\r\n")
		return False
	return True


# thread to read and queue serial input
# assumes opening the serial port is handled elsewhere
def serialPumpTask(usbdevice, baud):
	while True:
		if serialport and serialport.isOpen():
			try:
				data = serialport.read(1024);
				if data:
					if showflow: print "SER:",data
					serialq.put(data)
				else:
					time.sleep(0.1)
			except:
				print "Exception reading serial port"
				traceback.print_exc()
				closeSerialPort()
		else:
			time.sleep(1.0)

def kbdPumpTask(d1,d2):
	while True:
		try:
			netq.put(os.read(sys.stdin.fileno(), 1))
		except:
			print "Exception in keyboard handler"
			traceback.print_exc()

# send network data to the serial port
def handleNetworkInput(data):
	global serialport
	try:
		if serialport and serialport.isOpen(): 
			#serialport.write(data);
			for i in range(len(data)): 
				serialport.write(data[i])
				time.sleep(0.05)
	except:
		print "Exception writing serial port"
		traceback.print_exc()
		closeSerialPort()


# send serial port data to the network socket
def handleSerialInput(data):
	global netclient
	try:
		if netclient: netclient.send(data)
		if kbdpassthru: 
			sys.stdout.write(data)
			sys.stdout.flush()
	except:
		print "Exception writing network port"
		traceback.print_exc()
		closeSerialPort()


# thread to read and queue network input
def netPumpTask(port, dummy):

	global netsocket, netclient, clientaddress
	netsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	netsocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
	#socket.setblocking(0)
	netsocket.bind(('', port)) 
	netsocket.listen(1) 	# allow no waiting connections

	while True: 
		try:
			print "Listening for network connection on", socket.getfqdn(), ':', port, '/', socket.gethostname()
			netclient, clientaddress = netsocket.accept()
			print "Connection from", clientaddress
			netclient.send("g'day from bitty ")
			netclient.send(__version__)
			netclient.send(" -- type 'logout' to disconnect")
			netclient.send("\r\n");

			while True:
				if not netclient: break		# client went away: go get another one
				while not openSerialPort():
					netclient.send("Waiting for device...\r\n");
					time.sleep(2);

				while serialport.isOpen():
					data = netclient.recv(10240) 
					if data: 
						if showflow: print "NET:", data
						if data[0:6].find('logout') == 0:
							if netclient: netclient.send("Disconnected.\r\n");
							netclient.close()
							netclient = None
							closeSerialPort()
							time.sleep(1.0)		# pause to allow disconnect
						else: 
							netq.put(data)
					else: time.sleep(0.1)
		except:
			print "Exception in net pump"
			traceback.print_exc()
			closeSerialPort()


def parseOptions():
	# parse command line options
	from optparse import OptionParser
	usage = "usage: %prog [options]"
	parser = OptionParser()

	parser.add_option("-p", "--port",
						dest="port", type='int',
						help="network connection port [8080]")
	
	parser.add_option("-u", "--usbdevice",
						dest="usbdevice",
						help="name of USB serial port device for serial connection [/dev/tty.usbserial*]")

	parser.add_option("-b", "--baud",
						dest="baud", type='int',
						help="baud rate for port specified by -u [57600]")

	parser.add_option("-k", "--keyboard-passthru", action="store_true", dest="kbdpassthru")

	parser.add_option("-d", "--debug", action="store_true", dest="debug")

	(options, args) = parser.parse_args()

	if options.port:
		global port
		port = options.port
		print "Listening for connections on port:", port
	
	if options.baud:
		global baud
		baud = options.baud
		print "Serial port baud rate set to:", baud
	
	if options.usbdevice:
		global usbdevice
		usbdevice = options.usbdevice
		print "Using USB serial device: ", usbdevice

	if options.debug:
		global showflow
		showflow = True

	if options.kbdpassthru:
		global kbdpassthru
		kbdpassthru = True

	#if options.password:
	#	import getpass
	#	password = getpass.getpass("Password:")


if __name__ == '__main__':

	parseOptions()

	import thread
	net_pump_thread = thread.start_new_thread(netPumpTask, (port, 0))
	serial_pump_thread = thread.start_new_thread(serialPumpTask, (usbdevice, baud))
	if kbdpassthru:
		kbd_pump_thread = thread.start_new_thread(kbdPumpTask, (0,0))

	while True:
		while not netq.empty(): 
			handleNetworkInput(netq.get())
			netq.task_done()

		while not serialq.empty():
			handleSerialInput(serialq.get())
			serialq.task_done()

		time.sleep(0.1)