Added GetRange/SetRange functionality (untested)

Fixed custom mode on frontend
This commit is contained in:
Mark van Renswoude 2017-03-27 22:24:05 +02:00
parent 4fbbee8f5b
commit 8fa9b63dfc
7 changed files with 163 additions and 20 deletions

View File

@ -21,6 +21,8 @@ The first byte of a request is the command. Further data depends on the command.
| 0x01 | Ping |
| 0x03 | GetMode |
| 0x04 | SetMode |
| 0x05 | GetRange |
| 0x06 | SetRange |
Response
@ -73,6 +75,32 @@ Input parameters:<br>
Output parameters:<br>
_Same as input parameters_
### GetRange
Gets the current range configuration. Each step has it's own parameters which adjust the PWM curve, which can be used to compensate for the differences in LED strips or signal strength.
The ranges are stored on the ESP8266's filesystem and will be restored after a power loss.
Input parameters:<br>
_none_
Output parameters:<br>
*useScaling* (byte): 0 (off) or 1 (on), default 1. If enabled, the brightness value will be converted to a PWM value using an exponential function. If disabled, the brightness is used as is (but still accounting for rangeStart/rangeEnd).
_repeated for each step:_<br>
*rangeStart* (word): value in range 0 (off) to 4095 (fully on), default 0. Determines the minimum PWM on value for a brightness of 1.
*rangeEnd* (word): value in range 0 (off) to 4095 (fully on), default 4094. Determines the maximum PWM on value for a brightness of 4094.
### SetRange
Sets the current range configuration.
Input parameters:<br>
_See GetRange_
Output parameters:<br>
_Same as input parameters_
Modes
=====

View File

@ -89,10 +89,10 @@ uint32_t currentTime;
// command with the largest parameter list, there is no overflow
// checking in the mode classes!
//
// At the time of writing, CustomMode is the largest:
// Set Mode (1) + Custom (1) + StepCount * Word (2)
// = 30 for 14 steps
uint8_t packet[50];
// At the time of writing, Get/SetRange is the largest:
// Set Range (1) + UseScaling (1) + StepCount * 2x Word (4)
// = 66 for the maximum of 16 steps
uint8_t packet[100];
uint8_t* packetRef;
void loop()

View File

@ -12,6 +12,8 @@ class Command
static const uint8_t Reply = 0x02;
static const uint8_t GetMode = 0x03;
static const uint8_t SetMode = 0x04;
static const uint8_t GetRange = 0x05;
static const uint8_t SetRange = 0x06;
};

View File

@ -1,14 +1,26 @@
#include <Math.h>
#include <FS.h>
#include "stairs.h"
#include "config.h"
static const float brightnessFactor = ((IStairs::On - IStairs::Off) + 1) * log10(2) / log10(PCA9685::On);
const static float factorBase = log10(2) / log10(PCA9685::On);
struct Header
{
uint8_t version;
uint8_t rangeCount;
bool useScaling;
};
void Stairs::init(PCA9685* pwmDriver)
{
this->pwmDriver = pwmDriver;
SPIFFS.begin();
this->readRange();
}
@ -20,22 +32,95 @@ uint8_t Stairs::getCount()
void Stairs::set(uint8_t step, uint16_t brightness)
{
pwmDriver->setPWM(step, this->getPWMValue(brightness));
pwmDriver->setPWM(step, this->getPWMValue(step, brightness));
}
void Stairs::setAll(uint16_t brightness)
{
pwmDriver->setAll(this->getPWMValue(brightness));
//pwmDriver->setAll(this->getPWMValue(brightness));
for (uint8_t step = 0; step < StepCount; step++)
pwmDriver->setPWM(step, this->getPWMValue(step, brightness));
}
uint16_t Stairs::getPWMValue(uint16_t brightness)
uint16_t Stairs::getPWMValue(uint8_t step, uint16_t brightness)
{
if (brightness == IStairs::Off || brightness == IStairs::On)
return brightness;
// Correct for the exponential perception of brightness
// Source: https://diarmuid.ie/blog/pwm-exponential-led-fading-on-arduino-or-other-platforms/
return pow(2, (brightness / brightnessFactor)) - 1;
if (step < 0 || step >= StepCount)
return brightness;
Range* range = this->ranges[step];
if (this->useScaling)
{
float factor = ((range->end - range->start) + 1) * factorBase;
brightness = pow(2, (brightness / factor)) - 1 + range->start;
}
else
{
if (brightness < range->start) brightness = range->start;
if (brightness > range->end) brightness = range->end;
}
return brightness;
}
void Stairs::getRange(Stream* stream)
{
stream->write(this->useScaling ? 1 : 0);
stream->write(reinterpret_cast<uint8_t*>(&this->ranges), sizeof(this->ranges));
}
void Stairs::setRange(uint8_t* data)
{
this->useScaling = *data;
data++;
memcpy(this->ranges, data, sizeof(this->ranges));
}
void Stairs::readRange()
{
File f = SPIFFS.open("/range", "r");
if (!f)
return;
if (!f.available())
return;
Header header;
f.readBytes(reinterpret_cast<char*>(&header), sizeof(Header));
if (header.version != 1)
return;
this->useScaling = (header.useScaling == 1);
memset(this->ranges, 0, sizeof(this->ranges));
f.readBytes(reinterpret_cast<char*>(&this->ranges), header.rangeCount * sizeof(Range));
f.close();
}
void Stairs::writeRange()
{
File f = SPIFFS.open("/range", "w");
if (!f)
return;
Header header;
header.version = 1;
header.useScaling = this->useScaling;
header.rangeCount = StepCount;
f.write(reinterpret_cast<uint8_t*>(&header), sizeof(Header));
f.write(reinterpret_cast<uint8_t*>(&this->ranges), sizeof(this->ranges));
f.close();
}

