Current state of this scripts is: "Working for me, but let's make this better" stage.
While working from home, we all have a lot of meetings, and sometimes a family member or a roommate has no idea that you are in a meeting and they walk into the room without even a knock. Setting up a light to show when you are "ON AIR" or in a meeting is a great solution. There are several solutions out on the internet that can do this. Still, a lot of these solutions connect to your calendar via IFTTT or Zapier and then you need to create an action with homekit or homebridge to turn on a light with the colour red when in a meeting and green when it is safe to enter. Another straightforward way is to buy a button that turns a light on or off. (The trick is to try not to forget to push the button, I always forget)
In this case, I couldn't connect my work calendar these 3rd party services or send a webhook event from an API, when a meeting has started or ended.
Also, another challenge is that I am using multiple meeting applications (Zoom, WebEx, Microsoft Teams, Slack, etc.), and each application has its way of doing things.
Then when discussing with my manager and colleagues, I came up with an idea to have a script that monitors your mac for an active online meeting.
(When the camera and mic are on!! As in full active participation, not just listing in)
Once a meeting found, it can then control the HueLights via API.
What i used for this setup:
- Philips Hue Bridge v2
- Hue Bloom - Model: LLC011
- Web Browser (Safari, Google Chrome)
- Text Editor (BBEdit, Coderunner)
- macOS Automator.app (located in /Applications)
Create a API user in Bridge link to source
First we need to search for the Hue Bridge on your network. To lookup on your network use the following link: https://discovery.meethue.com/
Result example:
[{"id":"001234ddse27c6be","internalipaddress":"10.0.1.111"}]
Use the internal API Debugger to create an API User, go to https://10.0.1.111/debug/clip.html Change #internalipaddress into the internalipaddress you just found.
We the following command in the API Debugger tool, we are generating a API user.
Fill in the info like below (you can use your own name) in the API Debugger and press the POST
button.
URL: /api
Message Body:
{"devicetype":"my_hue_app#username"}
When you press the POST
button you should get back an error message letting you know that you have to press the link button
Result example:
[
{
"error": {
"type": 101,
"address": "/",
"description": "link button not pressed"
}
}
]
Now press the button on the bridge and then press the POST
button again and you should get a success response like below.
Result example:
[
{
"success": {
"username": "FIAqb-53KaLBVzXKscihomProgvhUkRko59TAuV"
}
}
]
We now have enabled an API user and now we can authenticate with the Hue Bridge for communication. Please write down the Hue API hash that you got.
Now we can test with the following command:
URL: https://10.0.1.111/api/FIAqb-53KaLBVzXKscihomProgvhUkRko59TAuV
Message Body:
When you press the GET
button you should get back a list of devices that are connected to the bridge
Result example:
{
"lights": {
"1": {
"state": {
"on": true,
"bri": 254,
"hue": 25600,
"sat": 254,
"effect": "none",
"xy": [
0.2151,
0.7106
],
"alert": "lselect",
"colormode": "xy",
"mode": "homeautomation",
"reachable": true
},
"swupdate": {
"state": "noupdates",
"lastinstall": "2018-12-12T19:06:42"
},
"type": "Color light",
"name": "Hue bloom 1",
"modelid": "LLC011",
"manufacturername": "Signify Netherlands B.V.",
"productname": "Hue bloom",
"capabilities": {
"certified": true,
"control": {
"mindimlevel": 10000,
"maxlumen": 120,
"colorgamuttype": "A",
"colorgamut": [
[
0.704,
0.296
],
[
0.2151,
0.7106
],
[
0.138,
0.08
]
]
},
"streaming": {
"renderer": true,
"proxy": false
}
},
"config": {
"archetype": "huebloom",
"function": "decorative",
"direction": "upwards",
"startup": {
"mode": "safety",
"configured": true
}
},
"uniqueid": "00:11:22:01:00:1c:4e:ec-0b",
"swversion": "5.127.1.26581"
},
}
}
For testing we are going to use the light with the ID 1
Why put cleartext passwords in scripts, when we can use the macOS Keychain to securely store this information for us.
I added this easy way to the script, so we can have a placeholder for the password rather then leaking this password within the script.
How to
After creating the API user with the API Debugger tool, we received the Hue API Hash.
We are going to add the Hue API Hash into the macOS Keychain with the security
command.
For this command we are using -T
to add an entry to the login keychain and add the security
binary to "Always allow access by these applications:" list in the Access Control preferences.
security add-generic-password [-s service] [-a account] [-w password] -T [appPath]
Usage:
-s service Specify service name (required)
-a account Specify account name (required)
-w password Specify password to be added. Put at end of command to be prompted (recommended)
-T appPath Specify an application which may access this item (multiple -T options are allowed)
Example:
security add-generic-password -s hueAPIHash -a HUEAPI -w FIAqb-53KaLBVzXKscihomProgvhUkRko59TAuV -T /usr/bin/security
Now we securely store the Hue API Hash into the macOS Keychain, and allowing the security
binary to access this entry.
We can use the security
command to fetch the Hue API Hash.
security find-generic-password [-s service] -w
Usage:
-s service Match service string
-w Display the password(only) for the item found
We only need to provide the service name and ask for the password
Example:
security find-generic-password -s "hueAPIHash" -w
#RESULT
FIAqb-53KaLBVzXKscihomProgvhUkRko59TAuV
See the man page security in terminal for more options. man security
Now we have all the info we need to fill in the Global variables in the script
# Global variables
hueBridge='10.0.1.111' #use your internal ipaddress
hueApiHash=$(security find-generic-password -s "hueAPIHash" -w) #use your service name by [-s "hueAPIHash"]
hueLight="1" #use your light ID that you wanna use
"How do we know when we are in a meeting" (even when we have turned off the Camera and/or mic)
Our options are:
- We could look for running process, but this doesn't mean you are in a meeting.
- Hook our calendar up to a 3rd party service (can't do that)
The only thing that seems to be content and reliable is if there is an open connection.
Running lsof
(List open files) command without any options will list all open files of your system that belongs to all active process.
This process takes a while and you will get a full list of everything, but we don't need all this information.
We are going to narrow this down to only internet related connections by adding -i
to the command.
Example
lsof -i | grep zoom
zoom.us 53231 mvdbent 26u IPv4 0x64763030ad598a1d 0t0 TCP 10.0.1.116:60144->ec2-3-235-72-248.compute-1.amazonaws.com:https (ESTABLISHED)
zoom.us 53231 mvdbent 48u IPv4 0x64763030acb3165d 0t0 TCP 10.0.1.116:63973->ec2-52-202-62-196.compute-1.amazonaws.com:https (ESTABLISHED)
zoom.us 53231 mvdbent 51u IPv4 0x64763030adc2b03d 0t0 TCP 10.0.1.116:55830->ec2-3-235-96-204.compute-1.amazonaws.com:https (ESTABLISHED)
zoom.us 53231 mvdbent 56u IPv4 0x64763030a88c4c7d 0t0 TCP 10.0.1.116:63978->149.137.8.183:https (ESTABLISHED)
We now add the following options to the lsof
command:
- -a option ( can be used to ANDed the selections)
- -n (inhibits the conversion of network numbers to host names for network files)
- -P (inhibits the conversion of port numbers to port names for network files)
We want to inhibit the output so
lsof
can give us results faster.
Example
lsof -anP -i | grep zoom
zoom.us 53231 mvdbent 26u IPv4 0x64763030ad598a1d 0t0 TCP 10.0.1.116:60144->3.235.72.248:https (ESTABLISHED)
zoom.us 53231 mvdbent 48u IPv4 0x64763030acb3165d 0t0 TCP 10.0.1.116:63973->52.202.62.196:https (ESTABLISHED)
zoom.us 53231 mvdbent 51u IPv4 0x64763030adc2b03d 0t0 TCP 10.0.1.116:55830->3.235.96.204:https (ESTABLISHED)
zoom.us 53231 mvdbent 56u IPv4 0x64763030a88c4c7d 0t0 TCP 10.0.1.116:63978->149.137.8.183:https (ESTABLISHED)
Now we found the active process that have a internet related connection, this still doesn't mean that we are in a meeting. This means that the Zoom.us app is opend and logged in with your account. After starting a meeting in zoom, we got extra connections based on UDP added.
Example
lsof -anP -i | grep zoom
zoom.us 53231 mvdbent 26u IPv4 0x64763030ad598a1d 0t0 TCP 10.0.1.116:60144->3.235.72.248:https (ESTABLISHED)
zoom.us 53231 mvdbent 48u IPv4 0x64763030acb3165d 0t0 TCP 10.0.1.116:63973->52.202.62.196:https (ESTABLISHED)
zoom.us 53231 mvdbent 51u IPv4 0x64763030adc2b03d 0t0 TCP 10.0.1.116:55830->3.235.96.204:https (ESTABLISHED)
zoom.us 53231 mvdbent 56u IPv4 0x64763030a88c4c7d 0t0 TCP 10.0.1.116:63978->149.137.8.183:https (ESTABLISHED)
zoom.us 53231 mvdbent 60u IPv4 0x64763030846e819d 0t0 UDP 10.0.1.116:63026
zoom.us 53231 mvdbent 61u IPv4 0x64763030846e8d3d 0t0 UDP 10.0.1.116:58615
zoom.us 53231 mvdbent 65u IPv4 0x64763030846e98dd 0t0 UDP *:53327
zoom.us 53231 mvdbent 67u IPv4 0x6476303084763a55 0t0 UDP *:55248
zoom.us 53231 mvdbent 68u IPv4 0x647630307bd37d3d 0t0 UDP *:53574
So i did a couple of test, ended the meeting, UDP connections where gone, started a new meeting, UDP connections are back turned. Turned off my Camera, then turned on, turned off the Microphone, and turned both off, the UDP connections where still there. Awesome No we now where to look for when it comes to Zoom.us.
We only need to list the network files with TCP state LISTEN, with the -sTCP:LISTEN
option
Optional: We can specifies the IP version, IPv4 or IPv6 by adding 4
or 6
, in the script we specify IPv4.
Example
lsof -anP -i4 -sTCP:LISTEN | grep zoom
zoom.us 53231 mvdbent 60u IPv4 0x64763030846e819d 0t0 UDP 10.0.1.116:63026
zoom.us 53231 mvdbent 61u IPv4 0x64763030846e8d3d 0t0 UDP 10.0.1.116:58615
zoom.us 53231 mvdbent 65u IPv4 0x64763030846e98dd 0t0 UDP *:53327
zoom.us 53231 mvdbent 67u IPv4 0x6476303084763a55 0t0 UDP *:55248
zoom.us 53231 mvdbent 68u IPv4 0x647630307bd37d3d 0t0 UDP *:53574
Usage:
-a causes list selection options to be ANDed, as described above.
-n inhibits the conversion of network numbers to host names for
network files. Inhibiting conversion may make lsof run
faster. It is also useful when host name lookup is not working properly.
-P inhibits the conversion of port numbers to port names for network files.
Inhibiting the conversion may make lsof run a little faster.
It is also useful when port name lookup is not working properly.
-i selects the listing of files any of whose Internet address
matches the address specified in i. If no address is specified,
this option selects the listing of all Internet and x.25
(HP-UX) network files
46 specifies the IP version, IPv4 or IPv6 that applies to the following address.
'6' may be be specified only if the UNIX dialect supports IPv6.
If neither '4' nor '6' is specified, the following address applies to all IP versions.
sTCP To list only network files with TCP state LISTEN, use: -sTCP:LISTEN
Fun fact is that beside of zoom.us, Microsoft Teams, Cisco WebEx, Slack and FaceTime is also using TCP state LISTEN. Only Microsoft Teams connected this to your localIP
Example
lsof -anP -i4 -sTCP:LISTEN | grep Microsoft | grep 10.0.1.116:'*'
Microsoft 67439 mvdbent 45u IPv4 0x647644287bdb076d 0t0 UDP 10.0.1.116:50023
This script will look for zoom.us, Microsoft Teams, Cisco WebEx, Slack and FaceTime online sessions. Want to have
Now we have a script that scans locally for any running meetings, so we need to create an easy way to start and stop this process.
For this version we will use the Apple Automator.app to create an application.
Create a New Document
and choose type Application
- Add Action
Run Shell Script
- Copy the contents of
autorunscript.sh
and paste this into theRun Shell Script
input field. - Add an other Action
Loop
. - Change
Ask to continue
toLoop automatically
and Stop after 480 Minutes (8 hours) - Save the Application to your preferred location, you can add this to your
Dock
where you can easy start the App, or add the App to theLogin Items
to open automatically.
If you want to stop/quit the App, in the menubar click on on the spinning gear icon, behind the status, App name (Loop), you can click the X to quit the App.