mirror of
https://github.com/bourquep/mysa2mqtt.git
synced 2025-11-02 04:39:40 +00:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4069f24880 | ||
|
|
a47cdbb45e | ||
|
|
2e075cd40d | ||
|
|
f2d35a1ca5 | ||
|
|
122ffde2f1 | ||
|
|
c259557da0 | ||
|
|
57bd430c98 | ||
|
|
8ca80acb49 | ||
|
|
3b5dafeda9 | ||
|
|
35f2effe9c | ||
|
|
c29eae97ed | ||
|
|
6502b76b77 | ||
|
|
39d0e64dc0 | ||
|
|
49c7a0fd8e | ||
|
|
36539b17b1 | ||
|
|
e1bd2e3a91 | ||
|
|
be163eddca | ||
|
|
8a8ab7ab07 | ||
|
|
a2f47220bd | ||
|
|
ec166cce61 | ||
|
|
d2f7c73d84 | ||
|
|
1bfb7e3add | ||
|
|
e9f2335c38 | ||
|
|
96114d2e91 | ||
|
|
21bc257b22 | ||
|
|
16a82f93f4 | ||
|
|
a95aee6c27 | ||
|
|
20b2866ee4 | ||
|
|
dd23fca857 |
22
.all-contributorsrc
Normal file
22
.all-contributorsrc
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"files": ["README.md"],
|
||||||
|
"imageSize": 100,
|
||||||
|
"commit": false,
|
||||||
|
"commitType": "docs",
|
||||||
|
"commitConvention": "angular",
|
||||||
|
"contributors": [
|
||||||
|
{
|
||||||
|
"login": "remiolivier",
|
||||||
|
"name": "remiolivier",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/1379047?v=4",
|
||||||
|
"profile": "https://github.com/remiolivier",
|
||||||
|
"contributions": ["code"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"contributorsPerLine": 7,
|
||||||
|
"skipCi": true,
|
||||||
|
"repoType": "github",
|
||||||
|
"repoHost": "https://github.com",
|
||||||
|
"projectName": "mysa2mqtt",
|
||||||
|
"projectOwner": "bourquep"
|
||||||
|
}
|
||||||
46
README.md
46
README.md
@@ -1,5 +1,11 @@
|
|||||||
# mysa2mqtt
|
# mysa2mqtt
|
||||||
|
|
||||||
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||||
|
|
||||||
|
[](#contributors-)
|
||||||
|
|
||||||
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||||
|
|
||||||
[](https://www.npmjs.com/package/mysa2mqtt)
|
[](https://www.npmjs.com/package/mysa2mqtt)
|
||||||
[](https://hub.docker.com/r/bourquep/mysa2mqtt)
|
[](https://hub.docker.com/r/bourquep/mysa2mqtt)
|
||||||
[](https://github.com/bourquep/mysa2mqtt/actions/workflows/github-code-scanning/codeql)
|
[](https://github.com/bourquep/mysa2mqtt/actions/workflows/github-code-scanning/codeql)
|
||||||
@@ -18,13 +24,13 @@ home automation platforms.
|
|||||||
|
|
||||||
## Supported hardware
|
## Supported hardware
|
||||||
|
|
||||||
| Model Number | Description | Supported |
|
| Model Number | Description | Supported |
|
||||||
| ------------ | --------------------------------------------------------- | ---------------------------------------------------------------- |
|
| ------------ | --------------------------------------------------------- | -------------------------------------------------------------------- |
|
||||||
| `BB-V1-X` | Mysa Smart Thermostat for Electric Baseboard Heaters V1 | ✅ Tested and working |
|
| `BB-V1-X` | Mysa Smart Thermostat for Electric Baseboard Heaters V1 | ✅ Tested and working |
|
||||||
| `BB-V2-X` | Mysa Smart Thermostat for Electric Baseboard Heaters V2 | ⚠️ Should work but not tested |
|
| `BB-V2-X` | Mysa Smart Thermostat for Electric Baseboard Heaters V2 | ⚠️ Partially working, in progress |
|
||||||
| `BB-V2-X-L` | Mysa Smart Thermostat LITE for Electric Baseboard Heaters | ⚠️ Should work but not tested; does not report power consumption |
|
| `BB-V2-X-L` | Mysa Smart Thermostat LITE for Electric Baseboard Heaters | ⚠️ Partially working, in progress; does not report power consumption |
|
||||||
| `unknown` | Mysa Smart Thermostat for Electric In-Floor Heating | ⚠️ Should work but not tested |
|
| `unknown` | Mysa Smart Thermostat for Electric In-Floor Heating | ⚠️ Should work but not tested |
|
||||||
| `AC-V1-X` | Mysa Smart Thermostat for Mini-Split Heat Pumps & AC | 🚫 Not supported (yet) |
|
| `AC-V1-X` | Mysa Smart Thermostat for Mini-Split Heat Pumps & AC | 🚫 Not supported (yet) |
|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
|
|
||||||
@@ -213,13 +219,11 @@ When using Home Assistant, devices will be automatically discovered and appear i
|
|||||||
### Common Issues
|
### Common Issues
|
||||||
|
|
||||||
1. **Authentication Failures**
|
1. **Authentication Failures**
|
||||||
|
|
||||||
- Verify your Mysa username and password
|
- Verify your Mysa username and password
|
||||||
- Check if session.json exists and is valid
|
- Check if session.json exists and is valid
|
||||||
- Try deleting session.json to force re-authentication
|
- Try deleting session.json to force re-authentication
|
||||||
|
|
||||||
2. **MQTT Connection Issues**
|
2. **MQTT Connection Issues**
|
||||||
|
|
||||||
- Verify MQTT broker hostname and port
|
- Verify MQTT broker hostname and port
|
||||||
- Check MQTT credentials if authentication is required
|
- Check MQTT credentials if authentication is required
|
||||||
- Ensure the MQTT broker is accessible from your network
|
- Ensure the MQTT broker is accessible from your network
|
||||||
@@ -364,7 +368,6 @@ copyright notice and license text in any copy of the software or substantial por
|
|||||||
## Acknowledgments
|
## Acknowledgments
|
||||||
|
|
||||||
- [mysa-js-sdk](https://github.com/bourquep/mysa-js-sdk) - Mysa API client library
|
- [mysa-js-sdk](https://github.com/bourquep/mysa-js-sdk) - Mysa API client library
|
||||||
|
|
||||||
- This library would not be possible without the amazing work by [@dlenski](https://github.com/dlenski) in his
|
- This library would not be possible without the amazing work by [@dlenski](https://github.com/dlenski) in his
|
||||||
[mysotherm](https://github.com/dlenski/mysotherm) repository. He's the one who reversed-engineered the Mysa MQTT
|
[mysotherm](https://github.com/dlenski/mysotherm) repository. He's the one who reversed-engineered the Mysa MQTT
|
||||||
protocol which is being used by this library.
|
protocol which is being used by this library.
|
||||||
@@ -372,3 +375,26 @@ copyright notice and license text in any copy of the software or substantial por
|
|||||||
- [mqtt2ha](https://github.com/bourquep/mqtt2ha) - MQTT to Home Assistant bridge library
|
- [mqtt2ha](https://github.com/bourquep/mqtt2ha) - MQTT to Home Assistant bridge library
|
||||||
- [Commander.js](https://github.com/tj/commander.js) - Command-line argument parsing
|
- [Commander.js](https://github.com/tj/commander.js) - Command-line argument parsing
|
||||||
- [Pino](https://github.com/pinojs/pino) - Fast JSON logger
|
- [Pino](https://github.com/pinojs/pino) - Fast JSON logger
|
||||||
|
|
||||||
|
## Contributors ✨
|
||||||
|
|
||||||
|
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
||||||
|
|
||||||
|
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
|
<!-- markdownlint-disable -->
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/remiolivier"><img src="https://avatars.githubusercontent.com/u/1379047?v=4?s=100" width="100px;" alt="remiolivier"/><br /><sub><b>remiolivier</b></sub></a><br /><a href="https://github.com/bourquep/mysa2mqtt/commits?author=remiolivier" title="Code">💻</a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- markdownlint-restore -->
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
|
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||||
|
|
||||||
|
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification.
|
||||||
|
Contributions of any kind welcome!
|
||||||
|
|||||||
4289
package-lock.json
generated
4289
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
38
package.json
38
package.json
@@ -46,29 +46,29 @@
|
|||||||
"build": "tsup"
|
"build": "tsup"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"commander": "14.0.0",
|
"commander": "14.0.1",
|
||||||
"dotenv": "16.5.0",
|
"dotenv": "17.2.3",
|
||||||
"mqtt2ha": "4.1.0",
|
"mqtt2ha": "4.1.2",
|
||||||
"mysa-js-sdk": "1.2.0",
|
"mysa-js-sdk": "2.0.0",
|
||||||
"pino": "9.7.0",
|
"pino": "10.1.0",
|
||||||
"pino-pretty": "13.0.0"
|
"pino-pretty": "13.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commander-js/extra-typings": "14.0.0",
|
"@commander-js/extra-typings": "14.0.0",
|
||||||
"@eslint/js": "9.28.0",
|
"@eslint/js": "9.38.0",
|
||||||
"@semantic-release/npm": "12.0.1",
|
"@semantic-release/npm": "13.1.1",
|
||||||
"@types/node": "22.15.30",
|
"@types/node": "24.9.2",
|
||||||
"conventional-changelog-conventionalcommits": "9.0.0",
|
"conventional-changelog-conventionalcommits": "9.1.0",
|
||||||
"eslint": "9.28.0",
|
"eslint": "9.39.0",
|
||||||
"eslint-plugin-jsdoc": "50.7.1",
|
"eslint-plugin-jsdoc": "61.1.11",
|
||||||
"eslint-plugin-tsdoc": "0.4.0",
|
"eslint-plugin-tsdoc": "0.4.0",
|
||||||
"prettier": "3.5.3",
|
"prettier": "3.6.2",
|
||||||
"prettier-plugin-jsdoc": "1.3.2",
|
"prettier-plugin-jsdoc": "1.5.0",
|
||||||
"prettier-plugin-organize-imports": "4.1.0",
|
"prettier-plugin-organize-imports": "4.3.0",
|
||||||
"semantic-release": "24.2.5",
|
"semantic-release": "25.0.1",
|
||||||
"tsup": "8.5.0",
|
"tsup": "8.5.0",
|
||||||
"tsx": "4.19.4",
|
"tsx": "4.20.6",
|
||||||
"typescript": "5.8.3",
|
"typescript": "5.9.3",
|
||||||
"typescript-eslint": "8.33.1"
|
"typescript-eslint": "8.46.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ SOFTWARE.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Logger } from 'mqtt2ha';
|
import { Logger } from 'mqtt2ha';
|
||||||
import { pino } from 'pino';
|
import pino from 'pino';
|
||||||
|
|
||||||
export class PinoLogger implements Logger {
|
export class PinoLogger implements Logger {
|
||||||
constructor(private readonly logger: pino.Logger) {}
|
constructor(private readonly logger: pino.Logger) {}
|
||||||
@@ -32,7 +32,7 @@ export class PinoLogger implements Logger {
|
|||||||
if (obj) {
|
if (obj) {
|
||||||
this.logger.debug(obj, message, ...meta);
|
this.logger.debug(obj, message, ...meta);
|
||||||
} else {
|
} else {
|
||||||
this.logger.debug(message, ...meta);
|
this.logger.debug(null, message, ...meta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ export class PinoLogger implements Logger {
|
|||||||
if (obj) {
|
if (obj) {
|
||||||
this.logger.info(obj, message, ...meta);
|
this.logger.info(obj, message, ...meta);
|
||||||
} else {
|
} else {
|
||||||
this.logger.info(message, ...meta);
|
this.logger.info(null, message, ...meta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ export class PinoLogger implements Logger {
|
|||||||
if (obj) {
|
if (obj) {
|
||||||
this.logger.warn(obj, message, ...meta);
|
this.logger.warn(obj, message, ...meta);
|
||||||
} else {
|
} else {
|
||||||
this.logger.warn(message, ...meta);
|
this.logger.warn(null, message, ...meta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ export class PinoLogger implements Logger {
|
|||||||
if (obj) {
|
if (obj) {
|
||||||
this.logger.error(obj, message, ...meta);
|
this.logger.error(obj, message, ...meta);
|
||||||
} else {
|
} else {
|
||||||
this.logger.error(message, ...meta);
|
this.logger.error(null, message, ...meta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ async function main() {
|
|||||||
serialNumbers.set(deviceId, serial);
|
serialNumbers.set(deviceId, serial);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
rootLogger.error(`Failed to retrieve serial number for device ${deviceId}`, error);
|
rootLogger.error(error, `Failed to retrieve serial number for device ${deviceId}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ function getPackageVersion(): string {
|
|||||||
*
|
*
|
||||||
* @param value - The value to parse.
|
* @param value - The value to parse.
|
||||||
* @returns The parsed integer value.
|
* @returns The parsed integer value.
|
||||||
* @throws InvalidArgumentError if the value is not a valid integer.
|
|
||||||
*/
|
*/
|
||||||
function parseRequiredInt(value: string) {
|
function parseRequiredInt(value: string) {
|
||||||
const parsedValue = parseInt(value, 10);
|
const parsedValue = parseInt(value, 10);
|
||||||
|
|||||||
@@ -187,18 +187,22 @@ export class Thermostat {
|
|||||||
try {
|
try {
|
||||||
const deviceStates = await this.mysaApiClient.getDeviceStates();
|
const deviceStates = await this.mysaApiClient.getDeviceStates();
|
||||||
const state = deviceStates.DeviceStatesObj[this.mysaDevice.Id];
|
const state = deviceStates.DeviceStatesObj[this.mysaDevice.Id];
|
||||||
|
const tstatMode = state.TstatMode?.v;
|
||||||
|
|
||||||
this.mqttClimate.currentTemperature = state.CorrectedTemp.v;
|
this.mqttClimate.currentTemperature = state.CorrectedTemp?.v;
|
||||||
this.mqttClimate.currentHumidity = state.Humidity.v;
|
this.mqttClimate.currentHumidity = state.Humidity?.v;
|
||||||
this.mqttClimate.currentMode = state.TstatMode.v === 1 ? 'off' : state.TstatMode.v === 3 ? 'heat' : undefined;
|
this.mqttClimate.currentMode = tstatMode === 1 ? 'off' : tstatMode === 3 ? 'heat' : undefined;
|
||||||
this.mqttClimate.currentAction = this.computeCurrentAction(undefined, state.Duty.v);
|
this.mqttClimate.currentAction = this.computeCurrentAction(undefined, state.Duty?.v);
|
||||||
this.mqttClimate.targetTemperature = this.mqttClimate.currentMode !== 'off' ? state.SetPoint.v : undefined;
|
this.mqttClimate.targetTemperature = this.mqttClimate.currentMode !== 'off' ? state.SetPoint?.v : undefined;
|
||||||
await this.mqttClimate.writeConfig();
|
await this.mqttClimate.writeConfig();
|
||||||
|
|
||||||
await this.mqttTemperature.setState('state_topic', state.CorrectedTemp.v.toFixed(2));
|
await this.mqttTemperature.setState(
|
||||||
|
'state_topic',
|
||||||
|
state.CorrectedTemp != null ? state.CorrectedTemp.v.toFixed(2) : 'None'
|
||||||
|
);
|
||||||
await this.mqttTemperature.writeConfig();
|
await this.mqttTemperature.writeConfig();
|
||||||
|
|
||||||
await this.mqttHumidity.setState('state_topic', state.Humidity.v.toFixed(2));
|
await this.mqttHumidity.setState('state_topic', state.Humidity != null ? state.Humidity.v.toFixed(2) : 'None');
|
||||||
await this.mqttHumidity.writeConfig();
|
await this.mqttHumidity.writeConfig();
|
||||||
|
|
||||||
// `state.Current.v` always has a non-zero value, even for thermostats that are off, so we can't use it to determine initial power state.
|
// `state.Current.v` always has a non-zero value, even for thermostats that are off, so we can't use it to determine initial power state.
|
||||||
@@ -242,7 +246,7 @@ export class Thermostat {
|
|||||||
this.mqttClimate.currentHumidity = status.humidity;
|
this.mqttClimate.currentHumidity = status.humidity;
|
||||||
this.mqttClimate.targetTemperature = this.mqttClimate.currentMode !== 'off' ? status.setPoint : undefined;
|
this.mqttClimate.targetTemperature = this.mqttClimate.currentMode !== 'off' ? status.setPoint : undefined;
|
||||||
|
|
||||||
if (status.current != null) {
|
if (this.mysaDevice.Voltage != null && status.current != null) {
|
||||||
const watts = this.mysaDevice.Voltage * status.current;
|
const watts = this.mysaDevice.Voltage * status.current;
|
||||||
await this.mqttPower.setState('state_topic', watts.toFixed(2));
|
await this.mqttPower.setState('state_topic', watts.toFixed(2));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
|
"lib": ["ES2022"],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user