Skip to content
Open
Show file tree
Hide file tree
Changes from 10 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
1 change: 1 addition & 0 deletions docs/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1089,6 +1089,7 @@ components:
- "rgb_cct"
- "rgb"
- "fut089"
- "fut100"
- "fut091"
- "fut020"
example: rgb_cct
Expand Down
1 change: 1 addition & 0 deletions lib/MQTT/HomeAssistantDiscoveryClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ void HomeAssistantDiscoveryClient::addConfig(const char* alias, const BulbId& bu
// These bulbs support switching between rgb/white, and have a "white_mode" command
switch (bulbId.deviceType) {
case REMOTE_TYPE_FUT089:
case REMOTE_TYPE_FUT100:
case REMOTE_TYPE_RGB_CCT:
case REMOTE_TYPE_RGBW:
effects.add("white_mode");
Expand Down
166 changes: 166 additions & 0 deletions lib/MiLight/FUT100PacketFormatter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
#include <FUT100PacketFormatter.h>
#include <V2RFEncoding.h>
#include <Units.h>
#include <MiLightCommands.h>

void FUT100PacketFormatter::updateStatus(MiLightStatus status, uint8_t groupId) {
this->groupId = groupId;

if (groupId <= 8) {
// Groups 0-8: Standard V2 logic
uint8_t arg = groupId + (status == OFF ? 9 : 0);
command(FUT100_ON_G_LOW, arg);
} else {
// Groups 9-100: Extended FUT100 logic
uint8_t arg = ((groupId - 9) * 2) + (status == ON ? 0 : 1);
command(FUT100_ON_G_HIGH, arg);
}
}

void FUT100PacketFormatter::modeSpeedDown() {
command(FUT100_ON_G_LOW, FUT100_MODE_SPEED_DOWN);
}

void FUT100PacketFormatter::modeSpeedUp() {
command(FUT100_ON_G_LOW, FUT100_MODE_SPEED_UP);
}

void FUT100PacketFormatter::updateMode(uint8_t mode) {
command(FUT100_MODE, mode);
}

void FUT100PacketFormatter::updateBrightness(uint8_t brightness) {
command(FUT100_BRIGHTNESS, brightness);
}

void FUT100PacketFormatter::updateHue(uint16_t value) {
uint8_t remapped = Units::rescale(value, 255, 360);
updateColorRaw(remapped);
}

void FUT100PacketFormatter::updateColorRaw(uint8_t value) {
command(FUT100_COLOR, value);
}

void FUT100PacketFormatter::updateTemperature(uint8_t value) {
const GroupState* ourState = this->stateStore->get(this->deviceId, this->groupId, REMOTE_TYPE_FUT100);
BulbMode originalBulbMode = BulbMode::BULB_MODE_WHITE;

if (ourState != NULL) {
originalBulbMode = ourState->getBulbMode();
if (originalBulbMode != BulbMode::BULB_MODE_WHITE) {
updateColorWhite();
}
}

command(FUT100_KELVIN, 100 - value);

if (ourState != NULL && (settings->enableAutomaticModeSwitching) && (originalBulbMode != BulbMode::BULB_MODE_WHITE)) {
switchMode(*ourState, originalBulbMode);
}
}

void FUT100PacketFormatter::updateSaturation(uint8_t value) {
const GroupState* ourState = this->stateStore->get(this->deviceId, this->groupId, REMOTE_TYPE_FUT100);
BulbMode originalBulbMode = BulbMode::BULB_MODE_WHITE;

if (ourState != NULL) {
originalBulbMode = ourState->getBulbMode();
}

if (ourState != NULL && (settings->enableAutomaticModeSwitching) && (originalBulbMode != BulbMode::BULB_MODE_COLOR)) {
updateHue(ourState->getHue());
}

command(FUT100_SATURATION, 100 - value);

if (ourState != NULL && (settings->enableAutomaticModeSwitching) && (originalBulbMode != BulbMode::BULB_MODE_COLOR)) {
switchMode(*ourState, originalBulbMode);
}
}

void FUT100PacketFormatter::updateColorWhite() {
command(FUT100_ON_G_LOW, FUT100_WHITE_MODE);
}

void FUT100PacketFormatter::enableNightMode() {
if (groupId <= 8) {
uint8_t arg = groupId + 9;
command(FUT100_ON_G_LOW | 0x80, arg);
} else {
uint8_t arg = ((groupId - 9) * 2) + 1;
command(FUT100_ON_G_HIGH | 0x80, arg);
}
}

BulbId FUT100PacketFormatter::parsePacket(const uint8_t *packet, JsonObject result) {
if (stateStore == NULL) {
Serial.println(F("ERROR: stateStore not set. Prepare was not called! **THIS IS A BUG**"));
BulbId fakeId(0, 0, REMOTE_TYPE_FUT100);
return fakeId;
}

uint8_t packetCopy[V2_PACKET_LEN];
memcpy(packetCopy, packet, V2_PACKET_LEN);
V2RFEncoding::decodeV2Packet(packetCopy);
Comment thread
KaiFassnacht marked this conversation as resolved.

BulbId bulbId(
(packetCopy[2] << 8) | packetCopy[3],
packetCopy[7],
REMOTE_TYPE_FUT100
);

uint8_t rawCmd = packetCopy[V2_COMMAND_INDEX];
uint8_t cmd = (rawCmd & 0x7F);
uint8_t arg = packetCopy[V2_ARGUMENT_INDEX];
bool isNightMode = (rawCmd & 0x80) == 0x80;

// Groups 0-8 logic
if (cmd == FUT100_ON_G_LOW) {
if (isNightMode) {
result[GroupStateFieldNames::COMMAND] = MiLightCommandNames::NIGHT_MODE;
if (arg >= 9 && arg <= 17) bulbId.groupId = arg - 9;
} else if (arg == FUT100_MODE_SPEED_DOWN) {
result[GroupStateFieldNames::COMMAND] = MiLightCommandNames::MODE_SPEED_DOWN;
} else if (arg == FUT100_MODE_SPEED_UP) {
result[GroupStateFieldNames::COMMAND] = MiLightCommandNames::MODE_SPEED_UP;
} else if (arg == FUT100_WHITE_MODE) {
result[GroupStateFieldNames::COMMAND] = MiLightCommandNames::SET_WHITE;
} else if (arg <= 8) {
result[GroupStateFieldNames::STATE] = "ON";
bulbId.groupId = arg;
} else if (arg >= 9 && arg <= 17) {
result[GroupStateFieldNames::STATE] = "OFF";
bulbId.groupId = arg - 9;
}
}
// Groups 9-100 logic
else if (cmd == FUT100_ON_G_HIGH) {
bulbId.groupId = (arg / 2) + 9;
Comment thread
cursor[bot] marked this conversation as resolved.
if (isNightMode) {
result[GroupStateFieldNames::COMMAND] = MiLightCommandNames::NIGHT_MODE;
} else {
result[GroupStateFieldNames::STATE] = ((arg % 2) == 0) ? "ON" : "OFF";
}
}
else if (cmd == FUT100_COLOR) {
result[GroupStateFieldNames::HUE] = Units::rescale<uint16_t, uint16_t>(arg, 360, 255.0);
} else if (cmd == FUT100_BRIGHTNESS) {
result[GroupStateFieldNames::BRIGHTNESS] = Units::rescale<uint8_t, uint8_t>(constrain(arg, 0, 100), 255, 100);
} else if (cmd == FUT100_KELVIN) {
const GroupState* state = stateStore->get(bulbId);
if (state != NULL && state->getBulbMode() == BULB_MODE_COLOR) {
result[GroupStateFieldNames::SATURATION] = 100 - constrain(arg, 0, 100);
} else {
result[GroupStateFieldNames::COLOR_TEMP] = Units::whiteValToMireds(100 - arg, 100);
}
} else if (cmd == FUT100_MODE) {
result[GroupStateFieldNames::MODE] = arg;
} else {
// Fallback for unknown buttons
result["button_id"] = cmd;
result["argument"] = arg;
}

return bulbId;
}
45 changes: 45 additions & 0 deletions lib/MiLight/FUT100PacketFormatter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#ifndef _FUT100_PACKET_FORMATTER_H
#define _FUT100_PACKET_FORMATTER_H

#include <V2PacketFormatter.h>


enum MiLightFUT100Command {
FUT100_ON_G_LOW = 0x01,
FUT100_COLOR = 0x02,
FUT100_BRIGHTNESS = 0x05,
FUT100_MODE = 0x06,
FUT100_KELVIN = 0x07,
FUT100_SATURATION = 0x07,
FUT100_ON_G_HIGH = 0x08
};

enum MiLightFUT100Arguments {
FUT100_MODE_SPEED_UP = 0x12,
FUT100_MODE_SPEED_DOWN = 0x13,
FUT100_WHITE_MODE = 0x14
};

class FUT100PacketFormatter : public V2PacketFormatter {
public:
FUT100PacketFormatter()
: V2PacketFormatter(REMOTE_TYPE_FUT100, 0x25, 100)
{ }

virtual void updateStatus(MiLightStatus status, uint8_t groupId) override;
virtual void updateBrightness(uint8_t value) override;
virtual void updateHue(uint16_t value) override;
virtual void updateColorRaw(uint8_t value) override;
virtual void updateColorWhite() override;
virtual void updateTemperature(uint8_t value) override;
virtual void updateSaturation(uint8_t value) override;
virtual void enableNightMode() override;

virtual void modeSpeedDown() override;
virtual void modeSpeedUp() override;
virtual void updateMode(uint8_t mode) override;

virtual BulbId parsePacket(const uint8_t* packet, JsonObject result) override;
};

#endif
21 changes: 19 additions & 2 deletions lib/MiLight/MiLightRemoteConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,18 @@ const MiLightRemoteConfig* MiLightRemoteConfig::ALL_REMOTES[] = {
&FUT007Config, // cct
&FUT092Config, // rgb+cct
&FUT098Config, // rgb
&FUT089Config, // 8-group rgb+cct (b8, fut089)
#ifdef USE_FUT100
&FUT100Config, // 100-group rgb+cct (fut100)
#else
&FUT089Config,
#endif
&FUT091Config,
&FUT020Config
&FUT020Config,
#ifdef USE_FUT100
&FUT089Config
#else
&FUT100Config
#endif
};

const size_t MiLightRemoteConfig::NUM_REMOTES = size(ALL_REMOTES);
Expand Down Expand Up @@ -91,6 +100,14 @@ const MiLightRemoteConfig FUT089Config( //rgb+cct B8 / FUT089
8
);

const MiLightRemoteConfig FUT100Config( //rgb+cct FUT100
new FUT100PacketFormatter(),
MiLightRadioConfig::ALL_CONFIGS[2],
REMOTE_TYPE_FUT100,
"fut100",
100
);

const MiLightRemoteConfig FUT098Config( //rgb
new RgbPacketFormatter(),
MiLightRadioConfig::ALL_CONFIGS[3],
Expand Down
4 changes: 3 additions & 1 deletion lib/MiLight/MiLightRemoteConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
#include <RgbCctPacketFormatter.h>
#include <CctPacketFormatter.h>
#include <FUT089PacketFormatter.h>
#include <FUT100PacketFormatter.h>
#include <FUT091PacketFormatter.h>
#include <FUT020PacketFormatter.h>
#include <PacketFormatter.h>

#ifndef _MILIGHT_REMOTE_CONFIG_H
#define _MILIGHT_REMOTE_CONFIG_H

class MiLightRemoteConfig {
public:
MiLightRemoteConfig(
Expand Down Expand Up @@ -46,6 +47,7 @@ extern const MiLightRemoteConfig FUT096Config; //rgbw
extern const MiLightRemoteConfig FUT007Config; //cct
extern const MiLightRemoteConfig FUT092Config; //rgb+cct
extern const MiLightRemoteConfig FUT089Config; //rgb+cct B8 / FUT089
extern const MiLightRemoteConfig FUT100Config; //rgb+cct FUT100
extern const MiLightRemoteConfig FUT098Config; //rgb
extern const MiLightRemoteConfig FUT091Config; //v2 cct
extern const MiLightRemoteConfig FUT020Config;
Expand Down
5 changes: 4 additions & 1 deletion lib/MiLight/V2PacketFormatter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,13 @@ void V2PacketFormatter::command(uint8_t command, uint8_t arg) {
currentPacket[V2_ARGUMENT_INDEX] = arg;
}


void V2PacketFormatter::updateStatus(MiLightStatus status, uint8_t groupId) {
command(0x01, GROUP_COMMAND_ARG(status, groupId, numGroups));
this->groupId = groupId;
command(0x01, groupCommandArg(status, groupId));
}


void V2PacketFormatter::unpair() {
for (size_t i = 0; i < 5; i++) {
updateStatus(ON, 0);
Expand Down
4 changes: 2 additions & 2 deletions lib/Radio/MiLightRadioConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
MiLightRadioConfig MiLightRadioConfig::ALL_CONFIGS[] = {
MiLightRadioConfig(0x147A, 0x258B, 7, 9, 40, 71, 0xAA, 0x05), // rgbw
MiLightRadioConfig(0x050A, 0x55AA, 7, 4, 39, 74, 0xAA, 0x05), // cct
MiLightRadioConfig(0x7236, 0x1809, 9, 8, 39, 70, 0xAA, 0x05), // rgb+cct, fut089
MiLightRadioConfig(0x7236, 0x1809, 9, 8, 39, 70, 0xAA, 0x05), // rgb+cct, fut089, fut100
MiLightRadioConfig(0x9AAB, 0xBCCD, 6, 3, 38, 73, 0x55, 0x0A), // rgb
MiLightRadioConfig(0x50A0, 0xAA55, 6, 6, 41, 76, 0xAA, 0x0A) // FUT020
};
};
2 changes: 1 addition & 1 deletion lib/Radio/NRF24MiLightRadio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

#include <PL1167_nRF24.h>
#include <NRF24MiLightRadio.h>

#define PACKET_ID(packet, packet_length) ( (packet[1] << 8) | packet[packet_length - 1] )

NRF24MiLightRadio::NRF24MiLightRadio(
Expand Down
10 changes: 10 additions & 0 deletions lib/Types/MiLightRemoteType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ static const char* REMOTE_NAME_RGBW = "rgbw";
static const char* REMOTE_NAME_CCT = "cct";
static const char* REMOTE_NAME_RGB_CCT = "rgb_cct";
static const char* REMOTE_NAME_FUT089 = "fut089";
static const char* REMOTE_NAME_FUT100 = "fut100";
static const char* REMOTE_NAME_RGB = "rgb";
static const char* REMOTE_NAME_FUT091 = "fut091";
static const char* REMOTE_NAME_FUT020 = "fut020";
Expand All @@ -26,6 +27,10 @@ const MiLightRemoteType MiLightRemoteTypeHelpers::remoteTypeFromString(const Str
return REMOTE_TYPE_FUT089;
}

if (type.equalsIgnoreCase(REMOTE_NAME_FUT100)) {
return REMOTE_TYPE_FUT100;
}

if (type.equalsIgnoreCase(REMOTE_NAME_RGB) || type.equalsIgnoreCase("fut098")) {
return REMOTE_TYPE_RGB;
}
Expand Down Expand Up @@ -54,6 +59,8 @@ const String MiLightRemoteTypeHelpers::remoteTypeToString(const MiLightRemoteTyp
return REMOTE_NAME_RGB_CCT;
case REMOTE_TYPE_FUT089:
return REMOTE_NAME_FUT089;
case REMOTE_TYPE_FUT100:
return REMOTE_NAME_FUT100;
case REMOTE_TYPE_RGB:
return REMOTE_NAME_RGB;
case REMOTE_TYPE_FUT091:
Expand All @@ -70,6 +77,7 @@ const String MiLightRemoteTypeHelpers::remoteTypeToString(const MiLightRemoteTyp
const bool MiLightRemoteTypeHelpers::supportsRgbw(const MiLightRemoteType type) {
switch (type) {
case REMOTE_TYPE_FUT089:
case REMOTE_TYPE_FUT100:
case REMOTE_TYPE_RGB_CCT:
case REMOTE_TYPE_RGBW:
return true;
Expand All @@ -81,6 +89,7 @@ const bool MiLightRemoteTypeHelpers::supportsRgbw(const MiLightRemoteType type)
const bool MiLightRemoteTypeHelpers::supportsRgb(const MiLightRemoteType type) {
switch (type) {
case REMOTE_TYPE_FUT089:
case REMOTE_TYPE_FUT100:
case REMOTE_TYPE_RGB:
case REMOTE_TYPE_RGB_CCT:
case REMOTE_TYPE_RGBW:
Expand All @@ -94,6 +103,7 @@ const bool MiLightRemoteTypeHelpers::supportsColorTemp(const MiLightRemoteType t
switch (type) {
case REMOTE_TYPE_CCT:
case REMOTE_TYPE_FUT089:
case REMOTE_TYPE_FUT100:
case REMOTE_TYPE_FUT091:
case REMOTE_TYPE_RGB_CCT:
return true;
Expand Down
10 changes: 8 additions & 2 deletions lib/Types/MiLightRemoteType.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,14 @@ enum MiLightRemoteType {
REMOTE_TYPE_RGBW = 0,
REMOTE_TYPE_CCT = 1,
REMOTE_TYPE_RGB_CCT = 2,
REMOTE_TYPE_RGB = 3,
REMOTE_TYPE_FUT089 = 4,
REMOTE_TYPE_RGB = 3,
#ifdef USE_FUT100
REMOTE_TYPE_FUT100 = 4,
REMOTE_TYPE_FUT089 = 7,
#else
REMOTE_TYPE_FUT100 = 7,
REMOTE_TYPE_FUT089 = 4,
#endif
REMOTE_TYPE_FUT091 = 5,
REMOTE_TYPE_FUT020 = 6
};
Expand Down
Loading