sinricpro / python-sdk

python3 sdk for sinric pro.
https://sinric.pro
23 stars 9 forks source link

Not able to set Thermostat Mode #67

Closed sharon-elihis closed 3 weeks ago

sharon-elihis commented 4 weeks ago

I am able to set thermostat temperature according to the example: https://github.com/sinricpro/python-sdk/blob/master/examples/temperature_sensor.py But I am not able to set thermostat mode. I tried the following:

 client.event_handler.raise_event(THERMOSTAT_SENSOR_ID,
                                   SinricProConstants.SET_THERMOSTAT_MODE,
                                   data= {SinricProConstants.THERMOSTATMODE : 'HEAT'})

 client.event_handler.raise_event(THERMOSTAT_SENSOR_ID,
                                   SinricProConstants.SET_THERMOSTAT_MODE,
                                   data= {SinricProConstants.SET_MODE : 'HEAT'})

But I'm getting an error: 'LeakyBucket' object has no attribute 'add_dropp'. Looks like anytime I tried SET_THERMOSTAT_MODE it gives me the same error.

But when I try SET_MODE:

        client.event_handler.raise_event(THERMOSTAT_SENSOR_ID,
                                          SinricProConstants.SET_MODE,
                                          data= {SinricProConstants.THERMOSTATMODE : 'HEAT'})

        client.event_handler.raise_event(THERMOSTAT_SENSOR_ID,
                                          SinricProConstants.SET_MODE,
                                          data= {SinricProConstants.MODE : 'HEAT'})

I don't get error but Mode was not changed, instead the portal shows a message that my device is online every time I trigger the event SET_MODE.

