Send PiVPN client info to HA with mqtt

Hey everyone,
I would like to share a simple script that sends your PiVPN client’s info to HA with mqtt.

With the PiVPN Project you can use a Raspberry Pi to set up a VPN Server (OpenVPN or Wireguard), so that you can access your local network when not at home.

The pivpn -c command will give you a table with all your clients and info on them, such as the network usage, the remote IP, when they were last seen etc.

To send this info to home assistant, you can use the following python3 script as a template.

I would like to point out that I have little experience with python, so someone else can probably make this script a lot better. So I am only publishing it as an idea.

import os
import paho.mqtt.client as mqtt
import json

## replace test with your client name ##

client = os.popen("pivpn -c | grep 'test' ").read().split()
if client[5]=="(not":
        data = json.dumps({"Device":client[0], "Remote Ip":client[1], "Local IP":client[2], "Received":client[3], "Sent":client[4], "Seen":client[5]+' '+client[6]})
else:
        data = json.dumps({"Device":client[0], "Remote Ip":client[1], "Local IP":client[2], "Received":client[3], "Sent":client[4], "Seen":client[5]+' '+client[6]+' '+client[7]+' '+client[8]+' '+client[9]})



send = mqtt.Client("pivpn")
send.username_pw_set(username="***",password="***")
send.connect("192.168.**") 
send.publish("pivpn/test", str(data), retain=True)


Make the script executable and call it with a crontab every couple of minutes.

*/3 * * * * sudo python3 /home/pi/pivpn.py

In your configuration.yaml add an mqtt sensor:

- platform: mqtt
  name: PiVPN Test
  state_topic: 'pivpn/test'
  value_template: "{{ value_json.Seen }}"
  json_attributes_topic: 'pivpn/test' 

Then you will have a new sensor, like this

pivpn2

6 Likes

Thanks @valvex

Inspired by your work, this got me thinking and I had a play, up to this point I had zero python experience but wanted a challenge and was intrigued by MQTT which I’d stumbled upon elsewhere.

I have done this through excessive use of Google, looking at other peoples work and lot & lots of trail and error. I would love it if anyone could offer advice as to where I could do things better, stuff I should not do etc. etc.

My approach was to use auto discovery in Home Assistant to create an entity for each VPN client within pivpn using the pivpn -l command (and remove them if they were deleted), then have MQTT check-in every so often with a status update on them using pivpn -c and your work.

#!/usr/bin/env python3

import os
import json
import paho.mqtt.client as mqtt
import time
import threading
import logging
from datetime import datetime

logging.basicConfig(level=logging.WARNING, format='%(asctime)s %(levelname)s: %(message)s')

# -----------------------------
# --- CONFIGURATION OPTIONS ---
# -----------------------------

discoveryTopicPrefix = 'homeassistant/sensor/'
topicPrefix = 'home/nodes/vpnclients/'              # suffix (state /state, attributes /attr, availability /status)
vpnType = 'WireGuard'                               # WireGuard or OpenVPN or other?
mqttUser = 'USERNAME'
mqttPassword = 'PASSWORD'
mqttAddress = 'IP.ADDRESS'                          # assumes standard ports
updateFrequency = 5                                 # in minutes

# ---------------------------------
# --- END CONFIGURATION OPTIONS ---
# ---------------------------------


# MQTT Connection Made
def on_connect(client, userdata, flags, rc):
    logging.debug('--> on_connect')
    logging.info('Connected with result code '+str(rc))
    stateTopic = '{}status'.format(topicPrefix)     # publish status update
    client.publish(stateTopic, payload='Online', qos=0, retain=True) 
    for device in deviceList:                       # call discovery for each device
        publishDiscovery(device)

# Timer based on update frequency
def periodTimeoutHandler():
    global deviceList
    logging.info('Timer interrupt')
    updatedDeviceList = getDeviceList()             # Get an upto date list of devices
    logging.info('Updated device list...')
    logging.info(updatedDeviceList)
    if deviceList != updatedDeviceList:             # Compare the previous and current lists
        logging.info('Device lists are different')
        newDevices = [i for i in updatedDeviceList if i not in deviceList]
        logging.info('New devices:')
        logging.info(newDevices)
        removedDevices = [i for i in deviceList if i not in updatedDeviceList]
        logging.info('Removed Devices')
        logging.info(removedDevices)
        for deviceName in newDevices:               # Create discovery data for new devices
            publishDiscovery(deviceName)
        for deviceName in removedDevices:            # Remove HA entity/device for removed devices
            removeDiscovery(deviceName)
    else:
        logging.info('Device lists are the same')
    deviceList = updatedDeviceList                  # Update the device list
    publishDeviceAttributes()                       # Call to publish the attributes for each device
    startPeriodTimer()

