MessagePanel.ino
  1  // SPDX-FileCopyrightText: 2019 Melissa LeBlanc-Williams for Adafruit Industries
  2  //
  3  // SPDX-License-Identifier: MIT
  4  
  5  // Message Panel
  6  // Reads an Adafruit IO Feed, then formats and displays the message
  7  // Author: Melissa LeBlanc-Williams
  8  
  9  #include <RGBmatrixPanel.h>
 10  #include <SPI.h>
 11  
 12  #define BASE_CHAR_WIDTH 6   // 5 pixels + 1 space
 13  #define BASE_CHAR_HEIGHT 8  // 7 pixels + 1 space
 14  
 15  // Most of the signal pins are configurable, but the CLK pin has some
 16  // special constraints.  On 8-bit AVR boards it must be on PORTB...
 17  // Pin 8 works on the Arduino Uno & compatibles (e.g. Adafruit Metro),
 18  // Pin 11 works on the Arduino Mega.  On 32-bit SAMD boards it must be
 19  // on the same PORT as the RGB data pins (D2-D7)...
 20  // Pin 8 works on the Adafruit Metro M0 or Arduino Zero,
 21  // Pin A4 works on the Adafruit Metro M4 (if using the Adafruit RGB
 22  // Matrix Shield, cut trace between CLK pads and run a wire to A4).
 23  
 24  //#define CLK  8   // USE THIS ON ADAFRUIT METRO M0 or adapting to use Airlift, etc.
 25  #define CLK A4 // USE THIS ON METRO M4 (not M0)
 26  #define OE   9
 27  #define LAT 10
 28  #define A   A0
 29  #define B   A1
 30  #define C   A2
 31  #define D   A3
 32  
 33  RGBmatrixPanel matrix(A, B, C, D, CLK, LAT, OE, false, 64);
 34  
 35  #include "config.h"
 36  
 37  // set up the 'messagepanel' feed
 38  AdafruitIO_Feed *counter = io.feed("messagepanel");
 39  
 40  void drawText(const char *text, bool resetPosition = true, uint16_t color = 0xffff, uint16_t textSize = 1) {
 41    matrix.setTextSize(textSize);     // size 1 == 8 pixels high
 42    if (resetPosition) {
 43      matrix.setCursor(0, 0);    // start at top left, with 8 pixel of spacing
 44    }
 45    matrix.setTextColor(color);
 46    matrix.print(text);
 47  }
 48  
 49  // This function is called whenever a 'messagepanel' message
 50  // is received from Adafruit IO. it was attached to
 51  // the counter feed in the setup() function above.
 52  void handleMessage(AdafruitIO_Data *data) {
 53    String message = data->toString();
 54    String plainText = data->toString();
 55    uint16_t color = matrix.Color333(7, 7, 7);
 56    uint16_t textSize = 1;
 57    uint16_t colorStartIndex = 0, colorEndIndex = 0;
 58    uint16_t sizeStartIndex = 0, sizeEndIndex = 0;
 59    uint16_t strpos = 0;
 60    byte lineLengths[] = {0, 0, 0, 0};
 61    byte lineNum = 0;
 62    byte messageHeight = 0;
 63    byte lineHeight = 0;
 64    // Calculate line lengths
 65    boolean paramRead = false;
 66    boolean newLine = false;
 67    
 68    matrix.setCursor(0, 0);
 69    matrix.fillScreen(matrix.Color333(0, 0, 0));
 70  
 71    // Strip out all color data first
 72    while(strpos < plainText.length()) {
 73      colorStartIndex = plainText.indexOf('{');
 74      colorEndIndex = plainText.indexOf('}');
 75      plainText.remove(colorStartIndex, colorEndIndex - colorStartIndex + 1);
 76      strpos++;
 77    }
 78  
 79    // Calculate the line lengths in pixels for fixed width text
 80    strpos = 0;
 81    while(strpos < plainText.length()) {
 82      sizeStartIndex = plainText.indexOf('<');
 83      sizeEndIndex = plainText.indexOf('>');
 84  
 85      if (strpos == sizeStartIndex) {
 86        textSize = atoi(plainText.substring(sizeStartIndex + 1, sizeEndIndex).c_str());
 87        plainText.remove(sizeStartIndex, sizeEndIndex - sizeStartIndex + 1);
 88      }
 89      
 90      if (plainText.charAt(strpos) != '\n') {
 91        lineLengths[lineNum] += textSize * BASE_CHAR_WIDTH;
 92        if (textSize * BASE_CHAR_HEIGHT > lineHeight) {
 93          lineHeight = textSize * BASE_CHAR_HEIGHT;
 94        }
 95      }
 96  
 97      // We want to keep adding up the characters * textSize until we hit a newline character
 98      // or we reach the width of the message panel. Then we go down to the next line
 99      if (plainText.charAt(strpos) == '\n' || lineLengths[lineNum] >= matrix.width()) {
100        messageHeight += lineHeight;
101        lineHeight = 0;
102        lineNum++;
103      }
104      
105      strpos++;
106    }
107  
108    // Add the last line
109    messageHeight += lineHeight;
110  
111    textSize = 1;
112    lineNum = 0;
113    for(uint16_t i=0; i<message.length(); i++) {
114      if (message.charAt(i) == '{') {
115        paramRead = true;
116        colorStartIndex = i + 1;
117      } else if (message.charAt(i) == '}') {
118        paramRead = false;
119        int wheelPos = atoi(message.substring(colorStartIndex, i).c_str());
120        if (wheelPos < 24) {
121          color = Wheel(wheelPos);
122        } else {
123          color = matrix.Color333(7, 7, 7);
124        }
125      } else if (message.charAt(i) == '<') {
126        paramRead = true;
127        sizeStartIndex = i + 1;
128      } else if (message.charAt(i) == '>') {
129        paramRead = false;
130        textSize = atoi(message.substring(sizeStartIndex, i).c_str());
131      } else {
132        if (paramRead) continue;
133  
134        if (matrix.getCursorX() == 0 && matrix.getCursorY() == 0) {
135          matrix.setCursor(floor((matrix.width() / 2) - (lineLengths[lineNum] / 2)), matrix.height() / 2 - messageHeight / 2);
136        } else if (newLine) {
137          matrix.setCursor(floor((matrix.width() / 2) - (lineLengths[++lineNum] / 2)), matrix.getCursorY());
138          newLine = false;
139        }
140        drawText(message.substring(i, i+1).c_str(), false, color, textSize);
141        if (message.charAt(i) == '\n' || matrix.getCursorX() >= matrix.width()) {
142          newLine = true;
143        }
144      }
145    } 
146  }
147  
148  void setup() {
149    matrix.begin();
150  
151    // fill the screen with 'black'
152    matrix.fillScreen(matrix.Color333(0, 0, 0));
153    // draw some text!
154    matrix.setTextWrap(true);
155    drawText("Connecting...");
156    Serial.begin(115200);
157    io.connect();
158  
159    counter->onMessage(handleMessage);
160  
161    while(io.mqttStatus() < AIO_CONNECTED) {
162      drawText(".");
163      delay(500);
164    }  
165  
166    counter->get();
167  
168    Serial.println();
169    Serial.println(io.statusText());
170  }
171  
172  void loop() {
173    io.run();
174  }
175  
176  // Input a value 0 to 23 to get a color value.
177  // The colours are a transition r - g - b - back to r.
178  uint16_t Wheel(byte WheelPos) {
179    if(WheelPos < 8) {
180     return matrix.Color333(7 - WheelPos, WheelPos, 0);
181    } else if(WheelPos < 16) {
182     WheelPos -= 8;
183     return matrix.Color333(0, 7 - WheelPos, WheelPos);
184    } else {
185     WheelPos -= 16;
186     return matrix.Color333(WheelPos, 0, 7 - WheelPos);
187    }
188  }