Add Support for Fujitsu wireless Air Conditioning control app - FGLair

Hi, I just went from Hassbian to Hassio and needed to get this thing working again.
(HassOS 2.12, Hass.io 192)

Here is my working setup:

  1. Get the files:
    https://github.com/xerxes87/pyfujitsu_for_homeassistant/tree/master/custom_components/fujitsu_general_heatpump
  2. Put files in the directory as shown below:
    image
  3. Get these files too:
    https://github.com/xerxes87/pyfujitseu/releases/tag/0.9.3.0
  4. Put those files here:
    image
  5. I needed to do some modifications on the api.py to get it working for eu region. My complete modified file is shown below:
import logging
import requests
import time
import os
import json

HEADER_CONTENT_TYPE = "Content-Type"
HEADER_VALUE_CONTENT_TYPE = "application/json"
HEADER_AUTHORIZATION = "Authorization"

#version 0.9.2.7

_LOGGER = logging.getLogger(__name__)

def _api_headers(access_token=None):
    headers = {
        HEADER_CONTENT_TYPE: HEADER_VALUE_CONTENT_TYPE
    }

    if access_token:
        headers[HEADER_AUTHORIZATION] = 'auth_token ' + access_token

    return headers

class Api:
    def __init__(self,username,password,region='us',tokenpath='token.txt'):
        self.username = username
        self.password = password
        self.region = region
        
        if region == 'eu':
            _LOGGER.info('Region eu')
            self._SIGNIN_BODY = "{\r\n    \"user\": {\r\n        \"email\": \"%s\",\r\n        \"application\": {\r\n            \"app_id\": \"FGLair-eu-id\",\r\n            \"app_secret\": \"FGLair-eu-gpFbVBRoiJ8E3QWJ-QRULLL3j3U\"\r\n        },\r\n        \"password\": \"%s\"\r\n    }\r\n}"
            self._API_GET_ACCESS_TOKEN_URL = "https://user-field-eu.aylanetworks.com/users/sign_in.json"
            API_BASE_URL = "https://ads-field-eu.aylanetworks.com/apiv1/"
        elif region == 'cn':
            _LOGGER.info('Region cn')
            self._SIGNIN_BODY = "{\r\n    \"user\": {\r\n        \"email\": \"%s\",\r\n        \"application\": {\r\n            \"app_id\": \"FGLairField-cn-id\",\r\n            \"app_secret\": \"FGLairField-cn-zezg7Y60YpAvy3HPwxvWLnd4Oh4\"\r\n        },\r\n        \"password\": \"%s\"\r\n    }\r\n}"
            self._API_GET_ACCESS_TOKEN_URL = "https://user-field.ayla.com.cn/users/sign_in.json"
            API_BASE_URL = "https://ads-field.ayla.com.cn/apiv1/"
        else:
            _LOGGER.info('Region us')
            self._SIGNIN_BODY = "{\r\n    \"user\": {\r\n        \"email\": \"%s\",\r\n        \"application\": {\r\n            \"app_id\": \"CJIOSP-id\",\r\n            \"app_secret\": \"CJIOSP-Vb8MQL_lFiYQ7DKjN0eCFXznKZE\"\r\n        },\r\n        \"password\": \"%s\"\r\n    }\r\n}"
            self._API_GET_ACCESS_TOKEN_URL = "https://user-field.aylanetworks.com/users/sign_in.json"
            API_BASE_URL = "https://ads-field.aylanetworks.com/apiv1/"
        
        self._API_GET_PROPERTIES_URL = API_BASE_URL + "dsns/{DSN}/properties.json"
        self._API_SET_PROPERTIES_URL = API_BASE_URL + "properties/{property}/datapoints.json"
        self._API_GET_DEVICES_URL =  API_BASE_URL + "devices.json"

        self._ACCESS_TOKEN_FILE = tokenpath
    
    def _get_devices(self,access_token=None):
        if not self._check_token_validity(access_token):

          ## Token invalid requesting authentication
            access_token = self._authenticate()
        response = self._call_api("get",self._API_GET_DEVICES_URL,access_token=access_token)
        return response.json()

    def get_devices_dsn(self, access_token=None):
        devices = self._get_devices()
        devices_dsn = []
        for device in devices:
            devices_dsn.append(device['device']['dsn'])
        return devices_dsn
      
    def _get_device_properties(self,dsn):
        access_token = self._read_token()
        if not self._check_token_validity(access_token):
            access_token = self._authenticate()

        response = self._call_api("get",self._API_GET_PROPERTIES_URL.format(DSN=dsn),access_token=access_token)
        return response.json()

    def _set_device_property(self,propertyCode,value):
        access_token = self._read_token()
        if not self._check_token_validity(access_token):
            access_token = self._authenticate()

        response = self._call_api("post",self._API_SET_PROPERTIES_URL.format(property=propertyCode),propertyValue=value,access_token=access_token)

        return response
    
    def _get_device_property(self,propertyCode):
        access_token = self._read_token()
        if not self._check_token_validity(access_token):
            access_token = self._authenticate()

        response = self._call_api("get",self._API_SET_PROPERTIES_URL.format(property=propertyCode),access_token=access_token)
        ## Pay Attention the response is a HTTP request response object 
        #  and by doing .json you would get a List
        return response


    def _check_token_validity(self,access_token=None):
        if not access_token:
            return False        
        try:
            self._call_api("get",self._API_GET_DEVICES_URL,access_token=access_token)
        except:
            return False        
        return True


    def _authenticate(self):
        
        response = self._call_api("POST",
         self._API_GET_ACCESS_TOKEN_URL,
         json=self._SIGNIN_BODY % (self.username,self.password),
         headers= _api_headers())

        response.json()['time'] = int(time.time())

        access_token = response.json()['access_token']
    
        #refresh_token = response.json()['refresh_token']
        #expires_in = response.json()['expires_in']

        fd = os.open(self._ACCESS_TOKEN_FILE, os.O_WRONLY | os.O_CREAT, 0o777)
        f = open(fd, "w")

        #f = open(self._ACCESS_TOKEN_FILE, "w")
        f.write(response.text) 

        
        return access_token
    
    def _read_token(self,access_token_file=None):
        if not access_token_file:
            access_token_file = self._ACCESS_TOKEN_FILE
        if (os.path.exists(access_token_file) and os.stat(access_token_file).st_size != 0):
            f = open(access_token_file, "r")
            access_token_file_content = f.read()

            #now = int(time.time())

            access_token = json.loads(access_token_file_content)['access_token']
            #refresh_token = access_token_file_content.json()['refresh_token']
            #expires_in = access_token_file_content.json()['expires_in']
            #auth_time = int(access_token_file_content.json()['time'])
            return access_token
        else:
            return self._authenticate()


    
    def _call_api(self, method, url, access_token=None, **kwargs):
        payload = ''
                
        
        if "propertyValue" in kwargs:
            propertyValue = kwargs.get("propertyValue")
            kwargs["json"] = '{\"datapoint\": {\"value\": '+ str(propertyValue) +' } }'
        payload = kwargs.get("json")

        if "headers" not in kwargs:
            if access_token:
                kwargs["headers"] = _api_headers(access_token=access_token)
            else:
                kwargs["headers"] = _api_headers()
        
        if method.lower() == 'post':
            if not payload:
              raise Exception('Post method needs a request body!')

        
        response = requests.request(method, url, data=kwargs.get("json"),headers=kwargs.get("headers"))
        response.raise_for_status()
        return response

  1. My configuration.yaml setup:
    image

  2. token file location:
    image

  3. Restart Home Assistant

  4. Works like a dream!
    image

1 Like

I have same issue. what I have to insert in token.txt file?

Nothing, the token file will (should) be generated by the script.

I was eventually able to get this working. Even though Iā€™m in the US region i also needed to make the same adjustments to the api.py file.

Hi,

I do have an issue sending a service to the AC.
I get

Failed to call service climate/set_hvac_mode. Service not found.

error massage.

Does anyone know, what is missing?
I am a newbie at Hass.io

The rest of the Fujitsu integration seems to work fine.

Thanks

Since upgrading to HA 0.108 the plug-in fails to load. Is anyone else seeing issues after upgrading.

Error while setting up fujitsu_general_heatpump platform for climate

Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/entity_platform.py", line 178, in _async_setup_platform
    await asyncio.wait_for(asyncio.shield(task), SLOW_SETUP_MAX_WAIT)
  File "/usr/local/lib/python3.7/asyncio/tasks.py", line 442, in wait_for
    return fut.result()
  File "/usr/local/lib/python3.7/concurrent/futures/thread.py", line 57, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/config/custom_components/fujitsu_general_heatpump/climate.py", line 77, in setup_platform
    if not fglairapi._authenticate():
  File "/config/deps/lib/python3.7/site-packages/pyfujitsu/api.py", line 111, in _authenticate
    headers= _api_headers())
  File "/config/deps/lib/python3.7/site-packages/pyfujitsu/api.py", line 17, in _api_headers
    HEADER_CONTENT_TYPE: HEADER_VALUE_CONTENT_TYPE
NameError: name 'HEADER_VALUE_CONTENT_TYPE' is not defined

I do have a similar/same issue:

Logger: homeassistant.helpers.service
Source: helpers/service.py:382 
First occurred: April 13, 2020, 10:59:59 PM (8 occurrences) 
Last logged: 10:51:54 AM

Unable to find referenced entities climate.sahan

Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/entity_platform.py", line 178, in _async_setup_platform
    await asyncio.wait_for(asyncio.shield(task), SLOW_SETUP_MAX_WAIT)
  File "/usr/local/lib/python3.7/asyncio/tasks.py", line 442, in wait_for
    return fut.result()
  File "/usr/local/lib/python3.7/concurrent/futures/thread.py", line 57, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/config/custom_components/fujitsu_general_heatpump/climate.py", line 77, in setup_platform
    if not fglairapi._authenticate():
  File "/config/deps/lib/python3.7/site-packages/pyfujitsu/api.py", line 120, in _authenticate
    fd = os.open(self._ACCESS_TOKEN_FILE, os.O_WRONLY | os.O_CREAT, 0o777)
TypeError: open: path should be string, bytes or os.PathLike, not NoneType
2020-04-13 22:44:03 WARNING (MainThread) [homeassistant.setup] Setup of default_config is taking over 10 seconds.
2020-04-13 22:46:11 ERROR (MainThread) [homeassistant.components.websocket_api.http.connection.2940654448] must contain at least one of temperature, target_temp_high, target_temp_low.
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/voluptuous/schema_builder.py", line 272, in __call__
    return self._compiled([], data)
  File "/usr/local/lib/python3.7/site-packages/voluptuous/schema_builder.py", line 817, in validate_callable
    return schema(data)
  File "/usr/src/homeassistant/homeassistant/helpers/config_validation.py", line 115, in validate
    raise vol.Invalid("must contain at least one of {}.".format(", ".join(keys)))
voluptuous.error.Invalid: must contain at least one of temperature, target_temp_high, target_temp_low.

I posted the topic here as well: https://github.com/Mmodarre/pyfujitsu_for_homeassistant/issues/14

After a reinstall of the plugin it started working again. Made no changes so not sure why it was failing.

Hi @vicfalls, has the plugin generated a token.txt file in your config folder?

Hi @vicfalls, I am also using the modified code from this post.

Add Support for Fujitsu wireless Air Conditioning control app - FGLair

yes it did and I had to make a hard shutdown of the HASS, unplug - waited about 1 minute - and restarted the system again. All error messages were gone and the HASS is communicating with the AC. It seems, that the system got hung somehow.

Hey guys,

Is anyone able to help me get this up and running please?
I keep getting this error when completing a configuration validationā€¦

Configuration invalid
Platform error climate.fujitsu_general_heatpump - Integration ā€˜fujitsu_general_heatpumpā€™ not found.

The code Iā€™m using is available for checking
hereā€¦https://drive.google.com/drive/folders/1K2N1hcDta0gJYlIUhMS7b8a4UA-TdMFp?usp=sharing

I am NZ based.
Running Home Assistant Core 0.108.9
Run in Docker.

Many thanks in advance for any help!

Phil.

look at this post.

that might help.
It combines the things everybody mentioned to get mine working

I didnā€™t modify the api.py file after reinstalling today.

Thanks for your reply Marco!
Success!

It is noteworthy that a restart within HA doesnā€™t kick the integration into life, a full docker container restart was needed.

I have tried mapping the display temperature value found in the json device properties to the current_temperature property in climate.py with the following code in SplitAC.py. It is a bit of a guess though on the assumption it is in Fahrenheit, does anyone know how to translate the display_temperature correctly?

    @property ##property to get temperature in degree C
    def display_temperature_degree(self): return round(((self._display_temperature['value'] / 100 - 32) / 9 * 5),1)
    
    @property ## property returns temperature dict in 10 times of degree C
    def display_temperature(self): return self._display_temperature(self)
    
    @display_temperature.setter
    def display_temperature(self,properties):
        if isinstance(properties,(list, tuple)):
            self._display_temperature = self._get_prop_from_json('display_temperature',properties)
        elif isinstance(properties,int) or isinstance(properties,float):
            self._api._set_device_property(self.display_temperature['key'],properties)
            self.refresh_properties()
        else:
            raise Exception('Wrong usage of the method!!')

Have you been able to verify that the display_temperature value is indeed the actual temperature being detected by the unit?
The adjust_temperature value is in Celsius, so it would seem a little strange that the display_temperature would be in Fahrenheit, but itā€™s entirely possible.

I got this working as well. With a lot of time spent trying to figure out why token.txt was not working, I finally found the solution to be giving it an absolute path in the configuration.yaml. Once I did that, the token.txt file that I had created and given permissions to was populated and the errors went away during startup.

Iā€™m new to this and not that adept with Linux to begin with. I had to figure out where to put the site-packages stuff. Iā€™m on a Raspberry Pi using venv installation and the location was under /svr/homeassistant where I needed to put that portion.

Now, the obvious, airflow direction control does not work. Minimum Heat option under operation mode is missing. Powerful mode isnā€™t there either. Economy Settings are missing. Maintenance settings are missing as well. Iā€™ll attach a screenshot to show what settings I have. Iā€™m not complaining, just trying to provide information for the smarter people out there that will hopefully work on this later.

Out of interest, has anyone had any problems with this recently?

Itā€™s been working flawlessly for me, and then just recently things have stopped working - specifically, with turning on the ā€˜heatā€™ function. (I have a ducted Fujitsu split AC that can do both heat and cooling). The weird thing is that I can put it on cool, or dry, and can change the temperature and fan speed, but if I try and put it on heat, it doesnā€™t work?!

It seemed to be when I upgraded to 0.111, but I donā€™t know if that is what caused it? Or if it was the update to their app or at their server?

Why only the heat? Any ideas anyone?

Just got my AC units installed, works perfectly with this integration, thanks to all contributors.
For those looking to get the current_temperature:
display_temperature does indeed look like the current temperature in Fahrenheit.

To get it mapped, I modified only climate.py as you can get all properties, including display_temperature from the SplitAC object:

    @property
    def current_temperature(self):
    	cur_temp = self._fujitsu_device._get_prop_from_json('display_temperature', self._fujitsu_device._properties)
    	return round((cur_temp['value'] / 100 - 32) * 5/9, 1)
2 Likes

Hi all, I have tried to get this working multiple times but fail miserably. Before Success!

The raspberry pi 4, 2GB is currently running homeassistant 111.4 on HassOS 4.10 (32bit)

I have followed the instructions from Ole Andor Hansen on the 1/12/19.
These instructions have a small error! The folder should

I used samba to create the folders and copy the files from a windows 10 machine.
I donā€™t know if my issue is because of this and the permissions are not correct.(?)

If i add the following to the configuration.yaml and reboot then all my other (previously working) climate entities stop working, and the fujitsu doesnā€™t work either.

climate:
  - platform: fujitsu_general_heatpump
    username: ********@live.com.au
    password: *******
    region: 'us'
    tokenpath: /config/token.txt

If i comment those rungs out of the configuration.yaml then the other climates work and check configuration takes about 1-2 seconds to run.

If i uncomment this and ā€œcheck configurationā€ then the check configuration hangs.

Summary of my questions:

  1. Was using samba incorrect cos it may have messed with permissions?
    How do I fix this - or how else do you put those files on?
  2. Should the user name have the full email address?
  3. If I am in Australia and in the FGLair app selected location Australia, do I use region ā€˜usā€™?

ls -l on fujitsu_general_heatpump shows all 3 files with -rwxrā€“r-- permissions
ls -l on pyfujitsu shows all 3 files with - rwxrā€“r-- permissions

Please let me know if you need me to post any logs or anything.

[Other climate devices that are working include ESPhome connected Mitsubishi ACs and Daikin ACs.]

Edit:
Success!

To answer my own questions.

  1. Permissions were fine.
  2. Yes, User name is the full email address
  3. Yes, even if your region is Australia in the app - use region ā€˜usā€™ in the configuration.yaml

What I changed to get it to work:
Right click save link as in git hub doesnt save the file. I am a github noob and couldnā€™t see how to download the files so i ended up creating the files and copy and pasting the code into them.

I think there is an error in Ole Andor Hansens post on the 1/12/19 which is causing the errors some of the others are getting. The path should be \config\deps\lib\python3.7\site-packages\pyfujitseu (pyfujitsā€™Eā€™u)