Problem Receiving SysEx Messages

Wow ok now I got it working! I reset to DualMoco and this is the code:

//********************************* Include libraries*********************************
#include <Control_Surface.h>  
//************************************* LEDs  **************************************
const pin_t ledA = 48;
const pin_t ledB = 49;
const pin_t ledP = 50;

const uint8_t sysex_prefix[] {0xF0, 0x29, 0x50, 0x10, 0x01};
const uint16_t sysex_len = 7; // expected length of the full sysex message
const uint16_t sysex_led_idx = 5; // byte of the message containing the LED states
const uint8_t ledA_mask = 0x01; // mask that selects the bit with the ledA state
const uint8_t ledB_mask = 0x02;
const uint8_t ledP_mask = 0x04;

bool sysex_matches(SysExMessage sysex) {
  return sysex.length == sysex_len &&                      // First check the length of the message,
         std::equal(std::begin(sysex_prefix), std::end(sysex_prefix), sysex.data); // then the data.
}

// Custom MIDI callback that updates the LEDs:
struct MyMIDI_Callbacks : MIDI_Callbacks {

  // This callback function is called whenever a SysEx message is received:
  void onSysExMessage(Parsing_MIDI_Interface &midi) override {
    // Get the message from the MIDI interface:
    SysExMessage sysex = midi.getSysExMessage();
    // Check if it matches the LED update SysEx message we expect:
    if (sysex_matches(sysex)) {
      // Turn on the right LEDs:
      const uint8_t led_data = sysex.data[sysex_led_idx];
      digitalWrite(ledA, led_data & ledA_mask ? HIGH : LOW);
      digitalWrite(ledB, led_data & ledB_mask ? HIGH : LOW);
      digitalWrite(ledP, led_data & ledP_mask ? HIGH : LOW);
    }
  }
};
//********************* Instantiate a MIDI Interface to use ************************
USBMIDI_Interface midi; 
// Instantiate the MIDI callbacks:
MyMIDI_Callbacks callback;

//******************** Instantiate analog multiplexer ****************************** 
//                         Analog input pin A0, Address pins S0, S1, S2, S3 
CD74HC4067 mux ={A0, {2, 3, 4, 5} };

//*********************************************************************************
//                                      Create an array of potentiometers that send out 
//                                     MIDI Control Change messages 
//                                     when you turn the potentiometers connected to 
//                                     the input pins of the mux 

CCPotentiometer volumePotentiometers[] = {
  { mux.pin(0), {25, CHANNEL_1 } },//0x19
  { mux.pin(1), {74, CHANNEL_1 } },//0x4A
  { mux.pin(2), {71, CHANNEL_1 } },//0x47
  { mux.pin(3), {26, CHANNEL_1 } },//0x1A
  { mux.pin(5), {28, CHANNEL_1 } },//0x1C
  { mux.pin(6), {29, CHANNEL_1 } },//0x1D
  { mux.pin(7), {30, CHANNEL_1 } },//0x1E
  { mux.pin(8), {68, CHANNEL_1 } },//0x44 
  { mux.pin(9), {13, CHANNEL_1 } },//0xD
  { mux.pin(10),{27, CHANNEL_1 } },//0x1B
};
//*********************************************************************************
//                                  Instantiate an array of CCPotentiometer objects
CCPotentiometer potentiometers[] = {
  {A1, 0x07},         // CC07 (Analog pin, Controller number)
  {A2, 0x08},         // CC08
  {A3, 0xC},          // CC12
  {A5, 0xE},          // CC14
  {A6, 0xF},          // CC15  
  {A7, 0x10},         // CC16
  {A8, 0x11},         // CC17
  {A9, 0x12},         // CC18
  {A10, 0x13},        // CC19  
  {A11, 0x14},        // CC20
  {A12, 0x15},        // CC21
  {A13, 0x16},        // CC22  
  {A14, 0x17},        // CC23
  {A15, 0x18},        // CC24
};
//********************************************************************
// Instantiate an array of CCButton object,  Push button on pin 5, CC message on MIDI channel 1
CCButton button[] = {
 {6, {92, CHANNEL_1}},    // 0x5C
 {7, {93, CHANNEL_1}},    // 0x5D
 {8, {94, CHANNEL_1}},    // 0x5E
 {9, {95, CHANNEL_1}},    // 0x5F
 {10, {96, CHANNEL_1}},   // 0x60
 {11, {97, CHANNEL_1}},   // 0x61 (Group)
 {12, {98, CHANNEL_1}},   // 0x62
 {13, {99, CHANNEL_1}},   // 0x63
 {14, {100,CHANNEL_1}},   // 0x64 (Panel)
 {15, {101,CHANNEL_1}},   // 0x65
 {16, {80, CHANNEL_1}},   // 0x50
 {17, {81, CHANNEL_1}},   // 0x51
 {18, {82, CHANNEL_1}},   // 0x52
 {19, {83, CHANNEL_1}},   // 0x53
 {20, {84, CHANNEL_1}},   // 0x54
 {21, {85, CHANNEL_1}},   // 0x55
 {22, {86, CHANNEL_1}},   // 0x56
 {23, {87, CHANNEL_1}},   // 0x57
 {24, {88, CHANNEL_1}},   // 0x58
 {25, {89, CHANNEL_1}},   // 0x59
 {26, {90, CHANNEL_1}},   // 0x5A
 {27, {91, CHANNEL_1}},   // 0x5B
};

