TTL Serial Camera


28 posts   Page 1 of 2   1, 2
by moallen » Fri Feb 08, 2013 1:56 am
For this test I am using Adafruit's VC0706 TTL serial camera. These are intelligent and impressive little cameras. The interface consists of just 3 wires; Tx, Rx and Gnd. Although the camera itself requires +5V, the interface functions at +3.3V levels making it ideal for connecting to a RPi. The default baud rate is 38400.

Its default and maximum image size is 640x480, which it can produce at 30fps. It produces a pretty good image. Its command set is extensive with many options, including motion detection. It also has a NTSC connection should someone want to connect it to a TV or recorder.

Here's picture of it mounted to a small tripod and connected to my Gertboard. As usual the RPi's GPIO pins are protected by running Tx and Rx through the Gertboard's buffers.

Image

This might be a good time to provide more details on these particular Gertboard connections, since Tx and Rx are going in opposite directions.

Camera Tx (blue) > BUF1 (J10), B1 IN jumper installed, B1 (J3) > GPIO15 (J2) > Rpi (RxD)
Camera Rx (green) < BUF2 (J10), B2 OUT jumper installed, B2 (J3) < GPIO14 (J2) < RPi (TxD)

I'm going to use a Python test script from Adafruit's website. It takes just 1 picture each time it is run, although you can see it takes quite a bit of code to do this.

raspi_camera.py

Code: Select all
#!/usr/bin/python
# python code for interfacing to VC0706 cameras and grabbing a photo
# pretty basic stuff
# written by ladyada. MIT license
# revisions for Raspberrry Pi by Gordon Rush

import serial

BAUD = 38400
# this is the port on the Raspberry Pi; it will be different for serial ports on other systems.
PORT = "/dev/ttyAMA0"
# PORT = "COM8"

TIMEOUT = 0.5    # I needed a longer timeout than ladyada's 0.2 value
SERIALNUM = 0    # start with 0, each camera should have a unique ID.

COMMANDSEND = 0x56
COMMANDREPLY = 0x76
COMMANDEND = 0x00

CMD_GETVERSION = 0x11
CMD_RESET = 0x26
CMD_TAKEPHOTO = 0x36
CMD_READBUFF = 0x32
CMD_GETBUFFLEN = 0x34

FBUF_CURRENTFRAME = 0x00
FBUF_NEXTFRAME = 0x01

FBUF_STOPCURRENTFRAME = 0x00

getversioncommand = [COMMANDSEND, SERIALNUM, CMD_GETVERSION, COMMANDEND]
resetcommand = [COMMANDSEND, SERIALNUM, CMD_RESET, COMMANDEND]
takephotocommand = [COMMANDSEND, SERIALNUM, CMD_TAKEPHOTO, 0x01, FBUF_STOPCURRENTFRAME]
getbufflencommand = [COMMANDSEND, SERIALNUM, CMD_GETBUFFLEN, 0x01, FBUF_CURRENTFRAME]

def checkreply(r, b):
    r = map( ord, r )
    if( r[0] == COMMANDREPLY and r[1] == SERIALNUM and r[2] == b and r[3] == 0x00):
        return True
    return False

def reset():
    cmd = ''.join( map( chr, resetcommand ) )
    s.write(cmd)
    reply = s.read(100)
    r = list(reply)
    if checkreply( r, CMD_RESET ):
        return True
    return False

def getversion():
    cmd = ''.join( map( chr, getversioncommand ))
    s.write(cmd)
    reply = s.read(16)
    r = list(reply)
    # print r
    if checkreply( r, CMD_GETVERSION ):
        # print r
        return True
    return False

def takephoto():
    cmd = ''.join( map( chr, takephotocommand ))
    s.write(cmd)
    reply = s.read(5)
    r = list(reply)
    # print r
    if( checkreply( r, CMD_TAKEPHOTO) and r[3] == chr(0x0)):
        return True
    return False

def getbufferlength():
    cmd = ''.join( map( chr, getbufflencommand ))
    s.write(cmd)
    reply = s.read(9)
    r = list(reply)
    if( checkreply( r, CMD_GETBUFFLEN) and r[4] == chr(0x4)):
        l = ord(r[5])
        l <<= 8
        l += ord(r[6])
        l <<= 8
        l += ord(r[7])
        l <<= 8
        l += ord(r[8])
        return l
    return 0

readphotocommand = [COMMANDSEND, SERIALNUM, CMD_READBUFF, 0x0c, FBUF_CURRENTFRAME, 0x0a]

def readbuffer(bytes):
    addr = 0   # the initial offset into the frame buffer
    photo = []

    # bytes to read each time (must be a mutiple of 4)
    inc = 8192
    #inc = 256

    while( addr < bytes ):
        # on the last read, we may need to read fewer bytes.
        chunk = min( bytes-addr, inc );

        # append 4 bytes that specify the offset into the frame buffer
        command = readphotocommand + [(addr >> 24) & 0xff,(addr>>16) & 0xff,(addr>>8 ) & 0xff,addr & 0xff]
       
        # append 4 bytes that specify the data length to read
        command += [(chunk >> 24) & 0xff,(chunk>>16) & 0xff,(chunk>>8 ) & 0xff,chunk & 0xff]

        # append the delay
        command += [1,0]

        # print map(hex, command)
        print "Reading", chunk, "bytes at", addr

        # make a string out of the command bytes.
        cmd = ''.join(map(chr, command))

        s.write(cmd)

        # the reply is a 5-byte header, followed by the image data
        #   followed by the 5-byte header again.
        reply = s.read(5+chunk+5)

        # convert the tuple reply into a list
        r = list(reply)
        if( len(r) != 5+chunk+5 ):
            # retry the read if we didn't get enough bytes back.
            print "Read", len(r), "Retrying."
            continue

        if( not checkreply(r, CMD_READBUFF)):
            print "ERROR READING PHOTO"
            return
       
        # append the data between the header data to photo
        photo += r[5:chunk+5]

        # advance the offset into the frame buffer
        addr += chunk

    print addr, "Bytes written"
    return photo

