Skip to content
Merged
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
4 changes: 2 additions & 2 deletions docs/moonbase/devices.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Shows which other MoonLight devices are present in your local network.

* Device name: name of this device (set the name in the [WiFi station module](https://moonmodules.org/MoonLight/network/sta/))
* Devices: Devices found on the network
* Click on the name to go to the device (controls module) via mDNS
* Click on IP to go to the device in a new window
* Click on the name to go to the device via mDNS
* Click on IP to go to the device via its IP address

* The functionality of this module will also be available in [ESP32 Devices](https://github.com/ewowi/ESP32Devices). ESP32 Devices is a MacOS and Windows application. 🚧
55 changes: 48 additions & 7 deletions interface/src/lib/components/moonbase/FieldRenderer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
// Send immediately on first interaction
onChange(event);
pendingSliderEvent = null;

throttleTimer = setTimeout(() => {
if (pendingSliderEvent) {
onChange(pendingSliderEvent);
Expand Down Expand Up @@ -128,6 +128,33 @@
//precent onClick when dblClick
let clickTimeout: any = null;
let preventClick = false;

// inspired by WLED
function genPalPrev(hexString: string) {
if (!hexString) return '';

// Convert hex string to byte array
const paletteData = [];
for (let i = 0; i < hexString.length; i += 2) {
paletteData.push(parseInt(hexString.substr(i, 2), 16));
}

// Parse palette data: groups of 4 bytes [index, r, g, b]
const gradient = [];
for (let i = 0; i < paletteData.length; i += 4) {
const index = paletteData[i];
const r = paletteData[i + 1];
const g = paletteData[i + 2];
const b = paletteData[i + 3];

// Convert index from 0-255 to percentage
const percent = Math.round((index / 255) * 100);

gradient.push(`rgb(${r},${g},${b}) ${percent}%`);
}

return `background: linear-gradient(to right,${gradient.join(',')});`;
}
</script>

<div class="flex-row flex items-center space-x-2 {!noPrompts ? 'mb-1' : ''}">
Expand All @@ -138,11 +165,8 @@
{/if}

{#if property.ro}
{#if property.type == 'ip'}
<a href="http://{value}" target="_blank">{value}</a>
{:else if property.type == 'mDNSName'}
<a href="http://{value}.local/moonbase/module?group=moonlight&module=lightscontrol">{value}</a
>
{#if property.type == 'ip' || property.type == 'mDNSName'}
<a href="http://{value}">{value}</a>
{:else if property.type == 'time'}
<span>{getTimeAgo(value, currentTime)}</span>
{:else if property.type == 'coord3D' && value != null}
Expand All @@ -153,7 +177,6 @@
{/if}
{:else if property.type == 'select' || property.type == 'selectFile'}
<select bind:value on:change={onChange} class="select">
<slot></slot>
{#each property.values as value, index}
<option value={property.type == 'selectFile' ? value : index}>
{value}
Expand All @@ -163,6 +186,24 @@
{#if property.type == 'selectFile'}
<FileEditWidget path={value} showEditor={false} />
{/if}
{:else if property.type == 'palette'}
<div style="display: flex; gap: 8px; align-items: center;">
<select bind:value on:change={onChange} class="select">
{#each property.values as val, index}
<option value={index}>{val.name}</option>
{/each}
</select>
<div class="palette-preview" style={genPalPrev(property.values[value]?.colors)}></div>
</div>

<style>
.palette-preview {
width: 250px;
height: 40px;
border: 1px solid #ccc;
border-radius: 3px;
}
</style>
{:else if property.type == 'checkbox'}
<input
type="checkbox"
Expand Down
23,025 changes: 11,519 additions & 11,506 deletions lib/framework/WWWData.h

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ build_flags =
-D BUILD_TARGET=\"$PIOENV\"
-D APP_NAME=\"MoonLight\" ; 🌙 Must only contain characters from [a-zA-Z0-9-_] as this is converted into a filename
-D APP_VERSION=\"0.8.1\" ; semver compatible version string
-D APP_DATE=\"20260118\" ; 🌙
-D APP_DATE=\"20260122\" ; 🌙

-D PLATFORM_VERSION=\"pioarduino-55.03.35\" ; 🌙 make sure it matches with above plaftform

Expand Down
10 changes: 8 additions & 2 deletions src/MoonBase/Module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -271,8 +271,8 @@ bool ModuleState::compareRecursive(const JsonString& parent, const JsonVariant&
return changed;
}

StateUpdateResult ModuleState::update(JsonObject& newData, ModuleState& state, const String& originId) { //, const String& originId
// if (state.data.isNull()) EXT_LOGD(ML_TAG, "state data is null %d %d", newData.size(), newData != state.data); // state.data never null here
StateUpdateResult ModuleState::update(JsonObject& newData, ModuleState& state, const String& originId) {
// if (state.data.isNull()) EXT_LOGD(ML_TAG, "state data is null %d %d", newData.size(), newData != state.data); // state.data never null here

updateOriginId = originId;

Expand All @@ -286,6 +286,12 @@ StateUpdateResult ModuleState::update(JsonObject& newData, ModuleState& state, c

bool changed = state.checkReOrderSwap("", state.data, newData, updatedItem);

// if (originId != "devicesserver" && originId != "tasksserver") {
// String ss;
// serializeJson(newData, ss);
// EXT_LOGD(ML_TAG, "newData %s from %s", ss.c_str(), originId.c_str());
// }

// EXT_LOGD(ML_TAG, "update isNew %d changed %d", isNew, changed);
// serializeJson(state.data, Serial);Serial.println();
// serializeJson(newData, Serial);Serial.println();
Expand Down
19 changes: 14 additions & 5 deletions src/MoonBase/Module.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,17 +115,26 @@ class ModuleState {
if (contains(taskName, "SvelteKit") || contains(taskName, "loopTask")) { // at boot, the loopTask starts, after that the loopTask is destroyed
if (processUpdatedItem) processUpdatedItem(updatedItem);
} else {
if (xSemaphoreTake(updateMutex, portMAX_DELAY) == pdTRUE) {
this->updatedItem = updatedItem;
updatePending = true;
xSemaphoreGive(updateMutex);
// Wait until previous update is processed
while (true) {
if (xSemaphoreTake(updateMutex, pdMS_TO_TICKS(10)) == pdTRUE) {
if (!updatePending) {
// EXT_LOGD(ML_TAG, "%s[%d]%s[%d].%s = %s -> %s", updatedItem.parent[0].c_str(), updatedItem.index[0], updatedItem.parent[1].c_str(), updatedItem.index[1], updatedItem.name.c_str(), updatedItem.oldValue.c_str(), updatedItem.value.as<String>().c_str());
this->updatedItem = updatedItem;
updatePending = true;
xSemaphoreGive(updateMutex);
break;
}
xSemaphoreGive(updateMutex);
}
vTaskDelay(pdMS_TO_TICKS(1)); // Need delay here when updatePending is true
}
}
}
// Called by consumer side
void getUpdate() {
// Try to acquire mutex without blocking
if (xSemaphoreTake(updateMutex, 0) == pdTRUE) {
if (xSemaphoreTake(updateMutex, pdMS_TO_TICKS(10)) == pdTRUE) {
if (updatePending) {
// Copy update data
UpdatedItem localCopy = updatedItem;
Expand Down
6 changes: 6 additions & 0 deletions src/MoonLight/Layers/VirtualLayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ void VirtualLayer::loop20ms() {
}
}

void VirtualLayer::presetCorrection(nrOfLights_t& indexP) const {
// RGB2040 has physical layout with alternating 20-light segments:
// virtual [0..19] -> physical [0..19], virtual [20..39] -> physical [40..59], etc.
if (layerP->lights.header.lightPreset == lightPreset_RGB2040) indexP += (indexP / 20) * 20;
}

void VirtualLayer::addIndexP(PhysMap& physMap, nrOfLights_t indexP) {
// EXT_LOGV(ML_TAG, "i:%d t:%d s:%d i:%d", indexP, physMap.mapType, mappingTableIndexes.size(), physMap.indexes);
switch (physMap.mapType) {
Expand Down
56 changes: 26 additions & 30 deletions src/MoonLight/Layers/VirtualLayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,24 +109,22 @@ class VirtualLayer {

nrOfLights_t XYZUnModified(const Coord3D& position) const { return position.x + position.y * size.x + position.z * size.x * size.y; }

void presetCorrection(nrOfLights_t& indexP) const;

template <typename Callback>
void forEachLightIndex(const nrOfLights_t indexV, Callback&& callback, bool onlyOne = false) {
if (indexV < mappingTableSize) {
switch (mappingTable[indexV].mapType) {
case m_oneLight: {
nrOfLights_t indexP = mappingTable[indexV].indexP;
// if (layerP->lights.header.lightPreset == lightPreset_RGB2040) {
// indexP += (indexP / 20) * 20;
// }
presetCorrection(indexP);
callback(indexP);
break;
}
case m_moreLights:
if (mappingTable[indexV].indexesIndex < mappingTableIndexes.size()) {
for (nrOfLights_t indexP : mappingTableIndexes[mappingTable[indexV].indexesIndex]) {
// if (layerP->lights.header.lightPreset == lightPreset_RGB2040) {
// indexP += (indexP / 20) * 20;
// }
presetCorrection(indexP);
callback(indexP);
if (onlyOne) return;
}
Expand All @@ -135,7 +133,7 @@ class VirtualLayer {
}
} else { // no mappnig
if ((indexV + 1) * layerP->lights.header.channelsPerLight <= layerP->lights.maxChannels) { // make sure the light is in the channels array
callback(indexV);
callback(indexV); // no presetCorrection here ATM as lightPreset_RGB2040 has a mapping and we want max speed here
}
}
}
Expand Down Expand Up @@ -302,64 +300,62 @@ class VirtualLayer {
const uint8_t keep = 255 - blur_amount;
const uint8_t seep = blur_amount >> 1;
CRGB carryover = CRGB::Black;
for (uint16_t i = 0; i < size.y; ++i) {
CRGB cur = getRGB(Coord3D(x, i));
for (uint16_t row = 0; row < size.y; ++row) {
CRGB cur = getRGB(Coord3D(x, row));
CRGB part = cur;
part.nscale8(seep);
cur.nscale8(keep);
cur += carryover;
if (i) addRGB(Coord3D(x, i - 1), part);
setRGB(Coord3D(x, i), cur);
if (row) addRGB(Coord3D(x, row - 1), part);
setRGB(Coord3D(x, row), cur);
carryover = part;
}
if (size.y) addRGB(Coord3D(x, size.y - 1), carryover);
}

void blur2d(fract8 blur_amount) {
blurRows(size.x, size.y, blur_amount);
blurColumns(size.x, size.y, blur_amount);
blurRows(blur_amount);
blurColumns(blur_amount);
}

void blurRows(uint16_t width, uint16_t height, fract8 blur_amount) {
/* for (uint16_t row = 0; row < height; row++) {
CRGB* rowbase = leds + (row * width);
blur1d( rowbase, width, blur_amount);
}
*/
void blurRows(fract8 blur_amount) {
// blur rows same as columns, for irregular matrix
uint8_t keep = 255 - blur_amount;
uint8_t seep = blur_amount >> 1;
for (uint16_t row = 0; row < height; row++) {
for (uint16_t row = 0; row < size.y; row++) {
CRGB carryover = CRGB::Black;
for (uint16_t i = 0; i < width; i++) {
CRGB cur = getRGB(Coord3D(i, row));
for (uint16_t col = 0; col < size.x; col++) {
CRGB cur = getRGB(Coord3D(col, row));
CRGB part = cur;
part.nscale8(seep);
cur.nscale8(keep);
cur += carryover;
if (i) addRGB(Coord3D(i - 1, row), part);
setRGB(Coord3D(i, row), cur);
if (col) addRGB(Coord3D(col - 1, row), part);
setRGB(Coord3D(col, row), cur);
carryover = part;
}
if (size.x) addRGB(Coord3D(size.x - 1, row), carryover);
}
}

// blurColumns: perform a blur1d on each column of a rectangular matrix
void blurColumns(uint16_t width, uint16_t height, fract8 blur_amount) {
void blurColumns(fract8 blur_amount) {
// blur columns
uint8_t keep = 255 - blur_amount;
uint8_t seep = blur_amount >> 1;
for (uint16_t col = 0; col < width; ++col) {
for (uint16_t col = 0; col < size.x; ++col) {
CRGB carryover = CRGB::Black;
for (uint16_t i = 0; i < height; ++i) {
CRGB cur = getRGB(Coord3D(col, i));
for (uint16_t row = 0; row < size.y; row++) {
CRGB cur = getRGB(Coord3D(col, row));
CRGB part = cur;
part.nscale8(seep);
cur.nscale8(keep);
cur += carryover;
if (i) addRGB(Coord3D(col, i - 1), part);
setRGB(Coord3D(col, i), cur);
if (row) addRGB(Coord3D(col, row - 1), part);
setRGB(Coord3D(col, row), cur);
carryover = part;
}
if (size.y) addRGB(Coord3D(col, size.y - 1), carryover);
}
}

Expand Down
13 changes: 10 additions & 3 deletions src/MoonLight/Modules/ModuleEffects.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,10 @@ class ModuleEffects : public NodeManager {
addControlValue(control, getNameAndTags<AudioRingsEffect>());
addControlValue(control, getNameAndTags<LinesEffect>());
addControlValue(control, getNameAndTags<FireEffect>());
addControlValue(control, getNameAndTags<StarSkyEffect>());
addControlValue(control, getNameAndTags<FixedRectangleEffect>());
addControlValue(control, getNameAndTags<ParticlesEffect>());
addControlValue(control, getNameAndTags<PraxisEffect>());
addControlValue(control, getNameAndTags<StarSkyEffect>());
#if USE_M5UNIFIED
addControlValue(control, getNameAndTags<MoonManEffect>());
#endif
Expand Down Expand Up @@ -115,6 +115,7 @@ class ModuleEffects : public NodeManager {
addControlValue(control, getNameAndTags<BlackholeEffect>());
addControlValue(control, getNameAndTags<BouncingBallsEffect>());
addControlValue(control, getNameAndTags<BlurzEffect>());
addControlValue(control, getNameAndTags<ColorTwinkleEffect>());
addControlValue(control, getNameAndTags<DistortionWavesEffect>());
addControlValue(control, getNameAndTags<DJLightEffect>());
addControlValue(control, getNameAndTags<DNAEffect>());
Expand All @@ -126,11 +127,14 @@ class ModuleEffects : public NodeManager {
addControlValue(control, getNameAndTags<FunkyPlankEffect>());
addControlValue(control, getNameAndTags<GEQEffect>());
addControlValue(control, getNameAndTags<HeartBeatEffect>());
addControlValue(control, getNameAndTags<JuliaEffect>());
addControlValue(control, getNameAndTags<LissajousEffect>());
addControlValue(control, getNameAndTags<Noise2DEffect>());
addControlValue(control, getNameAndTags<NoiseMeterEffect>());
addControlValue(control, getNameAndTags<OctopusEffect>());
addControlValue(control, getNameAndTags<PacManEffect>());
addControlValue(control, getNameAndTags<PlasmaEffect>());
addControlValue(control, getNameAndTags<PoliceEffect>());
addControlValue(control, getNameAndTags<PopCornEffect>());
addControlValue(control, getNameAndTags<RainEffect>());
addControlValue(control, getNameAndTags<TetrixEffect>());
Expand Down Expand Up @@ -176,12 +180,11 @@ class ModuleEffects : public NodeManager {
if (!node) node = checkAndAlloc<SolidEffect>(name);
if (!node) node = checkAndAlloc<AudioRingsEffect>(name);
if (!node) node = checkAndAlloc<FireEffect>(name);

if (!node) node = checkAndAlloc<StarSkyEffect>(name);
if (!node) node = checkAndAlloc<FixedRectangleEffect>(name);
if (!node) node = checkAndAlloc<FreqSawsEffect>(name);
if (!node) node = checkAndAlloc<LinesEffect>(name);
if (!node) node = checkAndAlloc<MarioTestEffect>(name);
if (!node) node = checkAndAlloc<StarSkyEffect>(name);
#if USE_M5UNIFIED
if (!node) node = checkAndAlloc<MoonManEffect>(name);
#endif
Expand Down Expand Up @@ -209,6 +212,7 @@ class ModuleEffects : public NodeManager {
if (!node) node = checkAndAlloc<BlackholeEffect>(name);
if (!node) node = checkAndAlloc<BouncingBallsEffect>(name);
if (!node) node = checkAndAlloc<BlurzEffect>(name);
if (!node) node = checkAndAlloc<ColorTwinkleEffect>(name);
if (!node) node = checkAndAlloc<DistortionWavesEffect>(name);
if (!node) node = checkAndAlloc<DJLightEffect>(name);
if (!node) node = checkAndAlloc<DNAEffect>(name);
Expand All @@ -220,11 +224,14 @@ class ModuleEffects : public NodeManager {
if (!node) node = checkAndAlloc<FunkyPlankEffect>(name);
if (!node) node = checkAndAlloc<GEQEffect>(name);
if (!node) node = checkAndAlloc<HeartBeatEffect>(name);
if (!node) node = checkAndAlloc<JuliaEffect>(name);
if (!node) node = checkAndAlloc<LissajousEffect>(name);
if (!node) node = checkAndAlloc<Noise2DEffect>(name);
if (!node) node = checkAndAlloc<NoiseMeterEffect>(name);
if (!node) node = checkAndAlloc<OctopusEffect>(name);
if (!node) node = checkAndAlloc<PacManEffect>(name);
if (!node) node = checkAndAlloc<PlasmaEffect>(name);
if (!node) node = checkAndAlloc<PoliceEffect>(name);
if (!node) node = checkAndAlloc<PopCornEffect>(name);
if (!node) node = checkAndAlloc<RainEffect>(name);
if (!node) node = checkAndAlloc<TetrixEffect>(name);
Expand Down
Loading