Closed mkz212 closed 2 years ago
full index.js code:
var request = require("request"),
inherits = require("util").inherits,
moment = require('moment');
var Accessory,
Characteristic,
Service,
FakeGatoHistoryService;
const REFRESH_INTERVAL = 60000;
const LOGIN_INTERVAL = 10800000;
const LOGIN_RETRY_DELAY = 360000;
module.exports = function(homebridge) {
Service = homebridge.hap.Service;
Characteristic = homebridge.hap.Characteristic;
Accessory = homebridge.hap.Accessory;
FakeGatoHistoryService = require('fakegato-history')(homebridge);
homebridge.registerAccessory("homebridge-panasonic-air-conditioner", "PanasonicAirConditioner", PanasonicAC);
};
function PanasonicAC(log, config) {
this.log = log;
this.debug = config.debug || false;
this.name = config.name || "Panasonic Air Conditioner";
this.email = config.email;
this.password = config.password;
this.deviceNumber = config.devicenumber || 1;
this.groupNumber = config.groupnumber || 1;
this.defaultMode = config.defaultMode;
this.uToken = null;
this.version = "1.10.0";
this.temperature = 0.0;
// Login for the first time and refresh
this._login();
}
PanasonicAC.prototype = {
_refreshInterval: null,
_loginInterval: null,
_loginRetry: null,
identify: function(callback) {
this.log("identify");
callback();
},
getServices: function() {
// Heater Cooler service
this.HeaterCooler = new Service.HeaterCooler(this.name);
// Heater Cooler - Active
this.HeaterCooler
.getCharacteristic(Characteristic.Active)
.on('set', this._setValue.bind(this, "Active"));
// Heater Cooler - Current Temperature
this.HeaterCooler
.getCharacteristic(Characteristic.CurrentTemperature)
.setProps({
minValue: -100,
maxValue: 100,
minStep: 0.01
});
// Heater Cooler - Current Heating Cooling State
// Heater Cooler - Target Heating Cooling State
this.HeaterCooler
.getCharacteristic(Characteristic.TargetHeaterCoolerState)
.on('set', this._setValue.bind(this, "TargetHeaterCoolerState"));
// Heater Cooler - Rotation Speed
this.HeaterCooler
.getCharacteristic(Characteristic.RotationSpeed)
.setProps({
minValue: 0,
maxValue: 6,
minStep: 1
})
.on('set', this._setValue.bind(this, "RotationSpeed"));
// Heater Cooler - Swing Mode
this.HeaterCooler
.getCharacteristic(Characteristic.SwingMode)
.on('set', this._setValue.bind(this, "SwingMode"));
// Heater Cooler - Cooling Threshold Temperature
this.HeaterCooler
.getCharacteristic(Characteristic.CoolingThresholdTemperature)
.setProps({
minValue: 16,
maxValue: 30,
minStep: 0.5
})
.on('set', this._setValue.bind(this, "ThresholdTemperature"));
// Heater Cooler - Heating Threshold Temperature
this.HeaterCooler
.getCharacteristic(Characteristic.HeatingThresholdTemperature)
.setProps({
minValue: 16,
maxValue: 30,
minStep: 0.5
})
.on('set', this._setValue.bind(this, "ThresholdTemperature"));
// Heater Cooler - Temperature Display Units
// FakeGato History service
this.FakeGatoHistory = new FakeGatoHistoryService("weather", Accessory);
// Accessory Information service
this.AccessoryInformation = new Service.AccessoryInformation();
this.AccessoryInformation
.setCharacteristic(Characteristic.Name, this.name)
.setCharacteristic(Characteristic.Manufacturer, "Panasonic")
.setCharacteristic(Characteristic.Model, "CZ-TACG1")
.setCharacteristic(Characteristic.FirmwareRevision, this.version)
.setCharacteristic(Characteristic.SerialNumber, this.device);
// Return the Accessory
return [
this.AccessoryInformation,
this.HeaterCooler,
this.FakeGatoHistory
];
},
_login: function() {
if(this.debug) {this.log("Login start");}
// Clear any pending timers
clearInterval(this._refreshInterval);
clearInterval(this._loginInterval);
clearTimeout(this._loginRetry);
// Call the API
request.post({
url: "https://accsmart.panasonic.com/auth/login/",
headers: {
"Accept": "application/json; charset=UTF-8",
"Content-Type": "application/json",
"X-APP-TYPE": 0,
"X-APP-VERSION": this.version
},
json: {
"loginId": this.email,
"language": "0",
"password": this.password
}
}, function(err, response, body) {
if (!err && response.statusCode == 200) {
this.uToken = body.uToken;
request.get({
url: "https://accsmart.panasonic.com/device/group/",
headers: {
"Accept": "application/json; charset=UTF-8",
"Content-Type": "application/json",
"X-APP-TYPE": 0,
"X-APP-VERSION": this.version,
"X-User-Authorization": this.uToken
},
json: ""
}, function(err, response, body) {
if (!err && response.statusCode == 200) {
body = JSON.parse(body);
try {
if(this.debug) {this.log("Login complete");}
this.device = body['groupList'][this.groupNumber-1]['deviceList'][this.deviceNumber-1]['deviceGuid'];
// Send a refresh off
this._refresh();
}
catch(err) {this.log("Could not find device by number.", "Check your device number and try again.", err, "Error #", body.code, body.message);}
}
else {this.log("Could not find any devices.", "Error #", body['code'], body['message']);}
// Set a timer to refresh the data
this._refreshInterval = setInterval(this._refresh.bind(this), REFRESH_INTERVAL);
// Set a timer to refresh the login token
this._loginInterval = setInterval(this._login.bind(this), LOGIN_INTERVAL);
}.bind(this));
}
else {
try {this.log("Login failed.", "Error #", body.code, body.message);}
catch(err) {this.log("Login failed.", "Unknown error.", "Did the API version change?", err);}
this._loginRetry = setTimeout(this._login.bind(this), LOGIN_RETRY_DELAY);
}
}.bind(this));
},
_getValue: function(CharacteristicName, callback) {
if(this.debug) {this.log("GET", CharacteristicName);}
callback(null);
},
_refresh: function() {
if(this.debug) {this.log("Refresh start");}
request.get({
url: "https://accsmart.panasonic.com/deviceStatus/now/" + this.device,
headers: {
"Accept": "application/json; charset=UTF-8",
"Content-Type": "application/json",
"X-APP-TYPE": 0,
"X-APP-VERSION": this.version,
"X-User-Authorization": this.uToken
}
}, function(err, response, body) {
if (!err && response.statusCode == 200) {
body = JSON.parse(body);
if(this.debug) {this.log(body);}
// Check the temperature
// Note - only update the temperature when the Heat Pump is reporting a valid temperature, otherwise it will just incorrectly report zero to HomeKit and FakeGato
if (
body.parameters.insideTemperature != 126 ||
body.parameters.outTemperature != 126
) {
// Temperature of 126 from the API = null
if (body.parameters.insideTemperature != 126) {this.temperature = body.parameters.insideTemperature;}
else if (body.parameters.outTemperature != 126) {this.temperature = body.parameters.outTemperature;}
this.HeaterCooler.getCharacteristic(Characteristic.CurrentTemperature).updateValue(this.temperature);
this.FakeGatoHistory.addEntry({time: moment().unix(), temp: this.temperature});
}
else {this.log("Temperature state is not available", body.parameters.insideTemperature, body.parameters.outTemperature);}
// Check the operating state
if(body.parameters.operate == 1) {
// Turn the Heater Cooler on
this.HeaterCooler.getCharacteristic(Characteristic.Active).updateValue(Characteristic.Active.ACTIVE);
// Set the Heater Cooler mode
switch (body.parameters.operationMode) {
// Auto
case 0:
if (this.temperature < body.parameters.temperatureSet) {this.HeaterCooler.getCharacteristic(Characteristic.CurrentHeaterCoolerState).updateValue(Characteristic.CurrentHeaterCoolerState.HEATING);}
else if (this.temperature > body.parameters.temperatureSet) {this.HeaterCooler.getCharacteristic(Characteristic.CurrentHeaterCoolerState).updateValue(Characteristic.CurrentHeaterCoolerState.COOLING);}
else {this.HeaterCooler.getCharacteristic(Characteristic.CurrentHeaterCoolerState).updateValue(Characteristic.CurrentHeaterCoolerState.IDLE);}
this.HeaterCooler.getCharacteristic(Characteristic.TargetHeaterCoolerState).updateValue(Characteristic.TargetHeaterCoolerState.AUTO);
break;
// Heat
case 3:
if (this.temperature < body.parameters.temperatureSet) {this.HeaterCooler.getCharacteristic(Characteristic.CurrentHeaterCoolerState).updateValue(Characteristic.CurrentHeaterCoolerState.HEATING);}
else {this.HeaterCooler.getCharacteristic(Characteristic.CurrentHeaterCoolerState).updateValue(Characteristic.CurrentHeaterCoolerState.IDLE);}
this.HeaterCooler.getCharacteristic(Characteristic.TargetHeaterCoolerState).updateValue(Characteristic.TargetHeaterCoolerState.HEAT);
break;
// Cool
case 2:
if (this.temperature > body.parameters.temperatureSet) {this.HeaterCooler.getCharacteristic(Characteristic.CurrentHeaterCoolerState).updateValue(Characteristic.CurrentHeaterCoolerState.COOLING);}
else {this.HeaterCooler.getCharacteristic(Characteristic.CurrentHeaterCoolerState).updateValue(Characteristic.CurrentHeaterCoolerState.IDLE);}
this.HeaterCooler.getCharacteristic(Characteristic.TargetHeaterCoolerState).updateValue(Characteristic.TargetHeaterCoolerState.COOL);
break;
// Dry (Dehumidifier)
case 1:
this.HeaterCooler.getCharacteristic(Characteristic.CurrentHeaterCoolerState).updateValue(Characteristic.CurrentHeaterCoolerState.IDLE);
this.HeaterCooler.getCharacteristic(Characteristic.TargetHeaterCoolerState).updateValue(Characteristic.TargetHeaterCoolerState.OFF);
break;
// Fan
case 4:
this.HeaterCooler.getCharacteristic(Characteristic.CurrentHeaterCoolerState).updateValue(Characteristic.CurrentHeaterCoolerState.IDLE);
this.HeaterCooler.getCharacteristic(Characteristic.TargetHeaterCoolerState).updateValue(Characteristic.TargetHeaterCoolerState.OFF);
break;
default:
this.log("Unknown TargetHeaterCoolerState state", body.parameters.operationMode);
break;
}
}
else {
// Turn the Heater Cooler off
this.HeaterCooler.getCharacteristic(Characteristic.Active).updateValue(Characteristic.Active.INACTIVE);
this.HeaterCooler.getCharacteristic(Characteristic.CurrentHeaterCoolerState).updateValue(Characteristic.CurrentHeaterCoolerState.IDLE);
this.HeaterCooler.getCharacteristic(Characteristic.TargetHeaterCoolerState).updateValue(Characteristic.TargetHeaterCoolerState.OFF);
}
// Heater Cooler - Target Temperature
this.HeaterCooler.getCharacteristic(Characteristic.HeatingThresholdTemperature).updateValue(body.parameters.temperatureSet);
this.HeaterCooler.getCharacteristic(Characteristic.CoolingThresholdTemperature).updateValue(body.parameters.temperatureSet);
// Heater Cooler - Temperature Display Units
this.HeaterCooler.getCharacteristic(Characteristic.TemperatureDisplayUnits).updateValue(body.temperatureUnit);
// Heater Cooler - Rotation Speed
if(body.parameters.fanSpeed == 0) {this.HeaterCooler.getCharacteristic(Characteristic.RotationSpeed).updateValue(6);}
else {this.HeaterCooler.getCharacteristic(Characteristic.RotationSpeed).updateValue(body.parameters.fanSpeed);}
// Heater Cooler - Swing Mode
if(body.parameters.airSwingLR == 2 && body.parameters.airSwingUD == 0) {this.HeaterCooler.getCharacteristic(Characteristic.SwingMode).updateValue(Characteristic.SwingMode.SWING_ENABLED);}
else {this.HeaterCooler.getCharacteristic(Characteristic.SwingMode).updateValue(Characteristic.SwingMode.SWING_DISABLED);}
// Status Fault
if(!body.parameters.online || body.parameters.errorStatusFlg) {this.log("Refresh failed.", "Device may be offline or in error state.", "OnlineStatus", body.parameters.online, "ErrorStatus", body.parameters.errorStatusFlg, "HTTPStatus", response.statusCode, "Error #", body.code, body.message);}
else if(this.debug) {this.log("Refresh complete");}
}
else if(response.statusCode == 403) {this.log("Refresh failed.", "Login error.", "Did you enter the correct username and password? Please check the details & restart Homebridge.", err);}
else if(response.statusCode == 401) {
this.log("Refresh failed.", "Token error.", "The token may have expired.", err);
this._loginRetry = setTimeout(this._login.bind(this), LOGIN_RETRY_DELAY);
}
else {
try {this.log("Refresh failed.", "HTTP", response.statusCode, "Error #", body.code, body.message);}
catch(err) {this.log("Refresh failed.", "Unknown error.", "Did the API version change?", err);}
}
}.bind(this));
},
_setValue: function(CharacteristicName, value, callback) {
if(this.debug) {this.log("SET", CharacteristicName, value, "start");}
var parameters;
switch (CharacteristicName) {
// Heater Cooler - Active
case "Active":
switch (value) {
case Characteristic.Active.ACTIVE:
if (this.defaultMode == "q") {
parameters = {
"operate": 1,
"ecoMode": 2,
"fanSpeed": 0
};
this.HeaterCooler.getCharacteristic(Characteristic.RotationSpeed).updateValue(1);
}
else if (this.defaultMode == "p") {
parameters = {
"operate": 1,
"ecoMode": 1,
"fanSpeed": 0
};
this.HeaterCooler.getCharacteristic(Characteristic.RotationSpeed).updateValue(5);
}
else if (this.defaultMode == "a") {
parameters = {
"operate": 1,
"ecoMode": 0,
"fanSpeed": 0
};
this.HeaterCooler.getCharacteristic(Characteristic.RotationSpeed).updateValue(6);
}
else {
parameters = {
"operate": 1
};
}
break;
case Characteristic.Active.INACTIVE:
parameters = {
"operate": 0
};
break;
}
break;
// Heater Cooler - Target Heating Cooling State
case "TargetHeaterCoolerState":
switch (value) {
case Characteristic.TargetHeaterCoolerState.AUTO:
parameters = {
"operate": 1,
"operationMode": 0
};
break;
case Characteristic.TargetHeaterCoolerState.HEAT:
parameters = {
"operate": 1,
"operationMode": 3
};
break;
case Characteristic.TargetHeaterCoolerState.COOL:
parameters = {
"operate": 1,
"operationMode": 2
};
break;
default: this.log("Unknown TargetHeaterCoolerState", value); break;
}
break;
// Heater Cooler - Target Temperature
case "ThresholdTemperature":
parameters = {
"temperatureSet": value
};
break;
// Heater Cooler - Temperature Display Units
// @TODO - we cannot easily set this here (needs to be set on a different part of the API)
// Heater Cooler - Rotation Speed
case "RotationSpeed":
if (value == 1)
{
parameters = {
"ecoMode": 2,
"fanSpeed": 0
}
}
else if (value == 5)
{
parameters = {
"ecoMode": 1,
"fanSpeed": 0
}
}
else
{
parameters = {
"ecoMode": 0,
"fanSpeed": value
};
}
break;
// Heater Cooler - Swing Mode ("Oscillate")
case "SwingMode":
switch (value) {
case Characteristic.SwingMode.SWING_ENABLED:
parameters = {
"fanAutoMode": 0,
"airSwingLR": 2,
"airSwingUD": 0
};
break;
case Characteristic.SwingMode.SWING_DISABLED:
parameters = {
"fanAutoMode": 1,
"airSwingLR": 2,
"airSwingUD": 0
};
break;
}
break;
}
// Call the API
request.post({
url: "https://accsmart.panasonic.com/deviceStatus/control/",
headers: {
"Accept": "application/json; charset=UTF-8",
"Content-Type": "application/json",
"X-APP-TYPE": 0,
"X-APP-VERSION": this.version,
"X-User-Authorization": this.uToken
},
json: {
"deviceGuid": this.device,
"parameters": parameters
}
}, function(err, response, body) {
if (!err && response.statusCode == 200) {
if (body.result !== 0) {
this.log("SET failed.", "Error #", body.code, body.message);
if(response.statusCode == 403) {this.log("Login error.", "Did you enter the correct username and password? Please check the details & restart Homebridge.", err);}
else if(response.statusCode == 401) {this.log("Token error.", "The token may have expired.", err);}
}
else if(this.debug) {this.log("SET", CharacteristicName, value, "complete");}
}
else {
try {this.log("SET failed.", "HTTP", response.statusCode, "Error #", body.code, body.message);}
catch(err) {this.log("SET failed.", "Unknown error.", "Did the API version change?", err);}
}
}.bind(this));
// Callback to HomeKit now that it's done
callback(null, value);
}
};
and config schema:
{
"pluginAlias":"PanasonicAirConditioner",
"pluginType":"accessory",
"singular":true,
"headerDisplay":null,
"schema":{
"type":"object",
"properties":{
"name":{
"title":"Accessory Name",
"type":"string",
"format":"string",
"placeholder":"Panasonic Air Conditioner",
"minLength": 1,
"required": true
},
"email":{
"title":"Email",
"type":"string",
"format":"email",
"placeholder":"Email",
"minLength": 1,
"required": true
},
"password":{
"title":"Password",
"type":"string",
"format":"password",
"placeholder":"Password",
"minLength": 1,
"required": true
},
"devicenumber":{
"title":"Device Number (optional)",
"type":"integer",
"format":"integer",
"placeholder":"1",
"minLength": 1,
"required": false,
"description":"If you have multiple devices, specify the device number here to select that one specifically. Otherwise, defaults to 1."
},
"defaultMode":{
"title":"Default Mode",
"type":"string",
"format":"",
"placeholder":"",
"minLength": 1,
"required": true,
"default": "no",
"description":"Choose whether to always start in Quiet or Powerfull mode",
"oneOf": [
{
"title": "Auto",
"enum": [
"a"
]
},
{
"title": "Quiet",
"enum": [
"q"
]
},
{
"title": "Powerful",
"enum": [
"p"
]
},
{
"title": "No default",
"enum": [
"no"
]
}
]
},
"debug":{
"title":"Debug logging",
"type":"boolean",
"description":"Turns on additional logging (can be helpful for diagnosing issues)"
}
}
},
"layout":[
{
"type":"flex",
"flex-flow":"row wrap",
"items":[
"name"
]
},
{
"type":"flex",
"flex-flow":"row wrap",
"items":[
{
"key":"email",
"type":"email"
},
{
"key":"password",
"type":"password"
}
]
},
{
"type":"fieldset",
"title":"Advanced configuration",
"expandable":true,
"items":[
"devicenumber",
"defaultMode",
"debug"
]
}
]
}
tested and running like a dream :)
Yes we had this function before, but the display of the switches in the plug-in was not ideal. Consequently the functions were removed. I do envision to add them back. Fortunately this is also made easier by the config-ui-x these days.
For me the solution that quiet and powerful are operated with a fan speed slider seems perfect. And that you can choose the default mode (speed) after starting I also find it very useful. Great plug anyway and only these 2 things I missed. :)
Sure, it works. However you can’t simply ask Siri to put the heat pump in powerful mode. Rather frustrating!
You can create scene with rotation speed 1 for quiet mode od rotation speed 5 for powerful mode :) Or you can set default mode to quiet or powerful :)
I used @mkz212 mod and it worked great. It enables me to setup Quiet Mode in the good night scene and back to Auto in the good Morning scene.
You can also change this part of code, in "_refresh" function. This will check for updates from native Comfort Cloud app.
// Heater Cooler - Rotation Speed
if (body.parameters.ecoMode == 2) {this.HeaterCooler.getCharacteristic(Characteristic.RotationSpeed).updateValue(1);} else if (body.parameters.ecoMode == 1) {this.HeaterCooler.getCharacteristic(Characteristic.RotationSpeed).updateValue(5);} else if (body.parameters.fanSpeed == 0) {this.HeaterCooler.getCharacteristic(Characteristic.RotationSpeed).updateValue(6);} else {this.HeaterCooler.getCharacteristic(Characteristic.RotationSpeed).updateValue(body.parameters.fanSpeed);}
And this in "_setValue" function:
// Heater Cooler - Rotation Speed case "RotationSpeed":
if (value == 6) {value = 0;}
if (value == 1) { parameters = { "fanSpeed": value, "ecoMode": 2 }; }
else if (value == 5) { parameters = { "fanSpeed": value, "ecoMode": 1 }; } else { parameters = { "fanSpeed": value, "ecoMode": 0 }; } break;
This plug-in is now archived and it is advised to use the homebridge-panasonic-ac-platform plug-in instead. It is based on this plug-in. This plug-in provides support for the devices as a platform (instead of just individual accessories).
Hi. Great plugin. Only Quiet mode and Powerful mode suport missing. But it can be added simply.
For adding Quiet and Powerful mode, replace "case "RotationSpeed":" in index.js. Now if you select 1 in fan speed it will enable Quiet Mode, and if you select 5 it will enable Powerful mode.
You can also add default startup mode.
in index.js, inside "function PanasonicAC(log, config)" add:
this.defaultMode = config.defaultMode;
In index.js, replace all code in "case Characteristic.Active.ACTIVE:"
and replace config schema to:
{ "pluginAlias":"PanasonicAirConditioner", "pluginType":"accessory", "singular":true, "headerDisplay":null, "schema":{ "type":"object", "properties":{ "name":{ "title":"Accessory Name", "type":"string", "format":"string", "placeholder":"Panasonic Air Conditioner", "minLength": 1, "required": true }, "email":{ "title":"Email", "type":"string", "format":"email", "placeholder":"Email", "minLength": 1, "required": true }, "password":{ "title":"Password", "type":"string", "format":"password", "placeholder":"Password", "minLength": 1, "required": true }, "devicenumber":{ "title":"Device Number (optional)", "type":"integer", "format":"integer", "placeholder":"1", "minLength": 1, "required": false, "description":"If you have multiple devices, specify the device number here to select that one specifically. Otherwise, defaults to 1." }, "defaultMode":{ "title":"Default Mode", "type":"string", "format":"", "placeholder":"", "minLength": 1, "required": true, "default": "no", "description":"Choose whether to always start in Quiet or Powerfull mode", "oneOf": [ { "title": "Auto", "enum": [ "a" ] }, { "title": "Quiet", "enum": [ "q" ] }, { "title": "Powerful", "enum": [ "p" ] }, { "title": "No default", "enum": [ "no" ] } ] }, "debug":{ "title":"Debug logging", "type":"boolean", "description":"Turns on additional logging (can be helpful for diagnosing issues)" } } }, "layout":[ { "type":"flex", "flex-flow":"row wrap", "items":[ "name" ] }, { "type":"flex", "flex-flow":"row wrap", "items":[ { "key":"email", "type":"email" }, { "key":"password", "type":"password" } ] }, { "type":"fieldset", "title":"Advanced configuration", "expandable":true, "items":[ "devicenumber", "defaultMode", "debug" ] } ] }