######## main
s = serial.Serial( PORT, baudrate=BAUD, timeout = TIMEOUT )

# forces camera to default settings, aka 38400 baud, etc
reset()

if( not getversion() ):
    print "Camera not found"
    exit(0)

print "VC0706 Camera found"

if takephoto():
    print "Snap!"

bytes = getbufferlength()

print bytes, "bytes to read"

photo = readbuffer( bytes )

f = open( "photo.jpg", 'w' )

photodata = ''.join( photo )

f.write( photodata )

f.close()


Image

This quick shot was taken inside on a cloudy day late in the afternoon with the above program. It is 320x240x24bit and is 12472 bytes in size. The camera has a 60 degree viewing angle, which causes a certain amount of curvature at the outer edges.
http://allenmlab.blogspot.com/
User avatar
Posts: 36
Joined: Fri Dec 28, 2012 6:37 pm
by Ravenous » Fri Feb 08, 2013 12:14 pm
Good write up.

I've not started investigating cameras yet, but it looks like this unit will be handy for low-resolution stuff in the future. Lots of people have been bemoaning the fact you can't attach two of the 5MP cameras for stereo (collision-avoiding robots and the like), but two of these would probably do the trick without tying up USB ports...
Posts: 1593
Joined: Fri Feb 24, 2012 1:01 pm
Location: UK
by mwilliams03 » Thu Feb 14, 2013 6:01 pm
impressive, i have been looking for a camera to attach to my Pi.
Is a GERT board required?
How hard do you think it would be to get video working?
Posts: 57
Joined: Mon Oct 29, 2012 2:56 pm
by moallen » Thu Feb 14, 2013 10:39 pm
The Gertboard isn't required at all. I have one so I try to use it to protect the RPi's GPIO pins. But sometimes I don't bother to connect it, if it's not already hooked up.

Not sure about video. Haven't tried that on the RPi yet. It did work ok when I had the camera connected to my regular laptop. The camera has a buffer, but I'm not sure how many frames it can hold before it gets full. I suppose that might be an issue using the RPi. Something to put on my to do list.

As far as using 2 of these for stereo video, the RPi just has 1 UART. But maybe other pins could be setup as a pseudo serial input. Haven't tried that yet. Something else for my to do list...
http://allenmlab.blogspot.com/
User avatar
Posts: 36
Joined: Fri Dec 28, 2012 6:37 pm
by feroze » Mon Apr 01, 2013 3:59 am
Hello,

I have the same TTL camera hooked up to my Raspberry Pi and I have having trouble running the Python script.

Here is what I have done.

Have Python installed
Installed PySerial.
Download the raspi_camera.py script from ladyadas github

in lxterminal I typed:

chmod +x raspi_camera.py - to make the file executable

then

sudo ./raspi_camera.py

When I run it I get IndexError: list index out of range and it lists certain lines of code.

I this error message:

Traceback (most recent call last):
File "*./raspi_camera.py", line 153, in <module>
reset()
File "./raspi_camera.py", line 47, in reset
if checkreply(r, CMD_RESET):
File "./raspi_camera.py", line 38, in checkreply
if (r[0] == 0x76 and r[1] == SERIALNUM and r[2] == b and r[3] == 0x00):
IndexError: list index out of range

What is the issue?
Last edited by feroze on Mon Apr 01, 2013 5:31 am, edited 3 times in total.
Posts: 25
Joined: Wed Mar 27, 2013 5:20 pm
by feroze » Mon Apr 01, 2013 4:37 am
Do I have to do this to use TTL Serial Camera with Raspberry Pi?

http://www.hobbytronics.co.uk/raspberry-pi-serial-port

Disable serial from console and enable it f or program use?

Will this affect using SSH or VPN?
Posts: 25
Joined: Wed Mar 27, 2013 5:20 pm
by feroze » Mon Apr 01, 2013 5:45 am
Ok so I powered the ttl camera with my arduino while keeping the data connections to the Raspberry Pi because I thought it might be a power issue. Now I am getting different results.

When I run sudo ./raspi_camera.py

It says Camera Not Found.

So the script is running perfectly right now. By the way I had to enable the serial connection by using this link: http://www.hobbytronics.co.uk/raspberry-pi-serial-port

Now I am thinking it must be a connection error but I dont get it, it should work.

I have the weatherproof camera and the white wire is Tx and Green wire is Rx.

I connected the white wire directly to Tx and the green wire directly to Rx, then power to 5v and gnd.

What could I be doing wrong?
Posts: 25
Joined: Wed Mar 27, 2013 5:20 pm
by feroze » Mon Apr 01, 2013 8:30 am
I added a couple debug statements

Code: Select all
def checkreply(r, b):
    r = map (ord, r)
    print 'checkreply: completed  map, len=', len(r)    #added for debug
    print 'r =', r          #added for debug
    if (r[0] == 0x76 and r[1] == SERIALNUM and r[2] == b and r[3] == 0x00):
        return True
    print 'checkReply(): failed, r=', r
    return False


This is what I get:

checkreply: completed map, len=4
r= [86, 0, 38, 0]
checkReply(): failed r= [86, 0, 38, 0]
checkreply: completed map, len=4
r = [86, 0, 17, 0]
checkReply(): failed r= [86, 0, 17, 0]
Camera not found
Posts: 25
Joined: Wed Mar 27, 2013 5:20 pm
by moallen » Mon Apr 01, 2013 1:12 pm
I might be reading your post incorrectly, but it sounds like you have the camera's Tx connected to the RPi's Tx and the camera's Rx connected to the RPi's Rx. If that's the case, be sure to reverse Tx and Rx. In other words, the camera's transmit data on Tx needs to go into the RPi's receive data pin Rx.

