Sinope Line Voltage Thermostats

I would love to get some help implementing a platform to control Sinope Thermostats. I found a python module to control them and updated it to work with Python 3.5. However I am unable to get it to install as a system wide module and I am having trouble figuring out how to write the platform code. Here is my modified pysinope module code.

import requests
#from .sinopeexceptions import InvalidSinopeAuthenticationError, UnknownSinopeError


class Thermostat(object):
    """ Implementation of a thermostat object of the Sinope interface."""
    MODE_MANUAL = "2"
    MODE_AUTOMATIC = "3"

    def __init__(self, json_thermostat=None, headers=None):
        self._headers = headers

        # Initialize the settings.
        self._active = None
        self._name = None
        self._gateway_id = None
        self._model = None
        self._type = None
        self._id = None

        # Initialize the parameters.
        self._alarm = None
        self._error_code = None
        self._heat_level = None
        self._mode = None
        self._rssi = None
        self._setpoint = None
        self._temperature = None
        self._name = None

        if json_thermostat:
            self.load_settings_from_json(json_thermostat)

    @property
    def temperature(self):
        return self._temperature

    @property
    def setpoint(self):
        return self._setpoint

    @setpoint.setter
    def setpoint(self, value):
        self._setpoint = value
        self.update_thermostat()

    @property
    def mode(self):
        return self._mode

    @mode.setter
    def mode(self, value):
        self._mode = value
        self.update_thermostat()

    @property
    def name(self):
        return self._name

    @property
    def id(self):
        return self._id

    def load_settings_from_json(self, json_thermostat):
        self._active = str(json_thermostat['active'])
        self._name = str(json_thermostat['name'])
        self._gateway_id = str(json_thermostat['gatewayId'])
        self._model = str(json_thermostat['model'])
        self._type = str(json_thermostat['type'])
        self._id = str(json_thermostat['id'])

    def load_parameters_from_json(self, json_thermostat):
        self._alarm = str(json_thermostat.get('alarm', self._alarm))
        self._error_code = str(json_thermostat.get('errorCode', self._error_code))
        self._heat_level = str(json_thermostat.get('heatLevel', self._heat_level))
        self._mode = str(json_thermostat.get('mode', self._mode))[:1]    # Mode is represented by the first digit.
        self._setpoint = str(json_thermostat.get('setpoint', self._setpoint))
        self._temperature = str(json_thermostat.get('temperature', self._temperature))

    def __str__(self):
        return str("Name : {}\n"
                       "Temperature : {}\n"
                       "Setpoint : {}\n"
                       "Heat level : {}%\n"
                       "Mode : {}").format(self._name,
                                           self._temperature,
                                           self._setpoint,
                                           self._heat_level,
                                           self._mode)

    def update_thermostat(self):
        params = {"temperature": self._setpoint,
                  "mode": self._mode}

        # Set the mode before the Temp.
        self._set_thermostat_value("mode", params)
        self._set_thermostat_value("setpoint", params)

    def _set_thermostat_value(self, name, params):
        r = requests.put("https://neviweb.com/api/device/{}/{}".format(self._id, name),
                         params,
                         headers=self._headers)

        self.load_parameters_from_json(r.json())


class Gateway(object):
    """Implementation of a Sinope's access point."""
    MODE_AWAY = '2'
    MODE_HOME = '0'

    def __init__(self, json_gateway=None, headers=None):
        self._headers = headers

        self._id = None
        self._mac = None
        self._name = None
        self._is_active = None
        self._city = None
        self._postal_code = None
        self._mode = None
        self._thermostats = list()

        if None is not json_gateway:
            self.load_from_json(json_gateway)

    def load_from_json(self, json_gateway):
        self._id = str(json_gateway.get('id', self._id))
        self._mac = str(json_gateway.get('mac', self._mac))
        self._name = str(json_gateway.get('name', self._name))
        self._is_active = bool(json_gateway.get('active', self._is_active))
        self._city = str(json_gateway.get('city', self._city))
        self._mode = str(json_gateway.get('mode', self._mode))
        self._postal_code = str(json_gateway.get('postalCode',
                                                     self._postal_code))

    @property
    def id(self):
        return self._id

    @property
    def name(self):
        return self._name

    @property
    def mode(self):
        return self._mode

    @mode.setter
    def mode(self, value):
        self._mode = value
        params = {"mode": self._mode}
        self._set_gateway_value('mode', params)

    @property
    def thermostats(self):
        return self._thermostats

    @thermostats.setter
    def thermostats(self, value):
        self._thermostats = value

    def __str__(self):
        thermostats = u', \n\n'.join(map(str,
                                         self._thermostats)).replace('\n',
                                                                     '\n    ')
        return str("ID : {}\n"
                       "MAC : {}\n"
                       "Name : {}\n"
                       "Active : {}\n"
                       "City : {}\n"
                       "Postal Code : {}\n"
                       "Thermostats : \n    {}").format(self._id,
                                                        self._mac,
                                                        self._name,
                                                        self._is_active,
                                                        self._city,
                                                        self._postal_code,
                                                        thermostats)

    def get_thermostat_by_name(self, name):
        for thermostat in self._thermostats:
            if thermostat.name == name:
                return thermostat
        raise Exception('Thermostat {} not found.'.format(name))

    def _set_gateway_value(self, name, params):
        r = requests.post("https://neviweb.com/api/gateway/{}/{}".format(self._id, name),
                          params,
                          headers=self._headers)
        test = r.text
        print(test)
        self.load_from_json(r.json())


