Skip to content

Commit

Permalink
Merge pull request #200 from nhjschulz/feature/GETDISP_OPT
Browse files Browse the repository at this point in the history
Optimize GETDISP Websocket Command
  • Loading branch information
BlueAndi authored Nov 11, 2024
2 parents 1235ccb + 5f52be5 commit caf86ab
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 49 deletions.
64 changes: 38 additions & 26 deletions data/display.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ <h5 class="modal-title" id="dialogTitle"></h5>
<h1 class="mt-5">Display</h1>
<p>Slot: <span id="slotId">-</span></p>
<canvas id="canvas" width="353" height="89" style="border: 1px solid #000000; background-color: black;"></canvas>
<p>
<button id="updateDisplayButton" class="btn btn-light" type="button" onclick="updateDisplay()" disabled>Enable auto. display update</button>
</p>
<p>Note, you can move the plugins with drag'n drop to other slots.</p>
<div id="divSlots">
</div>
Expand All @@ -62,7 +65,6 @@ <h1 class="mt-5">Display</h1>
<p>
<button class="btn btn-light" type="button" onclick="prevSlot()" disabled>Previous slot</button>
<button class="btn btn-light" type="button" onclick="nextSlot()" disabled>Next slot</button>
<button id="updateDisplayButton" class="btn btn-light" type="button" onclick="updateDisplay()" disabled>Enable auto. display update</button>
</p>
<p>Currently active fade effect: <span id="lableFadeEffect"> Linear</span></p>
<p><button class="btn btn-light" type="button" onclick="nextEffect()" disabled>Next fade effect</button></p>
Expand Down Expand Up @@ -117,8 +119,6 @@ <h1 class="mt-5">Display</h1>
<!-- Custom javascript -->
<script>
var ctx = null; // Canvas context
var pixelWidth = 10; // Width of a single LED in pixels
var pixelHeight = 10; // Height of a single LED in pixels
var isDisplayUpdateOn = false;
var timerId = 0; // Timer id
var period = 400; // Display refresh period in ms
Expand All @@ -128,6 +128,14 @@ <h1 class="mt-5">Display</h1>
var autoBrightnessCtrl = false; // Is automatic brightness control enabled or disabled?
var brightness = 0; // Brightness [0; 255]
var currentFadeEffect = 0 // Fade effect [1;3]

const PIXEL_WIDTH = 10; // Width of a single LED in pixels
const PIXEL_HEIGTH = 10; // Height of a single LED in pixels
const DISPLAY_WIDTH = ~DISPLAY_WIDTH~; // Width in pixels (Updated by webserver on send)
const DISPLAY_HEIGHT = ~DISPLAY_HEIGHT~; // Height in pixels (Updated by webserver on send)

const CANVAS_WIDTH = (DISPLAY_WIDTH) * PIXEL_WIDTH + (DISPLAY_WIDTH) + 1;
const CANVAS_HEIGHT = (DISPLAY_HEIGHT) * PIXEL_HEIGTH + (DISPLAY_HEIGHT) + 1;

/* Disable all UI elements. */
function disableUI() {
Expand Down Expand Up @@ -157,43 +165,47 @@ <h1 class="mt-5">Display</h1>
ctx.lineWidth = "1";
ctx.strokeStyle = color;
ctx.fillStyle = color;
ctx.fillRect(x * pixelWidth + x + 1, y * pixelHeight + y + 1, pixelWidth, pixelHeight);
ctx.fillRect(x * PIXEL_WIDTH + x + 1, y * PIXEL_HEIGTH + y + 1, PIXEL_WIDTH, PIXEL_HEIGTH);
}
}

