Skip to content
Open
Show file tree
Hide file tree
Changes from 9 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
157 changes: 157 additions & 0 deletions lib/MiLight/FUT100PacketFormatter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
#include <FUT100PacketFormatter.h>
#include <V2RFEncoding.h>
#include <Units.h>
#include <MiLightCommands.h>

void FUT100PacketFormatter::updateStatus(MiLightStatus status, uint8_t groupId) {
Serial.printf("FUT100 Debug: UpdateStatus Group %d, Status %d\n", groupId, status); //DEBUG
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
this->groupId = groupId;

if (groupId <= 8) {
// Gruppe 0-8: Nutzt Kommando 0x01
// ON: 0-8, OFF: 9-17 (0x09 - 0x11)
uint8_t arg = groupId + (status == OFF ? 9 : 0);
command(0x01, arg);
} else {
// Gruppe 9-100: Nutzt Kommando 0x08
// G9 ON = 0, G9 OFF = 1, G10 ON = 2...
uint8_t arg = ((groupId - 9) * 2) + (status == ON ? 0 : 1);
command(0x08, arg);
}
}

void FUT100PacketFormatter::modeSpeedDown() {
command(0x01, 0x13); // FUT100_MODE_SPEED_DOWN
}

void FUT100PacketFormatter::modeSpeedUp() {
command(0x01, 0x12); // FUT100_MODE_SPEED_UP
}

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

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

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

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

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(0x07, 100 - value); // KELVIN

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(0x07, 100 - value); // SATURATION

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

void FUT100PacketFormatter::updateColorWhite() {
command(0x01, 0x14); // FUT100_WHITE_MODE
}

void FUT100PacketFormatter::enableNightMode() {
if (groupId <= 8) {
// Standard V2 Nachtmodus für G0-G8
// Nutzt das "OFF" Argument der jeweiligen Gruppe (groupId + 9)
uint8_t arg = groupId + 9;
command(0x01 | 0x80, arg);
} else {
// Nachtmodus für G9-G100
// Basierend auf der FUT100 Logik: OFF-Argument für G9-G100 + High Bit
uint8_t arg = ((groupId - 9) * 2) + 1;
command(0x08 | 0x80, arg);
}
}

BulbId FUT100PacketFormatter::parsePacket(const uint8_t *packet, JsonObject result) {
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 cmd = (packetCopy[V2_COMMAND_INDEX] & 0x7F);
uint8_t arg = packetCopy[V2_ARGUMENT_INDEX];

// G0-G8 (Kommando 0x01) - Nutzt exakt die FUT100 Parse-Logik
if (cmd == 0x01) {
if ((packetCopy[V2_COMMAND_INDEX] & 0x80) == 0x80) {
result[GroupStateFieldNames::COMMAND] = MiLightCommandNames::NIGHT_MODE;
} else if (arg == 0x13) {
result[GroupStateFieldNames::COMMAND] = MiLightCommandNames::MODE_SPEED_DOWN;
} else if (arg == 0x12) {
result[GroupStateFieldNames::COMMAND] = MiLightCommandNames::MODE_SPEED_UP;
} else if (arg == 0x14) {
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;
}
}
// G9-G100 (Kommando 0x08) - Die neue FUT100 Parse-Logik
else if (cmd == 0x08) {
result[GroupStateFieldNames::STATE] = ((arg % 2) == 0) ? "ON" : "OFF";
bulbId.groupId = (arg / 2) + 9;
Comment thread
cursor[bot] marked this conversation as resolved.
}
// Restliche Befehle (Farbe, Helligkeit etc. sind bei FUT100 und FUT100 identisch)
else if (cmd == 0x02) {
result[GroupStateFieldNames::HUE] = Units::rescale<uint16_t, uint16_t>(arg, 360, 255.0);
} else if (cmd == 0x05) {
result[GroupStateFieldNames::BRIGHTNESS] = Units::rescale<uint8_t, uint8_t>(constrain(arg, 0, 100), 255, 100);
} else if (cmd == 0x07) {
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 == 0x06) {
result[GroupStateFieldNames::MODE] = arg;
}

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

#include <V2PacketFormatter.h>

#define FUT100_COLOR_OFFSET 0
#define FUT100_GROUP_ON_OFF_COMMAND 0x08 // Das spezielle Kommando für G9-G100

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

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