void setup() {
  Control_Surface.begin();            // Initialize the Control Surface
  pinMode(ledA, OUTPUT);
  pinMode(ledB, OUTPUT);
  pinMode(ledP, OUTPUT);
  midi.begin();
  midi.setCallbacks(callback);
  }

void loop()
{
  Control_Surface.loop();             // Update the Control Surface 
  midi.update();
}

Next step is to get the LCD work...
Thanks a lot guys! I really really appreciate the help.

Glad to hear you got it working!

Since you're using Control_Surface.begin() and .loop(), you can omit the calls to midi.begin() and midi.update(), Control Surface will call those for you.

Do you understand how this code works? You should be able to use it to receive and match any kind of SysEx message, including the ones for your LCD display.

I believe I understand, you have 2 variables, one for the prefix and one for the length. Then you create another one sysex_matches and compare it to the first 2. The thing is my LCD length is variable because it only writes new characters and sometimes there's nothing to write: It has a prefix though:

0xF0 (SOX)
0x29 (ID)
0x50 (MIDI Ch)
0x11 (COMMAND)
0X24 (LENGTH OF SYSEX)
0X6 (TYPE OF CURSOR)
0X0E (CURSOR POSITION AFTER WRITING)
0x0E (CURSOR START POSITION FIRST LINE)
0x33 (CURSOR START POSITION SECOND LINE)
Then DATA
0xF7 (EOS)

The (CURSOR START POSITION SECOND LINE) only appears if there's anything to write.
Can your code be modified for this case?

I think I can come up with something, will let you know how I make out.

Sure.

Feel free to post it here if you need help or if you want feedback.

Thanks. I'll definitely need some help but I'll do my homework and show you what I got.

I have a question,

The LED sysex full message length is constant at 7 bytes so we can have:

const uint8_t sysex_prefix[] {0xF0, 0x29, 0x50, 0x10, 0x01};
const uint16_t sysex_len = 7; // expected length of the full sysex message

but the LCD sysex message length is variable, although the length is at byte# 4.
Can we then have:

const uint8_t sysex_LCDprefix[] {0xF0, 0x29, 0x50, 0x11, 0x4D};
const uint16_t sysex_LCDlen = sysex_LCDprefix[4]; // expected length of the full sysex message

Actually, byte#4 is not the full length of the message but of the data that follows so it would be more like:

const uint16_t sysex_LCDprefix[] {0xF0, 0x29, 0x50, 0x11, 0x4D};
const uint16_t sysex_LCDlen = sysex_LCDprefix[4] + 5; // expected length of the full sysex message

So if I can get the prefix and mesasge length for the LCD, do I need to create a new sysex_matches and callback just for the LCD? Or can both the LED and LCD sysex be handled by the same callback?

That won't work. Think about when each variable is initialized: they're both constants, the sysex_LCDlen variable will always contain the value 0x4D + 5.

Ok so I revamped the code today but haven’t tried it yet. This should be better:


/********************************* Include libraries***************************
#include <Control_Surface.h> 
#include <Wire.h>
#include <LiquidCrystal_I2C.h> 
LiquidCrystal_I2C lcd(0x27,40,2); // set the LCD address to 0x27 for a 40 chars and 2 line display
//************************************* LEDs Variables *************************
const pin_t ledA = 48;
const pin_t ledB = 49;
const pin_t ledP = 50;

const uint8_t sysex_prefix[] {0xF0, 0x29, 0x50};
const uint16_t sysex_len_idx = 4; // byte containing the length of the data 
const uint16_t sysex_len = sysex.data[sysex_len_idx] + 6; // expected length of the full sysex message

const uint16_t sysex_led_idx = 5; // byte of the message containing the LED states
const uint8_t ledA_mask = 0x01; // mask that selects the bit with the ledA state
const uint8_t ledB_mask = 0x02;
const uint8_t ledP_mask = 0x04;

const uint16_t sysex_com_idx = 3; // byte containing the command code
const uint8_t led_com = 0x10; // LED command code
const uint8_t lcd_com = 0x11; // LCD command code
const uint8_t sysex_lcdCursorType_idx = 5 // Cursor type
const uint8_t sysex_lcdCursorEnd_idx = 6 // Cursor position after writing 
const uint8_t sysex_lcdCursorStart_idx = 7 // Cursor position before writing 

bool sysex_matches(SysExMessage sysex) {
 return sysex.length == sysex_len && // First check the length of the message,
 std::equal(std::begin(sysex_prefix), std::end(sysex_prefix), sysex.data); // then the data.
}
// ****************** Custom MIDI callback that updates the LEDs: *****************
struct MyMIDI_Callbacks : MIDI_Callbacks {
// This callback function is called whenever a SysEx message is received:
void onSysExMessage(Parsing_MIDI_Interface &midi) override {

 // Get the message from the MIDI interface:
 SysExMessage sysex = midi.getSysExMessage();

 // Check if byte 4 contains the command code for the LED
 const uint8_t sysex_com = sysex.data[sysex_com_idx];

 if sysex_com == led_com {
 // Turn on the right LEDs:
 const uint8_t led_data = sysex.data[sysex_led_idx];
 digitalWrite(ledA, led_data & ledA_mask ? HIGH : LOW);
 digitalWrite(ledB, led_data & ledB_mask ? HIGH : LOW);
 digitalWrite(ledP, led_data & ledP_mask ? HIGH : LOW);
 
 } else if sysex_com == lcd_com {
// ******************************** Print LCD data: ******************************
lcd.init(); // initialize the lcd 
// Print a message to the LCD.
const uint8_t sysex_lcdCursorStart = sysex.data[sysex_lcdCursorStart_idx];
const uint8_t sysex_lcdCursorEnd = sysex.data[sysex_lcdCursorEnd_idx];
lcd.backlight();
// Set cursor to before writing
if sysex_lcdCursorStart < 40 {
lcd.setCursor(sysex_lcdCursorStart,0); 

lcd.print(sysex ............); // data from byte #8 to sysex_len-1

} else if sysex_lcdCursorStart > 40 {
lcd.setCursor(sysex_lcdCursorStart,1);
}
// Set cursor after writing 
if sysex_lcdCursorEnd < 40 {
lcd.setCursor(sysex_lcdCursorEnd, 0); 
} else if sysex_lcdCursorEnd> 40 {
lcd.setCursor(sysex_lcdCursorEnd,1);
}

} 
 }
};

This is actually just the top portion of the code. This morning I included the void setup and loop and replaced the LCD code for an LED (I haven't received my screen from china yet!) to test the sysex_com == lcd_com and the led code worked as expected but didn't turn on the test led to show that I received a sysex message with the LCD command code. I'll try a few things again and let you know how I make out.

Same problem here, that will never work. sysex.data doesn't exist when you try to initialize sysex_len.

You need a better parser:

#include <Control_Surface.h>

const pin_t ledA = 48;
const pin_t ledB = 49;
const pin_t ledP = 50;

template<class T, size_t N> constexpr size_t len(const T (&)[N]) {
  return N;
}

namespace sysex_cmds {

const uint8_t prefix[]{ 0xF0, 0x29, 0x50 };
const uint16_t prefix_len = len(prefix);

const uint16_t cmd_idx = 3;  // command
const uint16_t len_idx = 4;  // payload length
const uint16_t led_idx = 5;  // LED states
const uint16_t min_led_len = led_idx + 2;
const uint16_t lcd_cursor_type_idx = 5;   // cursor type
const uint16_t lcd_cursor_end_idx = 6;    // cursor position after writing
const uint16_t lcd_cursor_start_idx = 7;  // cursor position before writing
const uint16_t lcd_text_idx = 8;
const uint16_t min_lcd_len = lcd_cursor_start_idx + 2;

const uint8_t led_cmd = 0x10;  // LED command code
const uint8_t lcd_cmd = 0x11;  // LCD command code

const uint8_t ledA_mask = 0x01;  // mask that selects the bit with the ledA state
const uint8_t ledB_mask = 0x02;
const uint8_t ledP_mask = 0x04;

void update_group_leds(SysExMessage sysex) {
  if (sysex.length < min_led_len)
    return;

  uint8_t led_data = sysex.data[led_idx];
  digitalWrite(ledA, led_data & ledA_mask ? HIGH : LOW);
  digitalWrite(ledB, led_data & ledB_mask ? HIGH : LOW);
  digitalWrite(ledP, led_data & ledP_mask ? HIGH : LOW);
}

void update_lcd(SysExMessage sysex) {
  if (sysex.length < min_lcd_len)
    return;

  uint8_t len = sysex.data[len_idx] - 3;  // length claimed by the sender
  uint8_t cursor_type = sysex.data[lcd_cursor_type_idx];
  uint8_t cursor_end = sysex.data[lcd_cursor_end_idx];
  uint8_t cursor_start = sysex.data[lcd_cursor_start_idx];
  auto text = reinterpret_cast<const char *>(sysex.data + lcd_text_idx);

  uint16_t max_len = sysex.length - 1 - lcd_text_idx;  // max length we actually received
  if (len > max_len)
    len = max_len;

  Serial << "len: " << len << ", cursor: " << cursor_type << ","
         << cursor_end << "," << cursor_start << ", text: ";
  Serial.write(text, len);
  Serial.println();
}

void handle_sysex(SysExMessage sysex) {
  // Check message length and make sure that the prefix matches
  if (sysex.length < prefix_len + 2)
    return;  // Message is too short, cannot possibly match prefix and command
  if (not std::equal(std::begin(prefix), std::end(prefix), sysex.data))
    return;  // Prefix doesn't match

  uint8_t cmd = sysex.data[cmd_idx];

  switch (cmd) {
    case led_cmd: update_group_leds(sysex); break;
    case lcd_cmd: update_lcd(sysex); break;
  }
}

}  // namespace sysex_cmds

struct MyMIDI_Callbacks : MIDI_Callbacks {
  // This callback function is called whenever a SysEx message is received:
  void onSysExMessage(Parsing_MIDI_Interface &midi) override {
    sysex_cmds::handle_sysex(midi.getSysExMessage());
  }
};

// Instantiate the MIDI over USB interface:
USBDebugMIDI_Interface midi{ 115200 };
// Instantiate the MIDI callbacks:
MyMIDI_Callbacks callback;

void setup() {
  pinMode(ledA, OUTPUT);
  pinMode(ledB, OUTPUT);
  pinMode(ledP, OUTPUT);
  Serial.begin(115200);
  midi.begin();
  midi.setCallbacks(callback);
}

void loop() {
  // Read incoming MIDI data and call the callback if a new
  // SysEx message has been received.
  midi.update();
}

When you send F0 29 50 11 B 6 0E 0E 33 20 4D 4F 3A 31 20 4D F7 in the Serial monitor, it prints len: 8, cursor: 6,14,14, text: 3 MO:1 M.

Wow! I would never have been able to do this on my own. I just tried it and it works beautifully!
Thank you so much...I can't wait to get my LCD screen and set it up!