Hisense air conditioning in Home Assistant - the k8s way
Posted on May 18, 2026 • 5 min read • 969 words
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: longhornC 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-optionsService.
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: TCPCronjob
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.ioThe 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 defaultThis will also make this piece of our home automation a little more stable.