Also, seems like Thermostat modes (COOL, HEAT, AUTO, OFF) do not exists in the SinricProConstants (https://github.com/sinricpro/python-sdk/blob/master/sinric/_sinricpro_constants.py)

Maybe separate issue: I'm able to set power state on and off, but in addition it will also show a message that my device is online every time I trigger it.

        client.event_handler.raise_event(TEMPERATURE_SENSOR_ID,
                                          SinricProConstants.SET_POWER_STATE,
                                          data= {SinricProConstants.STATE : SinricProConstants.POWER_STATE_ON})

When I try to use SinricProConstants.POWER_STATE (instead of STATE), which is more intuitive I think, it doesn't work.

        client.event_handler.raise_event(TEMPERATURE_SENSOR_ID,
                                          SinricProConstants.SET_POWER_STATE,
                                          data= {SinricProConstants.POWER_STATE : SinricProConstants.POWER_STATE_ON})
kakopappa commented 4 weeks ago

Hi @sharon-elihis

I used the following example and did not see the 'LeakyBucket' object has no attribute 'add_dropp' error.

Are you on the latest SDK 2.7.3?

from sinric import SinricPro, SinricProConstants
import asyncio
from asyncio import sleep

APP_KEY = ""
APP_SECRET = ""
THERMOSTAT_ID = ""

def power_state(device_id, state):
    print('device_id: {} state: {}'.format(device_id, state))
    return True, state

def target_temperature(device_id, temperature):
    print('device_id: {} set temperature: {}'.format(device_id, temperature))
    return True, temperature

def set_thermostate_mode(device_id, thermostat_mode):
    print('device_id: {} set thermostat mode: {}'.format(device_id, thermostat_mode))
    return True, thermostat_mode

def mode_value(device_id, mode_value):
    print(device_id, mode_value)
    return True, mode_value

async def events():
    while True:
        client.event_handler.raise_event(THERMOSTAT_ID, SinricProConstants.SET_THERMOSTAT_MODE, data= { SinricProConstants.MODE : 'COOL'})
        # client.event_handler.raise_event(THERMOSTAT_ID, SinricProConstants.SET_POWER_STATE, data= {SinricProConstants.STATE: SinricProConstants.POWER_STATE_OFF})
        # client.event_handler.raise_event(THERMOSTAT_ID, SinricProConstants.CURRENT_TEMPERATURE, data={'humidity': 75.3, 'temperature': 24})
        # Server will trottle / block IPs sending events too often.
        await sleep(60)

callbacks = {
    SinricProConstants.SET_POWER_STATE: power_state,
    SinricProConstants.TARGET_TEMPERATURE: target_temperature,
    SinricProConstants.SET_THERMOSTAT_MODE: set_thermostate_mode
}

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    client = SinricPro(APP_KEY, [THERMOSTAT_ID], callbacks, event_callbacks=events,
                       enable_log=True, restore_states=False, secret_key=APP_SECRET)
    loop.run_until_complete(client.connect())

image image

  1. SinricProConstants.SET_MODE has no effect.

  2. I will release a new version with the modes in the constant py.

sharon-elihis commented 3 weeks ago

Hi @kakopappa Thank you for the very fast reply and screenshots.

Yes, I am using 2.7.3

image

I took your example and only changed those 3 ids and it didn't work for me. I didn't get leakybucket error - that was because I did something stupid on my end. but it just didn't do the event, and again it popped up a message that my device is online - every time I run it.

image image

But if I comment out that event and uncomment the CURRENT_TEMPERATURE event it does work, and that "device is online" message does not popup.

image image image

I'm using python 3.12.5, not sure if it matters.

kakopappa commented 3 weeks ago

Is it possible that to share your code without credentials

sharon-elihis commented 3 weeks ago

It's a full copy-paste from the example code you provided, I only changed the 3 ids from sinric import SinricPro, SinricProConstants import asyncio from asyncio import sleep

APP_KEY = ''
APP_SECRET = ''
THERMOSTAT_ID = ""

def power_state(device_id, state):
    print('device_id: {} state: {}'.format(device_id, state))
    return True, state

def target_temperature(device_id, temperature):
    print('device_id: {} set temperature: {}'.format(device_id, temperature))
    return True, temperature

def set_thermostate_mode(device_id, thermostat_mode):
    print('device_id: {} set thermostat mode: {}'.format(device_id, thermostat_mode))
    return True, thermostat_mode

def mode_value(device_id, mode_value):
    print(device_id, mode_value)
    return True, mode_value

async def events():
    while True:
        client.event_handler.raise_event(THERMOSTAT_ID, SinricProConstants.SET_THERMOSTAT_MODE, data= { SinricProConstants.MODE : 'COOL'}) #*** this doesn't work for me
        # client.event_handler.raise_event(THERMOSTAT_ID, SinricProConstants.SET_POWER_STATE, data= {SinricProConstants.STATE: SinricProConstants.POWER_STATE_OFF})
        #client.event_handler.raise_event(THERMOSTAT_ID, SinricProConstants.CURRENT_TEMPERATURE, data={'humidity': 75.3, 'temperature': 11}) # *** this does works for me
        # Server will trottle / block IPs sending events too often.
        await sleep(60)

callbacks = {
    SinricProConstants.SET_POWER_STATE: power_state,
    SinricProConstants.TARGET_TEMPERATURE: target_temperature,
    SinricProConstants.SET_THERMOSTAT_MODE: set_thermostate_mode
}

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    client = SinricPro(APP_KEY, [THERMOSTAT_ID], callbacks, event_callbacks=events,
                       enable_log=True, restore_states=False, secret_key=APP_SECRET)
    loop.run_until_complete(client.connect())
kakopappa commented 3 weeks ago

I think I know why.

 elif response_cmd == SinricProConstants.SET_THERMOSTAT_MODE:
                if self.bucket.add_dropp(): << -- should be add_drop():

Sometime back we fixed this but never released it. So it was working on my local but not in production.

Please install the new version: 2.7.5 https://pypi.org/project/sinricpro/2.7.5/

sharon-elihis commented 3 weeks ago

Thank you for the change, I've update the package and it works - partially. Still using the example code you sent me, now it does triggers the event but the Mode does not update on the device UI in the SinricPro portal. I have to refresh the page and then it shows the change. But when I change the temperature via python, the UI portal reflects it immediately. Also, the esp8266 doesn't get the Mode change if triggered from python, but if I change the Mode via the portal UI, the esp8266 receives it properly.

sharon-elihis commented 3 weeks ago

Also, how can I adjust the target-temperature? I tried the following: client.event_handler.raise_event(THERMOSTAT_ID, SinricProConstants.ADJUST_TARGET_TEMPERATURE, data={'temperature': 33}) I get an error: adjustTargetTemperature not found!

client.event_handler.raise_event(THERMOSTAT_ID, SinricProConstants.TARGET_TEMPERATURE, data={'temperature': 33}) I get an error: targetTemperature not found! EDIT: When I change the target-temperature from the portal then python callback shows "payload": {"action": "targetTemperature" ...

kakopappa commented 3 weeks ago
  1. client.event_handler.raise_event(THERMOSTAT_ID, SinricProConstants.TARGET_TEMPERATURE, data= { SinricProConstants.TEMPERATURE : 10})

This has been fixed now. Please use https://pypi.org/project/sinricpro/2.7.7/

  1. Portal not automatically refreshing issue: I noticed this too. Will release a fix tomorrow.

  2. " the esp8266 doesn't get the Mode change if triggered from python, but if I change the Mode via the portal UI, the esp8266 receives it properly."

Yes. this is the intended behavior. Python and ESP8266 are clients. They send commands to the server, the server processes them. You can't send a command from client to client

kakopappa commented 3 weeks ago

Are you trying to control the ESP8266? If Yes, then you should use the API to send a command to ESP8266 https://help.sinric.pro/pages/tutorials/api-guide

sharon-elihis commented 3 weeks ago

The fix works. Yes now I understand, I will likely need to use the API. Unrelated to python, can I use the API from ESP8266 instead of having it as an official client? Being a SinricPro client on the ES8266 is blocking the device from doing api calls to another api/service. I think you replied to my email about it. It might be lack of memory.

If I can use the API from ESP8266, is there a way I can have a sudo client that will pretend the device is online just to be able to use the API?

kakopappa commented 3 weeks ago

You can call the SinricPro API to send a command to SinricPro device (same like portal or app) however it will not pretend to be a device in the system.

esp-8266-32-sdk and python sdk are used to represent devices in the system. They connect and receive a commands and send a response or an event.

If you can share more details about the project, we can try to help you to find the best solution.

sharon-elihis commented 3 weeks ago

I would make a feature suggestion of having a device "always online", to be able to use it with the API only, without a running client.

Thank you for offering to review my project. I use the esp8266 to control a servo motor based on temperature DS18B20. image I setup the esp as a "thermostat" device type on the SinricPro platform, it works great. The main reason I chose SinricPro was the Google Home integration, and it has been working great. Recently I found an API to interface with my wireless temperature sensors, they are connected to the main thermostat - Wyze thermostat and room sensors). I wanted to use them instead of the DS18B20 because they are already where I need them and it would simplify the board by having one less cable coming out of it. The challenge now is that I can't seem to call the Wyze API from the esp while having SinricPro.handle() running, it crashes and restarts the ESP, maybe memory issue. I haven't checked if it is working on the 32 but I'm already invested with the 8266. If there was a way to keep the device online without actually having a client than I would use API calls on the ESP for both SinricPro and Wyze. I guess now I'll have the python do those Wyze API calls and send it to the ESP SinricPro client via the SinricPro API. I do wonder if micropython would work.

Any recommendations?

sharon-elihis commented 3 weeks ago

Using the API, which event do I use to set the temperature (current temperature, not target temperature) With python as a client this works client.event_handler.raise_event(THERMOSTAT_ID, SinricProConstants.CURRENT_TEMPERATURE, data={'humidity': 75.3, 'temperature': 11}) In the ESP, this works: myThermostat.sendTemperatureEvent (temperatureC,humidity);

In the API this worked to set the TargetTemprature (not current temperature) ...&action=targetTemperature&value={"temperature": "25"}

For temperature (current temperature) I tried different things for the API and it wouldn't work.

...&action=temperature&value={"temperature": "25"}
...&action=setTemperature&value={"temperature": "25"}
...&action=currentTemperature&value={"temperature": "25"}
...&action=setCurrentTemperature&value={"temperature": "25"}

When I attempted these, I get these messages in the portal image

EDIT: By getting all devices using the API, I see the list of actions per device 'actions': ['setPowerState', 'targetTemperature', 'setThermostatMode'] There is no way to set the current temperature via API? only via clients?

I see in https://github.com/sinricpro/sample_messages/tree/master

What is an action?

Act of doing something using voice, app or website will generate an action message in the system. Eg: Alexa, turn on the tv will generate setPowerState action.

What is an event ?

Changing the device state physically should raise an event to let the server know about the changes the user made. Eg: pushing a button to turn on the device should send "setPowerState" event to let the server know.

Events cannot be triggered from the API?

kakopappa commented 3 weeks ago

Thanks for taking the time to explain what you are trying to achieve.

My suggestions are

  1. Use an ESP32.

Simply put ESP8266 cannot handle HTTPS and HTTP connections plus whatever you have. If you have added #define SINRICPRO_NOSSL on top of the sketch and still fail, you could try investing more by enabling logs to make sure it's related to memory or something else.

To enable ESP8266 logs, in Arduino IDE: Tools -> Debug Serial Port -> Serial Tools -> Debug Level -> SSL + HTTP_CLIENT

  1. Use the Python SDK, call wyze via wyze-sdk and then call client.event_handler.raise_event(THERMOSTAT_ID, SinricProConstants.CURRENT_TEMPERATURE, data={'humidity': 75.3, 'temperature': 24})

  2. Create a separate temperature sensor device type, call Wyze API, and send the event

You can't set the current temperature using the API. Current temperature is something that senses and notifies to the server via events from client sdks

  1. You could "hack" the API to do this, but it's not the right approach. Using the API in an unintended way may work now but break in the future.

GET https://api.sinric.pro/api/v1/devices//action?clientId=portal&type=event&action=currentTemperature&createdAt=&value=

eg:

GET https://api.sinric.pro/api/v1/devices/66c6c060deddexxxxxxxx/action?clientId=portal&type=event&action=currentTemperature&createdAt=1724461613&value=%7B%22temperature%22:19%7D

createdAt = unix time in seconds value = URL encoded.

image

BTW: like the persistence

sharon-elihis commented 3 weeks ago

Thank you again for the quick replies and all the great suggestions and information. I like #4 and I understand it might stop working in the future but for now I already want to make it work and then continue and improve it.

Use an ESP32. I will try just to see if it works on it.

If you have added #define SINRICPRO_NOSSL on top of the sketch and still fail I did try it and it didn't help.

To enable ESP8266 logs, in Arduino IDE:
Tools -> Debug Serial Port -> Serial
Tools -> Debug Level -> SSL + HTTP_CLIENT

I tried it before and the output didn't help me understand what is happening but I can paste it here, maybe you can help me "decode" it.

Use the Python SDK, call wyze via wyze-sdk and then call client.event_handler.raise_event(THERMOSTAT_ID, SinricProConstants.CURRENT_TEMPERATURE, data={'humidity': 75.3, 'temperature': 24})

But this means the same device_id would be used both in the python SDK client and in the ESP8266 cleint, When I did it before I think I noticed it makes the device offline, which make sense as it confuses the server who is the real client(?)

You can't set the current temperature using the API. Current temperature is something that senses and notifies to the server via events from client sdks Yes I understand, it makes perfect sense, however it wouldn't hurt anything if the API would have that capability, even though I admit it is an edge scenario.

createdAt = unix time in seconds What is this used for? what happens if I put a random value in it that doesn't represent that "now"?

sharon-elihis commented 3 weeks ago

I tried GET https://api.sinric.pro/api/v1/devices/<YOUR_DEVICE_ID>/action?clientId=portal&type=event&action=currentTemperature&createdAt=<TIME_STAMP>&value= and it does change the temperature in the portal but I realise it won't change the temp in the esp client. I see there is no event for that obviusly because the esp is supposed to be the one that send the temperature form the physical sensor. Do you have an advise how I can archive it? Meaning to send the temp to the esp.

kakopappa commented 3 weeks ago

Hello,

I think you can skip the timestamp, it’s filled by the server. It’s used to avoid replay attacks. Server will reject the response if it’s older than 4 mins

If you change “event” to “request” it should send the temperature to the esp

GET https://api.sinric.pro/api/v1/devices//action?clientId=portal&type=request …..

There are sample messages here for events and requests

https://github.com/sinricpro/sample_messages

sharon-elihis commented 3 weeks ago
If you change “event” to “request” it should send the temperature to the esp

GET https://api.sinric.pro/api/v1/devices/<YOUR_DEVICE_ID>/action?clientId=portal&type=request …..

clientId=portal&type=request&action=currentTemperature&createdAt=1550493108338&value=%7B%22temperature%22 Sorry for the silly question, which event would it fire on the ESP?

image
kakopappa commented 3 weeks ago

Please try

clientId=portal&type=request&action=targetTemperature&createdAt=1724721376&value=%7B%22temperature%22

Your createdAt (1550493108338) seems to be in milliseconds. please change it to seconds.

https://github.com/sinricpro/sample_messages/blob/master/07_Temperature/01_targetTemperature/01_Request.json

this would trigger the onTargetTemperature

https://github.com/sinricpro/esp8266-esp32-sdk/blob/master/examples/Thermostat/Thermostat.ino#L49C6-L49C25

sharon-elihis commented 3 weeks ago

that is for targetTemperature, but I'm looking for a way to send the esp the currentTemperature

sharon-elihis commented 3 weeks ago

I ended up doing the worlds ugliest hack... To send the currentTemp from python to the ESP using the SinricPro API, I piggyback on the TargetTemp to use the onTargetTemperature event on the ESP. TargetTemp allows long float value like 21.53456, so I've added the currentTemp value at the end of the actual TargetTemp. Once received at the esp I check if there is anything after the 1st fracture digit, if no it just changes the TargetTemp, if yes it needs to handle it. It extract the embedded temp value from the TargetTemp and sends the event sendTemperatureEvent i.e. when TargetTemp = 21.53456 then CurrentTemp = 34.56 and TargetTemp = 21.5 Even though the portal set-point increment is 1 degree, Google Home increment are 0.5.

This issue can be closed. Thank you again for all the help and tips.

kakopappa commented 3 weeks ago

Indeed.

Should consider moving to ESP32 in the future (any project)