Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/ControlProtocol.txt
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ buf[9] = App/Hardware Type
- 0xC1 - xSchedule
- 0xC2 - ESPixelStick - ESP8266
- 0xC3 - ESPixelStick - ESP32
- 0xC4 - Baldrick
- 0xC5 - JBoards
- 0xFA - Twinkly
- 0xFB - WLED
- 0xFC - DIYLEDExpress
- 0xFD - HinksPix
Expand Down
5 changes: 4 additions & 1 deletion src/MultiSync.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,8 @@ void MultiSyncSystem::update(MultiSyncSystemType type,
// learned via the UDP MultiSync protocol so that FPP remotes discovered
// via HTTP (e.g. on a network segment where multicast is blocked - the
// very case this mode exists for) are still targeted.
this->supportsUnicast = (this->type < kSysTypeFalconController) &&
this->supportsUnicast = ((this->type < kSysTypeFalconController) ||
(this->type == kSysTypeJBoards)) &&
(this->fppMode == REMOTE_MODE);
}

Expand Down Expand Up @@ -933,6 +934,8 @@ std::string MultiSync::GetTypeString(MultiSyncSystemType type, bool local) {
return "ESPixelStick-ESP32";
case kSysTypeBaldrick:
return "Baldrick";
case kSysTypeJBoards:
return "JBoards";
case kSysTypeMacOS:
return "MacOS";
case kSysTypeFPPArmbian:
Expand Down
1 change: 1 addition & 0 deletions src/MultiSync.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ typedef enum systemType {
kSysTypeESPixelStick = 0xC2,
kSysTypeESPixelStickESP32 = 0xC3,
kSysTypeBaldrick = 0xC4,
kSysTypeJBoards = 0xC5,
kSysTypeNonMultiSyncCapable = 0xF0,
kSysTypeTwinkly = 0xFA,
kSysTypeWLED = 0xFB,
Expand Down
61 changes: 61 additions & 0 deletions src/NetworkController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class NetworkController::Detection {
&NetworkController::DetectSanDevicesController,
&NetworkController::DetectESPixelStickController,
&NetworkController::DetectBaldrickController,
&NetworkController::DetectJBoardsController,
&NetworkController::DetectAlphaPixController,
&NetworkController::DetectHinksPixController,
&NetworkController::DetectDIYLEDExpressController,
Expand Down Expand Up @@ -564,6 +565,66 @@ void NetworkController::DetectBaldrickController(Detection* st) {
});
}

void NetworkController::DetectJBoardsController(Detection* st) {
LogExcess(VB_SYNC, "Checking if %s is a JBoards controller\n", st->ip.c_str());

if (!contains(st->html, "JBoards")) {
st->noMatch();
return;
}

LogExcess(VB_SYNC, "%s is potentially a JBoards controller, checking further\n", st->ip.c_str());

st->fetch(buildHttpURL(st->ip, "/api/system/summary"), [this, st](bool ok, const std::string& resp) {
if (!ok) {
st->noMatch();
return;
}
Json::Value v;
LoadJsonFromString(resp, v, JsonRoot::Object);
if (v.get("product", "").asString() != "JBoards") {
st->noMatch();
return;
}

vendor = "JBoards";
vendorURL = "https://pixelpropshop.com";
typeId = kSysTypeJBoards;

typeStr = v.get("model", "").asString();
if (typeStr.empty()) {
typeStr = "JBoards";
}
hostname = v.get("hostname", "").asString();
if (hostname.empty()) {
hostname = ip;
}

std::string id = v.get("uuid", "").asString();
if (!id.empty()) {
uuid = id;
}
std::string r = v.get("channelRanges", "").asString();
if (!r.empty()) {
ranges = r;
}

systemMode = (v.get("syncRole", "").asString() == "remote") ? REMOTE_MODE : PLAYER_MODE;
sendingMultiSync = v.get("sendingSync", false).asBool();

version = v.get("firmwareVersion", "").asString();
std::size_t verDot = version.find(".");
if (verDot != std::string::npos) {
majorVersion = atoi(version.substr(0, verDot).c_str());
std::size_t verDot2 = version.find(".", verDot + 1);
minorVersion = atoi(version.substr(verDot + 1, verDot2 - (verDot + 1)).c_str());
}

DumpControllerInfo();
st->matched();
});
}

void NetworkController::DetectAlphaPixController(Detection* st) {
LogExcess(VB_SYNC, "Checking if %s is a AlphaPix controller\n", st->ip.c_str());

Expand Down
1 change: 1 addition & 0 deletions src/NetworkController.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ class NetworkController {
void DetectDIYLEDExpressController(Detection* st);
void DetectWLEDController(Detection* st);
void DetectTwinklyController(Detection* st);
void DetectJBoardsController(Detection* st);
void DetectExperienceController(Detection* st);
void DetectFPP(Detection* st);

Expand Down
2 changes: 2 additions & 0 deletions www/api/controllers/system.php
Original file line number Diff line number Diff line change
Expand Up @@ -799,6 +799,8 @@ function SystemGetStatus()
$curl = curl_init("http://" . $urlHost . "/json/info");
} else if ($type == "Baldrick") {
$curl = curl_init("http://" . $urlHost . "/system_state");
} else if ($type == "JBoards") {
$curl = curl_init("http://" . $urlHost . "/api/system/summary");
} else if ($type == "FV3") {
$curl = curl_init("http://" . $urlHost . "/status.xml");
} else if ($type == "FV4") {
Expand Down
90 changes: 88 additions & 2 deletions www/multisync.php
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ function isUserInteracting() {
const wledPoller = new AutoRefreshController();
const geniusPoller = new AutoRefreshController();
const baldrickPoller = new AutoRefreshController();
const jboardsPoller = new AutoRefreshController();

var unavailables = [];
var reorderModeActive = false;
Expand Down Expand Up @@ -318,6 +319,8 @@ function isUserInteracting() {
WLED: new Set([0xFB]),
// 0xC4 = 196
BALDRICK: new Set([0xC4]),
// 0xC5 = 197
JBOARDS: new Set([0xC5]),
};

function isDeviceType(typeId, category) {
Expand All @@ -338,6 +341,7 @@ function isGenius(typeId) { return isDeviceType(typeId, 'GENIUS'); }
function isWLED(typeId) { return isDeviceType(typeId, 'WLED'); }
function isTwinkly(typeId) { return isDeviceType(typeId, 'TWINKLY'); }
function isBaldrick(typeId) { return isDeviceType(typeId, 'BALDRICK'); }
function isJBoards(typeId) { return isDeviceType(typeId, 'JBOARDS'); }

// ============================================================
// SECTION: Utilities
Expand Down Expand Up @@ -1800,6 +1804,7 @@ function parseFPPSystems(data) {
var wledIpAddresses = [];
var geniusIpAddresses = [];
var baldrickIpAddresses = [];
var jboardsIpAddresses = [];
var espIpAddresses = [];
var falconV4Addresses = [];
var falconV3Addresses = [];
Expand Down Expand Up @@ -1938,7 +1943,7 @@ function parseFPPSystems(data) {
// poll slot (and its action buttons, via _dataIp) onto it.
if (ipLocalityScore(ip, localIps) > ipLocalityScore(mergeItem._dataIp, localIps)) {
swapPollAddress([fppIpAddresses, wledIpAddresses, geniusIpAddresses,
baldrickIpAddresses, espIpAddresses,
baldrickIpAddresses, jboardsIpAddresses, espIpAddresses,
falconV4Addresses, falconV3Addresses],
mergeItem._dataIp, ip);
mergeItem._dataIp = ip;
Expand Down Expand Up @@ -2134,12 +2139,15 @@ function parseFPPSystems(data) {
geniusIpAddresses.push(ip);
} else if (isBaldrick(data[i].typeId)) {
baldrickIpAddresses.push(ip);
} else if (isJBoards(data[i].typeId)) {
jboardsIpAddresses.push(ip);
}
}
getFPPSystemStatus(fppIpAddresses, false);
getWLEDControllerStatus(wledIpAddresses, false);
getGeniusControllerStatus(geniusIpAddresses, false);
getBaldrickControllerStatus(baldrickIpAddresses, false);
getJBoardsControllerStatus(jboardsIpAddresses, false);
for (var ei = 0; ei < espIpAddresses.length; ei++) {
getESPixelStickBridgeStatus(espIpAddresses[ei]);
}
Expand Down Expand Up @@ -2825,6 +2833,78 @@ function getESPixelStickBridgeStatus(ip) {
}
}

// ============================================================
// SECTION: JBoards polling
// ============================================================

async function getJBoardsControllerStatus(ipAddresses, refreshing = false) {
ips = "";
if (Array.isArray(ipAddresses)) {
jboardsPoller.cancel();
ipAddresses.forEach(function (entry) {
ips += "&ip[]=" + entry;
});
} else {
ips = "&ip[]=" + ipAddresses;
}
if (ips == "") {
return;
}
try {
const r = await fetch("api/system/status?type=JBoards" + ips);
const alldata = await r.json();
var $tbl = $('#fppSystemsTable');
Object.entries(alldata).forEach(function (entry) {
var ip = entry[0], data = entry[1];
if (data == null || data == "" || data == "null" || !data.state) {
return;
}
var uf = formatUptime(data.uptimeSeconds);

var u = "<table class='multiSyncVerboseTable'>";
u += '<tr><td><small class="text-muted">UP:</small></td><td><span title="since ' + uf.since + '">' + uf.short + '</span></td></tr>';
u += "</table>";

var rowId = hostRows[ip.replace(/\./g, '_')];
var item = $tbl.bootstrapTable('getRowByUniqueId', rowId);
if (!item) return;

item.utilization = u;
if (data.model && item.hostname.indexOf("class='hostDescriptionSM'></small>") >= 0) {
item.hostname = item.hostname.replace(
"class='hostDescriptionSM'></small>",
"class='hostDescriptionSM'>" + msEscape(data.model) + "</small>"
);
}

var status = data.state.charAt(0).toUpperCase() + data.state.slice(1);
var elapsed = "";
if (data.state == 'playing' || data.state == 'paused') {
var files = [];
if (data.playlist) files.push(msEscape(data.playlist));
if (data.sequence) files.push(msEscape(data.sequence));
if (files.length) {
status += ":<br>" + files.join("<br>");
}
if (data.durationSeconds > 0) {
elapsed = SecondsToHuman(data.elapsedSeconds);
}
}
item.status = status;
item.elapsed = elapsed;

if (data.firmwareVersion) {
item.version = msEscape(data.firmwareVersion);
}
});
safeInitBody($tbl);
} finally {
if (Array.isArray(ipAddresses) && $('#MultiSyncRefreshStatus').is(":checked") && !isUserInteracting()) {
jboardsPoller.schedule(() => getJBoardsControllerStatus(ipAddresses, true));
}
}
}

// ============================================================
// SECTION: Refresh orchestration
// ============================================================
Expand All @@ -2835,11 +2915,12 @@ function clearRefreshTimers() {
wledPoller.cancel();
geniusPoller.cancel();
baldrickPoller.cancel();
jboardsPoller.cancel();
}

function RefreshStats() {
var $tbl = $('#fppSystemsTable');
var ips = [], gips = [], wips = [], bips = [], fv3ips = [], fv4ips = [];
var ips = [], gips = [], wips = [], bips = [], jips = [], fv3ips = [], fv4ips = [];
var seenRows = {};

Object.keys(hostRows).forEach(function (key) {
Expand Down Expand Up @@ -2872,13 +2953,16 @@ function RefreshStats() {
gips.push(ip);
} else if (isBaldrick(typeId)) {
bips.push(ip);
} else if (isJBoards(typeId)) {
jips.push(ip);
}
});

getFPPSystemStatus(ips, true);
getGeniusControllerStatus(gips, true);
getWLEDControllerStatus(wips, true);
getBaldrickControllerStatus(bips, true);
getJBoardsControllerStatus(jips, true);
getFalconControllerStatus(fv3ips, fv4ips, true);
}

Expand Down Expand Up @@ -4390,6 +4474,7 @@ function key(raw, row) {
'SanDevices': 'SanDevices',
'WLED': 'WLED',
'Twinkly': 'Twinkly',
'JBoards': 'JBoards',
'Unknown': 'Unknown'
};
window.platformFilterSearch = function (text, value, field, data) {
Expand Down Expand Up @@ -4418,6 +4503,7 @@ function key(raw, row) {
case 'sandevices': return isSanDevices(typeId);
case 'wled': return isWLED(typeId);
case 'twinkly': return isTwinkly(typeId);
case 'jboards': return isJBoards(typeId);
case 'unknown': return isUnknownController(typeId);
default: return true;
}
Expand Down