Skip to content

Instantly share code, notes, and snippets.

@jaygarcia
Last active April 19, 2019 19:41
Show Gist options
  • Save jaygarcia/c8b0ce341b36c453c869fc2fe8c4f29c to your computer and use it in GitHub Desktop.
Save jaygarcia/c8b0ce341b36c453c869fc2fe8c4f29c to your computer and use it in GitHub Desktop.
rpi-rgb-led-matrix example using TCP/IP to fill a display (RECEIVER)
/*
This gist demonstrates how to setup threaded & blocking (probably could have used the async transfer instead)
Boost ASIO server to receive a number of bytes from a client. It was developed as a proof of concept and has many areas that
could be improved for speed. I ran this on a Raspberry PI 3b+ using the active RGB Matrix board from electrodragon.
Here's a demonstration this code executing on the PI 3b+ pumping 49,152 bytes for the video data ((6 x (64x64 rgb matrices) * 2).
https://www.youtube.com/watch?v=ODnbhvbLX9E&feature=youtu.be
This works spawning two threads (Network & Display)
We first spawn a thread that will constantly listen on the specified port & IP. We expect a certain number of bytes.
After the TCIP/IP transfer is complete, the networked buffer data is then copied to the networkScreenBuffer array.
Next, we kick off the RGB Matrix thread, and upon every execution of Run(), the contents of networkScreenBuffer is
split into rgb values. We use those rgb values to paint the off screen canvas via it's SetPixel method.
Sender example:
https://gist.github.com/jaygarcia/5d77e6687e742dcfb19f58728c997b76
Resources:
LibBoost 1.7.0 (installed from scratch -- not hard!): https://dl.bintray.com/boostorg/release/1.70.0/source/boost_1_70_0.tar.gz
RGB Matrix library: https://github.com/hzeller/rpi-rgb-led-matrix/
Issue on the RGB Matrix library project: https://github.com/hzeller/rpi-rgb-led-matrix/issues/796
*/
#include <cstdlib>
#include <iostream>
#include <thread>
#include <utility>
#include <boost/asio.hpp>
#include <cstring>
#include <pthread.h>
#include "../rpi-rgb-led-matrix/include/led-matrix.h"
#include "../rpi-rgb-led-matrix/include/threaded-canvas-manipulator.h"
#include "../rpi-rgb-led-matrix/include/pixel-mapper.h"
#include "../rpi-rgb-led-matrix/include/graphics.h"
#include <assert.h>
#include <getopt.h>
#include <limits.h>
#include <math.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <algorithm>
using std::min;
using std::max;
using boost::asio::ip::tcp;
using namespace rgb_matrix;
pthread_mutex_t bufferMutex;
volatile bool interrupt_received = false;
static void InterruptHandler(int signo) {
interrupt_received = true;
}
// Change the following constants to your liking. The number of bytes that are sent
// will need to match readBufferSize!
const uint16_t matrixWidth = 64;
const uint16_t matrixHeight = 64;
const uint16_t matrixSize = (matrixHeight * matrixWidth);
const uint16_t numMatricesWide = 3;
const uint16_t numMatricesTall = 2;
const uint16_t totalPixels = matrixSize * numMatricesWide * numMatricesTall;
const size_t readBufferSize = totalPixels * sizeof(uint16_t);
// This is the array of uint16_t's that will be populated via TCIP/IP network
uint16_t *networkScreenBuffer;
//**** NETWORK START *****//
void recievePixelData(tcp::socket sock) {
int numBytesRecieved = 0;
int loopNum = 0;
uint16_t sbIndex = 0;
try {
while (! interrupt_received) {
uint16_t data[readBufferSize];
boost::system::error_code error;
size_t length = boost::asio::read(sock, boost::asio::buffer(data), boost::asio::transfer_exactly(readBufferSize), error);
// Ended early! No bueno!
if (error == boost::asio::error::eof){
break;
}
else if (error) {
throw boost::system::system_error(error); // Some other error.
}
pthread_mutex_lock(&bufferMutex);
numBytesRecieved += length;
// probably could be made faster with memcpy instead!
for (int i = 0; i < totalPixels; i++) {
networkScreenBuffer[sbIndex++] = data[i];
}
pthread_mutex_unlock(&bufferMutex);
char *returnData = "K";
boost::asio::write(sock, boost::asio::buffer(returnData, 1));
// Successful transfer (before EOF is reached above)
if (sbIndex == totalPixels) {
// printf("Finished %lu bytes (BREAK!)\n---------------\n\n", readBufferSize);
break;
}
}
}
catch (std::exception& e) {
std::cerr << "Exception in thread: " << e.what() << "\n";
}
}
void server(boost::asio::io_context& io_context, unsigned short port) {
tcp::acceptor a(io_context, tcp::endpoint(tcp::v4(), port));
for (;;) {
std::thread(recievePixelData, a.accept()).detach();
}
}
void serverThread() {
unsigned short port = 9898;
printf("Spawning server thread\n");fflush(stdout);
boost::asio::io_context io_context;
server(io_context, port);
}
// This is a derivative of the ColorPulseGenerator example.
// https://github.com/hzeller/rpi-rgb-led-matrix/blob/master/examples-api-use/demo-main.cc#L44
class NetworkedMatrixDisplay : public ThreadedCanvasManipulator {
public:
NetworkedMatrixDisplay(RGBMatrix *m) : ThreadedCanvasManipulator(m), mMatrix(m) {
mReceiverBuffer = (uint16_t *)std::malloc(sizeof(uint16_t) * totalPixels);
mOffScreenCanvas = m->CreateFrameCanvas();
mCanvasWidth = mOffScreenCanvas->width();
mCanvasHeight = mOffScreenCanvas->height();
printf("NetworkedMatrixDisplay mOffScreenCanvas dimensions :: %ix%i\n", mCanvasWidth, mCanvasHeight);
}
void Run() {
uint32_t continuum = 0;
while (running() && !interrupt_received) {
pthread_mutex_lock(&bufferMutex);
memcpy(mReceiverBuffer, networkScreenBuffer, totalPixels);
int ptrIndex = 0;
for (int row = 0; row < mCanvasHeight; row++) {
for (int col = 0; col < mCanvasWidth; col++) {
uint16_t pixel = networkScreenBuffer[ptrIndex];
// Color separation based off : https://stackoverflow.com/questions/38557734/how-to-convert-16-bit-hex-color-to-rgb888-values-in-c
uint8_t r = (pixel & 0xF800) >> 8; // rrrrr... ........ -> rrrrr000
uint8_t g = (pixel & 0x07E0) >> 3; // .....ggg ggg..... -> gggggg00
uint8_t b = (pixel & 0x1F) << 3; // ............bbbbb -> bbbbb000
mOffScreenCanvas->SetPixel(col, row, r, g, b);
ptrIndex++;
}
}
pthread_mutex_unlock(&bufferMutex);
mOffScreenCanvas = mMatrix->SwapOnVSync(mOffScreenCanvas);
}
}
private:
uint16_t mCanvasWidth;
uint16_t mCanvasHeight;
uint16_t *mReceiverBuffer;
RGBMatrix *const mMatrix;
FrameCanvas *mOffScreenCanvas;
};
ThreadedCanvasManipulator *image_gen = NULL;
Canvas *canvas = NULL;
void start_matrix() {
int runtime_seconds = -1;
const char *demo_parameter = NULL;
RGBMatrix::Options mMatrixoptions;
rgb_matrix::RuntimeOptions runtime_opt;
// I hard coded options here. You'll need to change this per your own specs!
mMatrixoptions.chain_length = numMatricesWide;
mMatrixoptions.cols = matrixWidth;
mMatrixoptions.rows = matrixHeight;
mMatrixoptions.parallel = 2;
RGBMatrix *matrix = CreateMatrixFromOptions(mMatrixoptions, runtime_opt);
if (matrix == NULL) {
printf("ERROR! Could not create RGBMatrix instance!!!!\n");
exit(1);
}
printf("Size: %dx%d. Hardware gpio mapping: %s\n",
matrix->width(), matrix->height(), mMatrixoptions.hardware_mapping);
canvas = matrix;
// The ThreadedCanvasManipulator objects are filling
// the matrix continuously.
image_gen = new NetworkedMatrixDisplay(matrix);
// Image generating demo is crated. Now start the thread.
image_gen->Start();
}
/**** MAIN *****/
int main(int argc, char* argv[]) {
printf("Server starting & expecting (%lu bytes)...\n", readBufferSize); fflush(stdout);
signal(SIGTERM, InterruptHandler);
signal(SIGINT, InterruptHandler);
networkScreenBuffer = (uint16_t *)std::malloc(sizeof(uint16_t) * totalPixels);
try {
std::thread(serverThread).detach();
}
catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << "\n";
exit(2);
}
start_matrix();
printf("Press <CTRL-C> to exit and reset LEDs\n");
while (!interrupt_received) {
sleep(1); // Time doesn't really matter. The syscall will be interrupted.
}
printf("\%s. Exiting.\n",
interrupt_received ? "Received CTRL-C" : "Timeout reached");
// Stop image generating thread. The delete triggers
delete image_gen;
delete canvas;
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment