mathieu-mp / aio-intex-spa

Python client for Intex Spa wifi interface
https://pypi.org/project/aio-intex-spa/
MIT License
17 stars 2 forks source link

MQTT implementation #30

Closed TRIROG closed 12 months ago

TRIROG commented 1 year ago

Awesome work @mathieu-mp !

Compared to intex app which if flaky and moody, this is straight forward and works without a hitch. I was wondering if there is any chance to implement MQTT support as this would make this solution even more universal.

mathieu-mp commented 1 year ago

Hi @TRIROG,

Thanks for your feedback!

This python package aims to provide an interface with the Intex Spa wifi module. It can be used as the core interface to the spa for any type of client. I personally use the home assistant intex spa integration as a client. An intex spa mqtt python package could be one of these.

As I have nearly no experience with mqtt, nor the use of such a package, I wouldn't be in a good position to develop it. But I would gladly offer my support to help using intex-spa from a new client. I can even improve the API to ease development of a new client.

First step would be: what would the MQTT interface of the intex-spa-mqtt client look like (topics for status and commands) ?

Regards, Mathieu.

mathieu-mp commented 1 year ago

I must add that such a client would still not be native to the spa and would just pass data to and from MQTT to the spa control panel connected via wifi. It would still need to run python, as a background service. Could be inside a docker container, for example.

TRIROG commented 1 year ago

@mathieu-mp hi!

Of course, i understand completely! The package itself is great, and of course i understand that since you use HAS, you need nothing more.

I have in mind a simple aproach: client that would periodically (every minute) submit status in JSON to /status topic. There could be a callback that would subscribe to /command topic for different types of commands and upon executing a command a status update would occur to /status topic to reflect the change (e.g. heater on, temperature change, ...)

I'm trying to hack up something now (but my programing skills are on a beginner level).

Currently i'm stuck on trying to convert the output of intex_spa.async_update_status() function to JSON format. json.dumps doesn't seem to like the output from intex_spa.async_update_status(). is the output a standard python dict format?

jurajs5 commented 1 year ago

Mqtt will be great. I would love to test it.

mathieu-mp commented 1 year ago

@TRIROG

IntexSpa method async_update_status() returns an IntexSpaStatus object. (Note the -> IntexSpaStatus). IntexSpaStatus itself offers an as_dict() method to return its status as a dict.

So you could run this:

"""Usage example file"""
import os
import logging
import asyncio

from intex_spa.intex_spa import IntexSpa

SPA_ADDRESS = os.getenv("SPA_ADDRESS") or "SPA_DEVICE"

logging.basicConfig(level=logging.DEBUG)

async def example_intex_spa():
    """Example for intex_spa"""
    intex_spa = IntexSpa(SPA_ADDRESS)

    spa_status = await intex_spa.async_update_status()
    spa_status.as_dict() # here is your dict

asyncio.run(example_intex_spa())
TRIROG commented 1 year ago

Yes. I was going through the code and came across this.

below is what i came up with today (with my limited knowledge). not much but it should work. @jurajs5 feel free to use it if it helps, but please note that commands for jets and sanitizer are not included as i have no need for them.

to control a feature send a single mqtt formated command e.g. {"bubbles": true} to turn on bubbles.

most likely could be optimized... but i'll leave this for some rainy day ...

import logging
import asyncio
import json
import time

from paho.mqtt import client as mqtt_client
from intex_spa.intex_spa import IntexSpa

SPA_ADDRESS = '#POOL IP HERE#'
broker = '#MQTT BROKER IP HERE"'
port = 1883
topic_status = 'spa2MQTT/status'
topic_command = 'spa2MQTT/CMD'
# Generate a Client ID with the subscribe prefix.
#client_id = f'subscribe-{random.randint(0, 100)}'
client_id = 'spa2MQTT'
# username = 'emqx'
# password = 'public'

logging.basicConfig(level=logging.DEBUG)

def connect_mqtt() -> mqtt_client:
    def on_connect(client, userdata, flags, rc):
        if rc == 0:
            print("Connected to MQTT Broker!")
        else:
            print("Failed to connect, return code %d\n", rc)

    client = mqtt_client.Client(client_id)
    # client.username_pw_set(username, password)
    client.on_connect = on_connect
    client.connect(broker, port)
    return client

def subscribe(client: mqtt_client):
    def on_message(client, userdata, msg):
        logging.info(f"Received `{msg.payload.decode()}` from `{msg.topic}` topic")
        command = json.loads(msg.payload.decode()) # {'power': True, 'filter': False, 'heater': False, 'jets': False, 'bubbles': False, 'sanitizer': False, 'unit': '°C', 'current_temp': 34, 'preset_temp': 37, 'error_code': False}
        if 'power' in command:
            if command['power']:
                logging.debug("power ON command received")
                asyncio.run(intex_spa_power_on(client))
            else:
                logging.debug("power OFF command received")
                asyncio.run(intex_spa_power_off(client))
        elif 'filter' in command:
            if command['filter']:
                logging.debug("filter ON command found")
                asyncio.run(intex_spa_filter_on(client))
            else:
                logging.debug("filter OFF command received")
                asyncio.run(intex_spa_filter_off(client))
        elif 'heater' in command:
            if command['heater']:
                logging.debug("heater ON command found")
                asyncio.run(intex_spa_heater_on(client))
            else:
                logging.debug("heater OFF command received")
                asyncio.run(intex_spa_heater_off(client))
        elif 'bubbles' in command:
            if command['bubbles']:
                logging.debug("bubbles ON command found")
                asyncio.run(intex_spa_bubbles_on(client))
            else:
                logging.debug("bubbles OFF command found")
                asyncio.run(intex_spa_bubbles_off(client))
        elif 'preset_temp' in command:
            logging.debug("preset_temp command found")
            logging.debug("preset_temp set to: ",command['preset_temp'] )
            preset_temp = command['preset_temp']
            asyncio.run(intex_spa_set_temperature(client,preset_temp))

    client.subscribe(topic_command)
    client.on_message = on_message

async def intex_spa_status(client):
    intex_spa = IntexSpa(SPA_ADDRESS)
    data = await intex_spa.async_update_status()
    message_status = json.dumps(data.as_dict())
    client.publish(topic_status, message_status)

async def intex_spa_power_on(client):
    intex_spa = IntexSpa(SPA_ADDRESS)
    data = await intex_spa.async_set_power()
    message_response = json.dumps(data.as_dict())
    client.publish(topic_status, message_response)

async def intex_spa_power_off(client):
    intex_spa = IntexSpa(SPA_ADDRESS)
    data = await intex_spa.async_set_power(False)
    message_response = json.dumps(data.as_dict())
    client.publish(topic_status, message_response)

async def intex_spa_filter_on(client):
    intex_spa = IntexSpa(SPA_ADDRESS)
    data = await intex_spa.async_set_filter()
    message_response = json.dumps(data.as_dict())
    client.publish(topic_status, message_response)

async def intex_spa_filter_off(client):
    intex_spa = IntexSpa(SPA_ADDRESS)
    data = await intex_spa.async_set_filter(False)
    message_response = json.dumps(data.as_dict())
    client.publish(topic_status, message_response)

async def intex_spa_heater_on(client):
    intex_spa = IntexSpa(SPA_ADDRESS)
    data = await intex_spa.async_set_heater()
    message_response = json.dumps(data.as_dict())
    client.publish(topic_status, message_response)

async def intex_spa_heater_off(client):
    intex_spa = IntexSpa(SPA_ADDRESS)
    data = await intex_spa.async_set_heater(False)
    message_response = json.dumps(data.as_dict())
    client.publish(topic_status, message_response)

async def intex_spa_bubbles_on(client):
    intex_spa = IntexSpa(SPA_ADDRESS)
    data = await intex_spa.async_set_bubbles()
    message_response = json.dumps(data.as_dict())
    client.publish(topic_status, message_response)

async def intex_spa_bubbles_off(client):
    intex_spa = IntexSpa(SPA_ADDRESS)
    data = await intex_spa.async_set_bubbles(False)
    message_response = json.dumps(data.as_dict())
    client.publish(topic_status, message_response)

async def intex_spa_set_temperature(client,preset_temp):
    intex_spa = IntexSpa(SPA_ADDRESS)
    data = await intex_spa.async_set_preset_temp(preset_temp)
    message_response = json.dumps(data.as_dict())
    client.publish(topic_status, message_response)

def run():
    client = connect_mqtt()
    subscribe(client)
    client.loop_start()
    while True:
        asyncio.run(intex_spa_status(client))
        time.sleep(60)

if __name__ == '__main__':

    run()
stale[bot] commented 1 year ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.