Skip to content

Commit

Permalink
Messing with UDP. Compressing data sent to board.
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewmunro committed Feb 4, 2024
1 parent 86af79c commit 4e0a819
Show file tree
Hide file tree
Showing 8 changed files with 340 additions and 61 deletions.
123 changes: 75 additions & 48 deletions arduino/bitmapstream.ino
Original file line number Diff line number Diff line change
@@ -1,34 +1,26 @@
#include <stdlib.h>
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
#include <WiFiManager.h>
#include <ArduinoWebsockets.h>
#include <WebSocketsClient.h>
#include <zlib.h>

#define PANEL_WIDTH 64
#define PANEL_HEIGHT 32
#define PANELS_NUMBER 2
#define E_PIN_DEFAULT 18

const char* websockets_connection_string = "ws://rgb.mun.sh/sub"; //Enter server adress
using namespace websockets;
WiFiManagerParameter brightness("brightness", "Display Brightness", "128", 40);
WiFiManagerParameter serverAddress("serverAddress", "Server Address", "rgb.mun.sh", 40);
WiFiManagerParameter serverPath("serverPath", "Server Path", "/sub", 40);
WiFiManagerParameter serverPort("serverPort", "Server Port", "80", 40);
WiFiManagerParameter serverReconnectInterval("serverReconnectInterval", "Server Reconnect Delay", "5000", 40);

MatrixPanel_I2S_DMA* dma_display;
WebsocketsClient client;
WebSocketsClient webSocket;

uint16_t* uint16_data;

void onMessageCallback(WebsocketsMessage message) {
uint16_data = (uint16_t *) message.c_str();
}

void onEventsCallback(WebsocketsEvent event, String data) {
if(event == WebsocketsEvent::ConnectionOpened) {
Serial.println("Connnection Opened");
} else if(event == WebsocketsEvent::ConnectionClosed) {
Serial.println("Connnection Closed");
delay(2000);
ESP.restart();
}
}
uLongf destLen = 8192;
Bytef destBuffer[8192];
Bytef resultBuffer[8192];

