Arduino and Python  

  RSS

Pugwash
(@pugwash)
Prominent Member
Joined: 10 months ago
Posts: 969
2019-06-27 10:57 am  

I got my first Arduino a couple of years ago and as most people do one of the first projects I endeavored to achieve was to hook up the Arduino to a DHT sensor. Not satisfied with that I added 433Mhz transmitters/receivers for measuring the environment in the bedroom/living room/kitchen/attic/cellar etc.

Sending all the info back to one receiver. I was pleased as Punch!

Then I decided that I wanted to log the data and have a nice graphics display on my computer. For this, I used Python and TKinter.

So I wrote the program in Python to get the data from the USB and display on the screen.

I want to share some code which was the kicking off point to the development, rather than publish the whole Python program and the Arduino sketch which not easily readable for the beginner. This is just for motivation purposes.

First the Arduino sketch

void setup() {
Serial.begin(9600);

}

void loop() {
// Change this string to anything you like
// but remember to leave \n at the end, as this
// marker is required by Python readline()

Serial.write("I am the Walrus, Coo Coo Ca Choo\n");
delay(5000);

}

Now the Python code

#!/usr/bin/python

import serial

import time

# you may need to run pip install pyserial first

# change port to port name you are using

# to get the list of available ports

# run "python -m serial.tools.list_ports" command in the terminal

# initialise the serial object

ser = serial.Serial(

port='/dev/cu.usbmodem1461',\
baudrate=9600,\
parity=serial.PARITY_NONE,\
stopbits=serial.STOPBITS_ONE,\
bytesize=serial.EIGHTBITS,\
timeout=None

)

print("connected to: " + ser.portstr)

while True: # loop forever
print ("Waiting....")
# get data from serial port
byteArray = ser.readline() # reads the incoming bytearray until a linefeed (\n) is encountered
print(byteArray) # prints the received data array it's raw form

mList = [] # initialise zero list

for mByte in byteArray:
if mByte !=10: # ignore the linefeed
# print the byte value, then a space, then the equivalent ASCII code
print(str(mByte) +" "+chr(mByte))
# append bytes to list
mList.append(mByte)

# print full byte list
print(mList)

# just for fun, print the timestamp of the incoming data
localtime = time.asctime( time.localtime(time.time()) )
print(localtime)

# clear serial buffer
ser.reset_input_buffer()

ser.close()

I have tried to make this very self-explanatory and as simple as possible as a jumping off point for anyone who cares to use it.

I also use Python/Arduino for setting the date/time on RTC chips and getting CSV data from my hard drive.

Have fun expanding on this! It is all about the learning process!

SteveC - Topple Rudd Poltman


jscottbee liked
Quote
Robo Pi
(@robo-pi)
Robotics Engineer Moderator
Joined: 10 months ago
Posts: 1590
2019-06-27 3:02 pm  

@pugwash

Great example Steve.  I love the way you kept the example as simple as possible.  Great work!

I do have a question concerning hardware and OS.    What are  you running the python on? 

I notice that when you open the serial  port you use the following code:

port='/dev/cu.usbmodem1461',\

Is that port going to be machine or OS dependent? For example, if I ran this Python code on a Raspberry Pi would it still work?

I'm looking forward to trying to run this on my Windows 10 Notebook computer.   But I don't have time to do it at the moment.   But my first thought was to wonder if it would also run on a Raspberry Pi, or if it would need to be modified to run on the Pi.

DroneBot Workshop Robotics Engineer
James


ReplyQuote
Pugwash
(@pugwash)
Prominent Member
Joined: 10 months ago
Posts: 969
2019-06-27 3:21 pm  

Python, in my case, is running on MacOS but that is irrelevant, just use the same port name as shown in the Arduino IDE. Or check the System Info on Windows. It should run on a RaspPI as well.

Can't say much more about Windows operating system, because my last Windows computer ran 98SE and landed in the corner of my office in pieces after the 10th BSOD in one day, and being thrown with considerable force at the wall.

SteveC - Topple Rudd Poltman


ReplyQuote
Robo Pi
(@robo-pi)
Robotics Engineer Moderator
Joined: 10 months ago
Posts: 1590
2019-06-27 3:26 pm  

Thanks for the info.  I'll see if I can find time to try on my Windows notebook and see if it works.  Then maybe later I'll try it on a Pi.   I'm only just now learning to use Python.

DroneBot Workshop Robotics Engineer
James


ReplyQuote
Pugwash
(@pugwash)
Prominent Member
Joined: 10 months ago
Posts: 969
2019-06-27 3:38 pm  

