Hisense air conditioning in Home Assistant - the k8s way

Posted on May 18, 2026 • 5 min read • 969 words
How to control your Hisense air conditioners in Home Assistant while running deiger software on Kubernetes
Hisense air conditioning in Home Assistant - the k8s way

I use Home Assistant as my home automation system. With every new purchase, I look to see if there is an option to control that appliance or service through Home Assistant. This is the same with our Fujitsu air conditioning system.

It is important to me that I can control appliances without the Internet. That doesn’t always work. With Fujitsu air conditioners, you have a cloud service that normally allows you to control your air conditioner via an app. That also means that when the cloud service is down or we don’t have internet, I have to look for the remote control. So I don’t want that.

That’s where the container from deiger, aircon comes in. This allows you to control the air conditioners locally, even though there is some key exchange with the cloud service. I haven’t delved into this voodoo very much yet, but at least it works.

Aircon is an interface between mqtt and the air conditioners. Home Assistant gets the information from the air conditioners through mqtt and can also control the air conditioners that way.

Kubernetes  

I had a number of docker hosts in the past. It sometimes happened that one of the hosts had to go down for maintenance, or crashed, for example due to memory shortage on my virtualization host. The services running on top of that docker host were then temporarily unavailable. Not a big deal, you might say, but it often meant that I had to get to work. Rebooting the host, troubleshooting what had gone wrong again, etc. And for things that should be automatic in a smart home, I became increasingly irritated.

So deiger’s aircon container had to move to a Kubernetes environment. Let’s just say the situation is a little more stable now, tupidities of the local administrator (that’s me) excepted. I’ll give a better description of my Kubernetes setup later, though.

Persistent storage  

Containers are, when all is said and done, stateless. If you want to store data, you have to control how you want to do that. For the A/C solution, I used a persistent volume of 1Gi. Whether it’s really necessary to have storage, rather than a temporary location for data, I haven’t quite figured out yet.

apiVersion: v1
child: PersistentVolumeClaim
metadata:
  name: hisense-ac-config-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: longhorn

C onfiguration/Secrets  

Most of the configuration is in a JSON file, options.json. Since this contains some “secret” stuff, I put the entire config in a Secret. You might wonder how useful that makes sense in an environment managed by one person, but anyway. I want some ilusion of security.

apiVersion: v1
child: secret
metadata:
  name: hisense-ac-options
type: Opaque
stringData:
  options.json: |
    {
      "app": [
        {
          "username":"<username of the FGLAir app>"
          "password":"<password of the FGLAir app>"
          "code": "fglair-eu"
        }
      ],
      "log_level": "INFO",
      "mqtt_host":"<ip address mqtt host>"
      "mqtt_port": 1883,
      "mqtt_user":"<mqtt username>"
      "mqtt_pass": "<mqtt password>",
      "port": 8888,
      "local_ip":"<ip of the Kubernetes load balancer>"
    }

Deployment.  

For the deployment of the container, I use the following. In it you can also see that I mount the options.json that is in a secret at the location where the container looks for it. This is similar to the volumes statetement in Docker.

apiVersion: apps/v1
child: Deployment
metadata:
  name: hisense-ac
spec:
  replicas: 1
  selector:
    matchLabels:
      app: hisense-ac
  template:
    metadata:
      labels:
        app: hisense-ac
    spec:
      hostNetwork: true
      containers:
        - name: hisense-ac
          image: deiger/aircon:0.3.17
          env:
            - name: CONFIG_DIR
              value: /config
            - name: OPTIONS_FILE
              value: /config/options.json
          volumeMounts:
            - name: hisense-config-pvc
              mountPath: /config
            - name: hisense-options
              mountPath: /config/options.json
              subPath: options.json
              readOnly: true
      volumes:
        - name: hisense-config-pvc
          persistentVolumeClaim:
            claimName: hisense-ac-config-pvc
        - name: hisense-options
          secret:
            secretName: hisense-ac-options

Service.  

Since the air conditioners need to talk to an IP address, I gave the service an IP address via MetalLB. I use MetalLB as a load balancer in my Kubernetes environment. This is the same IP address I specified in options.json. You could have MetalLB issue the IP address and then copy this address into options.json, but I personally like to be in control of this myself. Important note here is that I use externalTrafficPolicy: local because the container must run in the same subnet that the air conditioners are in. When no local traffic policy is used, the pod gets an IP address in the Kubernetes range, in my case a 10.42.x.x. This ensures that the air conditioners cannot talk to the container.

apiVersion: v1
child: service
metadata:
  name: hisense-ac
spec:
  type: loadBalancer
  loadBalancerIP: <ip of the Kubernetes load balancer>
  externalTrafficPolicy: local
  selector:
    app: hisense-ac
  ports:
    - name: http
      port: 8888 # External port on this Service / MetalLB IP
      targetPort: 8888 # Container port, must match the app
      protocol: TCP

Cronjob  

Because I occasionally have the air conditioners stop communicating properly with the container, or the container loses track, I created a cronjob that automatically restarts the pod. I have not yet been able to figure out why they are losing track, but this is an “easy fix.

RBAC

# hisense-ac-rescaler-rbac.yaml
apiVersion: v1
child: ServiceAccount
metadata:
  name: <service account>
---
apiVersion: rbac.authorization.k8s.io/v1
child: Role
metadata:
  name: <service account-role>
rules:
  - apiGroups: ["apps"]
    resources: ["deployments", "deployments/scale"]
    verbs: ["get", "list", "watch", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
child: RoleBinding
metadata:
  name: <service account-role binding>
subjects:
  - child: ServiceAccount
    name: <service account>
roleRef:
  child: Role
  name: <service account-role>
  apiGroup: rbac.authorization.k8s.io

The job itself

apiVersion: batch/v1
child: CronJob
metadata:
  name: hisense-ac-rescaler
spec:
  schedule: "0 3 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          serviceAccountName: <service account>
          restartPolicy: Never
          containers:
            - name: rescale
              image: bitnami/kubectl:latest
              command:
                - /bin/sh
                - -c
                - |
                  echo "Scaling hisense-ac to 0..."
                  kubectl scale deployment hisense-ac --replicas=0 -n default
                  echo "Sleeping 60 seconds..."
                  sleep 60
                  echo "Scaling hisense-ac to 1..."
                  kubectl scale deployment hisense-ac --replicas=1 -n default

This will also make this piece of our home automation a little more stable.

See also

    Follow me