function getDisplayContent() {
wsClient.getDisplayContent().then(function(rsp) {
var x = 0;
var y = 0;
var index = 0;
var color = 0;

let index = 0; // Index in input sequence.
let pixelIndex = 0; // Index as pixel offset from framebuffer start.

$("#slotId").text(rsp.slotId);

/* If necessary, resize the canvas. */
var $canvas = $("#canvas");
var ctx = $canvas[0].getContext('2d');
var width = rsp.width * pixelWidth + rsp.width + 1;
var height = rsp.height * pixelWidth + rsp.height + 1;

if ((ctx.canvas.width != width) || (ctx.canvas.height != height)) {
ctx.canvas.width = width;
ctx.canvas.height = height;
}
if ((ctx.canvas.width != CANVAS_WIDTH) || (ctx.canvas.height != CANVAS_HEIGHT)) {
ctx.canvas.width = CANVAS_WIDTH;
ctx.canvas.height = CANVAS_HEIGHT;
}

/* Handle display data */
for(y = 0; y < rsp.height; ++y) {
for(x = 0; x < rsp.width; ++x) {
if (rsp.data.length > index) {
color = rsp.data[index];
red = (color & 0xff0000) >> 16;
green = (color & 0x00ff00) >> 8;
blue = (color & 0x0000ff) >> 0;
plot(x, y, "rgb(" + red + ", " + green + ", " + blue + ")");
++index;
}
}
while (index < rsp.data.length) {
let color = rsp.data[index];
let repeat = (color & 0xFF000000) >>> 24;
let red = (color & 0x00FF0000) >>> 16;
let green = (color & 0x0000FF00) >>> 8;
let blue = (color & 0x000000FF) >>> 0;
let colFmt = "rgb(" + red + ", " + green + ", " + blue + ")";

do {
let x = pixelIndex % DISPLAY_WIDTH;
let y = Math.floor(pixelIndex / DISPLAY_WIDTH);

plot(x, y, colFmt);

--repeat;
++pixelIndex;
} while (repeat >= 0);
++index;
}

}).catch(function(err) {
if ("undefined" !== typeof err) {
console.error(err);
Expand Down
2 changes: 0 additions & 2 deletions data/js/ws.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,6 @@ pixelix.ws.Client.prototype._onMessage = function(msg) {
this._pendingCmd.resolve(rsp);
} else if ("GETDISP" === this._pendingCmd.name) {
rsp.slotId = parseInt(data.shift());
rsp.width = parseInt(data.shift());
rsp.height = parseInt(data.shift());
rsp.data = [];
for(index = 0; index < data.length; ++index) {
rsp.data.push(parseInt(data[index], 16));
Expand Down
6 changes: 3 additions & 3 deletions src/Web/WsCommand/WsCmd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,13 @@
*****************************************************************************/

/* Delimiter of websocket parameters */
const char* WsCmd::DELIMITER = ";";
const char WsCmd::DELIMITER[] = ";";

/* Positive response code */
const char* WsCmd::ACK = "ACK";
const char WsCmd::ACK[] = "ACK";

/* Negative response code. */
const char* WsCmd::NACK = "NACK";
const char WsCmd::NACK[] = "NACK";

/******************************************************************************
* Public Methods
Expand Down
6 changes: 3 additions & 3 deletions src/Web/WsCommand/WsCmd.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,13 @@ class WsCmd
protected:

/** Delimiter of websocket parameters */
static const char* DELIMITER;
static const char DELIMITER[];

/** Positive response code */
static const char* ACK;
static const char ACK[];

/** Negative response code. */
static const char* NACK;
static const char NACK[];

/**
* Prepare a positive response message.
Expand Down
86 changes: 71 additions & 15 deletions src/Web/WsCommand/WsCmdGetDisp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,38 +77,94 @@ void WsCmdGetDisp::execute(AsyncWebSocket* server, uint32_t clientId)
}
else
{
IDisplay& display = Display::getInstance();
size_t fbLength = display.getWidth() * display.getHeight();
uint32_t* framebuffer = new(std::nothrow) uint32_t[fbLength];
constexpr size_t fbLength = CONFIG_LED_MATRIX_WIDTH * CONFIG_LED_MATRIX_HEIGHT;
uint32_t* framebuffer = new (std::nothrow) uint32_t[fbLength];

if (nullptr == framebuffer)
{
sendNegativeResponse(server, clientId, "\"Internal error.\"");
}
else
{
uint32_t index = 0U;
String msg;
uint8_t slotId = SlotList::SLOT_ID_INVALID;
String msg;
uint8_t slotId = SlotList::SLOT_ID_INVALID;

uint32_t lastColor; /* The color that started a repeat sequence. */
uint32_t color = 0U; /* Actual color in read order. */
size_t index = 1U; /* Next value from framebuffer to used. */
uint32_t repeat = 0U; /* Repeat count for current color. */
const uint32_t REPEAT_MAX = 0xFFU; /* Maximum repeat color counter value. */
GetDispState state = STATE_GETDISP_COLLECT; /* Frame buffer reading state. */

DisplayMgr::getInstance().getFBCopy(framebuffer, fbLength, &slotId);

preparePositiveResponse(msg);

msg += slotId;
msg += DELIMITER;
msg += display.getWidth();
msg += DELIMITER;
msg += display.getHeight();

for(index = 0U; index < fbLength; ++index)
/* RGB data is send in a "compressed" format using a repeat counter in
* the upper 8 bits. The send values are <rep>:<r>:<g>:<b>.
* The repeat counter indicates how often the same color shall be used
* in subsequent pixels. Use a small state machine to calculate the
* repeat counter.
*
* Example:
* A black only 32x8 framebuffer would be send as a single 0xFF000000 value.
*
*/
lastColor = framebuffer[0];

while (state != STATE_GETDISP_FINISH)
{
msg += DELIMITER;
msg += Util::uint32ToHex(framebuffer[index]);
if (STATE_GETDISP_COLLECT == state)
{
if (index < fbLength)
{
color = framebuffer[index];
if (color != lastColor)
{
/* Color has changed, send out current sequence */
state = STATE_GETDISP_SEND;
}
else
{
++repeat;
if (REPEAT_MAX == repeat)
{
/* 8-bit repeat counter maximum reached, send color sequence. */
state = STATE_GETDISP_SEND;
}
}
}
else
{
/* End of frame buffer reached, send final color sequence. */
state = STATE_GETDISP_SEND;
}
++index;
}
else /* STATE_GETDISP_SEND */
{
msg += DELIMITER;

/* Lower 24 bits is RGB, upper 8 bits repeat count. */
msg += Util::uint32ToHex(lastColor | (repeat << 24U));

if (index < fbLength)
{
lastColor = color;
repeat = 0U;
state = STATE_GETDISP_COLLECT;
}
else
{
/* Frame buffer length consumed, terminate state machine loop. */
state = STATE_GETDISP_FINISH;
}
}
}

delete[] framebuffer;

sendResponse(server, clientId, msg);
}
}
Expand Down
14 changes: 14 additions & 0 deletions src/Web/WsCommand/WsCmdGetDisp.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,20 @@ class WsCmdGetDisp: public WsCmd

bool m_isError; /**< Any error happened during parameter reception? */

/**
* RGB data calculation states.
*
* The RGB data is send in a "compressed" format using a repeat counter.
* The algorithm to calculate the format is implemented as a small state
* machine that creates sequences of repeated colors from the frame buffer.
*/
enum GetDispState
{
STATE_GETDISP_COLLECT = 0, /**< Collect sequences of same color. */
STATE_GETDISP_SEND, /**< Send current color sequence. */
STATE_GETDISP_FINISH /**< Terminate state machine. */
};

WsCmdGetDisp(const WsCmdGetDisp& cmd);
WsCmdGetDisp& operator=(const WsCmdGetDisp& cmd);
};
Expand Down

0 comments on commit caf86ab

Please sign in to comment.