def startPeriodTimer():
    logging.debug('--> startPeriodTimer')
    global endPeriodTimer
    global periodTimeRunningStatus
    stopPeriodTimer()
    endPeriodTimer = threading.Timer(updateFrequency * 60.0, periodTimeoutHandler)
    endPeriodTimer.start()
    periodTimeRunningStatus = True
    logging.info('Timer Started')
    
def stopPeriodTimer():
    global endPeriodTimer
    global periodTimeRunningStatus
    endPeriodTimer.cancel()
    periodTimeRunningStatus = False
    logging.info('Timer stopped')


# Get VPN Device List
def getDeviceList():
    logging.debug('--> getDeviceList')
    rawDevices = os.popen("pivpn -l").read().split()
    deviceCount = (len(rawDevices) - 9) / 7
    x = 0
    namePosition = 9
    deviceList = []
    while x < deviceCount:
        deviceName = rawDevices[namePosition]
        logging.info('Appending device ' + deviceName + ' to deviceList')
        deviceList.append(deviceName)
        x += 1
        namePosition += 7
    return deviceList   

# Publish discovery data for a device
def publishDiscovery(deviceName):
    logging.debug('--> publishDiscovery(' + deviceName + ')')
    discoveryTopic = '{}{}/config'.format(discoveryTopicPrefix, deviceName)
    payload = {}
    payload['name'] = 'VPN Client {}'.format(deviceName.title())
    payload['unique_id'] = 'VPN{}{}Client'.format(vpnType, deviceName)
    #payload['device_class'] = 'timestamp'
    payload['state_topic'] = '{}{}/state'.format(topicPrefix, deviceName)
    payload['payload_available'] = 'Online'
    payload['payload_not_available'] = 'Offline'
    payload['availability_topic'] = '{}status'.format(topicPrefix)
    payload['icon'] = 'mdi:vpn'
    payload['json_attributes_topic'] = '{}{}/attr'.format(topicPrefix, deviceName)
    payload['dev'] = {
            'identifiers' : ['vpnClient{}'.format(deviceName)],
            'manufacturer' : vpnType,
            'name' : 'VPN-Client-{}'.format(deviceName.title()),
            'model' : 'VPN Client',
            'sw_version': "not applicable"            
        }
    client.publish(discoveryTopic, json.dumps(payload), 0, retain=True)

# Remove discovery data for deleted devices
def removeDiscovery(deviceName):
    logging.debug('--> publishDiscovery(' + deviceName + ')')
    discoveryTopic = '{}{}/config'.format(discoveryTopicPrefix, deviceName)
    payload = {}
    client.publish(discoveryTopic, json.dumps(payload), 0, retain=True)

# Publish attribute data for devices
def publishDeviceAttributes():
    logging.debug('--> publishDeviceAttributes')
    for deviceName in deviceList:
        logging.info('Getting device attributes for ' + deviceName)
        query = "pivpn -c | grep '" + deviceName + "'"          # Get device row data
        clientRecord = os.popen(query).read().split()
        if clientRecord[5]=="(not":
            data = json.dumps({"device":clientRecord[0], "remote_ip":clientRecord[1], "local_ip":clientRecord[2], "received":clientRecord[3], "sent":clientRecord[4], "seen":clientRecord[5]+' '+clientRecord[6]})
            state = clientRecord[5] + ' ' + clientRecord[6]
        else:
            data = json.dumps({"device":clientRecord[0], "remote_ip":clientRecord[1], "local_ip":clientRecord[2], "received":clientRecord[3], "sent":clientRecord[4], "seen":clientRecord[5]+' '+clientRecord[6]+' '+clientRecord[7]+' '+clientRecord[8]+' '+clientRecord[9]})
            state = clientRecord[5] + ' ' + clientRecord[6] + ' ' + clientRecord[7] + ' ' + clientRecord[8] + ' ' + clientRecord[9]
        logging.info('Device attributes...')
        logging.info(data)
        logging.info('Device state...')
        logging.info(state)
        topic = '{}{}/attr'.format(topicPrefix, deviceName) 
        client.publish(topic, str(data), retain=False)      # Publish attributes
        topic = '{}{}/state'.format(topicPrefix, deviceName)
        client.publish(topic, state, retain=False)          # Publish state