The most significant discovery I made when I first ran this code was that the Arduino communicates exclusively with the outside world using byte arrays, both for serial and I2C communication.

At that point, the penny dropped and explained why some of the things I was trying to do, just didn't work.

I was thinking too much in terms of a microprocessor and not enough about microcontrollers.

SteveC - Topple Rudd Poltman


ReplyQuote
jscottbee
(@jscottbee)
Estimable Member
Joined: 10 months ago
Posts: 121
2019-06-27 4:29 pm  
Posted by: Pugwash

Have fun expanding on this! It is all about the learning process!

Great post @pugwash!

I use python to link to my microcontrollers and the RPI. For a few of my projects, I have added a GUI element to the app. I have used Tkinter since it's already included with the python installs. Sometimes is nice to add an easy interface to your project.

Here is an example app:

The code:

#!/usr/bin/env python
#
# For Linux, BSD or Mac OSX you can chmod +x on this script to make executable
#
###########
# Rover control app
#
# Allows for simple multi-byte commands.
#
# Written by Scott Beasley - 2015
# Free to use or modify. Enjoy.
#
###########

import sys
import serial
import time
from Tkinter import *
import tkFont
import tkMessageBox

# Create the window for the application
class App (Frame):
    # Make the window
    def createWidgets (self):
        self.connected = False
        self.message = StringVar ( )

        # Make a little font for the gripper set buttons.
        helv6 = tkFont.Font (family = 'Helvetica',
                             size = 6, weight = 'normal')

        self.frame = Frame (self.master)
        self.frame.pack ( )

        self.f1 = Frame (self.frame)
        self.l1 = Label (self.f1, text = "Comm Port: ")
        self.l1.pack (side = LEFT)
        self.comm_entry = Entry (self.f1, bd = 5, name = "comm_entry")
        self.comm_entry.pack (side = LEFT)
        self.connectButton = Button (self.f1, text = "Connect",
                                    command = self.SerialConnect,
                                    name = "b_connect")
        self.connectButton.pack (side = LEFT)
        self.disconnectButton = Button (self.f1, text = "Disconnect",
                                    command = self.SerialDisconnect,
                                    name = "b_disconnect")
        self.disconnectButton.pack (side = RIGHT)
        self.f1.grid (row = 0, column = 0)

        self.f2 = LabelFrame (self.frame, bd = 3, relief = "groove",
                              text="Ground Control")

        self.g_vert_fm = Frame (self.f2)
        self.grip_vert = Scale (self.g_vert_fm, from_ = 0, to = 180)
        # Bind on mouse button 1 release to set the boom position
        self.grip_vert.bind ('', self.GripperY)
        self.grip_vert.grid (row = 0, column = 0, rowspan = 4, sticky = W)
        self.master.bind ("", self.GripperY)
        self.g_vert_fm.grid (row = 0, column = 0, rowspan = 4, sticky = W)

        self.leftforwardButton = Button (self.f2, text = "\\",
                                 command = self.TurnLeft45,
                                 name = "b_left_forward")
        self.leftforwardButton.grid (row = 0, column = 1)
        self.master.bind ("<a rel="nofollow">", self.TurnLeft45)

        self.leftButton = Button (self.f2, text = "<",
                                 command = self.TurnLeft, name = "b_left")
        self.leftButton.grid (row = 1, column = 1)
        self.master.bind ("</a><a rel="nofollow">", self.TurnLeft)

        self.rightforwardButton = Button (self.f2, text = "/",
                                 command = self.TurnRight45,
                                 name = "b_right_forward")
        self.rightforwardButton.grid (row = 0, column = 3)
        self.master.bind ("</a><a rel="nofollow">", self.TurnRight45)

        self.haltButton = Button (self.f2, text = "Halt!",
                                 command = self.Halt, name = "b_halt")
        self.haltButton.grid (row = 1, column = 2)
        self.master.bind ("</a><a rel="nofollow">", self.Halt)

        self.rightButton = Button (self.f2, text=">",
                                  command = self.TurnRight, name = "b_right")
        self.rightButton.grid(row = 1, column = 3)
        self.master.bind ("</a><a rel="nofollow">", self.TurnRight)

        self.upButton = Button (self.f2, text="^",
                               command = self.Forward, name = "b_forward")
        self.upButton.grid (row = 0, column = 2)
        self.master.bind ("</a><a rel="nofollow">", self.Forward)

        self.leftdownButton = Button (self.f2, text = "/",
                                      command = self.TurnRight45,
                                      name = "b_left_down")
        self.leftdownButton.grid (row = 2, column = 1)

        self.downButton = Button (self.f2, text="V",
                                  command = self.Reverse, name = "b_reverse")
        self.downButton.grid (row = 2, column = 2)
        self.master.bind ("</a><a rel="nofollow">", self.Reverse)
        self.f2.grid (row = 1, column = 0, pady = 25)

        self.rightdownButton = Button (self.f2, text = "\\",
                                       command = self.TurnLeft45,
                                       name = "b_right_down")
        self.rightdownButton.grid (row = 2, column = 3)

        self.g_horz_fm = Frame (self.f2)
        self.grip_horz = Scale (self.g_horz_fm, from_ = 0, to = 180,
                                orient = HORIZONTAL)
        # Bind on mouse button 1 release to set the gripper position
        self.grip_horz.bind ('</a><a rel="nofollow">', self.GripperX)
        self.grip_horz.grid (row = 0, column = 0, columnspan = 7, sticky = E)

        self.master.bind ("<u>", self.GripperX)
        self.g_horz_fm.grid (row = 4, column = 0, columnspan = 7, sticky = E)
        self.master.bind ("</u></a><a rel="nofollow"><u>", self.GripperHome)

        self.f_spd_dist = Frame (self.frame)
        self.l_speed = Label (self.f_spd_dist, text = "  Speed: ")
        self.l_speed.grid (row = 0, column = 0)
        self.speed_spin = Spinbox (self.f_spd_dist, values = (1, 2, 3),
                                   width = 2, command = self.SetSpeed)
        self.speed_spin.grid (row = 0, column = 1)
        self.l_dist = Label (self.f_spd_dist, text = "  Distance: ")
        self.l_dist.grid (row = 0, column = 3)
        self.dist_spin = Spinbox (self.f_spd_dist,
                                  values = ('Short', 'Medium', 'Continuous'),
                                  width = 10, command = self.SetDistance)
        self.dist_spin.grid (row = 0, column = 4)
        self.l_turns = Label (self.f_spd_dist, text = "  Turns: ")
        self.l_turns.grid (row = 0, column = 5)
        self.turns_spin = Spinbox (self.f_spd_dist,
                                   values = ('Hard', 'Soft'),
                                   width = 5, command = self.SetTurns)
        self.turns_spin.grid (row = 0, column = 6)
        self.f_spd_dist.grid (row = 2, column = 0)

        self.f3 = Frame (self.frame)
        self.l2 = Label (self.f3, text = "Last action: ")
        self.l2.pack (side = LEFT)
        self.l3 = Label (self.f3, text=" ", textvariable = self.message)
        self.l3.pack (side = RIGHT)
        self.f3.grid (row = 5, column = 0, pady = 8)

    # Set the state of the bot control buttons. Enable when connected,
    # Disabled otherwise.
    def CtrlButtonsState (self, bstate):
        self.leftforwardButton.config (state = bstate)
        self.leftButton.config (state = bstate)
        self.rightforwardButton.config (state = bstate)
        self.rightButton.config (state = bstate)
        self.upButton.config (state = bstate)
        self.leftdownButton.config (state = bstate)
        self.downButton.config (state = bstate)
        self.rightdownButton.config (state = bstate)
        self.haltButton.config (state = bstate)
        self.disconnectButton.config (state = bstate)
        self.grip_horz.config (state = bstate)
        self.grip_vert.config (state = bstate)

    # Set the state of the comm port entry. Enable when not connected,
    # Disabled when the bot is connected.
    def ConnCtrlsState (self, bstate):
        self.connectButton.config (state = bstate)
        self.comm_entry.config (state = bstate)

    # Connect to the comm port typed in the comm entry field.
    def SerialConnect (self):
        try:
            # Change the baud rate here if diffrent then 9600
            self.ser = serial.Serial (self.comm_entry.get ( ), 9600)
        except IOError:
            tkMessageBox.showerror ("Invalid comm port", "Comm port not found.")
            return

        self.ConnCtrlsState (DISABLED)
        self.CtrlButtonsState (NORMAL)
        self.message.set ("SerialConnect")
        self.connected = True
        time.sleep (3) # Dwell a bit for the connection to happen

        # Do some after connection bot talking.
        if self.connected == True:
            self.setbotParms ( )

    # Disconnect from the bot (close the comm port).
    def SerialDisconnect (self):
        try:
            # Send a Halt command just in case the bot is still moving.
            self.send_cmd ('h', "Halt!")
            time.sleep (1)
            self.ser.close ( )
        except IOError:
            print "Could not close port..."

        self.message.set ("SerialDisconnect")
        self.ConnCtrlsState (NORMAL)
        self.CtrlButtonsState (DISABLED)
        self.connected = False
        time.sleep (2) # Dwell a bit for the disconnection to happen

    # Send the command to the open comm port
    # the action input variable is a tuple that allows for multy part commands
    def send_cmd (self, action, msg):
        if self.connected == True:
            for val in action:
                self.ser.write (val)
            self.ser.flush ( )
            self.message.set (msg)

    # Send the bot a turn-left command.
    def TurnLeft (self, event = None):
        self.send_cmd ('a', "Left")

    # Send the bot a turn-left-up command.
    def TurnLeft45 (self, event = None):
        self.send_cmd ('q', "Left45")

    # Send the bot a turn-right command.
    def TurnRight (self, event = None):
        self.send_cmd ('s', "Right")

    # Send the bot a turn-right-up command.
    def TurnRight45 (self, event = None):
        self.send_cmd ('e', "Right45")

    # Send the bot a Forward command.
    def Forward (self, event = None):
        self.send_cmd ('w', "Up")

    # Send the bot a Reverse command.
    def Reverse (self, event = None):
        self.send_cmd ('z', "Down")

    # Send the bot a Halt command.
    def Halt (self, event = None):
        self.send_cmd ('h', "Halt!")

    # Set the gripper (X).
    def GripperX (self, event = None):
        # Read the slider control and send the value to the bot controller
        # Note: 0 is all the way closed and 180 is all the way open
        # This is a multi-byte command. The command + the servo set val
        # + a buffer clear command (optional)
        grp_change = ('>', chr (self.grip_horz.get ( )), chr (255))
        self.send_cmd (grp_change, "Gripper X")
        self.boomchange = None

    # Set the gripper Y.
    def GripperY (self, event = None):
        # Read the slider control and send the value to the bot controller
        # Note: 0 is all the way up and 180 is all the way down
        # This is a multi-byte command. The command + the servo set val
        # + a buffer clear command (optional)
        grp_change = ('^', chr (self.grip_vert.get ( )), chr (255))
        self.send_cmd (grp_change, "Gripper Y")
        self.gripperchange = None

    # Set the gripper to the "home" position.
    def GripperHome (self, event = None):
        self.send_cmd (('c', chr (255)), "Gripper Home")
        self.grip_horz.set (90)
        self.grip_vert.set (90)

    # Set the speed of the rover.
    def SetSpeed (self, event = None):
        spd_str = self.speed_spin.get ( )

        if spd_str == "1":
            speed = 1
        if spd_str == "2":
            speed = 2
        if spd_str == "3":
            speed = 3

        # Sends a multi-byte command to set the speed.
        self.send_cmd (('v', chr (speed)), "Set Speed")

    # Set the distance travled when a for or rev is issued to 
    # the rover.
    def SetDistance (self, event = None):
        dist = 1
        dist_str = self.dist_spin.get ( )

        if dist_str == "Short":
            dist = 1
        if dist_str == "Medium":
            dist = 2
        if dist_str == "Continuous":
            dist = 3

        # Sends a multi-byte command to set the distance.
        self.send_cmd (('d', chr (dist)), "Set Distance")

    # Set the turn style to use turning.
    def SetTurns (self, event = None):
        turn = 1
        turn_str = self.turns_spin.get ( )

        if turn_str == "Hard":
            turn = 1
        if turn_str == "Soft":
            turn = 2

        # Sends a multi-byte command to set the turn style.
        self.send_cmd (('t', chr (turn)), "Set Turns")

    def setbotParms (self):
        self.Halt ( )
        self.GripperHome ( )
        self.SetSpeed ( )
        self.SetDistance ( )
        self.SetTurns ( )
        
    def __init__ (self, master = None):
        Frame.__init__ (self, master)
        self.pack ( )
        self.createWidgets ( )
        self.CtrlButtonsState (DISABLED)

# Kick off the GUI (Tk) then size and title the app window
def main ( ):
    root = Tk ( )
    root.geometry ("450x360")
    root.wm_title ("Rover Control Center (RCC)")
    app = App (master = root)
    app.mainloop ( )

if __name__ == '__main__':
    main ( )
This post was modified 9 months ago by jscottbee

ReplyQuote
Robo Pi
(@robo-pi)
Robotics Engineer Moderator
Joined: 10 months ago
Posts: 1590
2019-06-27 4:58 pm  

Thanks for sharing your Tkinter GUI code Scott.   I've messed around with Tkinter GUI before myself.  But  it's nice to have example code like you've posted to work from.

This DroneBot Forum was a GREAT idea that Bill had.   It's going to be a gold mine of resources.

DroneBot Workshop Robotics Engineer
James


ReplyQuote

Please Login or Register