If you're OK on that, and it still doesn't work, I would try to communicate with the camera directly using a terminal program (minicom or screen) and verify data is being received on the right wire.

Actually, if I remember correctly, the camera sends out a short message every time it is powered up on the camera's Tx wire. You should see that in a terminal program, too. Maker sure the terminal program is set to 38400 baud.

The main problem I ran into was trying to use the camera in Python on a Windows machine. I never could get that to work. It ran fine though on Python on a Linux machine or RPi's Python/Linux.
http://allenmlab.blogspot.com/
User avatar
Posts: 36
Joined: Fri Dec 28, 2012 6:37 pm
by feroze » Mon Apr 01, 2013 2:58 pm
Thanks for the reply! I really want to get this working.

How can I use minicom to see the output of the camera? I installed minicom using these instructions: http://www.hobbytronics.co.uk/raspberry-pi-serial-port

Then opened up minicom in terminal using:

minicom -b 38400 -o -D /dev/ttyAMA0

Then unplugged in plugged in the camera and I see nothing.

How do I test the camera?

Also does the checkreply r = [86, 0, 38, 0] mean anything? It is looking for [118, 0, 38, 0] for checkreply to become true.
Posts: 25
Joined: Wed Mar 27, 2013 5:20 pm
by feroze » Mon Apr 01, 2013 3:48 pm
So I connected the TTL Camera to the Arduino, and when I open serial monitor I can see some data being transmitted.

But when the TTL Camera is connected to the Raspberry Pi, and when I change the BAUD rate to anything, I still get the same error, Camera Not Found, so the camera is not talking serial to my raspberry Pi. I don't know why the connections seem all fine, and I have tried it both ways.

I am pretty sure I have it connected right because with the camera connected to the Pi this way, I get the Camera not found error, any other way I connect it, the script crashes, with some traceback errors.

So if the Camera is connected right, and I am using the same script as others why is it failing for me? I mean Ive tried Tx and Rx wires both ways, there aren't any other ways to connect it.

The major clue I have it the r array. It does generate an r array of [86, 0, 38, 0] but it expects [118, 0, 38, 0] The fact that it generates an r array means something.
Posts: 25
Joined: Wed Mar 27, 2013 5:20 pm
by feroze » Mon Apr 01, 2013 7:40 pm
Check out this protocol sheet: http://www.adafruit.com/datasheets/VC0706protocol.pdf

in hexadecimal 86 is 0x56 and 118 is 0x76 which match send and receive protocols.

in checkReply() its looking for r = [118, 0, 38, 0]

but it tries to match it with r = [86, 0, 38, 0]

whats going on? btw I have the weatherproof version of the ttl camera from adafruit
Posts: 25
Joined: Wed Mar 27, 2013 5:20 pm
by feroze » Mon Apr 01, 2013 10:00 pm
Here is the debug code I am using the in the checkReply function:

Code: Select all
def checkreply(r, b):
   r = map( ord, r )
   print 'checkreply: completed map, len= ', len(r) #added for debug
   print 'r = ', r #added for debug
   if( r[0] == COMMANDREPLY and r[1] == SERIALNUM and r[2] == b and r[3] == 0x00):
      return True
   print 'checkReply(): failed, r =', r
   print 'COMMANDREPLY = ', COMMANDREPLY, 'SERIALNUM= ', SERIALNUM, 'b= ', b
   return False


Here is the output in terminal:

Code: Select all
checkreply: completed map, len= 4
r = [86, 0, 38, 0]
checkReply(): failed, r= [86, 0, 38, 0]
COMMANDREPLY =118, SERIALNUM = 0, b = 38
checkreply: completed map, len= 4
r = [86, 0, 17, 0]
checkReply(): failed, r= [86, 0, 17, 0]
COMMANDREPLY =118, SERIALNUM = 0, b = 17
Camera not found


Any ideas? This is really weird.
Posts: 25
Joined: Wed Mar 27, 2013 5:20 pm
by moallen » Mon Apr 01, 2013 11:11 pm
I just reread through your posts and have a couple questions. Did you go ahead and disable getty and kernel console output on ttyAMA0? BTW, that won't affect SSH or VNC. It only disables a direct console connection. I use SFTP, SSH and VNC all the time with my RPi's.

Also, have you verified your RPi serial pins are working using a turn-around-jumper on tx/rx with minicom?
http://allenmlab.blogspot.com/
User avatar
Posts: 36
Joined: Fri Dec 28, 2012 6:37 pm
by moallen » Mon Apr 01, 2013 11:18 pm
Have you tried to communicate with the camera from a Windows computer? For example, using their VC0706 COMM tool? It's complicated but would also be a good way to test the camera.

Here's the tests I did using another com program RealTerm (also from Windows). I used it because it's easier to send strings and blocks than other programs.

REALTERM
Display Ascii (lower one)
Can copy/paste Dec code from this doc
Send Numbers 86 0 17 0 (x56 x00 x11 x00), Gets firmware version
Returns in hex: 76 00 11 00 0B 56 43 30 33 20 31 2E 30 30
Returns in Ascii: vVC0703 1.00
Send Numbers: 86 0 36 3 1 86 228 (x56 x00 x24 x03 x01 x56 xE4), Sets baud rate to 19200
Send Numbers: 86 0 36 3 1 42 242 (x56 x00 x24 x03 x01 x2A xF2), Sets baud rate to 38400
Returns in hex: 76 00 24 00 00 if ok
Change Port Baud in RealTerm as appropriate
Be sure to close port before leaving RealTerm by clicking “Open” button
See: VC0706protocol.pdf for command structure 
To read an image in RealTerm:
1st send FBUF_CNTRL to stop the current frame
$56 $00 $36 $01 $00
Send Numbers or decimal in RealTerm: 86 0 54 1 0
Returns hex 76 00 36 00 00 if ok

2nd send GET_FBUF_LEN to get the frame’s length
$56 $00 $34 $01 $00
Send Numbers or decimal in RealTerm: 86 0 52 1 0
Returns hex 76 00 34 00 04 00 00 49 28 as an example

3rd send READ_FBUF to get the frame after starting capture.txt
$56 $00 $32 $0C $00 $0A $00 $00 $00 $10 $00 $00 $49 $28 $10 $00 using example
Send Numbers or decimal in RealTerm: 86 0 50 12 0 10 0 0 0 16 0 0 73 40 16 0
Returns: 7600320000 if ok at beginning and end of the large block of image data
NOTE: hex 4928 = dec 18728, capture file length was 18738 or 18728 + 5 ok bytes at beginning and 5 ok bytes at end, hex 49 = dec 73, hex 28 = dec 40
http://allenmlab.blogspot.com/
User avatar
Posts: 36
Joined: Fri Dec 28, 2012 6:37 pm
by moallen » Mon Apr 01, 2013 11:29 pm
feroze wrote:Check out this protocol sheet: http://www.adafruit.com/datasheets/VC0706protocol.pdf

in hexadecimal 86 is 0x56 and 118 is 0x76 which match send and receive protocols.

in checkReply() its looking for r = [118, 0, 38, 0]

but it tries to match it with r = [86, 0, 38, 0]

whats going on? btw I have the weatherproof version of the ttl camera from adafruit


It's been quite awhile since I've done anything with this camera. I'm going to have to try this from a terminal program, but in the back of my mind I'm thinking the camera returns the same thing it was sent if it doesn't like something about a command it gets. Maybe that's what you're seeing. It really is easier to test it from a terminal program, because you know exactly what you send it and what to expect back. The Python program can be misleading if something isn't working right.
http://allenmlab.blogspot.com/
User avatar
Posts: 36
Joined: Fri Dec 28, 2012 6:37 pm
by feroze » Tue Apr 02, 2013 12:17 am
Ok so I tried to use Minicom to detect the startup serial from the camera following your instructions, and nothing, no serial input detected. So I guess the Raspberry Pi is not picking up any serial input.

I did disable getty and the kernel output following these instructions: http://www.hobbytronics.co.uk/raspberry-pi-serial-port

So now the next step is to verify the RPi serial pins are working using a turn-around-jumper on tx/rx with minicom.

How do I do this? I have a bunch of electronic components, but what is a turn around jumper?
Posts: 25
Joined: Wed Mar 27, 2013 5:20 pm
by feroze » Tue Apr 02, 2013 2:39 am
You are exactly right about the r array! The camera is just sending back the same serial signal it was sent, I have verified this in minicom, with the camera hooked up, whatever I type just comes out onto terminal.

So this must be what is going on with the r array and explains a lot.

The real issue here is the camera is not sending any serial data to the raspberry pi. Testing it with minicom shows this.

At least I know the serial ports on the Raspberry Pi are working with the minicom test.

Secondly, the Camera is definitely working too and I know for sure which wires go where. I hooked the camera up to my arduino and then used the Com Tool for Windows to check if it can read images. And it can!

So Now I am going to hook the camera back up to the Raspberry Pi with all the right connections.

I just did that and tested it with Minicom and still nothing. With the camera powered off, I start minicom 'minicom -b 38400 -o -D /dev/ttyAMA0'

Then I plug in the Camera and nothing appears in the terminal. Nothing at all.

Is there anything else I can do to get the Camera to talk serial? It worked with the Com Tool, but maybe it is still malfunctioning somehow?
Posts: 25
Joined: Wed Mar 27, 2013 5:20 pm
by moallen » Tue Apr 02, 2013 3:06 am
hmm... it sure sounds like your outdoor version of this camera works differently than mine.

I've been looking around for a Linux serial terminal emulation program that can send & receive frames or blocks of data in various formats. That's what we need to test the camera from the RPi like I've tested it on Windows using RealTerm. Programs like minicom work in a character mode, so you can't send a command string followed by a carriage return.

I might have found a Python program that will work. At least it seems to work on my Linux laptop. In the morning I'll see if I can get it to work on the RPi and see if I can send full command strings to my camera.

The command to get it to return its version is only 4 bytes long, but I don't think you can send it correctly with minicom. I'm not even sure if it has a way to send hex x56 x00 x11 x00 or even decimal. I think it just sends ascii characters.

I wonder if Adafruit can confirm for sure that the firmware in your outdoor version is exactly the same as my indoor camera. I bought mine there, too.

Also, I was wondering if you used the code on their site or the code I had posted in my blog. I did have to make a couple changes to get mine to work on the RPi.
http://allenmlab.blogspot.com/
User avatar
Posts: 36
Joined: Fri Dec 28, 2012 6:37 pm
by feroze » Tue Apr 02, 2013 3:25 am
What about this program called WebIOPi?

http://code.google.com/p/webiopi/

Check it out. I think I might be able to send serial blocks of data using the program.

Can you tell me the version block to try it with?
Posts: 25
Joined: Wed Mar 27, 2013 5:20 pm
by feroze » Tue Apr 02, 2013 3:57 am
So I tried typing in: 86 0 17 0 and the same in hex with spaces and then the same in binary with no spaces, still with no response.

It just might be this weatherproof camera is different than the other kind, but it seems like the same arduino sketches work?

Check out WebIOPi, the serial monitor in the program is really cool. I am not sure if this will work for the type of test you are talking about but it seems to take in a string of characters before you send the output unlike minicom which is letter by letter.