# Timer configuration
deviceList = []
endPeriodTimer = threading.Timer(updateFrequency * 60.0, periodTimeoutHandler)
periodTimeRunningStatus = False
reported_first_time = False


# MQTT connection
client = mqtt.Client()
client.on_connect = on_connect
client.username_pw_set(username=mqttUser,password=mqttPassword)
client.connect(mqttAddress, 1883, 60)
stateTopic = '{}status'.format(topicPrefix)     # set last will
client.will_set(stateTopic, payload='Offline', qos=0, retain=True) 

# Commence Timer & get initial device list
deviceList = getDeviceList()
logging.info('Inital device list...')
logging.info(deviceList)
startPeriodTimer()


client.loop_forever()

copy here: https://github.com/woodmj74/pivpn_vpnrpt/blob/50150a5241c41455ef07e408878622004b2e6196/vpnrpt.py

I created a service on the PI to run this and then with with Thomas Loven’s auto entities card to auto populate these:

    type: 'custom:auto-entities'
    show_empty: false
    card:
      type: entities
      title: VPN Clients
    filter:
      include:
        - entity_id: sensor.*vpn_cl*
          options:
            secondary_info: last-changed

There’s still loads I think I need to do and will likely change it as I learn more, as said above happy to receive any guidance from those wiser than me and like valvex putting this out there as an idea.

I should also credit https://github.com/ironsheep/RPi-Reporter-MQTT2HA-Daemon where I learnt/‘borrowed’ quite a bit from.

2 Likes

Wow, this is really awesome!
I recently switched to wireguard on my edge x router, however I wish I was still on piVPN in order to try your script!
Thank you so much for your efforts and for sharing!!

Hello @woodmj74 , @valvex nice work!

sorry to disturb, i have try it and get this error :

vpnrpt.service - VPN Report clients
   Loaded: loaded (/lib/systemd/system/vpnrpt.service; enabled; vendor preset: enabled)
   Active: active (running) since Sun 2021-06-06 19:20:03 +04; 9min ago
 Main PID: 1099 (python)
    Tasks: 1 (limit: 877)
   CGroup: /system.slice/vpnrpt.service
           └─1099 /usr/bin/python /home/pi/vpn_report/vpnrpt.py

juin 06 19:22:36 vpnpi0w python[1099]: Traceback (most recent call last):
juin 06 19:22:36 vpnpi0w python[1099]:   File "/usr/lib/python2.7/threading.py", line 801, in __bootstrap_inner
juin 06 19:22:36 vpnpi0w python[1099]:     self.run()
juin 06 19:22:36 vpnpi0w python[1099]:   File "/usr/lib/python2.7/threading.py", line 1073, in run
juin 06 19:22:36 vpnpi0w python[1099]:     self.function(*self.args, **self.kwargs)
juin 06 19:22:36 vpnpi0w python[1099]:   File "/home/pi/vpn_report/vpnrpt.py", line 61, in periodTimeoutHandler
juin 06 19:22:36 vpnpi0w python[1099]:     publishDeviceAttributes()                       # Call to publish the attributes for each device
juin 06 19:22:36 vpnpi0w python[1099]:   File "/home/pi/vpn_report/vpnrpt.py", line 135, in publishDeviceAttributes
juin 06 19:22:36 vpnpi0w python[1099]:     if clientRecord[5]=="(not":
juin 06 19:22:36 vpnpi0w python[1099]: IndexError: list index out of range

Any idea please? i am not very good in python…

this is the result of the lovelace card :

perhaps i have made something wrong…

i installed like this :

cd /lib/systemd/system/
sudo nano vpnrpt.service

-------------------------------------------------------
[Unit]
Description=VPN Report clients
After=multi-user.target

[Service]
Type=simple
ExecStart=/usr/bin/python /home/pi/vpn_report/vpnrpt.py
Restart=on-abort

[Install]
WantedBy=multi-user.target

------------------------------------------------------


sudo chmod 644 /lib/systemd/system/vpnrpt.service
chmod +x /home/pi/vpn_report/vpnrpt.py
sudo systemctl daemon-reload
sudo systemctl enable vpnrpt.service
sudo systemctl start vpnrpt.service

i get a complain about “ImportError: No module named paho.mqtt.client”,

which is already installed with pip3, so i try to install with pip, throw an error, so install pip with
sudo apt install python-pip
sudo pip install paho-mqtt

then the service is running with no error but after one minute throw the error above and the card doesnt work.

If you can help, thanks!

EDIT :

understand i have initialy install paho.mqtt with python3, i modify the service like this :

[Unit]
Description=VPN Report clients
After=multi-user.target

[Service]
Type=idle
ExecStart=/usr/bin/python3 /home/pi/vpn_report/vpnrpt.py
Restart=on-abort

[Install]
WantedBy=multi-user.target

no more error but doesnt work better… :

 vpnrpt.service - VPN Report clients
   Loaded: loaded (/lib/systemd/system/vpnrpt.service; enabled; vendor preset: enabled)
   Active: active (running) since Sun 2021-06-06 19:51:56 +04; 3min 36s ago
 Main PID: 1856 (python3)
    Tasks: 2 (limit: 877)
   CGroup: /system.slice/vpnrpt.service
           └─1856 /usr/bin/python3 /home/pi/vpn_report/vpnrpt.py

juin 06 19:51:56 vpnpi0w systemd[1]: Started VPN Report clients.

any idea please?

thanks

Hey @Olivier974 - I can take a look but to be honest with you; I’ve done what I did through a lot of ‘cut & paste’ and trial and error without really knowing what I was doing, and hey it worked. I put this up here in case anyone wanted to improve on it and make it better! I’ll let you know if I have any ideas but you might need a pro to help you here :slight_smile:

thanks for your quick reply :wink:

i am like you…no knowledge about python…

the 2 problems are its only send mqtt state once and the second, there is no data payload in the mqtt message, that why the status is “unknow”.

i use openVPN with pivpn on a rpi0w, and you?

When you run pivpn -c are you seeing the clients and their last connected date/time? (which is basically what my script is ripping off)

yes it works with pivpn -c

how did you make your vpnrpt.py as a service? i think the problem is there…

thanks

can you give me the content of your vpnrpt.service please?

EDIT : if i launch manually with python3 vpnrpt.py i got this error :

Exception in thread Thread-2:
Traceback (most recent call last):
  File "/usr/lib/python3.7/threading.py", line 917, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.7/threading.py", line 1166, in run
    self.function(*self.args, **self.kwargs)
  File "/home/pi/vpn_report/vpnrpt.py", line 61, in periodTimeoutHandler
    publishDeviceAttributes()                       # Call to publish the attributes for each device
  File "/home/pi/vpn_report/vpnrpt.py", line 135, in publishDeviceAttributes
    if clientRecord[5]=="(not":
IndexError: list index out of range

@Olivier974 Hi and really sorry for the late reply!
I’ve been extremely busy during the past weeks and only today I managed to catch up with HA’s latest news.
Anyway, first of all, I think paho.mqtt runs on python3, so the script should be run with python3.

Also, before trying woodmj74’s script, which is a bit more complicated to analyze, please try this

import os
import paho.mqtt.client as mqtt
import json

## replace "test" with your client's name ##

client = os.popen("pivpn -c | grep 'test' ").read().split()
if client[5]=="(not":
        data = json.dumps({"Device":client[0], "Remote Ip":client[1], "Local IP":client[2], "Received":client[3], "Sent":client[4], "Seen":client[5]+' '+client[6]})
else:
        data = json.dumps({"Device":client[0], "Remote Ip":client[1], "Local IP":client[2], "Received":client[3], "Sent":client[4], "Seen":client[5]+' '+client[6]+' '+client[7]+' '+client[8]+' '+client[9]})

print (data)

Run this with python3 and let me know of the output!

1 Like

Here you go, but not sure the problem’s here. You might want to try out Valvex’s suggestions as he seems to know more than us!

[Unit]
Description=Send VPN client data to HomeAssistant
After=network.target mosquitto.service network-online.target
Wants=network-online.target
Requires=network.target

