Commit bbc7a4dc authored by luz's avatar luz

LedChain devices: segments with soft edges can be defined on a WS281x chain,...

LedChain devices: segments with soft edges can be defined on a WS281x chain, each segment becomes a color dS light
parent b71c0e67
......@@ -36,6 +36,13 @@ else
vdcd_DEBUG =
endif
if RASPBERRYPI
vdcd_PLATFORM = -D RASPBERRYPI=1
else
vdcd_PLATFORM =
endif
nodist_vdcd_SOURCES = $(PROTOBUF_GENERATED)
......@@ -44,6 +51,26 @@ if RASPBERRYPI
# Note: the entire library situation is ugly, as toolchain is not complete and autoconf lib macros dont work.
TOOLCHAIN_SYSROOT = /Volumes/xtools/arm-none-linux-gnueabi/arm-none-linux-gnueabi/sysroot
vdcd_LDADD = $(PTHREAD_LIBS) -lmongoose -lprotobuf-c -lsqlite3 -ljson-c -ldl -L${TOOLCHAIN_SYSROOT}/${libdir}/arm-linux-gnueabihf -lcrypto -lz
# sources only supported on RPI
LEDCHAIN_SRC = \
src/thirdparty/rpi_ws281x/clk.h \
src/thirdparty/rpi_ws281x/gpio.h \
src/thirdparty/rpi_ws281x/dma.h \
src/thirdparty/rpi_ws281x/dma.c \
src/thirdparty/rpi_ws281x/pwm.h \
src/thirdparty/rpi_ws281x/pwm.c \
src/thirdparty/rpi_ws281x/ws2811.h \
src/thirdparty/rpi_ws281x/ws2811.c \
src/deviceclasses/ledchain/ws281xcomm.hpp \
src/deviceclasses/ledchain/ws281xcomm.cpp \
src/deviceclasses/ledchain/ledchaindevice.hpp \
src/deviceclasses/ledchain/ledchaindevice.cpp \
src/deviceclasses/ledchain/ledchaindevicecontainer.hpp \
src/deviceclasses/ledchain/ledchaindevicecontainer.cpp
LEDCHAIN_INCLUDES = \
-I ${srcdir}/src/deviceclasses/ledchain \
-I ${srcdir}/src/thirdparty/rpi_ws281x
else
# automatic libs does not work right now due to commented out checks in autoconf.ac, so specify -l directly
# vdcd_LDADD = $(JSONC_LIBS) $(PTHREAD_LIBS) $(SQLITE3_LIBS) $(PROTOBUFC_LIBS)
......@@ -65,17 +92,20 @@ vdcd_CXXFLAGS = \
-I ${srcdir}/src/deviceclasses/enocean \
-I ${srcdir}/src/deviceclasses/dali \
-I ${srcdir}/src/deviceclasses/hue \
${LEDCHAIN_INCLUDES} \
${BOOST_CPPFLAGS} \
${JSONC_CFLAGS} \
${PTHREAD_CFLAGS} \
${SQLITE3_CFLAGS} \
${PROTOBUFC_CFLAGS} \
${vdcd_DEBUG} \
${vdcd_PLATFORM} \
-D DISABLE_OLA=1
vdcd_SOURCES = \
${MONGOOSE_SRC} \
${LEDCHAIN_SRC} \
src/p44utils/p44obj.cpp \
src/p44utils/p44obj.hpp \
src/p44utils/application.cpp \
......@@ -251,12 +281,14 @@ olavdcd_CXXFLAGS = \
-I ${srcdir}/src/deviceclasses/dali \
-I ${srcdir}/src/deviceclasses/hue \
-I ${srcdir}/src/deviceclasses/ola \
${LEDCHAIN_INCLUDES} \
${BOOST_CPPFLAGS} \
${JSONC_CFLAGS} \
${PTHREAD_CFLAGS} \
${SQLITE3_CFLAGS} \
${PROTOBUFC_CFLAGS} \
${vdcd_DEBUG} \
${vdcd_PLATFORM} \
-D DISABLE_OLA=0
olavdcd_SOURCES = \
......
//
// Copyright (c) 2015 plan44.ch / Lukas Zeller, Zurich, Switzerland
//
// Author: Lukas Zeller <luz@plan44.ch>
//
// This file is part of vdcd.
//
// vdcd is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// vdcd is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with vdcd. If not, see <http://www.gnu.org/licenses/>.
//
#include "ledchaindevice.hpp"
#if !DISABLE_LEDCHAIN
#include "lightbehaviour.hpp"
#include "colorlightbehaviour.hpp"
using namespace p44;
#pragma mark - LedChainDevice
LedChainDevice::LedChainDevice(LedChainDeviceContainer *aClassContainerP, uint16_t aFirstLED, uint16_t aNumLEDs, const string &aDeviceConfig) :
inherited(aClassContainerP),
firstLED(aFirstLED),
numLEDs(aNumLEDs),
transitionTicket(0),
startSoftEdge(0),
endSoftEdge(0),
r(0), g(0), b(0)
{
// type:config_for_type
// Where:
// with type=segment
// config=b:e
// b:0..n size of softedge at beginning
// e:0..n size of softedge at end
// evaluate config
string config = aDeviceConfig;
string mode, s;
size_t i = config.find(":");
ledchainType = ledchain_unknown;
bool configOK = false;
if (i!=string::npos) {
mode = config.substr(0,i);
config.erase(0,i+1);
}
if (mode=="segment") {
ledchainType = ledchain_softsegment;
i = config.find(":");
if (i!=string::npos) {
s = config.substr(0,i);
config.erase(0,i+1);
if (sscanf(s.c_str(), "%hd", &startSoftEdge)==1) {
if (sscanf(config.c_str(), "%hd", &endSoftEdge)==1) {
// complete config
if (startSoftEdge+endSoftEdge<=numLEDs) {
// correct config
configOK = true;
}
}
}
}
}
if (!configOK) {
LOG(LOG_ERR,"invalid LedChain device config: %s\n", aDeviceConfig.c_str());
}
// by default, act as black device so we can configure colors
primaryGroup = group_black_joker;
// - is RGB
primaryGroup = group_yellow_light;
// just color light settings, which include a color scene table
installSettings(DeviceSettingsPtr(new ColorLightDeviceSettings(*this)));
// - add multi-channel color light behaviour (which adds a number of auxiliary channels)
RGBColorLightBehaviourPtr l = RGBColorLightBehaviourPtr(new RGBColorLightBehaviour(*this));
addBehaviour(l);
// - create dSUID
deriveDsUid();
}
bool LedChainDevice::isSoftwareDisconnectable()
{
return true; // these are always software disconnectable
}
LedChainDeviceContainer &LedChainDevice::getLedChainDeviceContainer()
{
return *(static_cast<LedChainDeviceContainer *>(classContainerP));
}
void LedChainDevice::disconnect(bool aForgetParams, DisconnectCB aDisconnectResultHandler)
{
// clear learn-in data from DB
if (ledChainDeviceRowID) {
getLedChainDeviceContainer().db.executef("DELETE FROM devConfigs WHERE rowid=%d", ledChainDeviceRowID);
}
// disconnection is immediate, so we can call inherited right now
inherited::disconnect(aForgetParams, aDisconnectResultHandler);
}
#define TRANSITION_STEP_TIME (10*MilliSecond)
void LedChainDevice::applyChannelValues(SimpleCB aDoneCB, bool aForDimming)
{
MLMicroSeconds transitionTime = 0;
// abort previous transition
MainLoop::currentMainLoop().cancelExecutionTicket(transitionTicket);
// full color device
RGBColorLightBehaviourPtr cl = boost::dynamic_pointer_cast<RGBColorLightBehaviour>(output);
if (cl) {
if (needsToApplyChannels()) {
// needs update
// - derive (possibly new) color mode from changed channels
cl->deriveColorMode();
// - calculate and start transition
// TODO: depending to what channel has changed, take transition time from that channel. For now always using brightness transition time
transitionTime = cl->transitionTimeToNewBrightness();
cl->colorTransitionStep(); // init
applyChannelValueSteps(aForDimming, transitionTime==0 ? 1 : (double)TRANSITION_STEP_TIME/transitionTime);
}
// consider applied
cl->appliedColorValues();
}
inherited::applyChannelValues(aDoneCB, aForDimming);
}
void LedChainDevice::applyChannelValueSteps(bool aForDimming, double aStepSize)
{
// RGB, RGBW or RGBWA dimmer
RGBColorLightBehaviourPtr cl = boost::dynamic_pointer_cast<RGBColorLightBehaviour>(output);
// RGB lamp, get components for rendering loop
cl->getRGB(r, g, b, 255); // get brightness per R,G,B channel
// trigger rendering the LEDs soon
getLedChainDeviceContainer().triggerRenderingRange(firstLED, numLEDs);
// next step
if (cl->colorTransitionStep(aStepSize)) {
LOG(LOG_DEBUG,
"Ledchain device %s: transitional values R=%d, G=%d, B=%d\n",
shortDesc().c_str(),
(int)r, (int)g, (int)b
);
// not yet complete, schedule next step
transitionTicket = MainLoop::currentMainLoop().executeOnce(
boost::bind(&LedChainDevice::applyChannelValueSteps, this, aForDimming, aStepSize),
TRANSITION_STEP_TIME
);
return; // will be called later again
}
if (!aForDimming) LOG(LOG_INFO,
"Ledchain device %s: final values R=%d, G=%d, B=%d\n",
shortDesc().c_str(),
(int)r, (int)g, (int)b
);
}
double LedChainDevice::getLEDColor(uint16_t aLedNumber, uint8_t &aRed, uint8_t &aGreen, uint8_t &aBlue)
{
// index relative to beginning of my segment
uint16_t i = aLedNumber-firstLED;
if (i<0 || i>=numLEDs)
return 0; // no color at this point
// color at this point
aRed = r; aGreen = g; aBlue = b;
// for soft edges
if (i>=startSoftEdge && i<=numLEDs-endSoftEdge) {
// not withing soft edge range, full opacity
return 1;
}
else {
if (i<startSoftEdge) {
// zero point is LED *before* first LED!
return 1.0/(startSoftEdge+1)*(i+1);
}
else {
// zero point is LED *after* last LED!
return 1.0/(endSoftEdge+1)*(numLEDs-i);
}
}
}
void LedChainDevice::deriveDsUid()
{
// vDC implementation specific UUID:
// UUIDv5 with name = classcontainerinstanceid::ledchainType:firstLED:lastLED
DsUid vdcNamespace(DSUID_P44VDC_NAMESPACE_UUID);
string s = classContainerP->deviceClassContainerInstanceIdentifier();
string_format_append(s, "%d:%d:%d", ledchainType, firstLED, numLEDs);
dSUID.setNameInSpace(s, vdcNamespace);
}
string LedChainDevice::modelName()
{
if (ledchainType==ledchain_softsegment)
return "LED Chain Segment";
return "LedChain device";
}
bool LedChainDevice::getDeviceIcon(string &aIcon, bool aWithData, const char *aResolutionPrefix)
{
const char *iconName = "rgbchain";
if (iconName && getIcon(iconName, aIcon, aWithData, aResolutionPrefix))
return true;
else
return inherited::getDeviceIcon(aIcon, aWithData, aResolutionPrefix);
}
string LedChainDevice::getExtraInfo()
{
string s;
s = string_format("Led Chain Color Light from LED #%d..%d", firstLED, firstLED+numLEDs-1);
return s;
}
string LedChainDevice::description()
{
string s = inherited::description();
string_format_append(s, "- Led Chain Color Light from LED #%d..%d\n", firstLED, firstLED+numLEDs-1);
return s;
}
#endif // !DISABLE_LEDCHAIN
//
// Copyright (c) 2015 plan44.ch / Lukas Zeller, Zurich, Switzerland
//
// Author: Lukas Zeller <luz@plan44.ch>
//
// This file is part of vdcd.
//
// vdcd is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// vdcd is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with vdcd. If not, see <http://www.gnu.org/licenses/>.
//
#ifndef __vdcd__ledchaindevice__
#define __vdcd__ledchaindevice__
#include "device.hpp"
#if !DISABLE_LEDCHAIN
#include "ledchaindevicecontainer.hpp"
using namespace std;
namespace p44 {
class LedChainDeviceContainer;
class LedChainDevice : public Device
{
typedef Device inherited;
friend class LedChainDeviceContainer;
uint16_t firstLED;
uint16_t numLEDs;
typedef enum {
ledchain_unknown,
ledchain_softsegment,
ledchain_
} LedChainType;
LedChainType ledchainType;
uint16_t startSoftEdge;
uint16_t endSoftEdge;
long long ledChainDeviceRowID; ///< the ROWID this device was created from (0=none)
long transitionTicket;
/// current color values
double r,g,b;
public:
LedChainDevice(LedChainDeviceContainer *aClassContainerP, uint16_t aFirstLED, uint16_t aNumLEDs, const string &aDeviceConfig);
/// device type identifier
/// @return constant identifier for this type of device (one container might contain more than one type)
virtual const char *deviceTypeIdentifier() { return "ledchain"; };
/// @name interaction with subclasses, actually representing physical I/O
/// @{
/// apply all pending channel value updates to the device's hardware
/// @note this is the only routine that should trigger actual changes in output values. It must consult all of the device's
/// ChannelBehaviours and check isChannelUpdatePending(), and send new values to the device hardware. After successfully
/// updating the device hardware, channelValueApplied() must be called on the channels that had isChannelUpdatePending().
/// @param aCompletedCB if not NULL, must be called when values are applied
/// @param aForDimming hint for implementations to optimize dimming, indicating that change is only an increment/decrement
/// in a single channel (and not switching between color modes etc.)
virtual void applyChannelValues(SimpleCB aDoneCB, bool aForDimming);
/// Get color and opacity of light for a specific LED position
/// @param aLedNumber LED position
/// @param aRed will receive red intensity
/// @param aGreen will receive green intensity
/// @param aBlue will receive blue intensity
/// @return opacity of light (0=no light, 1=only this light source, between: weight in mix with other sources)
double getLEDColor(uint16_t aLedNumber, uint8_t &aRed, uint8_t &aGreen, uint8_t &aBlue);
/// @}
LedChainDeviceContainer &getLedChainDeviceContainer();
/// description of object, mainly for debug and logging
/// @return textual description of object
virtual string description();
/// Get icon data or name
/// @param aIcon string to put result into (when method returns true)
/// - if aWithData is set, binary PNG icon data for given resolution prefix is returned
/// - if aWithData is not set, only the icon name (without file extension) is returned
/// @param aWithData if set, PNG data is returned, otherwise only name
/// @return true if there is an icon, false if not
virtual bool getDeviceIcon(string &aIcon, bool aWithData, const char *aResolutionPrefix);
/// Get extra info (plan44 specific) to describe the addressable in more detail
/// @return string, single line extra info describing aspects of the device not visible elsewhere
virtual string getExtraInfo();
/// check if device can be disconnected by software (i.e. Web-UI)
/// @return true if device might be disconnectable by the user via software (i.e. web UI)
/// @note devices returning true here might still refuse disconnection on a case by case basis when
/// operational state does not allow disconnection.
/// @note devices returning false here might still be disconnectable using disconnect() triggered
/// by vDC API "remove" method.
virtual bool isSoftwareDisconnectable();
/// disconnect device. For static device, this means removing the config from the container's DB. Note that command line
/// static devices cannot be disconnected.
/// @param aForgetParams if set, not only the connection to the device is removed, but also all parameters related to it
/// such that in case the same device is re-connected later, it will not use previous configuration settings, but defaults.
/// @param aDisconnectResultHandler will be called to report true if device could be disconnected,
/// false in case it is certain that the device is still connected to this and only this vDC
virtual void disconnect(bool aForgetParams, DisconnectCB aDisconnectResultHandler);
/// @name identification of the addressable entity
/// @{
/// @return human readable model name/short description
virtual string modelName();
/// @return Vendor ID in URN format to identify vendor as uniquely as possible
virtual string vendorId() { return "vendorname:plan44.ch"; };
/// @}
protected:
void deriveDsUid();
private:
virtual void applyChannelValueSteps(bool aForDimming, double aStepSize);
};
typedef boost::intrusive_ptr<LedChainDevice> LedChainDevicePtr;
} // namespace p44
#endif // !DISABLE_LEDCHAIN
#endif /* defined(__vdcd__ledchaindevice__) */
//
// Copyright (c) 2015 plan44.ch / Lukas Zeller, Zurich, Switzerland
//
// Author: Lukas Zeller <luz@plan44.ch>
//
// This file is part of vdcd.
//
// vdcd is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// vdcd is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with vdcd. If not, see <http://www.gnu.org/licenses/>.
//
#include "ledchaindevicecontainer.hpp"
#if !DISABLE_LEDCHAIN
#include "ledchaindevice.hpp"
using namespace p44;
#pragma mark - DB and initialisation
// Version history
// 1 : First version
#define LEDCHAINDEVICES_SCHEMA_MIN_VERSION 1 // minimally supported version, anything older will be deleted
#define LEDCHAINDEVICES_SCHEMA_VERSION 1 // current version
string LedChainDevicePersistence::dbSchemaUpgradeSQL(int aFromVersion, int &aToVersion)
{
string sql;
if (aFromVersion==0) {
// create DB from scratch
// - use standard globs table for schema version
sql = inherited::dbSchemaUpgradeSQL(aFromVersion, aToVersion);
// - create my tables
sql.append(
"CREATE TABLE devConfigs ("
" firstLED INTEGER,"
" numLEDs INTEGER,"
" deviceconfig TEXT"
");"
);
// reached final version in one step
aToVersion = LEDCHAINDEVICES_SCHEMA_VERSION;
}
return sql;
}
LedChainDeviceContainer::LedChainDeviceContainer(int aInstanceNumber, int aNumLedsInChain, DeviceContainer *aDeviceContainerP, int aTag) :
DeviceClassContainer(aInstanceNumber, aDeviceContainerP, aTag),
numLedsInChain(aNumLedsInChain),
renderStart(0),
renderEnd(0),
renderTicket(0)
{
}
void LedChainDeviceContainer::initialize(StatusCB aCompletedCB, bool aFactoryReset)
{
ErrorPtr err;
// initialize database
string databaseName = getPersistentDataDir();
string_format_append(databaseName, "%s_%d.sqlite3", deviceClassIdentifier(), getInstanceNumber());
err = db.connectAndInitialize(databaseName.c_str(), LEDCHAINDEVICES_SCHEMA_VERSION, LEDCHAINDEVICES_SCHEMA_MIN_VERSION, aFactoryReset);
// Initialize chain driver
ws281xcomm = WS281xCommPtr(new WS281xComm(numLedsInChain));
ws281xcomm->begin();
// trigger a full chain rendering
triggerRenderingRange(0, numLedsInChain);
// done
aCompletedCB(ErrorPtr());
}
//// Input a value 0 to 255 to get a color value.
//// The colours are a transition r - g - b - back to r.
//static void wheel(uint8_t WheelPos, uint8_t &red, uint8_t &green, uint8_t &blue)
//{
// if(WheelPos < 85) {
// red = WheelPos * 3;
// green = 255 - WheelPos * 3;
// blue = 0;
// } else if(WheelPos < 170) {
// WheelPos -= 85;
// red = 255 - WheelPos * 3;
// green = 0;
// blue = WheelPos * 3;
// } else {
// WheelPos -= 170;
// red = 0;
// green = WheelPos * 3;
// blue = 255 - WheelPos * 3;
// }
//}
//
//
//static int cnt=0;
//
//void LedChainDeviceContainer::render()
//{
// cnt++;
// uint8_t r,g,b;
// for(int i=0; i<ws281xcomm->getNumLeds(); i++) {
// wheel(((i * 256 / ws281xcomm->getNumLeds()) + cnt) & 255, r, g, b);
// ws281xcomm->setColorDimmed(i, r, g, b, 128); // only half brightness for full area color
// }
// ws281xcomm->show();
// MainLoop::currentMainLoop().executeOnce(boost::bind(&LedChainDeviceContainer::render, this), 200*MilliSecond);
//}
#define MIN_RENDER_INTERVAL (5*MilliSecond)
void LedChainDeviceContainer::triggerRenderingRange(uint16_t aFirst, uint16_t aNum)
{
if (!renderTicket) {
// no rendering pending, initialize range
renderStart = aFirst;
renderEnd = aFirst+aNum;
}
else {
// enlarge range
if (aFirst<renderStart) renderStart = aFirst;
if (aFirst+aNum>renderEnd) renderEnd = aFirst+aNum;
}
if (!renderTicket) {
renderTicket = MainLoop::currentMainLoop().executeOnce(boost::bind(&LedChainDeviceContainer::render, this), MIN_RENDER_INTERVAL);
}
}
static inline void increase(uint8_t &aByte, uint8_t aAmount, uint8_t aMax = 255)
{
uint16_t r = aByte+aAmount;
if (r>aMax)
aByte = aMax;
else
aByte = (uint8_t)r;
}
void LedChainDeviceContainer::render()
{
renderTicket = 0; // done
for (uint16_t i=renderStart; i<renderEnd; i++) {
// TODO: optimize asking only devices active in this range
// for now, just ask all
uint8_t r,g,b;
uint8_t rv=0,gv=0,bv=0; // composed
for (LedChainDeviceList::iterator pos = sortedSegments.begin(); pos!=sortedSegments.end(); ++pos) {
double opacity = (*pos)->getLEDColor(i, r, g, b);
if (opacity>0) {
increase(rv, opacity*r);
increase(gv, opacity*g);
increase(bv, opacity*b);
}
}
ws281xcomm->setColorDimmed(i, rv, gv, bv, 128); // only half brightness for full area color
}
// transfer to hardware
ws281xcomm->show();
}
bool LedChainDeviceContainer::getDeviceIcon(string &aIcon, bool aWithData, const char *aResolutionPrefix)
{
if (getIcon("vdc_rgbchain", aIcon, aWithData, aResolutionPrefix))
return true;
else
return inherited::getDeviceIcon(aIcon, aWithData, aResolutionPrefix);
}
// device class name
const char *LedChainDeviceContainer::deviceClassIdentifier() const
{
return "LedChain_Device_Container";
}
// Binary predicate that, taking two values of the same type of those contained in the list,
// returns true if the first argument goes before the second argument in the strict weak ordering
// it defines, and false otherwise.
bool LedChainDeviceContainer::segmentCompare(LedChainDevicePtr aFirst, LedChainDevicePtr aSecond)
{
return aFirst->firstLED < aSecond->firstLED;
}
LedChainDevicePtr LedChainDeviceContainer::addLedChainDevice(uint16_t aFirstLED, uint16_t aNumLEDs, string aDeviceConfig)
{
LedChainDevicePtr newDev;
newDev = LedChainDevicePtr(new LedChainDevice(this, aFirstLED, aNumLEDs, aDeviceConfig));
// add to container if device was created
if (newDev) {
// add to container
addDevice(newDev);
// add to my list and sort
sortedSegments.push_back(newDev);
sortedSegments.sort(segmentCompare);
return boost::dynamic_pointer_cast<LedChainDevice>(newDev);
}
// none added
return LedChainDevicePtr();
}
void LedChainDeviceContainer::removeDevice(DevicePtr aDevice, bool aForget)
{
LedChainDevicePtr dev = boost::dynamic_pointer_cast<LedChainDevice>(aDevice);
if (dev) {
// - remove single device from superclass
inherited::removeDevice(aDevice, aForget);
// - remove device from sorted segments list
for (LedChainDeviceList::iterator pos = sortedSegments.begin(); pos!=sortedSegments.end(); ++pos) {
if (*pos==aDevice) {
sortedSegments.erase(pos);
triggerRenderingRange(0,numLedsInChain); // fully re-render to remove deleted light immediately
break;
}
}
}
}
void LedChainDeviceContainer::collectDevices(StatusCB aCompletedCB, bool aIncremental, bool aExhaustive, bool aClearSettings)
{
// incrementally collecting static devices makes no sense. The devices are "static"!
if (!aIncremental) {