Also thanks a bunch! Your help has been so valuable to me, I have learn't a lot about computers and hardware trying to get this to work, even if it doesn't in the end.
Posts: 25
Joined: Wed Mar 27, 2013 5:20 pm
by moallen » Tue Apr 02, 2013 2:12 pm
feroze:

Here's a few more things to try.

If you have a USB/FTDI/TTL adapter, you can use it to try a different serial port with your RPi. I just now successfully tested and ran my camera on it. This would eliminate whether or not your RPi's standard serial port is causing a problem. My RPi recognized mine as /dev/ttyUSB0.

Next thing to try - I did find a great little Python serial terminal emulator program that does exactly what I usually do with RealTerm on a Windows machine. It can send & receive strings of serial data as ascii, hex, decimal and binary. I did have to first install the wx library stuff on the RPi with the following command:

sudo apt-get install python-wxgtk2.8 python-wxtools wx2.8-i18n libwxgtk2.8-dev

I do use wx with Python on my other computers. I just hadn't gotten around to trying it on the RPi. So now I have it there, too. It's a library for creating Python windows, frames, buttons, etc.

The Python serial terminal emulator program is called RandTerm.py It should be pasted below.
From a terminal screen, run: sudo python RandTerm.py Set your port to /dev/ttyAMA0 if using the standard RPi serial port or /dev/ttyUSB0 if using an USB/FTDI/TTL port. Verify all is working by seeing if your camera sends out a boot up message on power up with RandTerm RX Format set to Ascii. Then set both receive & send to hex and try sending commands to the camera. For example sending 56 00 11 00 causes it to return the firmware version in hex as 76 00 11 00 0b 56 43 30 37 30 33 20 31 2e 30 30. Switch to ascii and it will be something like VC0703 1.00
Yours will probably be a little different.

Good find on WebIOPi. It looks like another good tool. I'll have to give it a try, although it looks like overkill for just sending a receiving simple strings of serial data to things like a jpg camera, RFID reader, Xbee's, etc.

Code: Select all
#  Copyright 2010 Randolph C Voorhies
#  http://ilab.usc.edu/~rand
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.

import wx
import serial
import threading
from threading import Thread
import time


rxStyle = wx.TextAttr(
  colText = wx.Colour(100, 0, 0)
)
txStyle = wx.TextAttr(
  colText = wx.Colour(0, 0, 100)
)

parityMap = {
  'None':  serial.PARITY_NONE,
  'Even':  serial.PARITY_EVEN,
  'Odd':   serial.PARITY_ODD,
}

stopMap = {
  '1':   serial.STOPBITS_ONE,
  '2':   serial.STOPBITS_TWO
}

bytesizeMap = {
  '5': serial.FIVEBITS,
  '6': serial.SIXBITS,
  '7': serial.SEVENBITS,
  '8': serial.EIGHTBITS
}