class PySinope(object):

    def __init__(self):
        """

        @return:
        """
        self._headers = {'Content-type': r'application/x-www-form-urlencoded'}
        self._session_id = None
        self._gateways = list()

    def connect(self, email, password):
        login_parameters = {'email': email,
                            'password': password,
                            'stayConnected': "0"}

        r = requests.post("https://neviweb.com/api/login",
                          data=login_parameters,
                          headers=self._headers)
        response = r.json()
        self._session_id = response['session']
        self._headers['Session-Id'] = self._session_id
        return self

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):

        requests.get("https://neviweb.com/api/logout",
                     headers=self._headers)
        return self

    def read_gateway(self):
        r = requests.get("https://neviweb.com/api/gateway",
                     headers=self._headers)

        for json_gateway in r.json():
            self._gateways.append(
                Gateway(json_gateway, self._headers))

        for gateway in self._gateways:
            r = requests.get(
                    "https://neviweb.com/api/device?gatewayId={}".format(gateway.id),
                    headers=self._headers)

            for json_thermostat in r.json():
                gateway.thermostats.append(Thermostat(
                    json_thermostat, self._headers))

            for thermostat in gateway.thermostats:
                self.read_thermostat(thermostat)

    def read_thermostat(self, thermostat):
        r = requests.get(
                "https://neviweb.com/api/device/{}/data?".format(thermostat.id),
                headers=self._headers)

        thermostat.load_parameters_from_json(r.json())

    @property
    def gateways(self):
        return self._gateways

    def get_gateway_by_name(self, name):
        for gateway in self._gateways:
            if gateway.name == name:
                return gateway
        raise Exception('Gateway {} not found.'.format(name))

class InvalidSinopeAuthenticationError(Exception):
    def __init__(self, email):
        super(InvalidSinopeAuthenticationError,
              self).__init__("Invalid authentication for '{}'.".format(email))


class UnknownSinopeError(Exception):
    def __init__(self, details):
        super(UnknownSinopeError,
              self).__init__("Unknown error from the Sinope interface. Details : '{}'.".format(details))

Unfortunately I’m not much help on the HASS side, I do have 13 Sinope modules so I can help test and I’ve written a Sinope module in Perl when I was using Openhab so can help troubleshoot the code as I do know some Python.

Hello,

I just purchased Sinopé Thermostats and will create a component to integrate them in Home Assistant.
I contacted Sinopé to see if they can provide the API documentation so I won’t need to “deconstruct” their API through the web interface.

Using Curl, by injecting the current Session-Id from my browser, I’m able to get thermostats information in JSON and change settings easily. However, a documentation from Sinopé could help to build a sturdier component.

I’ll keep you posted.

1 Like

Last time I asked they told me they don’t give it out to individuals or something like that… kind of a silly stance. The website is using the API so was I was able to get most of it from there. If you want I can send you a perl script I wrote that does pretty much everything you can do with the website. It’s not well documented but should be easy to read if you know perl.

You can get API access, you just have to request it and answer a few questions. I requested it myself as I was hoping to work on the HASS integration but have had absolutely no time to do so at this point.

Hi,

Yep. They just answered me with a couple of questions:

