Skip to content

Add "nofrost mode" at 8c #750

@bernardlabossiere

Description

@bernardlabossiere

cat > /homebridge/startup.sh <<'SH'
#!/bin/sh

---------------------------

PATCH_GREE (homebridge-gree-ac)

- NoFrost switch + sendCmd

- Display fix (no rebound)

- Restore last setpoint on OFF

- Extra log on ON

- Separate accessory for Automations (FIXED: use this.platform.api.platformAccessory)

- Sync this.targetTemperature so HomeKit thermostat tile updates when toggling switch manually

---------------------------

GREE_FILE="/homebridge/node_modules/homebridge-gree-ac/dist/platformAccessory.js"

if [ -f "$GREE_FILE" ]; then

Fix ESM vs CJS leftovers if an update reintroduces them

if grep -q "commands_1.default" "$GREE_FILE"; then
sed -i 's/commands_1.default/commands/g' "$GREE_FILE"
echo "[startup] Gree fixed: commands_1 -> commands"
fi
if grep -q "crypto_1.default" "$GREE_FILE"; then
sed -i 's/crypto_1.default/crypto/g' "$GREE_FILE"
echo "[startup] Gree fixed: crypto_1 -> crypto"
fi

1) Display patch: keep HomeKit at 8 when StHt is ON (prevents 8->16 rebound)

if ! grep -q "PATCH_GREE_DISPLAY_8C_ON_REFRESH_V2" "$GREE_FILE"; then
node <<'NODE'
const fs = require("fs");
const FILE = "/homebridge/node_modules/homebridge-gree-ac/dist/platformAccessory.js";
const MARK = "PATCH_GREE_DISPLAY_8C_ON_REFRESH_V2";

let s = fs.readFileSync(FILE, "utf8");
if (s.includes(MARK)) process.exit(0);

let n = 0;
s = s.replace(/\bupdateValue(\sthis.targetTemperature\s)/g, () => {
n++;
return updateValue((this.status?.[commands.nofrost.code] === commands.nofrost.value.on) ? 8 : this.targetTemperature) /* ${MARK} */;
});

if (n > 0) {
s += \n// ${MARK} (patched ${n} occurrence(s))\n;
fs.writeFileSync(FILE, s, "utf8");
console.log([startup] Gree patched display (StHt=>8): ${n} occurrence(s));
}
NODE
fi

2) Main patch: switch + sendCmd + restore + log + sync targetTemperature

if ! grep -q "PATCH_GREE_NOFROST_SWITCH_V230_REQ3" "$GREE_FILE"; then
node <<'NODE'
const fs = require("fs");
const FILE = "/homebridge/node_modules/homebridge-gree-ac/dist/platformAccessory.js";
const MARK = "PATCH_GREE_NOFROST_SWITCH_V230_REQ3";

let s = fs.readFileSync(FILE, "utf8");
if (s.includes(MARK)) process.exit(0);

function softReplace(re, repl, label) {
const before = s;
s = s.replace(re, repl);
if (s === before) {
console.log([startup] WARN: pattern not found for ${label} (no patch applied));
process.exit(0);
}
}