class randtermFrame(wx.Frame, Thread):
  ##################################################
  def __init__(self, parent, title):
    Thread.__init__(self)
    wx.Frame.__init__(self, parent, title=title, size=(600, 400))

    self.cfg = wx.Config('randterm')

    self.historyLock = threading.Lock()
    self.history = []

    self.Bind(wx.EVT_ACTIVATE, self.OnActivate)


    self.CreateStatusBar()
    # File Menu
    fileMenu = wx.Menu()
    menuAbout = fileMenu.Append(
      wx.ID_ABOUT, "&About", " Information about randterm")
    self.Bind(wx.EVT_MENU, self.OnAbout, menuAbout)
    menuExit = fileMenu.Append(wx.ID_EXIT, "E&xit", " Exit")
    self.Bind(wx.EVT_MENU, self.OnExit, menuExit)

    # Connect Menu
    self.connectMenu = wx.Menu()
    ## Port Selection
    self.setPort = self.connectMenu.Append(wx.ID_ANY, 'Set Port...')
    self.Bind(wx.EVT_MENU, self.OnSetPort, self.setPort)
    self.connectMenu.AppendSeparator()
    self.portName = ""
    ## Baud SubMenu
    self.baudRadios = []
    self.baudMenu = wx.Menu()
    self.baudRadios.append(self.baudMenu.AppendRadioItem(wx.ID_ANY, '2400'))
    self.baudRadios.append(self.baudMenu.AppendRadioItem(wx.ID_ANY, '4800'))
    self.baudRadios.append(self.baudMenu.AppendRadioItem(wx.ID_ANY, '9600'))
    self.baudRadios.append(self.baudMenu.AppendRadioItem(wx.ID_ANY, '19200'))
    self.baudRadios.append(self.baudMenu.AppendRadioItem(wx.ID_ANY, '38400'))
    self.baudRadios.append(self.baudMenu.AppendRadioItem(wx.ID_ANY, '57600'))
    self.baudRadios.append(self.baudMenu.AppendRadioItem(wx.ID_ANY, '115200'))
    self.baudRadios.append(self.baudMenu.AppendRadioItem(wx.ID_ANY, '312500'))
    self.connectMenu.AppendMenu(wx.ID_ANY, 'Baud Rate',     self.baudMenu)
    ## Parity SubMenu
    self.parityRadios = []
    self.parityMenu = wx.Menu()
    for k,v in parityMap.items():
      self.parityRadios.append(self.parityMenu.AppendRadioItem(wx.ID_ANY, k))
    self.connectMenu.AppendMenu(wx.ID_ANY, 'Parity',        self.parityMenu)
    ## Byte Size SubMenu
    self.byteRadios = []
    self.byteMenu = wx.Menu()
    for k,v in bytesizeMap.items():
      self.byteRadios.append(self.byteMenu.AppendRadioItem(wx.ID_ANY, k))
    self.connectMenu.AppendMenu(wx.ID_ANY, 'Byte Size',     self.byteMenu)
    ## Stop Bits SubMenu
    self.stopbitsRadios = []
    self.stopbitsMenu = wx.Menu()
    for k,v in stopMap.items():
      self.stopbitsRadios.append(self.stopbitsMenu.AppendRadioItem(wx.ID_ANY, k))
    self.connectMenu.AppendMenu(wx.ID_ANY, 'Stop Bits',     self.stopbitsMenu)
    ## Flow Control SubMenu
    self.flowMenu = wx.Menu()
    self.xonoffCheck = self.flowMenu.AppendCheckItem(wx.ID_ANY, 'Xon/Xoff')
    self.rtsctsCheck = self.flowMenu.AppendCheckItem(wx.ID_ANY, 'RTS/CTS')
    self.dsrdtrCheck = self.flowMenu.AppendCheckItem(wx.ID_ANY, 'DSR/DTR')
    self.connectMenu.AppendMenu(wx.ID_ANY, 'Flow Control',  self.flowMenu)
    ## Open Connection Item
    self.connectMenu.AppendSeparator()
    openConnection  = self.connectMenu.Append(
      wx.ID_ANY, '&Open Connection', 'Open Connection')
    self.Bind(wx.EVT_MENU, self.OnSetConnection, openConnection)
    closeConnection = self.connectMenu.Append(
      wx.ID_ANY, '&Close Connection', 'Close Connection')
    self.Bind(wx.EVT_MENU, self.OnCloseConnection, closeConnection)
    # Menu Bar
    menuBar = wx.MenuBar()
    menuBar.Append(fileMenu,    "&File")
    menuBar.Append(self.connectMenu, "&Connect")
    self.SetMenuBar(menuBar)

    # Setup the defaults
    self.readDefaults()

    # Main Window
    mainSizer = wx.BoxSizer(wx.VERTICAL)
    # Serial Output Area
    outputSizer = wx.BoxSizer(wx.VERTICAL)
    ## Output Type
    topSizer = wx.BoxSizer(wx.HORIZONTAL)
    self.displayTypeRadios = wx.RadioBox(self, wx.ID_ANY,
                                       style=wx.RA_HORIZONTAL, label="RX Format",
                                       choices = ('Ascii', 'Decimal', 'Hex', 'Binary'))
    self.Bind(wx.EVT_RADIOBOX, self.OnChangeDisplay, self.displayTypeRadios)
    topSizer.Add(self.displayTypeRadios, 0)
    self.clearOutputButton = wx.Button(self, id=wx.ID_ANY, label="Clear")
    self.Bind(wx.EVT_BUTTON, self.OnClearOutput, self.clearOutputButton)
    topSizer.AddStretchSpacer()
    topSizer.Add(self.clearOutputButton, flag=wx.ALIGN_CENTER_VERTICAL | wx.RIGHT )
    outputSizer.Add(topSizer, flag=wx.EXPAND)
    ## Output Area
    serialFont = wx.Font(10, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)
    self.serialOutput = wx.TextCtrl(self, style=wx.TE_MULTILINE | wx.TE_READONLY)
    self.serialOutput.SetFont(serialFont)
    outputSizer.Add(self.serialOutput, 1, wx.EXPAND)
    mainSizer.Add(outputSizer, 1, wx.EXPAND)
    # Input Area
    lowerAreaSizer = wx.BoxSizer(wx.VERTICAL)
    ## LiveType
    liveTypeSizer = wx.BoxSizer(wx.HORIZONTAL)
    lowerAreaSizer.Add(liveTypeSizer)
    liveTypeSizer.Add(wx.StaticText(self, wx.ID_ANY, " LiveType: "))
    self.liveType = wx.TextCtrl(self, wx.ID_ANY, '',
                                style=wx.TE_LEFT|wx.TE_MULTILINE|wx.TE_RICH, size=(160,25))
    liveTypeSizer.Add(self.liveType)
    self.Bind(wx.EVT_TEXT, self.OnSendLiveType, self.liveType)
    ## Input Array
    lowerAreaSizer2 = wx.BoxSizer(wx.HORIZONTAL)
    lowerAreaSizer.Add(lowerAreaSizer2)
    inputAreasSizer = wx.BoxSizer(wx.VERTICAL)
    lowerAreaSizer2.Add(inputAreasSizer)
    formatTypes = ['Ascii', 'Decimal', 'Hex', 'Binary']
    self.inputAreas = []
    self.inputFormats = []
    for i in range(1, 6):
      inputSizer = wx.BoxSizer(wx.HORIZONTAL)
      self.inputAreas.append(
        wx.TextCtrl(self, wx.ID_ANY, '',
                    style=wx.TE_LEFT|wx.TE_PROCESS_ENTER,
                    size=(200,25)))
      self.Bind(wx.EVT_TEXT_ENTER, self.OnSendInput, self.inputAreas[-1])
      inputSizer.Add(wx.StaticText(self, wx.ID_ANY, " " + str(i)+" : "))
      inputSizer.Add(self.inputAreas[-1], 4)
      self.inputFormats.append(
        wx.Choice(self, id=wx.ID_ANY, choices=formatTypes))
      inputSizer.Add(self.inputFormats[-1])
      inputAreasSizer.Add(inputSizer)
    ### Input Type Radios
    #self.inputTypeRadios = wx.RadioBox(self, wx.ID_ANY,
    #                                   style=wx.RA_VERTICAL, label="TX Format",
    #                                   choices = ('Ascii', 'Decimal', 'Hex', 'Binary'),
    #                                   size=(100,25*len(self.inputAreas)))
    #lowerAreaSizer2.Add(self.inputTypeRadios)
    # Connect Quick Buttons
    connectButtonSizer = wx.BoxSizer(wx.VERTICAL)
    ## Connect/Disconnect Button
    self.connectButton = wx.Button(self, id=wx.ID_ANY, label="  Disconnect  ")
    self.connectButton.SetBackgroundColour(wx.Colour(255, 0, 0))
    self.Bind(wx.EVT_BUTTON, self.OnToggleConnectButton, self.connectButton)
    connectButtonSizer.Add(self.connectButton)
    ## Auto Disconnect CheckBox
    self.autoDisconnectCheck = wx.CheckBox(self, id=wx.ID_ANY,
      label="Auto Disconnect")
    connectButtonSizer.Add(self.autoDisconnectCheck)
    ## Print Sent Data to Terminal CheckBox
    self.printSentData = wx.CheckBox(self, id=wx.ID_ANY,
      label="Print Sent Data to Terminal")
    self.printSentData.SetValue(True) # checked by default
    connectButtonSizer.Add(self.printSentData)       
    lowerAreaSizer2.AddStretchSpacer()
    lowerAreaSizer2.Add(connectButtonSizer)
    mainSizer.Add(lowerAreaSizer, 0)

    # Setup and get ready to roll
    self.serialCon = serial.Serial()
    self.SetStatusText('Not Connected...')
    self.SetSizer(mainSizer)
    self.Show(True)
    self.connected = False
    self.start()
    self.connectButton.SetLabel("Connect")


  ##################################################
  def OnChangeDisplay(self, event):
    """Gets called when the user changes the display format"""
    self.serialOutput.Clear()
    self.historyLock.acquire()
    self.appendToDisplay(self.history)
    self.historyLock.release()

  ##################################################
  def readDefaults(self):
    menumap = {
      'baud'     : self.baudMenu,
      'parity'   : self.parityMenu,
      'bytesize' : self.byteMenu,
      'stopbits' : self.stopbitsMenu
    }
    for k, v in menumap.items():
      if self.cfg.Exists(k):
        default = self.cfg.Read(k)
        for item in v.GetMenuItems():
          if item.GetLabel() == default:
            item.Check(True)

    for item in self.flowMenu.GetMenuItems():
        item.Check(self.cfg.ReadBool(item.GetLabel(), defaultVal=False))

    if self.cfg.Exists('portname'):
      self.portName = self.cfg.Read('portname')

  ##################################################
  def run(self):
    """The runtime thread to pull data from the open serial port"""
    while True:
      if self.connected:

        try:
          byte = self.serialCon.read()
        except:
          self.connected = False
          self.SetStatusText('Not Connected...')
          continue

        if byte != '':
          historyEntry = {'type':'RX', 'data':byte}
          self.historyLock.acquire()
          self.history.append(historyEntry)
          self.historyLock.release()
          wx.CallAfter(self.appendToDisplay,[historyEntry])
      else:
        time.sleep(.2)


  ##################################################
  def intToBinString(self,n):
    string = ''
    for i in range(0, 8):
      if (n&1): string = '1' + string
      else:     string = '0' + string
      n = n >> 1
    return string

  ##################################################
  def appendToDisplay(self, newEntries):
    if newEntries == None:
      return

    typeString = self.displayTypeRadios.GetStringSelection()

    entryCopies = []

    if typeString == 'Ascii':
      entryCopies = newEntries
    else:
      trans = None
      if(typeString   == 'Binary'):
        trans = self.intToBinString
      elif(typeString == 'Decimal'):
        trans = str
      elif(typeString == 'Hex'):
        trans = hex
      for entry in newEntries:
        entryCopies.append({'type':entry['type'], 'data':trans(ord(entry['data']))})

    for entry in entryCopies:
      # Set the proper output color
      if(entry['type'] == 'RX'):
        self.serialOutput.SetDefaultStyle(rxStyle)
      else:
        self.serialOutput.SetDefaultStyle(txStyle)

      # If the byte to show isn't valid ascii, then just print out ascii 1
      # as a placeholder
      try:
        self.serialOutput.AppendText(entry['data'])
      except:
        self.serialOutput.AppendText(chr(1))

      if typeString != 'Ascii':
        self.serialOutput.AppendText(' ')


  ##################################################
  def OnSetPort(self, event):
    self.portName = wx.GetTextFromUser('Port: ', 'Select Port Name', self.portName)

  ##################################################
  def OnSetConnection(self, event):
    if self.portName == "":
      self.OnSetPort(None)
      return

    baudRadio   = None
    for b in self.baudRadios:   
      if b.IsChecked(): baudRadio   = b
    parityRadio = None
    for p in self.parityRadios:
      if p.IsChecked(): parityRadio = p
    byteRadio   = None
    for b in self.byteRadios:
      if b.IsChecked(): byteRadio   = b
    stopRadio   = None
    for s in self.stopbitsRadios:
      if s.IsChecked(): stopRadio   = s

    self.serialCon = serial.Serial()
    self.serialCon.port     = self.portName
    self.serialCon.baudrate = int(baudRadio.GetLabel())
    self.serialCon.bytesize = bytesizeMap[byteRadio.GetLabel()]
    self.serialCon.parity   = parityMap[parityRadio.GetLabel()]
    self.serialCon.stopbits = stopMap[stopRadio.GetLabel()]
    self.serialCon.xonxoff  = self.xonoffCheck.IsChecked()
    self.serialCon.rtscts   = self.rtsctsCheck.IsChecked()
    self.serialCon.dsrdtr   = self.dsrdtrCheck.IsChecked()
    self.serialCon.timeout  = .3

    self.cfg.Write('portname', self.portName)
    self.cfg.Write('baud',     baudRadio.GetLabel())
    self.cfg.Write('parity',   parityRadio.GetLabel())
    self.cfg.Write('bytesize', byteRadio.GetLabel())
    self.cfg.Write('stopbits', stopRadio.GetLabel())
    for item in self.flowMenu.GetMenuItems():
      self.cfg.WriteBool(item.GetLabel(),item.IsChecked())

    try:
      self.serialCon.open()
    except serial.SerialException as ex:
      self.autoDisconnectCheck.SetValue(False)
      wx.MessageDialog(None, str(ex), 'Serial Error', wx.OK | wx.ICON_ERROR).ShowModal()
      self.SetStatusText('Not Connected...')
      self.connected = False
      return

    self.SetStatusText('Connected to ' + self.portName + ' ' + baudRadio.GetLabel() + 'bps')
    self.connected = True
    self.connectButton.SetBackgroundColour(wx.Colour(0, 255, 0))
    self.connectButton.SetLabel("Disconnect")

  ##################################################
  def OnClearOutput(self, event):
    self.historyLock.acquire()
    self.serialOutput.Clear()
    self.history = []
    self.historyLock.release()

  ##################################################
  def OnCloseConnection(self, event):
    self.connected = False

    self.serialCon.close()

    self.SetStatusText('Not Connected...')
    self.connectButton.SetBackgroundColour(wx.Colour(255, 0, 0))
    self.connectButton.SetLabel("Connect")

  ##################################################
  def OnSendLiveType(self, event):
    inputArea = event.GetEventObject()
    inputString = str(inputArea.GetString(0,-1))
    if inputString == "":
      return
    inputArea.Clear()
    if self.serialCon.isOpen():
      newHistoryVals = []
      for c in inputString:
        newHistoryVals.append({'type':'TX', 'data':c})

      self.historyLock.acquire()
      self.history = self.history + newHistoryVals
      self.historyLock.release()
      if self.printSentData.IsChecked():
        self.appendToDisplay(newHistoryVals)
      self.serialCon.write(inputString)

  ##################################################
  def OnSendInput(self, event):
    inputArea = event.GetEventObject()
    inputArea.SetSelection(0,-1)
    inputString = inputArea.GetString(0,-1)

    inputAreaIdx = -1
    for i in range(0, len(self.inputAreas)):
      if inputArea == self.inputAreas[i]:
        inputAreaIdx = i
        break
    if inputAreaIdx == -1:
      print "ERROR! Bad Input Area!"
      exit()

    inputVal = ''
    typeString = self.inputFormats[i].GetStringSelection()

    if(typeString == 'Ascii'):
      inputVal = str(inputString)
    else:
      base = 0
      if(typeString   == 'Binary'):
        base = 2
      elif(typeString == 'Decimal'):
        base = 10
      elif(typeString == 'Hex'):
        base = 16
      numStrings = inputString.split(" ")
      for numString in numStrings:
        numString = numString.strip()
        if numString == '': continue
        intVal = int(numString, base)
        inputVal += chr(intVal)
     
    if self.serialCon.isOpen():
      newHistoryVals = []
      for c in inputVal:
        newHistoryVals.append({'type':'TX', 'data':c})
      self.historyLock.acquire()
      self.history = self.history + newHistoryVals
      self.historyLock.release()
      self.serialCon.write(inputVal)
      if self.printSentData.IsChecked():
        self.appendToDisplay(newHistoryVals)

  ##################################################
  def OnAbout(self, event):
    dlg = wx.MessageDialog(self, "A set of useful serial utilities by "
                                 "Randolph Voorhies (rand.voorhies@gmail.com)\n"
                                 "http://ilab.usc.edu/~rand",
                                 "About randterm", wx.OK)
    dlg.ShowModal()
    dlg.Destroy()

  ##################################################
  def OnExit(self, e):
    self.Close(True)

  ##################################################
  def OnToggleConnectButton(self, event):
    if(self.connected):
      self.OnCloseConnection(None)
    else:
      self.OnSetConnection(None)

  def OnActivate(self, event):
    if self.autoDisconnectCheck.IsChecked():
      if event.GetActive():
        self.OnSetConnection(None)
      else:
        self.OnCloseConnection(None)


