-
-
Notifications
You must be signed in to change notification settings - Fork 33
Description
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