Serial in class does not work, works fine outside of class

Hello everyone,

I am experiencing very odd behavior in my arduino code. I do not believe the python code i wrote is the issue since it acts the same way with the serial monitor through the IDE. I am using an uno at the moment.

The comments in command_exec_test.ino outline much of the behavior for each line commented in/out.

I am basically just trying to pass Serial into a class and be able to call things like print. I have tried doing it as a pointer and a reference but both have the same behavior.

I am passing it as a stream object into the class.

Outside of the class using sprintf to construct a message and print to print it the code works as expected. I then commented this out for printing in the class.

Once entering the class it repeatedly reads "setup complete" from the setup code in the arduino but i do not see the arduino resetting via the led lights.

I also put a third test which doesn't include the sprintf and it reads twice from my python code. (both reads read, "in loop")

I can only imagine this is some sort of buffer issue? Or something with hardware vs software serial and the stream class. But i am unsure and haven't been able to figure it out. The code is basic enough i didn't expect to have these issues.

The behavior is similar if not the same from using the serial monitor from the arudino IDE although the python code reads twice without sending two messages.

Note: In the python code (arduino_serial.py) we wait for "setup complete" to send before continuing with the rest of the code. This is not printed. Therefore we should not see "setup complete" sent in the output. (unless on IDE serial monitor)

Arduino code:
command.h

#ifndef _COMMAND_H
#define _COMMAND_H

#include <stdint.h>
#include <Arduino.h>

#define S0_ANGLE 0x0

class command {
    public:
        command(Stream &serial); 
        ~command();
        
        void parse_msg(uint8_t *msg);
        void exec_command(uint8_t *msg);

    private:
        Stream &my_serial;
};

#endif

command.cpp (you can ignore parse_msg it is not used)

#include "command.h"

#define PARAM_BUFF 100

command::command(Stream &serial)
{
    my_serial = serial;
}

command::~command()
{
}

void command::parse_msg(uint8_t *msg)
{
    int i = 0;
    uint8_t msg_type = 0;
    uint8_t cmd = 0;
    uint8_t param[PARAM_BUFF] = {'\0'};

    // parse msg type
    msg_type = msg[0]; 

    // parse cmd type
    cmd = msg[1];
    
    if (cmd == S0_ANGLE) {
        // 1 uint8_t param for S0_ANGLE
        param[0] = msg[2];
    }
}

void command::exec_command(uint8_t *msg)
{
    my_serial.print("printing from exec_command() in command class\n");
}

command_exec_test.ino

/*
 * Program to test sending and recieving data from serial.
 * written by: James Ross
 */

#include "command.h"

#define BAUD_RATE 115200
#define MSG_BUFF 256
#define S_IN_BUFF 10 // serial input buffer size

uint8_t serial_in[S_IN_BUFF] = {'\0'};
char serial_msg[MSG_BUFF] = {'\0'};

// initialize cmd class with serial
command cmd = command(Serial);

void setup()
{
    Serial.begin(BAUD_RATE);
    while (!Serial) {} // wait for serial interace to connect
    Serial.setTimeout(1);
    Serial.print("Setup Complete\n"); // let serial com know setup complete
}

void loop()
{
    int i = 0;
    int num_recv = 0;
    if (Serial.available()) {
        num_recv = Serial.readBytes(serial_in, S_IN_BUFF-1); // room for '\0'
        serial_in[num_recv] = '\0';

        /* Below two lines work fine with python code */
        //sprintf(serial_msg, "serial available, %s\n", serial_in);
        //Serial.print(serial_msg);

        /* printing in class does not work. Behavior makes me see 
         * "setup complete" each read from the setup function for some reason.
         * (when above two lines are commented out)
         */
        cmd.exec_command(serial_in);
        
        /* if above 3 lines are removed and line below is uncommented, i can
         * read it multiple times from the python code.
         */
        //Serial.println("in loop");
    }
}

Python Code (if needed, though this code should be working as expected)

arduino_serial.py (connects to the arduino via serial)

import serial
import time

class arduino_com:

    def __init__(self, port, baudrate, rtimeout):
        self.port = port
        self.baudrate = baudrate
        self.rtimeout = rtimeout
        self.arduino = 0 # will be serial object

    def begin(self, delay=2):
        self.arduino = serial.Serial(port=self.port, baudrate=self.baudrate, 
                                     timeout=self.rtimeout)
        # arduino resets after serial connection, wait for arduino to setup 
        time.sleep(delay)
        while (not self.read_line()):
            pass

    def write(self, msg, delay=0.05):
        self.arduino.write(msg) 
        time.sleep(delay)

    def read(self):
        return self.arduino.read()

    def read_line(self):
        return self.arduino.readline()

command.py (just builds a message to send to the arduino)

import struct

class command_msg:
    S0_ANGLE = 0x0 # change servo 1 angle

    # private
    _SHIFT_BYTE = 8
    _CMD_MSG = 2
    _msg = 0

    def __init__(self):
        pass

    def build_cmd_msg(self, cmd, *argv):
        if (cmd == self.S0_ANGLE):
            arg = argv
            self._msg =  struct.pack("bbb", self._CMD_MSG, cmd, arg[0])
        
        return self._msg

main.py (sends and receives the messages from the arduino)

from arduino_serial import arduino_com
from command import command_msg
from command import command_interface

import time

BAUD_RATE = 115200
SERIAL_PORT = "/dev/ttyACM0"
RTIMEOUT = 0.5 # read serial timeout

if __name__ == "__main__":
    command = command_interface()
    cmd_msg = command_msg()
    arduino_serial = arduino_com(SERIAL_PORT, BAUD_RATE, RTIMEOUT)
    arduino_serial.begin()

    msg = cmd_msg.build_cmd_msg(cmd_msg.S0_ANGLE, 90) 

    arduino_serial.write(msg)
    print("reading message from arduino")
    time.sleep(1)
    data = arduino_serial.read_line()
    print(data)
    
    # data should read empty in this iteration based on arduino code
    # It is NOT empty and repeats previous read when arduino sends "in loop" in
    # its code
    data = arduino_serial.read_line()
    print(data)

Hopefully the code isnt overwhelming. Its not too in depth of code just organized in classes.

Any help or insight as to what could be causing this would be greatly appreciated.

The compiler complains that your "Stream &my_serial;" is not initialized at constructor time. You have to move the initialization out of the constructor body and put it in the constructor declaration:

command::command(Stream &serial) 
  :  my_serial(serial)
{
}

Thanks for that, It seems to help.

What exactly is the difference tho? Shouldnt it turn out to be the same thing having it in the constructor vs having it in the way you did it (i dont know what that type of initialization is called).

also could you tell me how you got the compiler complaint? I dont see anything when i verify the code on the IDE as far as compiler complaints.

A reference has to have a value when it is created. When it is a member of an object it has to be created before the body of the constructor runs. The same thing happens with base classes that require constructor arguments.

In Preferences..., set "Compiler warnings: All". That's how I got these two warnings:

sketch_oct17a.ino: In constructor 'command::command(Stream&)':

sketch_oct17a.ino:27:1: warning: uninitialized reference member in 'class Stream&' [-fpermissive]
 command::command(Stream &serial)
 ^~~~~~~

sketch_oct17a.ino:18:17: note: 'Stream& command::my_serial' should be initialized
         Stream &my_serial;
                 ^~~~~~~~~