app = wx.App(False)
frame = randtermFrame(None, "randterm")
app.MainLoop()
http://allenmlab.blogspot.com/
User avatar
Posts: 36
Joined: Fri Dec 28, 2012 6:37 pm
by feroze » Tue Apr 02, 2013 4:52 pm
Whoah this program randterm is really cool! So useful for debugging serial devices.

Anyway though I just tried it out with baud set to 38400, port set to /dev/ttyAMA0. Nothing should up upon powering it up.

Then I selected hex in RX format and then again where I send the commands and I sent '56 00 11 00' and it just spits back the exact same thing I sent it. Which is precisely the problem now, that the camera is not sending any serial data to the Raspberry Pi.

The serial ports should be working because of the typing test we did with minicom correct? Or is there still a chance it could be the Raspberry Pi hardware?

Anyway though, thanks a ton! For troubleshooting with me. RandTerm is really cool and will be useful in the future. I think I might just buy a $20 usb camera which is known to work with the Pi =D
Posts: 25
Joined: Wed Mar 27, 2013 5:20 pm
by moallen » Tue Apr 02, 2013 5:05 pm
Your RPi should be OK if you can see what you type when tx and rx are jumpered. If you're going to go for a different camera, here's the one I'm on the waiting list for: http://www.mcmelectronics.com/product/28-17733 I think MCM is supposed to have it in stock in 14 more days. This is the one designed to work directly with RPi's cpu, making it faster and able to handle larger images.

And best of all... only $25!
http://allenmlab.blogspot.com/
User avatar
Posts: 36
Joined: Fri Dec 28, 2012 6:37 pm
by feroze » Tue Apr 02, 2013 5:24 pm
No way how awesome, I just signed up too.
Posts: 25
Joined: Wed Mar 27, 2013 5:20 pm