/**

  • A) Add switch after HeaterCooler.setPrimaryService(true)
    /
    softReplace(
    /this.HeaterCooler.setPrimaryService(true);\s
    /m,
    `this.HeaterCooler.setPrimaryService(true);

    // ${MARK}: Switch anti-gel (8°C) = StHt (NoFrost)
    this.NoFrostSwitch = this.accessory.getServiceById(this.platform.Service.Switch, 'NoFrost')
    || this.accessory.addService(this.platform.Service.Switch, 'Anti-gel (8°C)', 'NoFrost');

    this.NoFrostSwitch.getCharacteristic(this.platform.Characteristic.On)
    .onGet(this.getNoFrost.bind(this))
    .onSet(this.setNoFrost.bind(this));
    `,
    "add switch service"
    );

/**

  • B) Inject sendCmd/getNoFrost/setNoFrost before getActive()
    /
    softReplace(
    /async getActive()\s
    {/m,
    `// ${MARK}
    sendCmd(opt, p) {
    const address = this.accessory.context.device.address;
    const port = this.accessory.context.device.port || this.deviceConfig.port || 7000;
    const mac = this.accessory.context.device.mac;

const pack = { t: 'cmd', opt, p, mac, uid: 0 };

const encVer = this.accessory.context.device.encryptionVersion || 1;
let msg;

if (encVer === 1) {
const encrypted = crypto.encrypt_v1(pack, this.key);
msg = { cid: 'app', i: 0, t: 'pack', uid: 0, pack: encrypted };
} else {
const encrypted = crypto.encrypt_v2(pack, this.key);
msg = { cid: 'app', i: 0, t: 'pack', uid: 0, pack: encrypted.pack, tag: encrypted.tag };
}

const buf = Buffer.from(JSON.stringify(msg));
this.socket.send(buf, 0, buf.length, port, address);
}

async getNoFrost() {
try {
return this.status?.[commands.nofrost.code] === commands.nofrost.value.on;
} catch (e) {
return false;
}
}

async setNoFrost(value) {
const on = (value === true || value === 1);

if (on) {
this.platform.log.info(`[GREE Air Conditioner] [${this.getDeviceLabel()}] target temperature 8`);
}

if (!this.accessory.bound) {
this.platform.log.warn(`[GREE Air Conditioner] [${this.getDeviceLabel()}] appareil pas encore bindé`);
return;
}

// Mémoriser la dernière consigne chauffage "normale"
try {
const cur = Number(this.HeaterCooler?.getCharacteristic(this.platform.Characteristic.HeatingThresholdTemperature).value ?? this.targetTemperature);
if (!Number.isNaN(cur) && cur >= 16) this.lastHeatSetpoint = cur;
} catch (e) {}

if (on) {
try { this.status[commands.mode.code] = commands.mode.value.heat; } catch(e) {}
this.sendCmd(
[commands.nofrost.code, commands.targetTemperature.code],
[commands.nofrost.value.on, 8]
);

this.status[commands.nofrost.code] = commands.nofrost.value.on;
this.status[commands.targetTemperature.code] = 8;

// Sync internal target so thermostat tile updates immediately
this.targetTemperature = 8; // PATCH_GREE_SYNC_TARGETTEMP_WITH_NOFROST_V1
try { this.platform.api.updatePlatformAccessories([this.accessory]); } catch(e) {}

this.HeaterCooler?.getCharacteristic(this.platform.Characteristic.HeatingThresholdTemperature).updateValue(8);
this.HeaterCooler?.getCharacteristic(this.platform.Characteristic.TargetHeaterCoolerState)
  .updateValue(this.platform.Characteristic.TargetHeaterCoolerState.HEAT);

} else {
this.sendCmd([commands.nofrost.code], [commands.nofrost.value.off]);
this.status[commands.nofrost.code] = commands.nofrost.value.off;

// Restore dernière consigne
try {
  const t = (this.lastHeatSetpoint && this.lastHeatSetpoint >= 16) ? this.lastHeatSetpoint : null;
  if (t !== null) {
    this.sendCmd([commands.targetTemperature.code], [t]);
    this.status[commands.targetTemperature.code] = t;

    // Sync internal target so thermostat tile follows restore
    this.targetTemperature = t; // PATCH_GREE_SYNC_TARGETTEMP_WITH_NOFROST_V1
    try { this.platform.api.updatePlatformAccessories([this.accessory]); } catch(e) {}

    this.HeaterCooler?.getCharacteristic(this.platform.Characteristic.HeatingThresholdTemperature).updateValue(t);
    this.platform.log.info(\`[GREE Air Conditioner] [\${this.getDeviceLabel()}] restore target temperature \${t}\`);
  }
} catch(e) {}

}

this.NoFrostSwitch?.getCharacteristic(this.platform.Characteristic.On).updateValue(on);

// Si accessoire automations présent, refléter l'état
try { await this.updateNoFrostAccessory?.(on); } catch(e) {}
}

async getActive() {`,
"inject sendCmd + nofrost methods + restore + log + sync"
);

/**

  • C) Remember last heat setpoint on normal setTargetTemperature (>=16)
    /
    if (!s.includes("PATCH_GREE_REMEMBER_LAST_HEAT_V1")) {
    s = s.replace(
    /async setTargetTemperature(value)\s
    {\s*/m,
    m => m +
    // PATCH_GREE_REMEMBER_LAST_HEAT_V1: mémoriser la dernière consigne chauffage normale (>=16°C) try { const vv = Number(value); if (!Number.isNaN(vv) && vv >= 16) this.lastHeatSetpoint = vv; } catch(e) {}
    );
    }

s += \n// ${MARK}\n;
fs.writeFileSync(FILE, s, "utf8");
console.log("[startup] Gree patched: NoFrost switch + sendCmd + restore + log + sync target");
NODE
fi

3) Separate accessory for Automations (Switch standalone) - FIXED

if ! grep -q "PATCH_GREE_NOFROST_SEPARATE_ACCESSORY_V2" "$GREE_FILE"; then
node <<'NODE'
const fs = require("fs");
const FILE = "/homebridge/node_modules/homebridge-gree-ac/dist/platformAccessory.js";
const MARK = "PATCH_GREE_NOFROST_SEPARATE_ACCESSORY_V2";

let s = fs.readFileSync(FILE, "utf8");
if (s.includes(MARK)) process.exit(0);

function mustReplace(re, repl, label) {
const before = s;
s = s.replace(re, repl);
if (s === before) {
console.log([startup] WARN: pattern introuvable pour ${label} (patch non appliqué));
process.exit(0);
}
}

// Inject ensureNoFrostAccessory + updateNoFrostAccessory before getActive()
mustReplace(
/async getActive()\s*{/m,
`// ${MARK}
async ensureNoFrostAccessory() {
if (this._noFrostAccessoryReady) return;
this._noFrostAccessoryReady = true;

try {
const PlatformAccessory = this.platform.api.platformAccessory;
const Categories = this.platform.api.hap.Categories;

const uuid = this.platform.api.hap.uuid.generate(String(this.accessory.UUID) + ':nofrost');
const name = (this.accessory.displayName || this.getDeviceLabel?.() || 'Gree') + ' - Anti-gel (8°C)';

const acc = new PlatformAccessory(name, uuid, Categories.SWITCH);

acc.context = acc.context || {};
acc.context.parentUUID = this.accessory.UUID;
acc.context.device = this.accessory.context.device;

const svc = acc.addService(this.platform.Service.Switch, name);
svc.getCharacteristic(this.platform.Characteristic.On)
  .onGet(this.getNoFrost.bind(this))
  .onSet(async (v) => {
    await this.setNoFrost(v);
    try { await this.updateNoFrostAccessory?.(!!v); } catch(e) {}
  });

this.platform.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [acc]);

this._noFrostAccessory = acc;
this._noFrostService = svc;

try {
  const on = await this.getNoFrost();
  svc.getCharacteristic(this.platform.Characteristic.On).updateValue(!!on);
} catch(e) {}

this.platform.log.info(\`[GREE Air Conditioner] [\${this.getDeviceLabel()}] Accessoire Automatisation créé: \${name}\`);

} catch (e) {
this.platform.log.warn(`[GREE Air Conditioner] [${this.getDeviceLabel()}] Impossible de créer accessoire Automatisation: ${e?.message || e}`);
}
}

async updateNoFrostAccessory(on) {
try {
if (this._noFrostService) {
this._noFrostService.getCharacteristic(this.platform.Characteristic.On).updateValue(!!on);
}
} catch(e) {}
}

async getActive() {`,
"inject ensureNoFrostAccessory"
);

// Call ensureNoFrostAccessory after Name.onGet registration
mustReplace(
/this.HeaterCooler.getCharacteristic(this.platform.Characteristic.Name)[\s\S]?.onGet(this.getName.bind(this));\s/m,
m => m + // ${MARK}: publier un switch "Anti-gel (8°C)" comme accessoire séparé (Automatisation) this.ensureNoFrostAccessory?.();,
"call ensureNoFrostAccessory"
);

s += \n// ${MARK}\n;
fs.writeFileSync(FILE, s, "utf8");
console.log("[startup] Gree patched: separate accessory for Automations (fixed)");
NODE
fi

fi

SH

chmod +x /homebridge/startup.sh

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions