UPDATED: Automating Unifi WiFi SSID password changes and QR code generation

UPDATE: This functionality has been added to the official Unifi Network Integration: UniFi Network integration - official thread - #64 by Robban

Thank you for all the interest in this project, it was both humbling and exciting to see so many people interested in my work, and it was a most amazing feeling when @Robban chose to include it in the official integration.

Although I continue to use this original code from the project below (if it ain’t broke, don’t fix it), I would encourage you to use the official integration if it meets your needs. Robban is a much better coder than I am, and the official integration is much more foolproof to install and configure than what I’ve done below.

I’ll leave this thread up for historical preservation/education’s sake, but you should consider this project closed and no longer supported.

Thank you!!!


I saw lots and lots of people asking about how to do this when I was searching for how to do it, so hopefully this helps some people out. I can’t take all the credit for this, so have a look at this thread (Automating unifi port forwarding based upon presence detection) by @jon102034050, which was the genesis of my knowledge to get started with the project.

Since I’m a real n00b when it comes to HomeAssistant, a lot of this has been dumbed down, and there’s not as much variable passing as his stuff, nor is the secrets file leveraged since that can’t be used in the UI, and when I tried to manually add them to automations.yaml it broke all my automations, so…

This solution can be used to change the passwords for as many SSIDs as you like. The QR code output will be saved in /config/www, and the filename will be the unifi SSID name that is passed in from the automation call.

We will need the id of the wireless network you want to change the password on. To get this, log into your unifi controller and click “Edit” next to the wireless network. Then look in the address bar of your browser. The long alphanumeric string at the end of the URL is the ID of that network. Copy that.

So, the first thing we need is the following code, saved in the scripts directory, and named “wifichange.sh”

#!/bin/sh

cookie=$(mktemp)
headers=$(mktemp)

curl_cmd="curl --silent --output /dev/null --cookie ${cookie} --cookie-jar ${cookie} --insecure"
change() {
  
  # generate new password
  NEW_PWD=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1)

  # generate QR code
  wget --output-document=/config/www/${5}.png "http://api.qrserver.com/v1/create-qr-code/?data=WIFI:T:WPA;S:${5};P:${NEW_PWD};H:;&size=100x100"

  # authenticate against unifi controller
  ${curl_cmd} -H 'Content-Type: application/json' -D ${headers} -d "{\"username\":\"$1\", \"password\":\"$2\"}" https://${3}/api/auth/login

  # grab the `x-csrf-token` and strip the newline (added when upgraded to controller 6.1.26)
  csrf="$(awk -v FS=': ' '/^x-csrf-token/{print $2}' "${headers}" | tr -d '\r')"

  # change wifi password
  ${curl_cmd} -k -X PUT https://${3}/proxy/network/api/s/default/rest/wlanconf/${4} -H "Content-Type: application/json" -H "x-csrf-token: ${csrf}" -d @- <<-EOF
  {
    "_id":"$4",
    "x_passphrase":"$NEW_PWD"
  }
EOF
}
"$@"

Add this to your configuration.yaml:

shell_command:
    unifi_wlanchange: /bin/bash /config/scripts/wifichange.sh change {{ username }} {{ password }} {{ baseurl }} {{ network_id }} {{ ssid }}

camera:
  - platform: generic
    name: guestqrcode
    still_image_url: http://xxx.xxx.xxx.xxx:8123/local/<ssid>.png
    verify_ssl: false

Be sure to use http or https accordingly for the camera config!

And the automation (UI-friendly, can be copy/pasted into your automations.yaml file without breaking anything - just make sure to change the “- id” value to something unique). Be sure to modify the username, password, IP address, and the network_id in the data portion. The network_id is the id that you copied from the end of the URL when you clicked “Edit” on the wireless network in the unifi controller.

Note that you need to use a LOCAL user on the UBNT gear, the ssh account will not work.

- id: '1111111111111'
  alias: wifiPW
  description: ''
  trigger:
  - platform: time
    at: 03:00
  condition: []
  action:
  - service: shell_command.unifi_wlanchange
    data:
      username: myuser
      password: mypassword
      baseurl: unifi.ip.add.ress
      network_id: my_id_from_unifi_url
      ssid: ssid_of_wireless_network_to_change
  mode: single

Then, I just added a tab to the frontend, used a wifi icon, and added a picture card to it with these settings:

type: picture-entity
show_state: false
show_name: false
entity: camera.guestqrcode
camera_image: camera.guestqrcode
tap_action:
  action: none
hold_action:
  action: none

Viola! Your wifi password changes however often you specify, and is very secure, and still super-easy for your guests to connect to.

edit: 1/18/2022 with new version that is much easier to understand and maintain. Props to @rootnegativ1 for pointing me in the right direction about not sending the entire JSON payload with his link to the reddit post.

edit: 1/25/2022 updated code to allow passing the SSID as a variable, and to change the name of the QR code file to the SSID instead of the network_id.

edit: 7/22/2022 cleaned up code a bit in preparation for YouTube video

7 Likes

Ah sweet idea! I’ll probably integrate this into my “guest mode” boolean that I have, so whenever I flip that on, it generates a new password and then maybe I’ll dump it via Telegram to my devices.

1 Like

This made me think of a Reddit post from last year looking to do much the same thing. Thankfully, you’ve got a handle on the X-CSRF-Token … that one took me a looong time!

When using the PUT method in your curl command, it’s not necessary to recreate the entire JSON object. Instead only include what you’d like to replace. Consider something like:

id=611111b3dc237d01111111e8
${curl_cmd} -k -X PUT https://${3}/proxy/network/api/s/default/rest/wlanconf/${id} \
    -H "Content-Type: application/json" \
    -H "x-csrf-token: ${csrf}" \
    -d "{\"x_passphrase\": \"${NEW_PWD}\"}"

You know, that was my first thought, but when I included only that, it didn’t work. I think that some subset of the values are required, but rather than waste time figuring that out, it was easier to just send the whole thing. Copy/paste only took a minute. Lol

Edit: I just read through that post, and it looks like that guy got it to work by sending the ID and the password. If those are the only things required, I might give that a go, just in the interest of simplicity. Would also make it much easier for people to implement this themselves since they wouldn’t have to get the json payload using Chrome dev tools. Lol

Now where’s that free time generator…

Updated main post with better code.

1 Like

I liked this, but had to make some changes to get it to work best for me.
If you get an error code 2, try to change the indent on the EOF in the script. Quite a while to figure out this one.

Change this:

  }
  EOF
}
"$@"

To this:

  }
EOF
}
"$@"

Also, I was unable to use a picture card as the QR code would not update after a change do to a caching issue.
I used a picture entity card and setup a camera entity.

Lovelace Picture Entity Code:

type: picture-entity
camera_image: camera.wifiqrcode
show_state: false
show_name: false
entity: camera.wifiqrcode

Configuration.yaml

camera:
  - platform: generic
    name: wifiqrcode
    still_image_url: "http://xxx.xxx.xxx.xxx:8123/local/network_id.png"
    verify_ssl: false

Remember to reboot after configuration.yaml changes to reload the changes.

Enjoy!

1 Like

Good catch on the EOF. That’s what I get for trying to make my code pretty after I paste it into the message. It’s ugly on my system, but it works. :slight_smile:
I edited the original post to save the next guy the headache.

I thought about using a camera entity, but it turned out I didn’t have any caching issues - even though I thought I would. I suppose it’s probably browser dependent? The only place the QR code is really important to me that it’s seen and correct is on my kindle fire that’s mounted to the wall. For that, I’m using fully kiosk, and I have it set to refresh the page every time it wakes up, so… Maybe that saved me from the caching problem?

In any event - I’m glad you found it useful and were able to make it do what you wanted it to do. :slight_smile:

Also, in re-reading my code, I realized that I hard-coded “guest” into the qr code api call. I’m mobile right now, but I plan to come back and update all of that so you can pass in the SSID as well.

The caching issues I had were in Chrome. I was glad it did come up as it may have drove me nuts if it only happened on certain devices. I do use tablets with Fully Kiosk on a few as well but I did not test to see if the cache was an issue. I just went straight to the camera entity. Also, I may play with SVG QR Code as well then resizing is nice and crisp among devices.

Appreciate your work on this. This solution works well for me as vouchers can be a pain and slow. This provides a very quick solution for gatherings.

1 Like

Made a YouTube video on how to set this up: Automating secure guest wifi password changes using Home Assistant and Ubiquiti - YouTube

1 Like

Thanks for the script!
Would it be possible to create the qr code offline, for example with the https://github.com/igor-panteleev/lovelace-qr-code-card, instead of sending the password to “qrserver.com” in plain text via http?

I am using that qr-code-card now. Would it be possible to update the appropriate lovelace frontend config file(s) with the new password and SSID and then restart HA? Alternatively would it be possible to modify the qr-code-card to have an option to auto-generate password and update the unifi config on some sort of trigger or scheduled basis?

I just opened an issue (Feature Request) on the lovelace-qr-code-card maybe it can get modified to support this functionality then you could do it all in the frontend. link to the isseue https://github.com/igor-panteleev/lovelace-qr-code-card/issues/3

Thank you!

Hi!

Just reviewed everything, watched video, and still get:

Error running command: /bin/bash /config/scripts/wifichange.sh change {{ username }} {{ password }} {{ baseurl }} {{ network_id }} {{ ssid }}, return code: 2

Can you help, please?

You’ve either not copied the code above correctly, or you’re not using a local user on the unifi system. Those would be my top guesses. Honestly, I’m not a *nix expert, so the exit codes are all but gibberish to me. All I can say for sure is if the instructions are followed exactly, the solution works as designed. Without seeing all the code exactly as you’ve entered it, any guess would be no better than throwing darts blindfolded (including my two guesses above LOL).

Thanks for answer.

It ends to be a Linux file format problem.

This solved it:

Excellent, glad to hear you got it working!

You could modify the original script to output a json file to your local www folder

SSID=${4}
PASS=${NEW_PWD}
QRTEXT="WIFI:T:WPA;S:${SSID};P:${PASS};;"

jq -n --arg qr_text "${QRTEXT}" '$ARGS.named' > ${SSID}_wifi.json

mv ${SSID}_wifi.json /config/www/${SSID}_wifi.json

This could then be read by a rest sensor in HA. This would serve as an entity containing a QR generation string for the qr-code-card. Assuming the SSID is Guest,

rest:
  - resource: http://127.0.0.1:8123/local/Guest_wifi.json
    scan_interval: 600
    sensor:
      - name: Guest Wifi
        value_template: "{{ value_json.qr_text }}"

Finally, whenever the automation runs, it too can be modified to call the homeassistant.update_entity service and update the sensor and thus the lovelace card with the current string

automation:
  - id: '1111111111111'
    alias: wifiPW
    description: ''
    mode: single
    trigger:
      - platform: time
        at: 03:00
    condition: []
    action:
      - service: shell_command.unifi_wlanchange
        data:
          username: !secret unifi-user
          password: !secret unifi-password
          baseurl: !secret unifi-baseurl
          network_id: !secret unifi-network-id
          ssid: !secret unifi-ssid
      # the delay is not strictly necessary,
      # but gives time for the json file to be written
      - delay: '00:00:10'
      - service: homeassistant.update_entity
        data:
          entity_id:
            - sensor.guest_wifi

[edit: updated automation with secrets]

1 Like

I achieved exactly this but with an alternative interration.
QR Codes Integration (qrcam) - Share your Projects! / Custom Integrations - Home Assistant Community (home-assistant.io)

where do I find the ip address for the still image url?