Add Support for Fujitsu wireless Air Conditioning control app - FGLair

Hi @vandenbogerd,

Have you followed the notes from @qwackers :
Add Support for Fujitsu wireless Air Conditioning control app - FGLair - #84 by qwackers

How is your file structure in your custom_components/fujitsu_general_heatpump folder

Have you enabled logging, described here:

Hi @philwilldo

Logger activated. Not going further than: “2019-11-11 09:34:42 ERROR (MainThread) [homeassistant.components.hassio] Platform error climate.fujitsu_general_heatpump - Integration ‘fujitsu_general_heatpump’ not found.”

Yes I followed the notes from @qwackers.

The filestructure is as followed:

I had to work on the electricity so put the power down for several minutes.
But still can’t turn the airco on with the climate unit.
I’ve activated the logger so see what turns out of that.

Hey All,

Anyone having issues with Home Assistant controlling their Fujitsu in the last week?

My hass install has been rock solid for months right up to a week ago coinciding with an update to the FGLAir app.

Logs throwing no errors, only evidence of not working is the A/C does not turn off or on. FGLAir app works perfectly…

How’s the experience for others?

Thanks Simon

1 Like

For me it isn’t working at all.

Not even status updates when i use the remote control

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!!')