Merge branch 'master' of https://github.com/aircoookie/WLED into dev
Conflicts: tools/cdata.js usermods/PIR_sensor_switch/readme.md usermods/Temperature/readme.md wled00/FX.h wled00/FX_fcn.cpp wled00/bus_manager.h wled00/bus_wrapper.h wled00/cfg.cpp wled00/const.h wled00/data/settings.htm wled00/data/settings_leds.htm wled00/data/settings_um.htm wled00/html_settings.h wled00/json.cpp wled00/mqtt.cpp wled00/set.cpp wled00/wled.cpp wled00/wled.h wled00/wled_eeprom.cpp wled00/wled_server.cpp wled00/xml.cpp
This commit is contained in:
commit
bfd7be543a
30
CHANGELOG.md
30
CHANGELOG.md
@ -2,6 +2,36 @@
|
||||
|
||||
### Builds after release 0.12.0
|
||||
|
||||
#### Build 2105171
|
||||
|
||||
- Always copy MQTT payloads to prevent non-0-terminated strings
|
||||
- Updated ArduinoJson to 6.18.0
|
||||
- Added experimental support for `{"on":"t"}` to toggle on/off state via JSON
|
||||
|
||||
#### Build 2105120
|
||||
|
||||
- Fixed possibility of non-0-terminated MQTT payloads
|
||||
- Fixed two warnings regarding integer comparison
|
||||
|
||||
#### Build 2105112
|
||||
|
||||
- Usermod settings page no usermods message
|
||||
- Lowered min speed for Drip effect
|
||||
|
||||
#### Build 2105111
|
||||
|
||||
- Fixed various Codacy code style and logic issues
|
||||
|
||||
#### Build 2105110
|
||||
|
||||
- Added Usermod settings page and configurable usermods (PR #1951)
|
||||
- Added experimental `/json/cfg` endpoint for changing settings from JSON (see #1944, not part of official API)
|
||||
|
||||
#### Build 2105070
|
||||
|
||||
- Fixed not turning on after pressing "Off" on IR remote twice (#1950)
|
||||
- Fixed OTA update file selection from Android app (TODO: file type verification in JS, since android can't deal with accept='.bin' attribute)
|
||||
|
||||
#### Build 2104220
|
||||
|
||||
- Version bump to 0.12.1-b1 "Hikari"
|
||||
|
@ -114,9 +114,6 @@ build_unflags =
|
||||
|
||||
# enables all features for travis CI
|
||||
build_flags_all_features =
|
||||
-D WLED_USE_ANALOG_LED
|
||||
-D WLED_USE_H801
|
||||
-D WLED_ENABLE_5CH_LEDS
|
||||
-D WLED_ENABLE_ADALIGHT
|
||||
-D WLED_ENABLE_DMX
|
||||
-D WLED_ENABLE_MQTT
|
||||
@ -289,7 +286,7 @@ platform = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
board_build.ldscript = ${common.ldscript_1m128k}
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA -D WLED_USE_ANALOG_LEDS
|
||||
build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA
|
||||
|
||||
[env:esp8285_4CH_H801]
|
||||
board = esp8285
|
||||
@ -297,7 +294,7 @@ platform = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
board_build.ldscript = ${common.ldscript_1m128k}
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA -D WLED_USE_ANALOG_LEDS -D WLED_USE_H801
|
||||
build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA
|
||||
|
||||
[env:esp8285_5CH_H801]
|
||||
board = esp8285
|
||||
@ -305,7 +302,7 @@ platform = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
board_build.ldscript = ${common.ldscript_1m128k}
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA -D WLED_USE_ANALOG_LEDS -D WLED_USE_H801 -D WLED_ENABLE_5CH_LEDS
|
||||
build_flags = ${common.build_flags_esp8266} -D WLED_DISABLE_OTA
|
||||
|
||||
[env:d1_mini_5CH_Shojo_PCB]
|
||||
board = d1_mini
|
||||
@ -313,7 +310,7 @@ platform = ${common.platform_wled_default}
|
||||
platform_packages = ${common.platform_packages}
|
||||
board_build.ldscript = ${common.ldscript_4m1m}
|
||||
build_unflags = ${common.build_unflags}
|
||||
build_flags = ${common.build_flags_esp8266} -D WLED_USE_ANALOG_LEDS -D WLED_USE_SHOJO_PCB -D WLED_ENABLE_5CH_LEDS
|
||||
build_flags = ${common.build_flags_esp8266} -D WLED_USE_SHOJO_PCB
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# DEVELOPMENT BOARDS
|
||||
@ -405,6 +402,7 @@ build_flags = ${common.build_flags_esp32} ${common.debug_flags} ${common.build_f
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# codm pixel controller board configurations
|
||||
# codm-controller-0.6 can also be used for the TYWE3S controller
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
[env:codm-controller-0.6]
|
||||
|
@ -39,12 +39,8 @@ build_flags = ${common.build_flags_esp8266}
|
||||
; PIN defines for 2 wire LEDs
|
||||
-D CLKPIN=0
|
||||
-D DATAPIN=2
|
||||
; to drive analog LED strips (aka 5050), uncomment the following
|
||||
; PWM pins 5,12,13,15 are used with Magic Home LED Controller (default)
|
||||
-D WLED_USE_ANALOG_LEDS
|
||||
; for the H801 controller (PINs 15,13,12,14 (W2 = 04)) uncomment this
|
||||
; -D WLED_USE_H801
|
||||
; for the BW-LT11 controller (PINs 12,4,14,5 ) uncomment this
|
||||
; -D WLED_USE_BWLT11
|
||||
; and to enable channel 5 for RGBW-CT led strips this
|
||||
; -D WLED_USE_5CH_LEDS
|
||||
; to drive analog LED strips (aka 5050) hardware configuration is no longer necessary
|
||||
; configure the settings in the UI as follows (hard):
|
||||
; for the Magic Home LED Controller use PWM pins 5,12,13,15
|
||||
; for the H801 controller use PINs 15,13,12,14 (W2 = 04)
|
||||
; for the BW-LT11 controller use PINs 12,4,14,5
|
||||
|
@ -344,6 +344,10 @@ const char PAGE_settings_dmx[] PROGMEM = R"=====()=====";
|
||||
str
|
||||
.replace(/\<link rel="stylesheet".*\>/gms, "")
|
||||
.replace(/\<style\>.*\<\/style\>/gms, "%CSS%%SCSS%")
|
||||
.replace(
|
||||
/function GetV().*\<\/script\>/gms,
|
||||
"function GetV() {var d=document;\n"
|
||||
),
|
||||
}
|
||||
],
|
||||
"wled00/html_settings.h"
|
||||
|
232
tools/fps_test.htm
Normal file
232
tools/fps_test.htm
Normal file
@ -0,0 +1,232 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>WLED frame rate test tool</title>
|
||||
<style>
|
||||
body {
|
||||
background-color: #222;
|
||||
color: #fff;
|
||||
font-family: Helvetica, Verdana, sans-serif;
|
||||
}
|
||||
input {
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
}
|
||||
#ip {
|
||||
width: 100px;
|
||||
}
|
||||
#secs {
|
||||
width: 36px;
|
||||
}
|
||||
#csva {
|
||||
position: absolute;
|
||||
top: -100px; /*gtfo*/
|
||||
}
|
||||
button {
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
}
|
||||
table, th, td {
|
||||
border: 1px solid #aaa;
|
||||
border-collapse: collapse;
|
||||
text-align: center;
|
||||
}
|
||||
.red {
|
||||
color: #d20;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
var gotfx = false, running = false;
|
||||
var pos = 0, prev = 0, min = 999, max = 0, fpslist = [], names = [], names_checked = [];
|
||||
var to;
|
||||
function S() {
|
||||
document.getElementById('ip').value = localStorage.getItem('locIpFps');
|
||||
if (document.getElementById('ip').value) req(false);
|
||||
}
|
||||
function loadC() {
|
||||
hide(false);
|
||||
var list = localStorage.getItem('fpsFxSelection');
|
||||
if (!list) return;
|
||||
list = JSON.parse(list);
|
||||
var chks = document.querySelectorAll('.fxcheck');
|
||||
for (let i = 0; i < chks.length; i++) {
|
||||
if (i < list.length) chks[i].checked = list[i];
|
||||
}
|
||||
}
|
||||
function saveC() {
|
||||
var list = [];
|
||||
var chks = document.querySelectorAll('.fxcheck');
|
||||
for (let i = 0; i < chks.length; i++) {
|
||||
list.push(chks[i].checked);
|
||||
}
|
||||
localStorage.setItem('fpsFxSelection', JSON.stringify(list));
|
||||
}
|
||||
function setC(c) {
|
||||
hide(false);
|
||||
var chks = document.querySelectorAll('.fxcheck');
|
||||
for (let i = 0; i < chks.length; i++) {
|
||||
chks[i].checked = (c == 255);
|
||||
}
|
||||
if (c == 1 && chks.length > 100) {
|
||||
chks[1].checked = true; //Blink
|
||||
chks[15].checked = true; //Running
|
||||
chks[16].checked = true; //Saw
|
||||
chks[37].checked = true; //Running 2
|
||||
chks[44].checked = true; //Tetrix
|
||||
chks[63].checked = true; //Pride 2015
|
||||
chks[74].checked = true; //Colortwinkles
|
||||
chks[101].checked = true;//Pacifica
|
||||
}
|
||||
}
|
||||
function hide(h) {
|
||||
var trs = document.querySelectorAll('.trs');
|
||||
var chks = document.querySelectorAll('.fxcheck');
|
||||
for (let i = 0; i < trs.length; i++) {
|
||||
trs[i].style.display = (h && !chks[i].checked) ? "none":"table-row";
|
||||
}
|
||||
}
|
||||
function run(init) {
|
||||
if (init) {
|
||||
running = !running;
|
||||
document.getElementById('runbtn').innerText = running ? 'Stop':'Run';
|
||||
if (running) {pos = 0; prev = -1; min = 999; max = 0; fpslist = []; names_checked = []; hide(true);}
|
||||
clearTimeout(to);
|
||||
if (!running) {req({seg:{fx:0},v:true,stop:true}); return;}
|
||||
}
|
||||
if (!gotfx) {req(false); return;}
|
||||
var chks = document.querySelectorAll('.fxcheck');
|
||||
var fpsb = document.querySelectorAll('.fps');
|
||||
if (prev >= 0) {pos++};
|
||||
if (pos >= chks.length) {run(true); return;} //end
|
||||
while (!chks[pos].checked) {
|
||||
fpsb[pos].innerText = "-";
|
||||
pos++;
|
||||
if (pos >= chks.length) {run(true); return;} //end
|
||||
}
|
||||
names_checked.push(names[pos]);
|
||||
var extra = {};
|
||||
try {
|
||||
extra = JSON.parse(document.getElementById('ej').value);
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
var cmd = {seg:{fx:pos},v:true};
|
||||
Object.assign(cmd, extra);
|
||||
req(cmd);
|
||||
}
|
||||
function req(command) {
|
||||
var ip = document.getElementById('ip').value;
|
||||
if (!ip) {alert("Please enter WLED IP"); return;}
|
||||
if (ip != localStorage.getItem('locIpFps')) localStorage.setItem('locIpFps', document.getElementById('ip').value);
|
||||
var url = command ? `http://${ip}/json/si` : `http://${ip}/json/effects`;
|
||||
var type = command ? 'post':'get';
|
||||
var req = undefined;
|
||||
if (command)
|
||||
{
|
||||
req = JSON.stringify(command);
|
||||
}
|
||||
fetch
|
||||
(url, {
|
||||
method: type,
|
||||
headers: {
|
||||
"Content-type": "application/json; charset=UTF-8"
|
||||
},
|
||||
body: req
|
||||
})
|
||||
.then(res => {
|
||||
if (!res.ok) {
|
||||
alert('Data malfunction');
|
||||
}
|
||||
return res.json();
|
||||
})
|
||||
.then(json => {
|
||||
if (!json) {
|
||||
alert('Empty response'); return;
|
||||
}
|
||||
if (!command) {
|
||||
names = json;
|
||||
var tblc = '';
|
||||
for (let i = 0; i < json.length; i++) {
|
||||
tblc += `<tr class="trs"><td><input type="checkbox" class="fxcheck" /></td><td>${i}</td><td>${json[i]}</td><td class="fps"></td></tr>`
|
||||
}
|
||||
var tbl = `<table>
|
||||
<tr>
|
||||
<th>Test?</th><th>ID</th><th>Effect Name</th><th>FPS</th>
|
||||
</tr>
|
||||
${tblc}
|
||||
</table>`;
|
||||
document.getElementById('tablecon').innerHTML = tbl;
|
||||
setC(1);
|
||||
loadC();
|
||||
gotfx = true;
|
||||
document.getElementById('runbtn').innerText = "Run";
|
||||
} else {
|
||||
if (!json.info) return;
|
||||
document.getElementById('leds').innerText = json.info.leds.count;
|
||||
document.getElementById('seg').innerText = json.state.seg[0].len;
|
||||
document.getElementById('bri').innerText = json.state.bri;
|
||||
if (prev >= 0) {
|
||||
var lastfps = parseInt(json.info.leds.fps); //previous FX
|
||||
if (lastfps < min) min = lastfps;
|
||||
if (lastfps > max) max = lastfps;
|
||||
fpslist.push(lastfps);
|
||||
var sum = 0;
|
||||
for (let i = 0; i < fpslist.length; i++) {
|
||||
sum += fpslist[i];
|
||||
}
|
||||
sum /= fpslist.length;
|
||||
document.getElementById('fps_min').innerText = min;
|
||||
document.getElementById('fps_max').innerText = max;
|
||||
document.getElementById('fps_avg').innerText = Math.round(sum*10)/10;
|
||||
var fpsb = document.querySelectorAll('.fps');
|
||||
fpsb[prev].innerHTML = lastfps;
|
||||
}
|
||||
prev = pos;
|
||||
var delay = parseInt(document.getElementById('secs').value)*1000;
|
||||
delay = Math.min(Math.max(delay, 2000), 15000)
|
||||
if (!command.stop) to = setTimeout(run,delay);
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
alert('Comms malfunction');
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
function csv(n) {
|
||||
var txt = "";
|
||||
for (let i = 0; i < fpslist.length; i++) {
|
||||
if (!n) txt += names_checked[i] + ',';
|
||||
txt += fpslist[i]; txt += "\n";
|
||||
}
|
||||
document.getElementById('csva').value = txt;
|
||||
var copyText = document.getElementById('csva');
|
||||
|
||||
copyText.select();
|
||||
copyText.setSelectionRange(0, 999999);
|
||||
document.execCommand("copy");
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="S()">
|
||||
<h2>Starship monitoring dashboard</h2>
|
||||
(or rather just a WLED frame rate tester lol)<br><br>
|
||||
IP: <input id="ip" /><br>
|
||||
Time per effect: <input type=number id=secs value=5 max=15 min=2 />s<br>
|
||||
Effects to test:
|
||||
<button type="button" onclick="setC(255)">All</button>
|
||||
<button type="button" onclick="setC(1)">Selection 1</button>
|
||||
<button type="button" onclick="setC(0)">None</button>
|
||||
<button type="button" onclick="loadC()">Get LS</button>
|
||||
<button type="button" class="red" onclick="saveC()">Save to LS</button><br>
|
||||
Extra JSON: <input id="ej" /><br>
|
||||
|
||||
<button type="button" onclick="run(true)" id="runbtn">Fetch FX list</button><br>
|
||||
LEDs: <span id="leds">-</span>, Seg: <span id="seg">-</span>, Bri: <span id="bri">-</span><br>
|
||||
FPS min: <span id="fps_min">-</span>, max: <span id="fps_max">-</span>, avg: <span id="fps_avg">-</span><br><br>
|
||||
<div id="tablecon">
|
||||
</div><br>
|
||||
<button type="button" onclick="csv(false)">Copy csv to clipboard</button>
|
||||
<button type="button" onclick="csv(true)">Copy csv (FPS only)</button>
|
||||
<textarea id=csva></textarea>
|
||||
</body>
|
||||
</html>
|
@ -9,9 +9,7 @@ The LED strip is switched [using a relay](https://github.com/Aircoookie/WLED/wik
|
||||
|
||||
## Webinterface
|
||||
|
||||
The info page in the web interface shows the items below
|
||||
- the remaining time of the off timer.
|
||||
**I recommend to deactivate the sensor before an OTA update and activate it again afterwards**.
|
||||
The info page in the web interface shows the remaining time of the off timer.
|
||||
|
||||
## Sensor connection
|
||||
|
||||
@ -65,6 +63,9 @@ void registerUsermods()
|
||||
|
||||
To query or change the PIR sensor state the methods `bool PIRsensorEnabled()` and `void EnablePIRsensor(bool enable)` are available.
|
||||
|
||||
When the PIR sensor state changes an MQTT message is broadcasted with topic `wled/deviceMAC/motion` and message `on` or `off`.
|
||||
Usermod can also be configured to just send MQTT message and not change WLED state using settings page as well as responding to motion only during nighttime (assuming NTP and lattitude/longitude are set to determine sunrise/sunset times).
|
||||
|
||||
### There are two options to get access to the usermod instance:
|
||||
|
||||
1. Include `usermod_PIR_sensor_switch.h` **before** you include the other usermod in `usermods_list.cpp'
|
||||
|
@ -3,7 +3,7 @@
|
||||
Based on the excellent `QuinLED_Dig_Uno_Temp_MQTT` by srg74 and 400killer!
|
||||
This usermod will read from an attached DS18B20 temperature sensor (as available on the QuinLED Dig-Uno)
|
||||
The temperature is displayed both in the Info section of the web UI as well as published to the `/temperature` MQTT topic if enabled.
|
||||
This usermod will be expanded with support for different sensor types in the future.
|
||||
This usermod may be expanded with support for different sensor types in the future.
|
||||
|
||||
If temperature sensor is not detected during boot, this usermod will be disabled.
|
||||
|
||||
@ -16,18 +16,19 @@ Copy the example `platformio_override.ini` to the root directory. This file sho
|
||||
* `USERMOD_DALLASTEMPERATURE` - define this to have this user mod included wled00\usermods_list.cpp
|
||||
* `USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT` - the number of milliseconds after boot to take first measurement, defaults to 20 seconds
|
||||
|
||||
All parameters can be configured at runtime using Usermods settings page.
|
||||
All parameters can be configured at runtime using Usermods settings page, including pin, selection to display temerature in degrees Celsius or Farenheit mand measurement interval.
|
||||
|
||||
## Project link
|
||||
|
||||
* [QuinLED-Dig-Uno](https://quinled.info/2018/09/15/quinled-dig-uno/) - Project link
|
||||
* [Srg74-WLED-Wemos-shield](https://github.com/srg74/WLED-wemos-shield) - another great DIY WLED board
|
||||
|
||||
### PlatformIO requirements
|
||||
|
||||
If you are using `platformio_override.ini`, you should be able to refresh the task list and see your custom task, for example `env:d1_mini_usermod_dallas_temperature_C`.
|
||||
|
||||
|
||||
If you are not using `platformio_override.ini`, you might have to uncomment `DallasTemperature@~3.8.0`,`OneWire@~2.3.5 under` `[common]` section in `platformio.ini`:
|
||||
If you are not using `platformio_override.ini`, you might have to uncomment `OneWire@~2.3.5 under` `[common]` section in `platformio.ini`:
|
||||
|
||||
```ini
|
||||
# platformio.ini
|
||||
@ -41,8 +42,7 @@ default_envs = d1_mini
|
||||
...
|
||||
lib_deps =
|
||||
...
|
||||
#For Dallas sensor uncomment following 2 lines
|
||||
DallasTemperature@~3.8.0
|
||||
#For Dallas sensor uncomment following line
|
||||
OneWire@~2.3.5
|
||||
...
|
||||
```
|
||||
|
Binary file not shown.
@ -34,7 +34,7 @@
|
||||
*/
|
||||
uint16_t WS2812FX::mode_static(void) {
|
||||
fill(SEGCOLOR(0));
|
||||
return (SEGMENT.getOption(SEG_OPTION_TRANSITIONAL)) ? FRAMETIME : 380; //update faster if in transition
|
||||
return (SEGMENT.getOption(SEG_OPTION_TRANSITIONAL)) ? FRAMETIME : 350; //update faster if in transition
|
||||
}
|
||||
|
||||
|
||||
@ -1436,8 +1436,8 @@ uint16_t WS2812FX::mode_tricolor_fade(void)
|
||||
}
|
||||
|
||||
byte stp = prog; // % 256
|
||||
uint32_t color = 0;
|
||||
for(uint16_t i = 0; i < SEGLEN; i++) {
|
||||
uint32_t color;
|
||||
if (stage == 2) {
|
||||
color = color_blend(color_from_palette(i, true, PALETTE_SOLID_WRAP, 2), color2, stp);
|
||||
} else if (stage == 1) {
|
||||
@ -3050,7 +3050,7 @@ uint16_t WS2812FX::mode_drip(void)
|
||||
|
||||
numDrops = 1 + (SEGMENT.intensity >> 6); // 255>>6 = 3
|
||||
|
||||
float gravity = -0.001 - (SEGMENT.speed/50000.0);
|
||||
float gravity = -0.0005 - (SEGMENT.speed/50000.0);
|
||||
gravity *= SEGLEN;
|
||||
int sourcedrop = 12;
|
||||
|
||||
|
@ -612,6 +612,7 @@ class WS2812FX {
|
||||
|
||||
bool
|
||||
isRgbw = false,
|
||||
isOffRefreshRequred = false, //periodic refresh is required for the strip to remain off.
|
||||
gammaCorrectBri = false,
|
||||
gammaCorrectCol = true,
|
||||
applyToAllSelected = true,
|
||||
|
@ -1,124 +0,0 @@
|
||||
/*
|
||||
Editor: https://www.visualmicro.com/
|
||||
This file is for intellisense purpose only.
|
||||
Visual micro (and the arduino ide) ignore this code during compilation. This code is automatically maintained by visualmicro, manual changes to this file will be overwritten
|
||||
The contents of the _vm sub folder can be deleted prior to publishing a project
|
||||
All non-arduino files created by visual micro and all visual studio project or solution files can be freely deleted and are not required to compile a sketch (do not delete your own code!).
|
||||
Note: debugger breakpoints are stored in '.sln' or '.asln' files, knowledge of last uploaded breakpoints is stored in the upload.vmps.xml file. Both files are required to continue a previous debug session without needing to compile and upload again
|
||||
|
||||
Hardware: ESP32 Dev Module, Platform=esp32, Package=esp32
|
||||
*/
|
||||
|
||||
#if defined(_VMICRO_INTELLISENSE)
|
||||
|
||||
#ifndef _VSARDUINO_H_
|
||||
#define _VSARDUINO_H_
|
||||
#define __ESP32_esp32__
|
||||
#define __ESP32_ESP32__
|
||||
#define ESP_PLATFORM
|
||||
#define HAVE_CONFIG_H
|
||||
#define GCC_NOT_5_2_0 0
|
||||
#define WITH_POSIX
|
||||
#define F_CPU 240000000L
|
||||
#define ARDUINO 108011
|
||||
#define ARDUINO_ESP32_DEV
|
||||
#define ARDUINO_ARCH_ESP32
|
||||
#define ESP32
|
||||
#define CORE_DEBUG_LEVEL 0
|
||||
#define __cplusplus 201103L
|
||||
|
||||
#define _Pragma(x)
|
||||
#undef __cplusplus
|
||||
#define __cplusplus 201103L
|
||||
|
||||
#define __STDC__
|
||||
#define __ARM__
|
||||
#define __arm__
|
||||
#define __inline__
|
||||
#define __asm__(...)
|
||||
#define __extension__
|
||||
#define __ATTR_PURE__
|
||||
#define __ATTR_CONST__
|
||||
#define __volatile__
|
||||
|
||||
#define __ASM
|
||||
#define __INLINE
|
||||
#define __attribute__(noinline)
|
||||
|
||||
//#define _STD_BEGIN
|
||||
//#define EMIT
|
||||
#define WARNING
|
||||
#define _Lockit
|
||||
#define __CLR_OR_THIS_CALL
|
||||
#define C4005
|
||||
#define _NEW
|
||||
|
||||
typedef bool _Bool;
|
||||
typedef int _read;
|
||||
typedef int _seek;
|
||||
typedef int _write;
|
||||
typedef int _close;
|
||||
typedef int __cleanup;
|
||||
|
||||
//#define inline
|
||||
|
||||
#define __builtin_clz
|
||||
#define __builtin_clzl
|
||||
#define __builtin_clzll
|
||||
#define __builtin_labs
|
||||
#define __builtin_va_list
|
||||
typedef int __gnuc_va_list;
|
||||
|
||||
#define __ATOMIC_ACQ_REL
|
||||
|
||||
#define __CHAR_BIT__
|
||||
#define _EXFUN()
|
||||
|
||||
typedef unsigned char byte;
|
||||
extern "C" void __cxa_pure_virtual() {;}
|
||||
|
||||
typedef long __INTPTR_TYPE__ ;
|
||||
typedef long __UINTPTR_TYPE__ ;
|
||||
typedef long __SIZE_TYPE__ ;
|
||||
typedef long __PTRDIFF_TYPE__;
|
||||
|
||||
typedef long pthread_t;
|
||||
typedef long pthread_key_t;
|
||||
typedef long pthread_once_t;
|
||||
typedef long pthread_mutex_t;
|
||||
typedef long pthread_mutex_t;
|
||||
typedef long pthread_cond_t;
|
||||
|
||||
|
||||
|
||||
#include "arduino.h"
|
||||
#include <pins_arduino.h>
|
||||
|
||||
#define interrupts() sei()
|
||||
#define noInterrupts() cli()
|
||||
|
||||
#define ESP_LOGI(tag, ...)
|
||||
|
||||
#include "wled00.ino"
|
||||
#include "wled01_eeprom.ino"
|
||||
#include "wled02_xml.ino"
|
||||
#include "wled03_set.ino"
|
||||
#include "wled04_file.ino"
|
||||
#include "wled05_init.ino"
|
||||
#include "wled06_usermod.ino"
|
||||
#include "wled07_notify.ino"
|
||||
#include "wled08_led.ino"
|
||||
#include "wled09_button.ino"
|
||||
#include "wled10_ntp.ino"
|
||||
#include "wled11_ol.ino"
|
||||
#include "wled12_alexa.ino"
|
||||
#include "wled13_cronixie.ino"
|
||||
#include "wled14_colors.ino"
|
||||
#include "wled15_hue.ino"
|
||||
#include "wled16_blynk.ino"
|
||||
#include "wled17_mqtt.ino"
|
||||
#include "wled18_server.ino"
|
||||
#include "wled19_json.ino"
|
||||
#include "wled20_ir.ino"
|
||||
#endif
|
||||
#endif
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -63,7 +63,7 @@ class Bus {
|
||||
return _start;
|
||||
}
|
||||
|
||||
void setStart(uint16_t start) {
|
||||
inline void setStart(uint16_t start) {
|
||||
_start = start;
|
||||
}
|
||||
|
||||
@ -81,7 +81,7 @@ class Bus {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual uint8_t skipFirstLed() {
|
||||
virtual uint8_t skippedLeds() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -184,10 +184,10 @@ class BusDigital : public Bus {
|
||||
}
|
||||
|
||||
inline bool isRgbw() {
|
||||
return _rgbw;
|
||||
return (_rgbw || _type == TYPE_SK6812_RGBW || _type == TYPE_TM1814);
|
||||
}
|
||||
|
||||
inline uint8_t skipFirstLed() {
|
||||
inline uint8_t skippedLeds() {
|
||||
return _skip;
|
||||
}
|
||||
|
||||
@ -451,6 +451,11 @@ class BusManager {
|
||||
return Bus::isRgbw(type);
|
||||
}
|
||||
|
||||
//Return true if the strip requires a refresh to stay off.
|
||||
static bool isOffRefreshRequred(uint8_t type) {
|
||||
return type == TYPE_TM1814;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t numBusses = 0;
|
||||
Bus* busses[WLED_MAX_BUSSES];
|
||||
|
@ -12,24 +12,7 @@ void getStringFromJson(char* dest, const char* src, size_t len) {
|
||||
if (src != nullptr) strlcpy(dest, src, len);
|
||||
}
|
||||
|
||||
void deserializeConfig() {
|
||||
bool fromeep = false;
|
||||
bool success = deserializeConfigSec();
|
||||
if (!success) { //if file does not exist, try reading from EEPROM
|
||||
deEEPSettings();
|
||||
fromeep = true;
|
||||
}
|
||||
|
||||
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
|
||||
|
||||
DEBUG_PRINTLN(F("Reading settings from /cfg.json..."));
|
||||
|
||||
success = readObjectFromFile("/cfg.json", nullptr, &doc);
|
||||
if (!success) { //if file does not exist, try reading from EEPROM
|
||||
if (!fromeep) deEEPSettings();
|
||||
return;
|
||||
}
|
||||
|
||||
bool deserializeConfig(JsonObject doc, bool fromFS) {
|
||||
//int rev_major = doc["rev"][0]; // 1
|
||||
//int rev_minor = doc["rev"][1]; // 0
|
||||
|
||||
@ -103,6 +86,8 @@ void deserializeConfig() {
|
||||
CJSON(strip.rgbwMode, hw_led[F("rgbwm")]);
|
||||
|
||||
JsonArray ins = hw_led["ins"];
|
||||
|
||||
if (fromFS || !ins.isNull()) {
|
||||
uint8_t s = 0; // bus iterator
|
||||
strip.isRgbw = false;
|
||||
busses.removeAll();
|
||||
@ -112,6 +97,7 @@ void deserializeConfig() {
|
||||
uint8_t pins[5] = {255, 255, 255, 255, 255};
|
||||
JsonArray pinArr = elm["pin"];
|
||||
if (pinArr.size() == 0) continue;
|
||||
pins[0] = pinArr[0];
|
||||
uint8_t i = 0;
|
||||
for (int p : pinArr) {
|
||||
pins[i++] = p;
|
||||
@ -129,7 +115,9 @@ void deserializeConfig() {
|
||||
//RGBW mode is enabled if at least one of the strips is RGBW
|
||||
// if ((bool)elm[F("rgbw")]) SET_BIT(ledType,7); else UNSET_BIT(ledType,7); // hack bit 7 to indicate RGBW (as an override if necessary)
|
||||
// strip.isRgbw |= (bool)elm[F("rgbw")];
|
||||
strip.isRgbw = (strip.isRgbw || Bus::isRgbw(ledType));
|
||||
strip.isRgbw = (strip.isRgbw || BusManager::isRgbw(ledType));
|
||||
//refresh is required to remain off if at least one of the strips requires the refresh.
|
||||
strip.isOffRefreshRequred |= BusManager::isOffRefreshRequred(ledType);
|
||||
s++;
|
||||
lC += length;
|
||||
BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst);
|
||||
@ -138,44 +126,50 @@ void deserializeConfig() {
|
||||
DEBUG_PRINTLN(busses.getNumBusses());
|
||||
if (mem <= MAX_LED_MEMORY && busses.getNumBusses() <= WLED_MAX_BUSSES) busses.add(bc); // finalization will be done in WLED::beginStrip()
|
||||
}
|
||||
//strip.finalizeInit();
|
||||
}
|
||||
if (lC > ledCount) ledCount = lC; // fix incorrect total length (honour analog setup)
|
||||
DEBUG_PRINTLN(F(" Done LEDs."));
|
||||
|
||||
JsonObject hw_btn_ins_0 = hw[F("btn")][F("ins")][0];
|
||||
CJSON(buttonType, hw_btn_ins_0["type"]);
|
||||
int hw_btn_pin = hw_btn_ins_0["pin"][0];
|
||||
int hw_btn_pin = hw_btn_ins_0[F("pin")][0] | -2; //-2 = not present in doc, keep current. -1 = disable
|
||||
if (hw_btn_pin > -2) {
|
||||
if (pinManager.allocatePin(hw_btn_pin,false)) {
|
||||
btnPin = hw_btn_pin;
|
||||
pinMode(btnPin, INPUT_PULLUP);
|
||||
} else {
|
||||
btnPin = -1;
|
||||
}
|
||||
}
|
||||
|
||||
JsonArray hw_btn_ins_0_macros = hw_btn_ins_0[F("macros")];
|
||||
CJSON(macroButton, hw_btn_ins_0_macros[0]);
|
||||
CJSON(macroLongPress,hw_btn_ins_0_macros[1]);
|
||||
CJSON(macroDoublePress, hw_btn_ins_0_macros[2]);
|
||||
|
||||
//int hw_btn_ins_0_type = hw_btn_ins_0["type"]; // 0
|
||||
|
||||
#ifndef WLED_DISABLE_INFRARED
|
||||
int hw_ir_pin = hw["ir"]["pin"] | -1; // 4
|
||||
int hw_ir_pin = hw["ir"]["pin"] | -2; // 4
|
||||
if (hw_ir_pin > -2) {
|
||||
if (pinManager.allocatePin(hw_ir_pin,false)) {
|
||||
irPin = hw_ir_pin;
|
||||
} else {
|
||||
irPin = -1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
CJSON(irEnabled, hw["ir"]["type"]);
|
||||
|
||||
JsonObject relay = hw[F("relay")];
|
||||
int hw_relay_pin = relay["pin"] | -1;
|
||||
if (hw_relay_pin>=0 && pinManager.allocatePin(hw_relay_pin,true)) {
|
||||
int hw_relay_pin = relay["pin"] | -2;
|
||||
if (hw_relay_pin > -2) {
|
||||
if (pinManager.allocatePin(hw_relay_pin,true)) {
|
||||
rlyPin = hw_relay_pin;
|
||||
pinMode(rlyPin, OUTPUT);
|
||||
} else {
|
||||
rlyPin = -1;
|
||||
}
|
||||
}
|
||||
if (relay.containsKey("rev")) {
|
||||
rlyMde = !relay["rev"];
|
||||
}
|
||||
@ -202,8 +196,9 @@ void deserializeConfig() {
|
||||
|
||||
JsonObject light_nl = light["nl"];
|
||||
CJSON(nightlightMode, light_nl[F("mode")]);
|
||||
CJSON(nightlightDelayMinsDefault, light_nl["dur"]);
|
||||
nightlightDelayMins = nightlightDelayMinsDefault;
|
||||
byte prev = nightlightDelayMinsDefault;
|
||||
CJSON(nightlightDelayMinsDefault, light_nl[F("dur")]);
|
||||
if (nightlightDelayMinsDefault != prev) nightlightDelayMins = nightlightDelayMinsDefault;
|
||||
|
||||
CJSON(nightlightTargetBri, light_nl[F("tbri")]);
|
||||
CJSON(macroNl, light_nl[F("macro")]);
|
||||
@ -231,12 +226,14 @@ void deserializeConfig() {
|
||||
JsonObject if_sync_recv = if_sync["recv"];
|
||||
CJSON(receiveNotificationBrightness, if_sync_recv["bri"]);
|
||||
CJSON(receiveNotificationColor, if_sync_recv["col"]);
|
||||
CJSON(receiveNotificationEffects, if_sync_recv["fx"]);
|
||||
CJSON(receiveNotificationEffects, if_sync_recv[F("fx")]);
|
||||
//! following line might be a problem if called after boot
|
||||
receiveNotifications = (receiveNotificationBrightness || receiveNotificationColor || receiveNotificationEffects);
|
||||
|
||||
JsonObject if_sync_send = if_sync["send"];
|
||||
prev = notifyDirectDefault;
|
||||
CJSON(notifyDirectDefault, if_sync_send[F("dir")]);
|
||||
notifyDirect = notifyDirectDefault;
|
||||
if (notifyDirectDefault != prev) notifyDirect = notifyDirectDefault;
|
||||
CJSON(notifyButton, if_sync_send[F("btn")]);
|
||||
CJSON(notifyAlexa, if_sync_send[F("va")]);
|
||||
CJSON(notifyHue, if_sync_send[F("hue")]);
|
||||
@ -321,9 +318,10 @@ void deserializeConfig() {
|
||||
CJSON(latitude, if_ntp[F("lt")]);
|
||||
|
||||
JsonObject ol = doc[F("ol")];
|
||||
prev = overlayDefault;
|
||||
CJSON(overlayDefault ,ol[F("clock")]); // 0
|
||||
CJSON(countdownMode, ol[F("cntdwn")]);
|
||||
overlayCurrent = overlayDefault;
|
||||
if (prev != overlayDefault) overlayCurrent = overlayDefault;
|
||||
|
||||
CJSON(overlayMin, ol["min"]);
|
||||
CJSON(overlayMax, ol[F("max")]);
|
||||
@ -398,7 +396,32 @@ void deserializeConfig() {
|
||||
|
||||
DEBUG_PRINTLN(F("Starting usermod config."));
|
||||
JsonObject usermods_settings = doc["um"];
|
||||
usermods.readFromConfig(usermods_settings);
|
||||
if (!usermods_settings.isNull()) usermods.readFromConfig(usermods_settings);
|
||||
|
||||
if (fromFS) return false;
|
||||
doReboot = doc[F("rb")] | doReboot;
|
||||
return (doc["sv"] | true);
|
||||
}
|
||||
|
||||
void deserializeConfigFromFS() {
|
||||
bool fromeep = false;
|
||||
bool success = deserializeConfigSec();
|
||||
if (!success) { //if file does not exist, try reading from EEPROM
|
||||
deEEPSettings();
|
||||
fromeep = true;
|
||||
}
|
||||
|
||||
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
|
||||
|
||||
DEBUG_PRINTLN(F("Reading settings from /cfg.json..."));
|
||||
|
||||
success = readObjectFromFile("/cfg.json", nullptr, &doc);
|
||||
if (!success) { //if file does not exist, try reading from EEPROM
|
||||
if (!fromeep) deEEPSettings();
|
||||
return;
|
||||
}
|
||||
|
||||
deserializeConfig(doc.as<JsonObject>(), true);
|
||||
}
|
||||
|
||||
void serializeConfig() {
|
||||
@ -481,7 +504,7 @@ void serializeConfig() {
|
||||
for (uint8_t i = 0; i < nPins; i++) ins_pin.add(pins[i]);
|
||||
ins[F("order")] = bus->getColorOrder();
|
||||
ins["rev"] = bus->reversed;
|
||||
ins[F("skip")] = bus->skipFirstLed();
|
||||
ins[F("skip")] = bus->skippedLeds();
|
||||
ins["type"] = bus->getType();
|
||||
ins[F("rgbw")] = bus->isRgbw();
|
||||
}
|
||||
|
@ -10,18 +10,18 @@
|
||||
margin: 0;
|
||||
}
|
||||
html {
|
||||
--h: 10.55vh;
|
||||
--h: 10.2vh;
|
||||
}
|
||||
button {
|
||||
background: #333;
|
||||
color: #fff;
|
||||
font-family: Verdana, Helvetica, sans-serif;
|
||||
border: 0.3ch solid #333;
|
||||
display: inline-block;
|
||||
border: 1px solid #333;
|
||||
font-size: 6vmin;
|
||||
height: var(--h);
|
||||
width: 95%;
|
||||
margin-top: 2.4vh;
|
||||
margin-top: 2vh;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
|
@ -181,9 +181,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
function GetV()
|
||||
{
|
||||
}
|
||||
function GetV(){var d=document;}
|
||||
</script>
|
||||
<style>
|
||||
@import url("style.css");
|
||||
|
@ -3,14 +3,15 @@
|
||||
<head lang="en">
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=500">
|
||||
<title>UI Settings</title>
|
||||
<title>Usermod Settings</title>
|
||||
<script>
|
||||
var d = document;
|
||||
var umCfg = {};
|
||||
var pins = [6,7,8,9,10,11];
|
||||
var pinO = ["reserved","reserved","reserved","reserved","reserved","reserved"], owner;
|
||||
var pinO = ["rsvd","rsvd","rsvd","rsvd","rsvd","rsvd"], owner;
|
||||
var loc = false, locip;
|
||||
var urows;
|
||||
var numM = 0;
|
||||
function gId(s) { return d.getElementById(s); }
|
||||
function isO(i) { return (i && typeof i === 'object' && !Array.isArray(i)); }
|
||||
function H() { window.open("https://github.com/Aircoookie/WLED/wiki/Settings#usermod-settings"); }
|
||||
@ -24,7 +25,9 @@
|
||||
localStorage.setItem('locIp', locip);
|
||||
}
|
||||
}
|
||||
ldS();
|
||||
GetV();
|
||||
if (numM > 0 || locip) ldS();
|
||||
else gId("um").innerHTML = "No Usermods installed.";
|
||||
}
|
||||
function check(o,k) {
|
||||
var n = o.name.replace("[]","").substr(-3);
|
||||
@ -99,11 +102,8 @@
|
||||
urows += `<hr><h3>${k}</h3>`;
|
||||
addField(k,'unknown',o);
|
||||
}
|
||||
if (urows==="")
|
||||
urows = "No Usermods configuration found.<br>Press <i>Save</i> to initialize defaults if usermods were compiled in.";
|
||||
} else {
|
||||
urows = "Usermods configuration not found.<br>Most likely no Usermods exist.<br>Press <i>Save</i> to try to initialize defaults.";
|
||||
}
|
||||
if (urows==="") urows = "Usermods configuration not found.<br>Press <i>Save</i> to initialize defaults.";
|
||||
gId("um").innerHTML = urows;
|
||||
})
|
||||
.catch(function (error) {
|
||||
|
@ -43,7 +43,7 @@
|
||||
Installed version: ##VERSION##<br>
|
||||
Download the latest binary: <a href="https://github.com/Aircoookie/WLED/releases" target="_blank">
|
||||
<img src="https://img.shields.io/github/release/Aircoookie/WLED.svg?style=flat-square"></a><br>
|
||||
<input type='file' class="bt" name='update' accept=".bin" required><br>
|
||||
<input type='file' class="bt" name='update' required><br> <!--should have accept='.bin', but it prevents file upload from android app-->
|
||||
<input type='submit' class="bt" value='Update!' ><br>
|
||||
<button type="button" class="bt" onclick="B()">Back</button></form>
|
||||
<div id="msg"><b>Updating...</b><br>Please do not close or refresh the page :)</div>
|
||||
|
@ -26,7 +26,8 @@ void handleButton();
|
||||
void handleIO();
|
||||
|
||||
//cfg.cpp
|
||||
void deserializeConfig();
|
||||
bool deserializeConfig(JsonObject doc, bool fromFS = false);
|
||||
void deserializeConfigFromFS();
|
||||
bool deserializeConfigSec();
|
||||
void serializeConfig();
|
||||
void serializeConfigSec();
|
||||
|
@ -55,13 +55,12 @@ bool bufferedFind(const char *target, bool fromStart = true) {
|
||||
size_t targetLen = strlen(target);
|
||||
|
||||
size_t index = 0;
|
||||
uint16_t bufsize = 0, count = 0;
|
||||
byte buf[FS_BUFSIZE];
|
||||
if (fromStart) f.seek(0);
|
||||
|
||||
while (f.position() < f.size() -1) {
|
||||
bufsize = f.read(buf, FS_BUFSIZE);
|
||||
count = 0;
|
||||
uint16_t bufsize = f.read(buf, FS_BUFSIZE);
|
||||
uint16_t count = 0;
|
||||
while (count < bufsize) {
|
||||
if(buf[count] != target[index])
|
||||
index = 0; // reset index if any char does not match
|
||||
@ -97,13 +96,12 @@ bool bufferedFindSpace(uint16_t targetLen, bool fromStart = true) {
|
||||
if (!f || !f.size()) return false;
|
||||
|
||||
uint16_t index = 0;
|
||||
uint16_t bufsize = 0, count = 0;
|
||||
byte buf[FS_BUFSIZE];
|
||||
if (fromStart) f.seek(0);
|
||||
|
||||
while (f.position() < f.size() -1) {
|
||||
bufsize = f.read(buf, FS_BUFSIZE);
|
||||
count = 0;
|
||||
uint16_t bufsize = f.read(buf, FS_BUFSIZE);
|
||||
uint16_t count = 0;
|
||||
|
||||
while (count < bufsize) {
|
||||
if(buf[count] == ' ') {
|
||||
@ -140,13 +138,12 @@ bool bufferedFindObjectEnd() {
|
||||
if (!f || !f.size()) return false;
|
||||
|
||||
uint16_t objDepth = 0; //num of '{' minus num of '}'. return once 0
|
||||
uint16_t bufsize = 0, count = 0;
|
||||
//size_t start = f.position();
|
||||
byte buf[FS_BUFSIZE];
|
||||
|
||||
while (f.position() < f.size() -1) {
|
||||
bufsize = f.read(buf, FS_BUFSIZE);
|
||||
count = 0;
|
||||
uint16_t bufsize = f.read(buf, FS_BUFSIZE);
|
||||
uint16_t count = 0;
|
||||
|
||||
while (count < bufsize) {
|
||||
if (buf[count] == '{') objDepth++;
|
||||
|
@ -45,10 +45,10 @@ action="/update" id="uf" enctype="multipart/form-data" onsubmit="U()">
|
||||
Installed version: 0.12.2-bl3<br>Download the latest binary: <a
|
||||
href="https://github.com/Aircoookie/WLED/releases" target="_blank"><img
|
||||
src="https://img.shields.io/github/release/Aircoookie/WLED.svg?style=flat-square">
|
||||
</a><br><input type="file" class="bt" name="update" accept=".bin" required><br>
|
||||
<input type="submit" class="bt" value="Update!"><br><button type="button"
|
||||
class="bt" onclick="B()">Back</button></form><div id="msg"><b>Updating...</b>
|
||||
<br>Please do not close or refresh the page :)</div></body></html>)=====";
|
||||
</a><br><input type="file" class="bt" name="update" required><br><input
|
||||
type="submit" class="bt" value="Update!"><br><button type="button" class="bt"
|
||||
onclick="B()">Back</button></form><div id="msg"><b>Updating...</b><br>
|
||||
Please do not close or refresh the page :)</div></body></html>)=====";
|
||||
|
||||
|
||||
// Autogenerated from wled00/data/welcome.htm, do not edit!!
|
||||
|
@ -12,7 +12,7 @@ const char PAGE_settingsCss[] PROGMEM = R"=====(<style>body{font-family:Verdana,
|
||||
// Autogenerated from wled00/data/settings.htm, do not edit!!
|
||||
const char PAGE_settings[] PROGMEM = R"=====(<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>WLED Settings
|
||||
</title><style>
|
||||
body{text-align:center;background:#222;height:100px;margin:0}html{--h:10.55vh}button{background:#333;color:#fff;font-family:Verdana,Helvetica,sans-serif;border:.3ch solid #333;display:inline-block;font-size:6vmin;height:var(--h);width:95%%;margin-top:2.4vh}
|
||||
body{text-align:center;background:#222;height:100px;margin:0}html{--h:10.2vh}button{background:#333;color:#fff;font-family:Verdana,Helvetica,sans-serif;display:inline-block;border:1px solid #333;font-size:6vmin;height:var(--h);width:95%%;margin-top:2vh}
|
||||
</style><script>
|
||||
function BB(){window.frameElement&&(document.getElementById("b").style.display="none",document.documentElement.style.setProperty("--h","13.86vh"))}
|
||||
</script></head><body onload="BB()"><form action="/"><button type="submit"
|
||||
@ -395,9 +395,9 @@ type="submit">Save & Reboot</button></form></body></html>)=====";
|
||||
|
||||
// Autogenerated from wled00/data/settings_um.htm, do not edit!!
|
||||
const char PAGE_settings_um[] PROGMEM = R"=====(<!DOCTYPE html><html><head lang="en"><meta charset="utf-8"><meta
|
||||
name="viewport" content="width=500"><title>UI Settings</title><script>
|
||||
var owner,locip,urows,d=document,umCfg={},pins=[6,7,8,9,10,11],pinO=["reserved","reserved","reserved","reserved","reserved","reserved"],loc=!1;function gId(e){return d.getElementById(e)}function isO(e){return e&&"object"==typeof e&&!Array.isArray(e)}function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#usermod-settings")}function B(){window.open("/settings","_self")}function S(){"file:"==window.location.protocol&&(loc=!0,(locip=localStorage.getItem("locIp"))||(locip=prompt("File Mode. Please enter WLED IP!"),localStorage.setItem("locIp",locip))),ldS()}function check(e,o){var i=e.name.replace("[]","").substr(-3);if("number"==e.type&&"pin"==i.substr(0,3))for(var n=0;n<pins.length;n++)if(o!=pinO[n]){if(e.value==pins[n]||e.value<-1||e.value>39){e.style.color="red";break}e.style.color=e.value>33?"orange":"#fff"}}function getPins(e){if(isO(e))for(const[i,n]of Object.entries(e))if(isO(n))owner=i,getPins(n);else if("pin"==i.replace("[]","").substr(-3))if(Array.isArray(n))for(var o=0;o<n.length;o++)n[o]>=0&&(pins.push(n[o]),pinO.push(owner));else n>=0&&(pins.push(n),pinO.push(owner));else if(Array.isArray(n))for(o=0;o<n.length;o++)getPins(n[o])}function addField(e,o,i,n=!1){if(isO(i))for(const[o,n]of Object.entries(i))addField(e,o,n);else if(Array.isArray(i))for(var r=0;r<i.length;r++)addField(e,o,i[r],!0);else{var s,t;switch(typeof i){case"boolean":s="checkbox",t=i?'checked value="on"':"";break;case"number":s="number",t=`value="${parseInt(i,10)}"`;break;case"string":default:s="text",t=`value="${i}"`}"checkbox"==s&&(urows+=`<input type="hidden" name="${e}_${o}${n?"[]":""}" value="off">`),urows+=`${o}: <input type="${s}" name="${e}_${o}${n?"[]":""}" ${t} oninput="check(this,'${e}')"><br>`}}function ldS(){fetch((loc?"http://"+locip:"")+"/cfg.json",{method:"get"}).then(e=>(e.ok||(gId("lserr").style.display="inline"),e.json())).then(e=>{if(umCfg=e.um,getPins(e),urows="",isO(umCfg)){for(const[e,o]of Object.entries(umCfg))urows+=`<hr><h3>${e}</h3>`,addField(e,"unknown",o);""===urows&&(urows="No Usermods configuration found.<br>Press <i>Save</i> to initialize defaults if usermods were compiled in.")}else urows="Usermods configuration not found.<br>Most likely no Usermods exist.<br>Press <i>Save</i> to try to initialize defaults.";gId("um").innerHTML=urows}).catch((function(e){gId("lserr").style.display="inline",console.log(e)}))}function svS(e){e.preventDefault(),console.log(d.Sf),d.Sf.checkValidity()&&d.Sf.submit()}function GetV(){}
|
||||
</script>%CSS%%SCSS%</head><body onload="S()"><form
|
||||
name="viewport" content="width=500"><title>Usermod Settings</title><script>
|
||||
var owner,locip,urows,d=document,umCfg={},pins=[6,7,8,9,10,11],pinO=["rsvd","rsvd","rsvd","rsvd","rsvd","rsvd"],loc=!1,numM=0;function gId(e){return d.getElementById(e)}function isO(e){return e&&"object"==typeof e&&!Array.isArray(e)}function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#usermod-settings")}function B(){window.open("/settings","_self")}function S(){"file:"==window.location.protocol&&(loc=!0,(locip=localStorage.getItem("locIp"))||(locip=prompt("File Mode. Please enter WLED IP!"),localStorage.setItem("locIp",locip))),GetV(),numM>0||locip?ldS():gId("um").innerHTML="No Usermods installed."}function check(e,n){var o=e.name.replace("[]","").substr(-3);if("number"==e.type&&"pin"==o.substr(0,3))for(var i=0;i<pins.length;i++)if(n!=pinO[i]){if(e.value==pins[i]||e.value<-1||e.value>39){e.style.color="red";break}e.style.color=e.value>33?"orange":"#fff"}}function getPins(e){if(isO(e))for(const[o,i]of Object.entries(e))if(isO(i))owner=o,getPins(i);else if("pin"==o.replace("[]","").substr(-3))if(Array.isArray(i))for(var n=0;n<i.length;n++)i[n]>=0&&(pins.push(i[n]),pinO.push(owner));else i>=0&&(pins.push(i),pinO.push(owner));else if(Array.isArray(i))for(n=0;n<i.length;n++)getPins(i[n])}function addField(e,n,o,i=!1){if(isO(o))for(const[n,i]of Object.entries(o))addField(e,n,i);else if(Array.isArray(o))for(var t=0;t<o.length;t++)addField(e,n,o[t],!0);else{var r,s;switch(typeof o){case"boolean":r="checkbox",s=o?'checked value="on"':"";break;case"number":r="number",s=`value="${parseInt(o,10)}"`;break;case"string":default:r="text",s=`value="${o}"`}"checkbox"==r&&(urows+=`<input type="hidden" name="${e}_${n}${i?"[]":""}" value="off">`),urows+=`${n}: <input type="${r}" name="${e}_${n}${i?"[]":""}" ${s} oninput="check(this,'${e}')"><br>`}}function ldS(){fetch((loc?"http://"+locip:"")+"/cfg.json",{method:"get"}).then(e=>(e.ok||(gId("lserr").style.display="inline"),e.json())).then(e=>{if(umCfg=e.um,getPins(e),urows="",isO(umCfg))for(const[e,n]of Object.entries(umCfg))urows+=`<hr><h3>${e}</h3>`,addField(e,"unknown",n);""===urows&&(urows="Usermods configuration not found.<br>Press <i>Save</i> to initialize defaults."),gId("um").innerHTML=urows}).catch((function(e){gId("lserr").style.display="inline",console.log(e)}))}function svS(e){e.preventDefault(),console.log(d.Sf),d.Sf.checkValidity()&&d.Sf.submit()}function GetV() {var d=document;
|
||||
%CSS%%SCSS%</head><body onload="S()"><form
|
||||
id="form_s" name="Sf" method="post" onsubmit="svS(event)"><div class="toprow">
|
||||
<div class="helpB"><button type="button" onclick="H()">?</button></div><button
|
||||
type="button" onclick="B()">Back</button><button type="submit">Save</button><br>
|
||||
|
@ -227,7 +227,7 @@ void decodeIR24(uint32_t code)
|
||||
switch (code) {
|
||||
case IR24_BRIGHTER : incBrightness(); break;
|
||||
case IR24_DARKER : decBrightness(); break;
|
||||
case IR24_OFF : briLast = bri; bri = 0; break;
|
||||
case IR24_OFF : if (bri > 0) briLast = bri; bri = 0; break;
|
||||
case IR24_ON : bri = briLast; break;
|
||||
case IR24_RED : colorFromUint32(COLOR_RED); break;
|
||||
case IR24_REDDISH : colorFromUint32(COLOR_REDDISH); break;
|
||||
@ -259,7 +259,7 @@ void decodeIR24OLD(uint32_t code)
|
||||
switch (code) {
|
||||
case IR24_OLD_BRIGHTER : incBrightness(); break;
|
||||
case IR24_OLD_DARKER : decBrightness(); break;
|
||||
case IR24_OLD_OFF : briLast = bri; bri = 0; break;
|
||||
case IR24_OLD_OFF : if (bri > 0) briLast = bri; bri = 0; break;
|
||||
case IR24_OLD_ON : bri = briLast; break;
|
||||
case IR24_OLD_RED : colorFromUint32(COLOR_RED); break;
|
||||
case IR24_OLD_REDDISH : colorFromUint32(COLOR_REDDISH); break;
|
||||
@ -292,7 +292,7 @@ void decodeIR24CT(uint32_t code)
|
||||
switch (code) {
|
||||
case IR24_CT_BRIGHTER : incBrightness(); break;
|
||||
case IR24_CT_DARKER : decBrightness(); break;
|
||||
case IR24_CT_OFF : briLast = bri; bri = 0; break;
|
||||
case IR24_CT_OFF : if (bri > 0) briLast = bri; bri = 0; break;
|
||||
case IR24_CT_ON : bri = briLast; break;
|
||||
case IR24_CT_RED : colorFromUint32(COLOR_RED); break;
|
||||
case IR24_CT_REDDISH : colorFromUint32(COLOR_REDDISH); break;
|
||||
@ -327,7 +327,7 @@ void decodeIR40(uint32_t code)
|
||||
switch (code) {
|
||||
case IR40_BPLUS : incBrightness(); break;
|
||||
case IR40_BMINUS : decBrightness(); break;
|
||||
case IR40_OFF : briLast = bri; bri = 0; break;
|
||||
case IR40_OFF : if (bri > 0) briLast = bri; bri = 0; break;
|
||||
case IR40_ON : bri = briLast; break;
|
||||
case IR40_RED : colorFromUint24(COLOR_RED); break;
|
||||
case IR40_REDDISH : colorFromUint24(COLOR_REDDISH); break;
|
||||
@ -384,7 +384,7 @@ void decodeIR44(uint32_t code)
|
||||
switch (code) {
|
||||
case IR44_BPLUS : incBrightness(); break;
|
||||
case IR44_BMINUS : decBrightness(); break;
|
||||
case IR44_OFF : briLast = bri; bri = 0; break;
|
||||
case IR44_OFF : if (bri > 0) briLast = bri; bri = 0; break;
|
||||
case IR44_ON : bri = briLast; break;
|
||||
case IR44_RED : colorFromUint24(COLOR_RED); break;
|
||||
case IR44_REDDISH : colorFromUint24(COLOR_REDDISH); break;
|
||||
@ -447,7 +447,7 @@ void decodeIR21(uint32_t code)
|
||||
switch (code) {
|
||||
case IR21_BRIGHTER: incBrightness(); break;
|
||||
case IR21_DARKER: decBrightness(); break;
|
||||
case IR21_OFF: briLast = bri; bri = 0; break;
|
||||
case IR21_OFF: if (bri > 0) briLast = bri; bri = 0; break;
|
||||
case IR21_ON: bri = briLast; break;
|
||||
case IR21_RED: colorFromUint32(COLOR_RED); break;
|
||||
case IR21_REDDISH: colorFromUint32(COLOR_REDDISH); break;
|
||||
|
@ -166,7 +166,7 @@ void deserializeSegment(JsonObject elem, byte it)
|
||||
if (icol.isNull()) break;
|
||||
|
||||
byte sz = icol.size();
|
||||
if (sz == 0 && sz > 4) break;
|
||||
if (sz == 0 || sz > 4) break;
|
||||
|
||||
int rgbw[] = {0,0,0,0};
|
||||
copyArray(icol, rgbw);
|
||||
@ -198,6 +198,8 @@ bool deserializeState(JsonObject root)
|
||||
bool on = root["on"] | (bri > 0);
|
||||
if (!on != !bri) toggleOnOff();
|
||||
|
||||
if (root["on"].is<const char*>() && root["on"].as<const char*>()[0] == 't') toggleOnOff();
|
||||
|
||||
int tr = root[F("transition")] | -1;
|
||||
if (tr >= 0)
|
||||
{
|
||||
@ -813,6 +815,9 @@ void serveJson(AsyncWebServerRequest* request, uint8_t versionAPI)
|
||||
request->send_P(200, "application/json", JSON_palette_names);
|
||||
return;
|
||||
}
|
||||
else if (url.indexOf("cfg") > 0 && handleFileRead(request, "/cfg.json")) {
|
||||
return;
|
||||
}
|
||||
else if (url.length() > 6) { //not just /json
|
||||
request->send( 501, "application/json", F("{\"error\":\"Not implemented\"}"));
|
||||
return;
|
||||
|
@ -52,23 +52,22 @@ void onMqttConnect(bool sessionPresent)
|
||||
}
|
||||
|
||||
|
||||
void onMqttMessage(char* topic, char* payload0, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) {
|
||||
void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) {
|
||||
|
||||
DEBUG_PRINT(F("MQTT msg: "));
|
||||
DEBUG_PRINTLN(topic);
|
||||
|
||||
// paranoia check to avoid npe if no payload
|
||||
if (payload0==nullptr) {
|
||||
if (payload==nullptr) {
|
||||
DEBUG_PRINTLN(F("no payload -> leave"));
|
||||
return;
|
||||
}
|
||||
|
||||
// payload is not always null terminated
|
||||
char *payload = new char[len+1];
|
||||
if (payload==nullptr) return; // out of memory
|
||||
strncpy(payload,payload0,len);
|
||||
payload[len] = '\0';
|
||||
DEBUG_PRINTLN(payload);
|
||||
//make a copy of the payload to 0-terminate it
|
||||
char* payloadStr = new char[len+1];
|
||||
if (payloadStr == nullptr) return; //no mem
|
||||
strncpy(payloadStr, payload, len);
|
||||
payloadStr[len] = '\0';
|
||||
DEBUG_PRINTLN(payloadStr);
|
||||
|
||||
size_t topicPrefixLen = strlen(mqttDeviceTopic);
|
||||
if (strncmp(topic, mqttDeviceTopic, topicPrefixLen) == 0) {
|
||||
@ -79,8 +78,8 @@ void onMqttMessage(char* topic, char* payload0, AsyncMqttClientMessageProperties
|
||||
topic += topicPrefixLen;
|
||||
} else {
|
||||
// Non-Wled Topic used here. Probably a usermod subscribed to this topic.
|
||||
usermods.onMqttMessage(topic, payload);
|
||||
delete[] payload;
|
||||
usermods.onMqttMessage(topic, payloadStr);
|
||||
delete[] payloadStr;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -88,26 +87,26 @@ void onMqttMessage(char* topic, char* payload0, AsyncMqttClientMessageProperties
|
||||
//Prefix is stripped from the topic at this point
|
||||
|
||||
if (strcmp_P(topic, PSTR("/col")) == 0) {
|
||||
colorFromDecOrHexString(col, payload);
|
||||
colorFromDecOrHexString(col, (char*)payloadStr);
|
||||
colorUpdated(NOTIFIER_CALL_MODE_DIRECT_CHANGE);
|
||||
} else if (strcmp_P(topic, PSTR("/api")) == 0) {
|
||||
if (payload[0] == '{') { //JSON API
|
||||
DynamicJsonDocument doc(JSON_BUFFER_SIZE);
|
||||
deserializeJson(doc, payload);
|
||||
deserializeJson(doc, payloadStr);
|
||||
deserializeState(doc.as<JsonObject>());
|
||||
} else { //HTTP API
|
||||
String apireq = "win&";
|
||||
apireq += payload;
|
||||
apireq += (char*)payloadStr;
|
||||
handleSet(nullptr, apireq);
|
||||
}
|
||||
} else if (strlen(topic) != 0) {
|
||||
// non standard topic, check with usermods
|
||||
usermods.onMqttMessage(topic, payload);
|
||||
usermods.onMqttMessage(topic, payloadStr);
|
||||
} else {
|
||||
// topmost topic (just wled/MAC)
|
||||
parseMQTTBriPayload(payload);
|
||||
parseMQTTBriPayload(payloadStr);
|
||||
}
|
||||
delete[] payload;
|
||||
delete[] payloadStr;
|
||||
}
|
||||
|
||||
|
||||
@ -132,7 +131,7 @@ void publishMqtt()
|
||||
|
||||
strcpy(subuf, mqttDeviceTopic);
|
||||
strcat_P(subuf, PSTR("/status"));
|
||||
mqtt->publish(subuf, 0, true, "online"); // do not retain message
|
||||
mqtt->publish(subuf, 0, false, "online"); // do not retain message
|
||||
|
||||
char apires[1024];
|
||||
XML_response(nullptr, apires);
|
||||
|
@ -112,7 +112,7 @@ void updateTimezone() {
|
||||
break;
|
||||
}
|
||||
case TZ_AUSTRALIA_NORTHERN : {
|
||||
tcrStandard = {First, Sun, Apr, 3, 570}; //ACST = UTC + 9.5 hours
|
||||
tcrDaylight = {First, Sun, Apr, 3, 570}; //ACST = UTC + 9.5 hours
|
||||
tcrStandard = tcrDaylight;
|
||||
break;
|
||||
}
|
||||
|
@ -63,10 +63,9 @@ void _overlayAnalogClock()
|
||||
}
|
||||
if (analogClock5MinuteMarks)
|
||||
{
|
||||
int pix;
|
||||
for (int i = 0; i <= 12; i++)
|
||||
for (byte i = 0; i <= 12; i++)
|
||||
{
|
||||
pix = analogClock12pixel + round((overlaySize / 12.0) *i);
|
||||
int pix = analogClock12pixel + round((overlaySize / 12.0) *i);
|
||||
if (pix > overlayMax) pix -= overlaySize;
|
||||
strip.setPixelColor(pix, 0x00FFAA);
|
||||
}
|
||||
|
@ -19,14 +19,14 @@ uint16_t playlistEntryDur = 0;
|
||||
|
||||
|
||||
void shufflePlaylist() {
|
||||
int currentIndex = playlistLen, randomIndex;
|
||||
int currentIndex = playlistLen;
|
||||
|
||||
PlaylistEntry temporaryValue, *entries = reinterpret_cast<PlaylistEntry*>(playlistEntries);
|
||||
|
||||
// While there remain elements to shuffle...
|
||||
while (currentIndex--) {
|
||||
// Pick a random element...
|
||||
randomIndex = random(0, currentIndex);
|
||||
int randomIndex = random(0, currentIndex);
|
||||
// And swap it with the current element.
|
||||
temporaryValue = entries[currentIndex];
|
||||
entries[currentIndex] = entries[randomIndex];
|
||||
|
@ -31,7 +31,7 @@ bool isAsterisksOnly(const char* str, byte maxLen)
|
||||
void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
|
||||
{
|
||||
|
||||
//0: menu 1: wifi 2: leds 3: ui 4: sync 5: time 6: sec 7: DMX
|
||||
//0: menu 1: wifi 2: leds 3: ui 4: sync 5: time 6: sec 7: DMX 8: usermods
|
||||
if (subPage <1 || subPage >8) return;
|
||||
|
||||
//WIFI SETTINGS
|
||||
@ -84,7 +84,6 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
|
||||
if (btnPin>=0 && pinManager.isPinAllocated(btnPin)) pinManager.deallocatePin(btnPin);
|
||||
|
||||
strip.isRgbw = false;
|
||||
|
||||
uint8_t colorOrder, type, skip;
|
||||
uint16_t length, start;
|
||||
uint8_t pins[5] = {255, 255, 255, 255, 255};
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -220,7 +220,7 @@ void WLED::loop()
|
||||
|
||||
yield();
|
||||
|
||||
if (!offMode)
|
||||
if (!offMode || strip.isOffRefreshRequred)
|
||||
strip.service();
|
||||
#ifdef ESP8266
|
||||
else if (!noWifiSleep)
|
||||
@ -370,7 +370,7 @@ void WLED::setup()
|
||||
updateFSInfo();
|
||||
|
||||
DEBUG_PRINTLN(F("Reading config"));
|
||||
deserializeConfig();
|
||||
deserializeConfigFromFS();
|
||||
/*
|
||||
#if STATUSLED
|
||||
bool lStatusLed = false;
|
||||
|
@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
// version code in format yymmddb (b = daily build)
|
||||
#define VERSION 2105161
|
||||
#define VERSION 2105171
|
||||
|
||||
//uncomment this if you have a "my_config.h" file you'd like to use
|
||||
//#define WLED_USE_MY_CONFIG
|
||||
|
@ -321,7 +321,7 @@ void loadSettingsFromEEPROM()
|
||||
notifyMacro = EEPROM.read(2201);
|
||||
|
||||
strip.rgbwMode = EEPROM.read(2203);
|
||||
//was skipFirstLed = EEPROM.read(2204);
|
||||
//skipFirstLed = EEPROM.read(2204);
|
||||
|
||||
if (EEPROM.read(2210) || EEPROM.read(2211) || EEPROM.read(2212))
|
||||
{
|
||||
|
@ -85,6 +85,7 @@ void initServer()
|
||||
AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/json", [](AsyncWebServerRequest *request) {
|
||||
bool verboseResponse = false;
|
||||
uint8_t vAPI = 1;
|
||||
bool isConfig = false;
|
||||
{ //scope JsonDocument so it releases its buffer
|
||||
DynamicJsonDocument jsonBuffer(JSON_BUFFER_SIZE);
|
||||
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject));
|
||||
@ -92,6 +93,9 @@ void initServer()
|
||||
if (error || root.isNull()) {
|
||||
request->send(400, "application/json", F("{\"error\":9}")); return;
|
||||
}
|
||||
const String& url = request->url();
|
||||
isConfig = url.indexOf("cfg") > -1;
|
||||
if (!isConfig) {
|
||||
if (root.containsKey("rev"))
|
||||
{
|
||||
vAPI = root["rev"] | 1;
|
||||
@ -99,9 +103,16 @@ void initServer()
|
||||
fileDoc = &jsonBuffer; // used for applying presets (presets.cpp)
|
||||
verboseResponse = deserializeState(root);
|
||||
fileDoc = nullptr;
|
||||
} else {
|
||||
verboseResponse = deserializeConfig(root); //use verboseResponse to determine whether cfg change should be saved immediately
|
||||
}
|
||||
}
|
||||
if (verboseResponse) {
|
||||
if (!isConfig) {
|
||||
serveJson(request,vAPI); return; //if JSON contains "v"
|
||||
} else {
|
||||
serializeConfig(); //Save new settings to FS
|
||||
}
|
||||
if (verboseResponse) { //if JSON contains "v"
|
||||
serveJson(request,vAPI); return;
|
||||
}
|
||||
request->send(200, "application/json", F("{\"success\":true}"));
|
||||
});
|
||||
|
@ -185,7 +185,7 @@ void getSettingsJS(byte subPage, char* dest)
|
||||
obuf = dest;
|
||||
olen = 0;
|
||||
|
||||
if (subPage <1 || subPage >7) return;
|
||||
if (subPage <1 || subPage >8) return;
|
||||
|
||||
if (subPage == 1)
|
||||
{
|
||||
@ -361,7 +361,7 @@ void getSettingsJS(byte subPage, char* dest)
|
||||
sappend('v',co,bus->getColorOrder());
|
||||
sappend('v',ls,bus->getStart());
|
||||
sappend('c',cv,bus->reversed);
|
||||
sappend('c',sl,bus->skipFirstLed());
|
||||
sappend('c',sl,bus->skippedLeds());
|
||||
// sappend('c',ew,bus->isRgbw());
|
||||
}
|
||||
sappend('v',SET_F("MA"),strip.ablMilliampsMax);
|
||||
@ -579,5 +579,13 @@ void getSettingsJS(byte subPage, char* dest)
|
||||
sappend('i',SET_F("CH15"),DMXFixtureMap[14]);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (subPage == 8) //usermods
|
||||
{
|
||||
oappend(SET_F("numM="));
|
||||
oappendi(usermods.getModCount());
|
||||
oappend(";");
|
||||
}
|
||||
|
||||
oappend(SET_F("}</script>"));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user