[Service]
Type=simple
User=pi
ExecStart=/usr/bin/python3 /usr/vpnrpt/vpnrpt.py
Restart=always
RestartSec=3
	
[Install]
WantedBy=multi-user.target
1 Like

Thanks @Valvex for your reply, take your time…no p :wink:

i have test :

pi@vpnpi0w:~ $ python3 PiTest.py
Traceback (most recent call last):
  File "PiTest.py", line 10, in <module>
    if client[5]=="(not":
IndexError: list index out of range

i understand it missed some ( ) because of python3,

so after
if client[5] == ("(not") :

it works in the ssh terminal of the PiVpn :

pi@vpnpi0w:~ $ python3 PiTest.py
{"Device": "VpnLanHomeOlive", "Remote Ip": "xxxxxxxxx:xxxxxx", "Local IP": "10.8.0.3", "Received": "7,6KiB", "Sent": "7,5KiB", "Seen": "Jul 10 2021 - 15:46:44"}

but nothing in the sensor…

Thanks but same with @valvex example : the result in the ssh terminal is ok but no MQTT message published for the client.

i have a Mqtt listener (MQTT Explorer for Windows) and i see only this : a Topic named “21” :

{"name": "VPN Client 21", "unique_id": "VPNopenvpn21Client", "state_topic": "home/nodes/vpnclients/21/state", "payload_available": "Online", "payload_not_available": "Offline", "availability_topic": "home/nodes/vpnclients/status", "icon": "mdi:vpn", "json_attributes_topic": "home/nodes/vpnclients/21/attr", "dev": {"identifiers": ["vpnClient21"], "manufacturer": "openvpn", "name": "VPN-Client-21", "model": "VPN Client", "sw_version": "not applicable"}}

i dont understand, there is no info about ipadress, last seen, ect…

Are you seeing the state topic (home/nodes/sensor/…) as well as the discovery topic (homeassistant/sensor/…) for this 21 client?

TBH not sure what the issue is, have you tried deleting the messages using MQTT Explorer and then restarting the script again to repopulate HA? Another idea might be to publish an empty message to homeassistant/sensor/21 which will remove that and you can start again…

(you might need to remove any retain flags) image

I was recently playing with this and refactored it for better LWT messages. In the process I refactored the message/entity structure a bit so in HA the VPN is a device and each client now an entity in HA - it just seemed to make more sense. I’ve had this running now for a week or so without issue if you wanted to try this version?

Again, sorry I can’t be more help but perhaps wiping out what’s there and starting afresh could point to where the problem is. Another thing might be to add a new client in piVPN and see if that pops up in HA (you can delete them easier enough afterwards)

1 Like

thanks again for your reply,

yes i ll try, please give me the code :wink:

i have only home/nodes/vpnclients/status and homeassistant/sensor/21

i have make too much modifications, will restart from scracth…

wait for your code, thanks!

EDIT : i have download the code on yur Github (seems modified 4 days ago?),

and there is the same problem i have no data in the messages and on my MQTT broker :

New client connected from 192.168.1.120 as auto-EF9509C9-D0BC-84AE-5679-54031AE9A880 (p2, c1, k60, u'OliveMqttVPN').
1625920085: Socket error on client auto-EF9509C9-D0BC-84AE-5679-54031AE9A880, disconnecting.

there is a socket error and it disconnected…

No problem, you help, that s nice…

i regret to have so small knowledge about Python coding :wink:

the systemd status :

● vpnrpt.service - Send VPN client data to HomeAssistant
   Loaded: loaded (/lib/systemd/system/vpnrpt.service; enabled; vendor preset: enabled)
   Active: active (running) since Sat 2021-07-10 18:17:18 +04; 9min ago
 Main PID: 29278 (python3)
    Tasks: 2 (limit: 877)
   CGroup: /system.slice/vpnrpt.service
           └─29278 /usr/bin/python3 /home/pi/vpn_report/vpnrpt.py