View File

@ -3,14 +3,29 @@
#include "components/PCA9685.h"
#include "modes/base.h"
#include "config.h"
struct Range
{
uint16_t start;
uint16_t end;
};
class Stairs : public IStairs
{
private:
PCA9685* pwmDriver;
bool useScaling;
Range* ranges[StepCount];
protected:
uint16_t getPWMValue(uint16_t brightness);
void readRange();
void writeRange();
uint16_t getPWMValue(uint8_t step, uint16_t brightness);
public:
void init(PCA9685* pwmDriver);
@ -18,6 +33,9 @@ class Stairs : public IStairs
uint8_t getCount();
void set(uint8_t step, uint16_t brightness);
void setAll(uint16_t brightness);
void getRange(Stream* stream);
void setRange(uint8_t* data);
};
#endif

View File

@ -31,7 +31,7 @@
<div class="parameters" data-bind="visible: mode() == 'Static'" style="display: none">
<div class="header">Static parameters</div>
<div class="parameter">
Brightness: <input type="range" min="0" max="4095" data-bind="value: static.brightness, valueUpdate: 'input'" />
Brightness: <input type="range" min="0" max="4095" data-bind="value: static.brightness, valueUpdate: 'input'" /> <input type="number" min="0" max="4095" data-bind="value: static.brightness" />
</div>
</div>
@ -39,7 +39,7 @@
<div class="header">Custom parameters</div>
<!-- ko foreach: custom.brightness -->
<div class="parameter">
Step <span data-bind="text: $root.custom.brightness().length - $index()"></span>: <input type="range" min="0" max="4095" data-bind="value: $data, valueUpdate: 'input'" />
Step <span data-bind="text: $root.custom.brightness().length - $index()"></span>: <input type="range" min="0" max="4095" data-bind="value: $data.value, valueUpdate: 'input'" /> <input type="number" min="0" max="4095" data-bind="value: $data.value" />
</div>
<!-- /ko -->
</div>
@ -50,14 +50,14 @@
Interval: <input type="number" data-bind="value: alternate.interval" />
</div>
<div class="parameter">
Brightness: <input type="range" min="0" max="4095" data-bind="value: alternate.brightness, valueUpdate: 'input'" />
Brightness: <input type="range" min="0" max="4095" data-bind="value: alternate.brightness, valueUpdate: 'input'" /> <input type="number" min="0" max="4095" data-bind="value: alternate.brightness" />
</div>
</div>
<div class="parameters" data-bind="visible: mode() == 'Slide'" style="display: none">
<div class="header">Sliding parameters</div>
<div class="parameter">
Brightness: <input type="range" min="0" max="4095" data-bind="value: slide.brightness, valueUpdate: 'input'" />
Brightness: <input type="range" min="0" max="4095" data-bind="value: slide.brightness, valueUpdate: 'input'" /> <input type="number" min="0" max="4095" data-bind="value: slide.brightness" />
</div>
</div>
</div>

View File

@ -44,7 +44,9 @@ var StairsViewModel = function()
break;
case 'Custom':
url += '?brightness=' + encodeURIComponent(self.custom.brightness().map(function(value) { return value(); }).join());
var values = self.custom.brightness().map(function(item) { return item.value(); });
values.reverse();
url += '?brightness=' + encodeURIComponent(values.join());
break;
case 'Alternate':
@ -104,12 +106,20 @@ var StairsViewModel = function()
{
self.updatingFromServer = true;
// Initialize the 'Custom' values based on the step count
// Initialize the 'Custom' values based on the step count.
// Each item is an object containing an observable; we can't
// add the observable directly otherwise the foreach template
// will unwrap it before we can attach it to the range input.
var values = [];
for (var index = 0; index < data.stepCount; index++)
values.push(ko.observable(0));
values.push({ value: ko.observable(0) });
self.custom.brightness(values);
// TODO get current mode
// TODO get range settings
// TODO allow tweaking of range settings
self.loading(false);
self.updatingFromServer = false;