1

So I am trying to create a simple program that allows me to control the colors of a RGB LED with my computer. I created a little window with tkinter on python 3 in order to control the color, but the problem is that when I try to change the color it simply doesn't respond. I have no idea what is going on. I tried to put the string in the arduino code and it worked out, but it simply doesn't respond when I send through a serial communication.

Arduino code

//pin layout
int   red = 12;
int green = 11;
int  blue = 10;

//string that will receive
String    data;
String subData;

//Color values
int value[3];

void setup() {
  Serial.begin(9600);
  pinMode(red,OUTPUT);
  pinMode(green,OUTPUT);
  pinMode(blue,OUTPUT);  
  }

void loop() {
  while(Serial.available() == 0);
    data = Serial.readString();

    int initialVal =0;
    int val;

    int pos = 0;

    do{
    val = data.indexOf(',',initialVal);
    subData = data.substring(initialVal,val);
    value[pos] = subData.toInt();
    pos = pos + 1;
    initialVal = val + 1;
    }while(val != -1);
    Serial.println(data);
    analogWrite(red,value[0]);
    analogWrite(green,value[1]);
    analogWrite(blue,value[2]);  


  }

And here is the python code:

from tkinter import *
from serial import *


window = Tk()
#all definitions for the window
window.title("RGB LED control Panel")
window.geometry("300x180")
window.resizable(False,False)

Title = Label(window, text = "RGB control", width = 15)
Title.grid(row = 0, column = 0, columnspan = 3)

Explanation = Label(window, text = "  This window controls the \ncolor of an RGB LED. Have \n fun!!!")
Explanation.grid(row =1 , column = 3)

RedTitle = Label(window, text = "Red", width = 5, bg = "Red")
RedTitle.grid(row = 1, column = 0)

GreenTitle = Label(window, text = "Green", width = 5, bg = "Green")
GreenTitle.grid(row = 1, column = 1)

BlueTitle = Label(window, text = "Blue", width = 5, bg = "Blue")
BlueTitle.grid(row = 1, column = 2)


RedScale = Scale(window, from_ = 0, to = 255, orient = VERTICAL)
RedScale.grid(row = 2, column = 0)

GreenScale = Scale(window, from_ = 0, to = 255, orient = VERTICAL)
GreenScale.grid(row = 2, column = 1)

BlueScale = Scale(window, from_ = 0, to = 255, orient = VERTICAL)
BlueScale.grid(row = 2, column = 2)

#now the serial com with the arduino

arduino = Serial()
arduino.baudrate = 9600
arduino.port = "COM3"
arduino.open()

while 1:
    window.update_idletasks()
    window.update()

    RED = str(RedScale.get())
    GREEN = str(GreenScale.get())
    BLUE = str(BlueScale.get())

    finalString = RED + "," + GREEN + "," + BLUE

    arduino.write(finalString.encode("utf-8"))
    print(finalString)
    print("\n")

Update

So changing the arduino code (at the part that receives the string) for this:

  while(Serial.available() == 0);
  data = Serial.readStringUntil('\n');
  Serial.setTimeout(0.01);

And the part of the python code which sends the string to this: while 1: window.update_idletasks() window.update()

    RED = str(RedScale.get())
    GREEN = str(GreenScale.get())
    BLUE = str(BlueScale.get())

    finalString = RED + "," + GREEN + "," + BLUE + "\n"
    if lastMsg != finalString:
        finalString= finalString.encode("utf-8")
        arduino.write(finalString)
        lastMsg = finalString

    print(finalString)

The LED changes it's color, but it, sometimes, changes to other colors and the the python program crashes!!!! Is there anything missing in Serial.readStringUntil("\n") or in the arduino.write(finalString)?

David Martins
  • 63
  • 2
  • 7
  • Post a shorter question which point on the problem you have. Also send a stack trace. – Laurent LAPORTE Dec 31 '16 at 14:49
  • I don't see any `arduino.close()` call: maybe the data is not flushed when Python program exists. – Laurent LAPORTE Dec 31 '16 at 14:51
  • I have tried that but I think the problem is in the way a send the string. There is no problem with the serial door (I tried to write the string directly in the Serial monitor on the arduino and it worked fine), but when I send with the code it simply does not respond. – David Martins Dec 31 '16 at 15:20

1 Answers1

2

You are sending just too many messages one after another to Arduino, so what happens is that when it invokes readString() it takes a very long string, and increments pos past the legitimate interval 0..2, which means that you are corrupting the memory stack and from there anything can happen.


Proposed fixes:

  1. Replace Serial.readString() with Serial.readStringUntil('\n'), the former returns when it times out, whereas the latter returns when it matches a newline character or it times out. The default timeout is 1 second.

  2. Change

    finalString = RED + "," + GREEN + "," + BLUE
    

    to

    finalString = RED + "," + GREEN + "," + BLUE + "\n"
    

    and remove print("\n")

  3. Change your python code so that it sends a message to Arduino only when the content of the message has changed wrt. the last one that was sent:

    last_msg = ""
    while 1:
        window.update_idletasks()
        window.update()
    
        RED = str(RedScale.get())
        GREEN = str(GreenScale.get())
        BLUE = str(BlueScale.get())
    
        finalString = RED + "," + GREEN + "," + BLUE + "\n"
    
        if finalString != last_msg:
            arduino.write(finalString.encode("utf-8"))
            last_msg = finalString
    
            print(finalString)
    

Note 01: even after you fixed it, consider posting your Arduino code to code review for some feedback on code styling and strong design.

Note 02: Even with the proposed fixes, the source code remains vulnerable to wrong behaviours given the right set of circumstances (e.g.: what happens if readStringUntil() timeouts before \n is matched? how do you deal with partial input?)


EDIT 1: The python code crashes due to the fact that you do not check for the validity of objects RedScale, GreenScale and BlueScale before accessing them with get(), and this obviously fails right after the tk window is closed.

A naive solution would be the following:

import sys
import time

global exitFlag
exitFlag = False

...

def endProgram():
    global exitFlag
    exitFlag = True
    window.destroy()

window.protocol("WM_DELETE_WINDOW", endProgram)

...

last_msg = ""
finalString = ""
while 1:
    if not exitFlag:
        window.update_idletasks()

    if not exitFlag:
        window.update()

    if not exitFlag:
        RED = str(RedScale.get())
        GREEN = str(GreenScale.get())
        BLUE = str(BlueScale.get())

        finalString = RED + "," + GREEN + "," + BLUE + "\n"

    if finalString != last_msg:
        arduino.write(finalString.encode("utf-8"))
        last_msg = finalString

        print(finalString)

     if exitFlag:
         sys.exit()

Note that, although stackoverflow is overcrowded with people suggesting this solution, i think that it is bad design and I suspect still buggy. A proper solution would be to override the event listener for the adjustment of the Scale instances, so that the value of the Scale is only read and sent when it is actually changed by the user. I'll let you work out the details.

Community
  • 1
  • 1
Patrick Trentin
  • 7,126
  • 3
  • 23
  • 40
  • Hey thanks for the response, but now the problem is that if I change, for example, the green color it will be mainly green, but sometimes it changes to blue really quickly and it comes back to green, and then in the end the program crashes. – David Martins Dec 31 '16 at 16:34
  • @ddmdavid as I wrote in my **note**, your code is still *not robust enough* to deal with *partial inputs*. Either you send `int` values with a fixed *width* from *Python* to *Arduino* (e.g. `001` instead of `1`), and then wait until there are at least `12` bytes in the `Serial` buffer, or you accumulate all your bytes into a local array of length `12`, and only when you match a full line you decode the `RGB` values stored in it. – Patrick Trentin Dec 31 '16 at 16:42
  • The *crash* is due to the fact that you do not check for the validity of objects `RedScale`, `GreenScale` and `BlueScale` before accessing them with `get()`, and this obviously fails when you close your **tk window**, because *it closes only the window, freeing its resources, and does not exit the python code*. – Patrick Trentin Dec 31 '16 at 16:47
  • no but the thing is that the arduino is fine, but the python program simply crashes and I don't understand why. – David Martins Dec 31 '16 at 17:09
  • I just explained you why: *the python code is still running when you close the window, so it attempts to access an object that is no longer valid* :) – Patrick Trentin Dec 31 '16 at 17:10
  • I know but I didn't understand it :D. I am not even in university yet, I learn code by myself and sometimes I don't understand certain explanations!!!! – David Martins Dec 31 '16 at 17:12
  • I *edited* my answer to add some more info. There is nothing at *university* level here, so don't feel pushed back by it. – Patrick Trentin Dec 31 '16 at 17:47
  • I ended up to put it in a different way, I added a button and now I have to click to send every time. Didn't manage to do the way I intended :( Thanks any way you helped a lot!!! – David Martins Dec 31 '16 at 19:05
  • look at this http://stackoverflow.com/questions/3966303/tkinter-slider-how-to-trigger-the-event-only-when-the-iteraction-is-complete, the second answer is exactly what you need, so you can get rid of the button – Patrick Trentin Dec 31 '16 at 19:08
  • Also, consider changing *communication protocol*: send '[R|G|B]NNN\n' to Arduino, so you only update the value that is changed by the user and spare data, and you can simplify the aforementioned event listener quite a bit. – Patrick Trentin Dec 31 '16 at 19:18
  • I don't understand what do you mean by send [R|G|B]NNN\n – David Martins Dec 31 '16 at 20:35
  • e.g. "R1\n" update *red* value to *1*, "G255\n" update *green* to *255*, .. and so on.. for each update, which only ever changes the value of one colour because you can *drag-and-drop* only one *scale* at a time, you send one third of data you would with your current system, on average. – Patrick Trentin Dec 31 '16 at 20:36