1-      Êtes-vous un programmeur/développeur?
2-      Combien d’années d’expérience cumulez-vous?
3-      Quels langages de programmation maîtrisez-vous?
4-      Combien d’appareils Sinopé possédez-vous présentement et combien prévoyez-vous acheter? 

Sorry it’s in french, I figured I should talk to them in French since it’s a company from Québec.

I’ll let you know if I get the documentation. I’ll not post it.

Sinopé are really fast and nice.
They sent me all the docs for the API on the Hub and all the electronics datasheet for the different Sinopé modules.

Let the work begins!

1 Like

Awesome, I would be happy to test when you get something written.

Can I ask why you guys went with these instead of https://www.amazon.ca/Stelpro-STZW402WB-Thermostat-Baseboards-Convectors/dp/B01BYL1OAS

Stelpro?

I am looking at it now. I have no problem contributing etc to a Sinope solution but just wondering if you saw advantages / disadvantages versus the stelpro units. about the same$.

thx

For me the Stelpro’s were not out yet, really the only smart thermostat’s available at the time were the Sinope’s. If I bought new I’d probably go with the Stelpro’s as they are zwave and you are not locked into one vendor and can control them without the “cloud”.

With that said the Sinope’s are really good, the gui is great and you can do all your scheduling from in and don’t have to integrate it with anything else if you don’t want.

I’m looking into the Sinope thermostats as well and am hoping support can be added. I’m especially looking into Sinope since they have wireless dimmer/light switches that connect to existing 3-way wiring systems.

Has there been any progress in adding support?

I am still working on it. I can read all the values, I’m working on the API to “set the values”.

Great. Thank you @lrogoff! I’m going to put in my order today for the thermostats. Depending on shipping I can definitely help with some testing

I’m almost done with the “sensor” part. Meaning that I can read all the thermostats from a username, password and gateway id (the hub).
I’ll convert that to HA sensors to see if I can successfully get the temperatures from my rooms in HA. Note that this is the first time I’m coding for HA also I’m not normally a python dev, but code is code… so I’ll learn.

Results:

Sinope data for username: lavrenti
Room: Bathroom
Id: 74192
Wattage: 373
Set Point: 17
Temperature: 28.4

Room: Living Room
Id: 74012
Wattage: 3515
Set Point: 17
Temperature: 28.8

Room: Office
Id: 74015
Wattage: 1669
Set Point: 17
Temperature: 29.4

image

Voilà! I was able to convert my test code to a climate component. The reading and integration are working very nicely.
I’ll start to work on the “set temperature” function soon.

I’ll probably not integration all the functions available from Neviweb but minimum a way to set and check temperatures.

Guys,

I’m happy to say that my component is working successfully.
The setup is very simple, just provide username, password and gateway name (it will search for the ID automatically).

Example of config:

climate:
  - platform: sinope
    username: '[email protected]'
    password: 'SinopeJeTaime'
    gateway: 'Maison'

image

What’s working:

  • Reading current temperature of each thermostats
  • Reading target temperature of each thermostats
  • Reading state (Idle or Heat) of each thermostats
  • Changing target temperature (ByPass Thermostat mode)

I’ll setup a github so you can test on your end.

Summary

This text will be hidden

Great news, thank you for working on this. I’ve ordering my thermostats and should get them next week. I’ll also see if I can the sinope light switches working as well.

Great stuff! I was just sipping coffee this morning, wondering how I was going to script-my-way to the API, thinking that I’d start with a command-line sensor as the first exercice :blush:

Haha thanks guys.
I’ll put it on github when I’ll have few minutes.
I don’t know how I’ll get that component into a next HA release but I’ll try to see how. Right now, it’s been working pretty great. I added a script for each thermostat to be able to control it from Alexa.

Working like a charm.

  living_room_temperature:
    sequence:
      - service: climate.set_temperature
        data_template:
          entity_id: climate.sinope_living_room
          temperature: "{{ requested_level }}"
  office_temperature:
    sequence:
      - service: climate.set_temperature
        data_template:
          entity_id: climate.sinope_office
          temperature: "{{ requested_level }}"
  bathroom_temperature:
    sequence:
      - service: climate.set_temperature
        data_template:
          entity_id: climate.sinope_bathroom
          temperature: "{{ requested_level }}"

Just installed my thermostats today, would you be able to share your code @lrogoff? My light switches arrive tomorrow so hopefully next week I can put some cycles into getting those setup.