Thanks! For your detailed tutorial. I will dig into this today.
I am helping my Son teach Computer Science at an International School in Guyana, South America. They have good computers and resources and UNO based kits with sensors and actuators. They have advanced well with PYTHON on codeHS.
Now we need to be able to write PYTHON on the PC and interact with Arduino and devices. What have you made work in recent history?
I have PYTHON 3.12 on a WIN10 machine, And Arduino IDE 2.3.3 .
What are your thoughts on adapting your work to a student environment with HS students with decent PYTHON literacy? We will be teaching them Arduino and Electronics next, and want to use PYTHON in that environment.
I would like to put together something that can 'hide' at least some of the details so they can write PYTHON logic and interact with Arduino UNO.
THANKS!
Regards, Terry King
...In The Woods In Vermont
The one who dies with the most Parts LOSES! WHAT DO YOU NEED??
** @ johnpool Gave you thought of this use?? Thanks T
If they understand my tutorial and how the queues work as well as the principles explained in Serial Input Basics for the arduino side then they will be self sufficient pretty quickly.
Python uses semantic versioning, where the version number is structured as major.minor.patch.
• Major: Indicates a major release (e.g., Python 2 vs. Python 3).
•Minor: Indicates a feature update within the same major version (e.g., 3.4 vs. 3.13).
•Patch: Indicates bug fixes or security updates within the same minor version (e.g., 3.13.1 vs. 3.13.0).
Python 3.13 is several years newer than Python 3.4, which was released in March 2014. Python 3.4 is now obsolete and no longer supported but pySerial would still work there.
Many Thanks @J-M-L ! You got me off the ground with python serial stuff, like Robin2 did for all of us with Arduino back in the day..
I hope to have some students trying this out in a week or so. I need to adapt my older Arduino How-To materials to integrate with python. These students have little to no hardware / Arduino background. We want to keep their python concentration for much of the CS course. Later we plan to introduce Arduino IDE and an overview of C/C++ with basic electronics. And a decent overall Physical Computing overview with some pratical experience..
Hey LarryD!! Glad to hear from you. I was kinda out of touch here; I was I Egypt for two years and started a school for refugees that was (now IS, after I'm back in USA) teaching programming and electronics with Arduino/ESP32.. A little difficult keeping it going from a distance and the poor! resources and economic situation in Cairo.
Sometimes I worry we are getting close to TimingOut. I'm 84 and my second-youngest kid is the one teaching in Guyana. I'm sad that our old great helpers like Robin2 may be fading/faded - Not sure.
OK. PM me sometime if you ever HAVE spare time...
Regards, Terry King
...In The Woods In Vermont
The one who dies with the most Parts LOSES! WHAT DO YOU NEED??
Although not my personal favorite , you could look at Firmata which is a protocol for communicating with microcontrollers from software on a computer (or smartphone/tablet, etc).
The protocol can be implemented in firmware on any microcontroller architecture as well as software on any computer software package.
First a copy of the source had an unexpected indent. Fixed and it runs
Next there is a zero-origin issue with choosing a port. If I enter 2 it selects COM5 and I see activity on the UNO
But then it hangs as shown below. Any advice appreciated.
OTHER: I will look more at firmata; thanks for your research on that! I really would like to use your example with students as it has so many Teachable Moments in how you approached it with threads and queues.
---(copy(---
= RESTART: C:/Users/terry/AppData/Local/Programs/Python/Python313/Python_JML_TestOrg.py
PORT DEVICE MANUFACTURER
0 COM1 (Standard port types)
1 COM3 wch.cn
2 COM4 Intel
3 COM5 Arduino LLC (www.arduino.cc)
➜ Select your port: 2
selecting: COM5
Sure. The indent was in the top comment block; maybe just online formatting.
#!/usr/bin/python3
# ============================================
# code is placed under the MIT license
# Copyright (c) 2023 J-M-L
# For the Arduino Forum : https://forum.arduino.cc/u/j-m-l
#
# 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.
# ===============================================
import sys, threading, queue, serial
import serial.tools.list_ports
baudRate = 115200
arduinoQueue = queue.Queue()
localQueue = queue.Queue()
def selectArduino():
ports = serial.tools.list_ports.comports()
choices = []
print('PORT\tDEVICE\t\t\tMANUFACTURER')
for index,value in enumerate(sorted(ports)):
if (value.hwid != 'n/a'):
choices.append(index)
print(index, '\t', value.name, '\t', value.manufacturer) # https://pyserial.readthedocs.io/en/latest/tools.html#serial.tools.list_ports.ListPortInfo
choice = -1
while choice not in choices:
answer = input("➜ Select your port: ")
if answer.isnumeric() and int(answer) <= int(max(choices)):
choice = int(answer)
print('selecting: ', ports[choice].device)
return ports[choice].device
def listenToArduino():
message = b''
while True:
incoming = arduino.read()
if (incoming == b'\n'):
arduinoQueue.put(message.decode('utf-8').strip().upper())
message = b''
else:
if ((incoming != b'') and (incoming != b'\r')):
message += incoming
def listenToLocal():
while True:
command = sys.stdin.readline().strip().upper()
localQueue.put(command)
def configureUserInput():
localThread = threading.Thread(target=listenToLocal, args=())
localThread.daemon = True
localThread.start()
def configureArduino():
global arduinoPort
arduinoPort = selectArduino()
global arduino
arduino = serial.Serial(arduinoPort, baudrate=baudRate, timeout=.1)
arduinoThread = threading.Thread(target=listenToArduino, args=())
arduinoThread.daemon = True
arduinoThread.start()
# ---- CALLBACKS UPON MESSAGES -----
def handleLocalMessage(aMessage):
print("=> [" + aMessage + "]")
arduino.write(aMessage.encode('utf-8'))
arduino.write(bytes('\n', encoding='utf-8'))
def handleArduinoMessage(aMessage):
print("<= [" + aMessage + "]")
# ---- MAIN CODE -----
configureArduino() # will reboot AVR based Arduinos
configureUserInput() # handle stdin
print("Waiting for Arduino")
# --- A good practice would be to wait for a know message from the Arduino
# for example at the end of the setup() the Arduino could send "OK"
while True:
if not arduinoQueue.empty():
if arduinoQueue.get() == "OK":
break
print("Arduino Ready")
# --- Now you handle the commands received either from Arduino or stdin
while True:
if not arduinoQueue.empty():
handleArduinoMessage(arduinoQueue.get())
if not localQueue.empty():
handleLocalMessage(localQueue.get())
right, I fixed the carriage return that was wrongly set in the MIT License text. Not sure why it was there !
the issue you see is indeed a bug in my code and how I am mapping the user's input (2) to the list of available ports. thanks for catching this !
try with this updated code. If that works I'll modify the first post to include it
#!/usr/bin/python3
# ============================================
# code is placed under the MIT license
# Copyright (c) 2023 J-M-L
# For the Arduino Forum : https://forum.arduino.cc/u/j-m-l
#
# 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.
# ===============================================
import sys, threading, queue, serial
import serial.tools.list_ports
baudRate = 115200
arduinoQueue = queue.Queue()
localQueue = queue.Queue()
class NoValidPortError(Exception):
"""Exception raised when no valid Arduino ports are found."""
pass
def selectArduino():
ports = serial.tools.list_ports.comports()
valid_ports = [port for port in ports if port.hwid != 'n/a'] # Filter out ports with 'n/a' hwid
if not valid_ports:
raise NoValidPortError("No valid Arduino ports found.") # Raise an error if no valid ports
print('PORT\tDEVICE\t\t\tMANUFACTURER')
for index, value in enumerate(sorted(valid_ports)):
print(f"{index}\t{value.name}\t{value.manufacturer}") # Display sorted list with index
choice = -1
while choice < 0 or choice >= len(valid_ports):
answer = input("➜ Select your port: ")
if answer.isnumeric():
choice = int(answer)
selectedPort = sorted(valid_ports)[choice] # Map the user's choice to the filtered list
print(f"selecting: {selectedPort.device}")
return selectedPort.device
def listenToArduino():
message = b''
while True:
incoming = arduino.read()
if incoming == b'\n':
try:
arduinoQueue.put(message.decode('utf-8').strip().upper())
except UnicodeDecodeError as e:
# Handle the error: log it, ignore the message, or take other action
print(f"UnicodeDecodeError: {e}. Message skipped.")
message = b''
else:
if incoming not in (b'', b'\r'):
message += incoming
def listenToLocal():
while True:
command = sys.stdin.readline().strip().upper()
localQueue.put(command)
def configureUserInput():
localThread = threading.Thread(target=listenToLocal, args=())
localThread.daemon = True
localThread.start()
def configureArduino():
global arduinoPort
arduinoPort = selectArduino()
global arduino
arduino = serial.Serial(arduinoPort, baudrate=baudRate, timeout=.1)
arduinoThread = threading.Thread(target=listenToArduino, args=())
arduinoThread.daemon = True
arduinoThread.start()
# ---- CALLBACKS UPON MESSAGES -----
def handleLocalMessage(aMessage):
print("=> [" + aMessage + "]")
arduino.write(aMessage.encode('utf-8'))
arduino.write(bytes('\n', encoding='utf-8'))
def handleArduinoMessage(aMessage):
print("<= [" + aMessage + "]")
# ---- MAIN CODE -----
configureArduino() # will reboot AVR based Arduinos
configureUserInput() # handle stdin
print("Waiting for Arduino")
# --- A good practice would be to wait for a know message from the Arduino
# for example at the end of the setup() the Arduino could send "OK"
while True:
if not arduinoQueue.empty():
if arduinoQueue.get() == "OK":
break
print("Arduino Ready")
# --- Now you handle the commands received either from Arduino or stdin
while True:
if not arduinoQueue.empty():
handleArduinoMessage(arduinoQueue.get())
if not localQueue.empty():
handleLocalMessage(localQueue.get())
Hello (JAckson?). The newest version does select the correct com port. But then it fails as shown below.. On RUN I see:
---( copy)--
Python 3.13.0 (tags/v3.13.0:60403a5, Oct 7 2024, 09:38:07) [MSC v.1941 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license()" for more information.
= RESTART: C:/Users/terry/AppData/Local/Programs/Python/Python313/Python_JML_Test_update1.py
PORT DEVICE MANUFACTURER
0 COM1 (Standard port types)
1 COM4 Intel
2 COM5 Arduino LLC (www.arduino.cc)
➜ Select your port: 2
selecting: COM5
---( Then stops. no response 60 sec )-----
If I then hit enter I see:
--( copy )--
Waiting for Arduino
--( Again, no response [cursor remains at end of line] )---
So I try typing 'aaa' and enter. Then I see:
--(copy)--
aaa
Arduino Ready
---( then no more response )----
Try typing 'aaa' again and get this response:
--(copy)---
aaa
=> []
----( end)----
So ?? Seems to get to the Arduino. I do have the pingpong sketch loaded but learned I need to exit the Arduino IDE to release COM5.
Something with queueing / local device ?? I'm WAY over my head...
I appreciate your patience with this. Oh: You may be running an Apple machine? I'm on Win10..
Which code did you install on the arduino and what type of arduino is it?
There is an expectation that the arduino will reboot upon opening the serial line, not all arduino will do so and the Python code awaits for the command "OK" before doing anything.
The code is that you showed above on an Arduino UNO that I believe DOES reset. I used Arduino IDE 2.3.3 (current). Here's what it looked like on the IDE. I think the first reset is from the upload and the second is when the Serial Monitor interacts.