void setup() {
Serial.begin(115200);
Expand All @@ -50,50 +42,85 @@ void setup() {
dma_display->clearScreen();
dma_display->setTextSize(1);
dma_display->setTextWrap(true);
dma_display->setCursor(0,0);
dma_display->setCursor(0, 0);
dma_display->print("Connecting to WIFI...");
dma_display->setCursor(16, 16);
dma_display->print("SSID: TrainSignAP");

WiFiManager wm;
wm.addParameter(&brightness);
wm.addParameter(&serverAddress);
wm.addParameter(&serverPath);
wm.addParameter(&serverPort);
wm.addParameter(&serverReconnectInterval);

bool connected;
connected = wm.autoConnect("TrainSignAP");

if(!connected) {
Serial.println("Failed to connect");
dma_display->clearScreen();
dma_display->setTextSize(1); // size 1 == 8 pixels high
dma_display->setTextWrap(true); // Don't wrap at end of line - will do ourselves
dma_display->setCursor(0,0);
dma_display->print("Wifi failed :,(");
delay(2000);
ESP.restart();
if (!connected) {
Serial.println("Failed to connect");
dma_display->clearScreen();
dma_display->setTextSize(1); // size 1 == 8 pixels high
dma_display->setTextWrap(true); // Don't wrap at end of line - will do ourselves
dma_display->setCursor(0, 0);
dma_display->print("Wifi failed :,(");
delay(2000);
ESP.restart();
}

// run callback when messages are received
client.onMessage(onMessageCallback);

// run callback when events are occuring
client.onEvent(onEventsCallback);
Serial.println("WiFi Connected");

// Connect to server
connected = client.connect(websockets_connection_string);
if(!connected) {
Serial.println("Connnection Closed");
dma_display->clearScreen();
dma_display->setTextSize(1);
dma_display->setTextWrap(true);
dma_display->setCursor(0,0);
dma_display->print("Stream failed :,(");
String brightnessValue = brightness.getValue();
int brightnessInt = brightnessValue.toInt();
if (brightnessInt < 0) brightnessInt = 0;
if (brightnessInt > 255) brightnessInt = 255;
dma_display->setBrightness8(brightnessInt);

delay(2000);
ESP.restart();
String portValue = serverPort.getValue();
int port = portValue.toInt();
String serverReconnectIntervalValue = serverReconnectInterval.getValue();
int serverReconnect = serverReconnectIntervalValue.toInt();

webSocket.begin(serverAddress.getValue(), port, serverPath.getValue());
webSocket.onEvent(webSocketEvent);
webSocket.setReconnectInterval(serverReconnect);
}

void webSocketEvent(WStype_t type, uint8_t* payload, size_t length) {
// Decompress the payload
switch (type) {
case WStype_DISCONNECTED:
Serial.println("Disconnected!");
memset(resultBuffer, 0, 8192);

dma_display->clearScreen();
dma_display->setTextSize(1);
dma_display->setTextWrap(true);
dma_display->setCursor(0, 0);
dma_display->print("Reconnecting...");
break;
case WStype_CONNECTED:
Serial.printf("Connected to url: %s\n", payload);
break;
case WStype_BIN: {
int result = uncompress(destBuffer, &destLen, payload, length);
if (result != Z_OK) {
Serial.println("Decompression failed!");
}

// apply result delta to last frame
for (int i = 0; i < 8192; i++) {
resultBuffer[i] = resultBuffer[i] + destBuffer[i];
}

break;
}
}
}

void loop() {
client.poll();
if (uint16_data != nullptr) {
dma_display->drawRGBBitmap(0, 0, uint16_data, 128, 32);
webSocket.loop();
if (webSocket.isConnected()) {
dma_display->drawRGBBitmap(0, 0, (uint16_t*) resultBuffer, 128, 32);
}
}
96 changes: 96 additions & 0 deletions arduino/bitmapstreamudp.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#include <stdlib.h>
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
// #include <WiFiManager.h>
#include <WiFi.h>
#include <WiFiUdp.h>
#include <AsyncUDP.h>

#define PANEL_WIDTH 64
#define PANEL_HEIGHT 32
#define PANELS_NUMBER 2
#define E_PIN_DEFAULT 18

const char* ssid = "xxx";
const char* password = "xxx";
const int udpPort = 8080;

MatrixPanel_I2S_DMA* dma_display;
AsyncUDP udp;

byte buffers[4][1024]; // Adjust this according to your needs
bool received[4] = {false}; // Flag to track received packets
uint16_t assembledData[4096];
int expectedSequence = 0;

void setup() {
Serial.begin(115200);

HUB75_I2S_CFG mxconfig;
mxconfig.mx_height = PANEL_HEIGHT; // we have 64 pix heigh panels
mxconfig.chain_length = PANELS_NUMBER; // we have 2 panels chained
mxconfig.gpio.e = E_PIN_DEFAULT; // we MUST assign pin e to some free pin on a board to drive 64 pix height panels with 1/32 scan
mxconfig.clkphase = false;

dma_display = new MatrixPanel_I2S_DMA(mxconfig);

// Allocate memory and start DMA display
if (not dma_display->begin())
Serial.println("****** !KABOOM! I2S memory allocation failed ***********");

dma_display->setBrightness8(128); // range is 0-255, 0 - 0%, 255 - 100%

dma_display->clearScreen();
dma_display->setTextSize(1);
dma_display->setTextWrap(true);
dma_display->setCursor(0, 0);
dma_display->print("Connecting to WIFI...");
dma_display->setCursor(16, 16);

WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);

while (WiFi.status() != WL_CONNECTED) {
delay(1000);
}

Serial.println("WiFi Connected");

if (udp.connect(IPAddress(0,0,0,0), udpPort)) {
Serial.println("Server Connected");
udp.onPacket([](AsyncUDPPacket packet) {
int sequence = packet.data()[0]; // Assuming first byte is sequence number
memcpy(buffers[sequence], packet.data() + 1, packet.length() - 1);
received[sequence] = true;
});
udp.print("hi");
} else {
Serial.println("Failed to connect");
dma_display->clearScreen();
dma_display->setTextSize(1); // size 1 == 8 pixels high
dma_display->setTextWrap(true); // Don't wrap at end of line - will do ourselves
dma_display->setCursor(0, 0);
dma_display->print("Wifi failed :,(");
delay(2000);
ESP.restart();
}
}


void loop() {
bool allReceived = true;
for (int i = 0; i < 4; i++) {
if (!received[i]) {
allReceived = false;
break;
}
}

if (allReceived) {
for (int i = 0; i < 4; i++) {
memcpy(assembledData + (1024 * i), buffers[i], 1024);
received[i] = false;
}

dma_display->drawRGBBitmap(0, 0, assembledData, 128, 32);
}
}
Binary file modified bun.lockb
Binary file not shown.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
"bun-serve-express": "^1.0.4",
"express": "^4.18.2",
"gsap": "^3.11.5",
"pako": "^2.1.0",
"pixi.js-legacy": "^7.2.1",
"vite": "^5.0.12"
},
"devDependencies": {
"@types/bun": "^1.0.3",
"@types/express": "^4.17.21"
"@types/express": "^4.17.21",
"@types/pako": "^2.0.3"
}
}
38 changes: 26 additions & 12 deletions src/server.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,29 @@
import { ServerWebSocket, WebSocketServeOptions } from 'bun';
import bunExpress from 'bun-serve-express';
import express from 'express';
import pako from 'pako';

// import { webSocketServer } from './ws/websocketServer';
process.env.TZ = 'Europe/London';

type WebSocketData = {
url: URL;
id: string;
ready: boolean;
firstSend: boolean;
};

const randomId = () => {
return Math.random().toString(36).substring(2, 7);
};

let lastFrame: Buffer | null = null;
let latestFrame: Buffer | null = null;

const app = bunExpress({
websocket: {
open(ws) {
ws.data.id = randomId();
ws.data.ready = false;

setTimeout(
() => {
ws.data.ready = true;
},
process.env.SEND_DELAY ? parseInt(process.env.SEND_DELAY) : 3000
);
ws.data.firstSend = false;

if (ws.data.url.pathname == '/pub') {
pubSockets.push(ws);
Expand All @@ -37,7 +32,6 @@ const app = bunExpress({

if (ws.data.url.pathname == '/sub') {
subSockets.push(ws);

console.log('new subscriber', ws.data.id);
}
},
Expand Down Expand Up @@ -78,8 +72,22 @@ const sendInterval = process.env.SEND_INTERVAL ? parseInt(process.env.SEND_INTER
setInterval(() => {
// Echo data back to sub sockets
if (latestFrame == null) return;

// find delta between last frame and now
const deltaFrame = Buffer.alloc(latestFrame.length);
for (let i = 0; i < latestFrame.length; i++) {
deltaFrame[i] = latestFrame[i] - (lastFrame ? lastFrame[i] : 0);
}

lastFrame = latestFrame;

for (const sub of subSockets) {
if (sub.data.ready) sub.send(latestFrame);
if (!sub.data.firstSend) {
sub.data.firstSend = true;
sub.send(pako.deflate(latestFrame));
} else {
sub.send(pako.deflate(deltaFrame));
}
}
}, sendInterval);

Expand Down Expand Up @@ -195,7 +203,13 @@ app.get('/api/flights/arrivals', async (req, res) => {
date: new Date(fl.scheduled_time)
},
origin: fl.airport_name,
message: fl.message ? fl.message.replace('Expected', 'exp').replace('Landed', 'lnd').replace('Now ', '') : null,
message: fl.message
? fl.message
.replace('Expected', 'exp')
.replace('Landed', 'lnd')
.replace('Now ', '')
.replace(/Diverted.*/, 'Diverted')
: null,
status: fl.status
}))
.filter((fl: any) => {
Expand Down
8 changes: 8 additions & 0 deletions udpserver/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module udpserver

go 1.21.6

require (
github.com/gorilla/websocket v1.5.1 // indirect
golang.org/x/net v0.17.0 // indirect
)
4 changes: 4 additions & 0 deletions udpserver/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
Loading

0 comments on commit 4e0a819

Please sign in to comment.