mysa2mqtt 1.1.1 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -3
- package/dist/main.js +2 -2
- package/package.json +19 -19
package/README.md
CHANGED
|
@@ -213,13 +213,11 @@ When using Home Assistant, devices will be automatically discovered and appear i
|
|
|
213
213
|
### Common Issues
|
|
214
214
|
|
|
215
215
|
1. **Authentication Failures**
|
|
216
|
-
|
|
217
216
|
- Verify your Mysa username and password
|
|
218
217
|
- Check if session.json exists and is valid
|
|
219
218
|
- Try deleting session.json to force re-authentication
|
|
220
219
|
|
|
221
220
|
2. **MQTT Connection Issues**
|
|
222
|
-
|
|
223
221
|
- Verify MQTT broker hostname and port
|
|
224
222
|
- Check MQTT credentials if authentication is required
|
|
225
223
|
- Ensure the MQTT broker is accessible from your network
|
|
@@ -364,7 +362,6 @@ copyright notice and license text in any copy of the software or substantial por
|
|
|
364
362
|
## Acknowledgments
|
|
365
363
|
|
|
366
364
|
- [mysa-js-sdk](https://github.com/bourquep/mysa-js-sdk) - Mysa API client library
|
|
367
|
-
|
|
368
365
|
- This library would not be possible without the amazing work by [@dlenski](https://github.com/dlenski) in his
|
|
369
366
|
[mysotherm](https://github.com/dlenski/mysotherm) repository. He's the one who reversed-engineered the Mysa MQTT
|
|
370
367
|
protocol which is being used by this library.
|
package/dist/main.js
CHANGED
|
@@ -22,9 +22,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
22
22
|
SOFTWARE.
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
import{MysaApiClient as x}from"mysa-js-sdk";import{pino as k}from"pino";var p=class{constructor(t){this.logger=t}debug(t,...e){let i=e.at(0);i?this.logger.debug(i,t,...e):this.logger.debug(t,...e)}info(t,...e){let i=e.at(0);i?this.logger.info(i,t,...e):this.logger.info(t,...e)}warn(t,...e){let i=e.at(0);i?this.logger.warn(i,t,...e):this.logger.warn(t,...e)}error(t,...e){let i=e.at(0);i?this.logger.error(i,t,...e):this.logger.error(t,...e)}};import{Command as _,InvalidArgumentError as q,Option as s}from"commander";import{configDotenv as v}from"dotenv";import{readFileSync as M}from"fs";import{dirname as w,join as S}from"path";import{fileURLToPath as C}from"url";v({path:[".env",".env.local"],override:!0});function T(){try{let
|
|
25
|
+
import{MysaApiClient as x}from"mysa-js-sdk";import{pino as k}from"pino";var p=class{constructor(t){this.logger=t}debug(t,...e){let i=e.at(0);i?this.logger.debug(i,t,...e):this.logger.debug(null,t,...e)}info(t,...e){let i=e.at(0);i?this.logger.info(i,t,...e):this.logger.info(null,t,...e)}warn(t,...e){let i=e.at(0);i?this.logger.warn(i,t,...e):this.logger.warn(null,t,...e)}error(t,...e){let i=e.at(0);i?this.logger.error(i,t,...e):this.logger.error(null,t,...e)}};import{Command as _,InvalidArgumentError as q,Option as s}from"commander";import{configDotenv as v}from"dotenv";import{readFileSync as M}from"fs";import{dirname as w,join as S}from"path";import{fileURLToPath as C}from"url";v({path:[".env",".env.local"],override:!0});function T(){try{let n=C(import.meta.url),t=w(n),e=S(t,"..","package.json");return JSON.parse(M(e,"utf-8")).version||"unknown"}catch{return"unknown"}}function b(n){let t=parseInt(n,10);if(isNaN(t))throw new q("Must be a number.");return t}var h=T(),O=`
|
|
26
26
|
Copyright (c) 2025 Pascal Bourque
|
|
27
27
|
Licensed under the MIT License
|
|
28
28
|
|
|
29
29
|
Source code and documentation available at: https://github.com/bourquep/mysa2mqtt
|
|
30
|
-
`,
|
|
30
|
+
`,a=new _("mysa2mqtt").version(h).description("Expose Mysa smart thermostats to home automation platforms via MQTT.").addHelpText("afterAll",O).addOption(new s("-l, --log-level <logLevel>","log level").choices(["silent","fatal","error","warn","info","debug","trace"]).env("M2M_LOG_LEVEL").default("info").helpGroup("Configuration")).addOption(new s("-f, --log-format <logFormat>","log format").choices(["pretty","json"]).env("M2M_LOG_FORMAT").default("pretty").helpGroup("Configuration")).addOption(new s("-H, --mqtt-host <mqttHost>","hostname of the MQTT broker").env("M2M_MQTT_HOST").makeOptionMandatory().helpGroup("MQTT")).addOption(new s("-P, --mqtt-port <mqttPort>","port of the MQTT broker").env("M2M_MQTT_PORT").argParser(b).default(1883).helpGroup("MQTT")).addOption(new s("-U, --mqtt-username <mqttUsername>","username of the MQTT broker").env("M2M_MQTT_USERNAME").helpGroup("MQTT")).addOption(new s("-B, --mqtt-password <mqttPassword>","password of the MQTT broker").env("M2M_MQTT_PASSWORD").helpGroup("MQTT")).addOption(new s("-u, --mysa-username <mysaUsername>","Mysa account username").env("M2M_MYSA_USERNAME").makeOptionMandatory().helpGroup("Mysa")).addOption(new s("-p, --mysa-password <mysaPassword>","Mysa account password").env("M2M_MYSA_PASSWORD").makeOptionMandatory().helpGroup("Mysa")).addOption(new s("-s, --mysa-session-file <mysaSessionFile>","Mysa session file").env("M2M_MYSA_SESSION_FILE").default("session.json").helpGroup("Configuration")).addOption(new s("-N, --mqtt-client-name <mqttClientName>","name of the MQTT client").env("M2M_MQTT_CLIENT_NAME").default("mysa2mqtt").helpGroup("MQTT")).addOption(new s("-T, --mqtt-topic-prefix <mqttTopicPrefix>","prefix of the MQTT topic").env("M2M_MQTT_TOPIC_PREFIX").default("mysa2mqtt").helpGroup("MQTT")).parse().opts();import{readFile as A,rm as I,writeFile as P}from"fs/promises";async function g(n,t){try{t.info("Loading Mysa session...");let e=await A(n,"utf8");return JSON.parse(e)}catch{t.info("No valid Mysa session file found.")}}async function y(n,t,e){if(n)e.info("Saving Mysa session..."),await P(t,JSON.stringify(n));else try{e.debug("Removing Mysa session file..."),await I(t)}catch{}}import{Climate as D,Sensor as f}from"mqtt2ha";var c=class{constructor(t,e,i,u,d,l){this.mysaApiClient=t;this.mysaDevice=e;this.mqttSettings=i;this.logger=u;this.mysaDeviceFirmware=d;this.mysaDeviceSerialNumber=l;this.mqttDevice={identifiers:e.Id,name:e.Name,manufacturer:"Mysa",model:e.Model,sw_version:d==null?void 0:d.InstalledVersion,serial_number:l},this.mqttOrigin={name:"mysa2mqtt",sw_version:h,support_url:"https://github.com/bourquep/mysa2mqtt"},this.mqttClimate=new D({mqtt:this.mqttSettings,logger:this.logger,component:{component:"climate",device:this.mqttDevice,origin:this.mqttOrigin,unique_id:`mysa_${e.Id}_climate`,name:"Thermostat",min_temp:e.MinSetpoint,max_temp:e.MaxSetpoint,modes:["off","heat"],precision:.1,temp_step:.5,temperature_unit:"C",optimistic:!0}},["action_topic","current_humidity_topic","current_temperature_topic","mode_state_topic","temperature_state_topic"],async()=>{},["mode_command_topic","power_command_topic","temperature_command_topic"],async(r,o)=>{switch(r){case"mode_command_topic":this.mysaApiClient.setDeviceState(this.mysaDevice.Id,void 0,o==="off"?"off":o==="heat"?"heat":void 0);break;case"power_command_topic":this.mysaApiClient.setDeviceState(this.mysaDevice.Id,void 0,o==="OFF"?"off":o==="ON"?"heat":void 0);break;case"temperature_command_topic":o===""?this.mysaApiClient.setDeviceState(this.mysaDevice.Id,void 0,void 0):this.mysaApiClient.setDeviceState(this.mysaDevice.Id,parseFloat(o),void 0);break}}),this.mqttTemperature=new f({mqtt:this.mqttSettings,logger:this.logger,component:{component:"sensor",device:this.mqttDevice,origin:this.mqttOrigin,unique_id:`mysa_${e.Id}_temperature`,name:"Current temperature",device_class:"temperature",state_class:"measurement",unit_of_measurement:"\xB0C",suggested_display_precision:1,force_update:!0}}),this.mqttHumidity=new f({mqtt:this.mqttSettings,logger:this.logger,component:{component:"sensor",device:this.mqttDevice,origin:this.mqttOrigin,unique_id:`mysa_${e.Id}_humidity`,name:"Current humidity",device_class:"humidity",state_class:"measurement",unit_of_measurement:"%",suggested_display_precision:0,force_update:!0}}),this.mqttPower=new f({mqtt:this.mqttSettings,logger:this.logger,component:{component:"sensor",device:this.mqttDevice,origin:this.mqttOrigin,unique_id:`mysa_${e.Id}_power`,name:"Current power",device_class:"power",state_class:"measurement",unit_of_measurement:"W",suggested_display_precision:0,force_update:!0}})}isStarted=!1;mqttDevice;mqttOrigin;mqttClimate;mqttTemperature;mqttHumidity;mqttPower;mysaStatusUpdateHandler=this.handleMysaStatusUpdate.bind(this);mysaStateChangeHandler=this.handleMysaStateChange.bind(this);async start(){if(!this.isStarted){this.isStarted=!0;try{let e=(await this.mysaApiClient.getDeviceStates()).DeviceStatesObj[this.mysaDevice.Id];this.mqttClimate.currentTemperature=e.CorrectedTemp.v,this.mqttClimate.currentHumidity=e.Humidity.v,this.mqttClimate.currentMode=e.TstatMode.v===1?"off":e.TstatMode.v===3?"heat":void 0,this.mqttClimate.currentAction=this.computeCurrentAction(void 0,e.Duty.v),this.mqttClimate.targetTemperature=this.mqttClimate.currentMode!=="off"?e.SetPoint.v:void 0,await this.mqttClimate.writeConfig(),await this.mqttTemperature.setState("state_topic",e.CorrectedTemp.v.toFixed(2)),await this.mqttTemperature.writeConfig(),await this.mqttHumidity.setState("state_topic",e.Humidity.v.toFixed(2)),await this.mqttHumidity.writeConfig(),await this.mqttPower.setState("state_topic","None"),await this.mqttPower.writeConfig(),this.mysaApiClient.emitter.on("statusChanged",this.mysaStatusUpdateHandler),this.mysaApiClient.emitter.on("stateChanged",this.mysaStateChangeHandler),await this.mysaApiClient.startRealtimeUpdates(this.mysaDevice.Id)}catch(t){throw this.isStarted=!1,t}}}async stop(){this.isStarted&&(this.isStarted=!1,await this.mysaApiClient.stopRealtimeUpdates(this.mysaDevice.Id),this.mysaApiClient.emitter.off("statusChanged",this.mysaStatusUpdateHandler),this.mysaApiClient.emitter.off("stateChanged",this.mysaStateChangeHandler),await this.mqttPower.setState("state_topic","None"),await this.mqttTemperature.setState("state_topic","None"),await this.mqttHumidity.setState("state_topic","None"))}async handleMysaStatusUpdate(t){if(!(!this.isStarted||t.deviceId!==this.mysaDevice.Id)){if(this.mqttClimate.currentAction=this.computeCurrentAction(t.current,t.dutyCycle),this.mqttClimate.currentTemperature=t.temperature,this.mqttClimate.currentHumidity=t.humidity,this.mqttClimate.targetTemperature=this.mqttClimate.currentMode!=="off"?t.setPoint:void 0,t.current!=null){let e=this.mysaDevice.Voltage*t.current;await this.mqttPower.setState("state_topic",e.toFixed(2))}else await this.mqttPower.setState("state_topic","None");await this.mqttTemperature.setState("state_topic",t.temperature.toFixed(2)),await this.mqttHumidity.setState("state_topic",t.humidity.toFixed(2))}}async handleMysaStateChange(t){if(!(!this.isStarted||t.deviceId!==this.mysaDevice.Id))switch(t.mode){case"off":this.mqttClimate.currentMode="off",this.mqttClimate.currentAction="off",this.mqttClimate.targetTemperature=void 0;break;case"heat":this.mqttClimate.currentMode="heat",this.mqttClimate.targetTemperature=t.setPoint;break}}computeCurrentAction(t,e){switch(this.mqttClimate.currentMode==="heat"?"heat":this.mqttClimate.currentMode==="off"?"off":void 0){case"off":return"off";case"heat":return t!=null?t>0?"heating":"idle":(e??0)>0?"heating":"idle";default:return"idle"}}};var m=k({name:"mysa2mqtt",level:a.logLevel,transport:a.logFormat==="pretty"?{target:"pino-pretty",options:{colorize:!0,singleLine:!0,ignore:"hostname,module",messageFormat:"\x1B[33m[{module}]\x1B[39m {msg}"}}:void 0});async function F(){m.info("Starting mysa2mqtt...");let n=await g(a.mysaSessionFile,m),t=new x(n,{logger:new p(m.child({module:"mysa-js-sdk"}))});t.emitter.on("sessionChanged",async r=>{await y(r,a.mysaSessionFile,m)}),t.isAuthenticated||(m.info("Logging in..."),await t.login(a.mysaUsername,a.mysaPassword)),m.debug("Fetching devices and firmwares...");let[e,i]=await Promise.all([t.getDevices(),t.getDeviceFirmwares()]);m.debug("Fetching serial numbers...");let u=new Map;for(let[r]of Object.entries(e.DevicesObj))try{let o=await t.getDeviceSerialNumber(r);o&&u.set(r,o)}catch(o){m.error(o,`Failed to retrieve serial number for device ${r}`)}m.debug("Initializing MQTT entities...");let d={host:a.mqttHost,port:a.mqttPort,username:a.mqttUsername,password:a.mqttPassword,client_name:a.mqttClientName,state_prefix:a.mqttTopicPrefix},l=Object.entries(e.DevicesObj).map(([,r])=>new c(t,r,d,new p(m.child({module:"thermostat",deviceId:r.Id})),i.Firmware[r.Id],u.get(r.Id)));for(let r of l)await r.start()}F().catch(n=>{m.fatal(n,"Unexpected error"),process.exit(1)});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mysa2mqtt",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Expose Mysa smart thermostats to home automation platforms via MQTT.",
|
|
@@ -46,29 +46,29 @@
|
|
|
46
46
|
"build": "tsup"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"commander": "14.0.
|
|
50
|
-
"dotenv": "17.2.
|
|
51
|
-
"mqtt2ha": "4.1.
|
|
52
|
-
"mysa-js-sdk": "1.3.
|
|
53
|
-
"pino": "
|
|
54
|
-
"pino-pretty": "13.
|
|
49
|
+
"commander": "14.0.1",
|
|
50
|
+
"dotenv": "17.2.3",
|
|
51
|
+
"mqtt2ha": "4.1.2",
|
|
52
|
+
"mysa-js-sdk": "1.3.2",
|
|
53
|
+
"pino": "10.0.0",
|
|
54
|
+
"pino-pretty": "13.1.1"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@commander-js/extra-typings": "14.0.0",
|
|
58
|
-
"@eslint/js": "9.
|
|
59
|
-
"@semantic-release/npm": "12.0.
|
|
60
|
-
"@types/node": "24.
|
|
58
|
+
"@eslint/js": "9.37.0",
|
|
59
|
+
"@semantic-release/npm": "12.0.2",
|
|
60
|
+
"@types/node": "24.6.2",
|
|
61
61
|
"conventional-changelog-conventionalcommits": "9.1.0",
|
|
62
|
-
"eslint": "9.
|
|
63
|
-
"eslint-plugin-jsdoc": "
|
|
62
|
+
"eslint": "9.37.0",
|
|
63
|
+
"eslint-plugin-jsdoc": "60.8.1",
|
|
64
64
|
"eslint-plugin-tsdoc": "0.4.0",
|
|
65
|
-
"prettier": "3.
|
|
66
|
-
"prettier-plugin-jsdoc": "1.3.
|
|
67
|
-
"prettier-plugin-organize-imports": "4.
|
|
68
|
-
"semantic-release": "24.2.
|
|
65
|
+
"prettier": "3.6.2",
|
|
66
|
+
"prettier-plugin-jsdoc": "1.3.3",
|
|
67
|
+
"prettier-plugin-organize-imports": "4.3.0",
|
|
68
|
+
"semantic-release": "24.2.9",
|
|
69
69
|
"tsup": "8.5.0",
|
|
70
|
-
"tsx": "4.20.
|
|
71
|
-
"typescript": "5.
|
|
72
|
-
"typescript-eslint": "8.
|
|
70
|
+
"tsx": "4.20.6",
|
|
71
|
+
"typescript": "5.9.3",
|
|
72
|
+
"typescript-eslint": "8.45.0"
|
|
73
73
|
}
|
|
74
74
|
}
|