juil. 10 18:17:51 vpnpi0w python3[29278]: 2021-07-10 18:17:51,534 INFO: Appending client vpnpi0w_1c7fa683-633a-4b08-948c-033aa7b7192b to clientList
juil. 10 18:17:51 vpnpi0w python3[29278]: 2021-07-10 18:17:51,537 INFO: Appending client 21 to clientList
juil. 10 18:17:51 vpnpi0w python3[29278]: 2021-07-10 18:17:51,540 INFO: Updated client list...
juil. 10 18:17:51 vpnpi0w python3[29278]: 2021-07-10 18:17:51,543 INFO: ['which', 'Status', 'vpnpi0w_1c7fa683-633a-4b08-948c-033aa7b7192b', '21']
juil. 10 18:17:51 vpnpi0w python3[29278]: 2021-07-10 18:17:51,547 INFO: Client lists are the same
juil. 10 18:17:51 vpnpi0w python3[29278]: 2021-07-10 18:17:51,550 DEBUG: --> publishClientAttributes
juil. 10 18:17:51 vpnpi0w python3[29278]: 2021-07-10 18:17:51,554 INFO: Getting client attributes for which
juil. 10 18:17:51 vpnpi0w sudo[29406]:       pi : TTY=unknown ; PWD=/ ; USER=root ; COMMAND=/opt/pivpn/openvpn/clientStat.sh
juil. 10 18:17:51 vpnpi0w sudo[29406]: pam_unix(sudo:session): session opened for user root by (uid=0)
juil. 10 18:17:51 vpnpi0w sudo[29406]: pam_unix(sudo:session): session closed for user root

dont know if there is a problem or not…but i have no client attribute in HA lol, its “unknow” status…so bad, love your work, nice!

So it looks to be like its finding four configured vpn clients ['which', 'Status', 'vpnpi0w_1c7fa683-633a-4b08-948c-033aa7b7192b', '21'], which seems wrong. The when trying to find the attributes for these they don’t exist, hence the problems (I’m guessing :slight_smile: )

The client list is built from the pivpn -l command by grabbing the client name out of the results returned by checking for a value in a certain position. So in my case, it looks like this:

Want to try pivpn -l and see what you get; its trying to get the first client name from position 10 (position 1 is ‘:::’, position 9 is ‘date’), something seems out here so it could be a version thing maybe with PiVPN etc.

(I wonder if the results are different between openVPN and Wireguard - I’m configured up for Wireguard, you seem to be using openVPN, let me have those results but I’ll have a go at spinning one up when I get chance)

Think I’ve got it @Olivier974

For an OpenVPN set up the pivpn -l command, it returns different results. Should be quite an easy change but haven’t got time at the moment, will look at it over the next couple of days and let you know.

1 Like

thanks Mike…it make sense lol…i will try by miself but not sure it will works lol

So you have to make 2 versions, one for wireguard and an other for pivpn :wink:

The first client is on position [14] and second one position is [19] and ect…24…29…if more clients.

EDIT : i try…but i m so bad lol…he found 4 clients…bad ones like “valid” and the server…sorry…

i wait for your code, when you have time…no problem…and thanks for your help!

@Olivier974 I’ve uploaded a new version of the script which looks like it should work with either WireGuard or OpenVPN (you need to set this in the script configuration section). I’ve had a quick play with it and it seems to stand up, had to create a new OpenVPN instance to test against so haven’t done major testing and would appreciate any feedback.

Question: in WireGuard when you delete a client the record is removed, my script then sends an empty discovery topic to HA and the entity is removed. However, for OpenVPN it marks them as revoked. What do you think is the correct behaviour to be reflected back in Home Assistant for these?

1 Like

You’re the boss! thanks a lot it works great!

i have just the “which” : sensor.vpn_client_which that appear before, but perhaps just a bug, i can delete this entity or desactivate anyway, no matter.

Response : yes its the correct behaviour for me, i know i can delete the revoked clients from

/etc/openvpn/easy-rsa/pki/index.txt

you make my day! thanks a lot for your time and support!

have a nice day

Last detail : there is a websocket connection error in my mqtt broker every minute…perhaps the way its disconnected in your script? just a detail…but perhaps you know its coming from…

EDIT : no more “which” client after restart HA sorry…

Thanks for all your work on this!
I just added this to my setup (with WireGuard as the VPN) and kept getting an IndexError: list index out of range - I traced it back to line 86: deviceCount = (len(rawDevices) - 9) / 7
The problem was that the last line of output from pivpn -c now is ::: Disabled clients ::: and the script was trying to run on this
Fix is simple: deviceCount = ((len(rawDevices) - 9) / 7) - 1
This cuts off that last line, and now it works well.