I wrote a Python script to configure Crestron AirMedia devices, via the REST api interface on each device. The intention for this script is to use the command line to configure many dozens of AirMedia devices at once, by typing “python AirMediaAPI.py” (or, use the filename you save the python as).
The script imports a .csv file that contains a line entry for each AirMedia to be configured. The .csv file has the following fields:
- target_ip (current ip address of the AirMedia to be configured)
- room_name (name of the intended room – the script will also turn this into the device’s Hostname)
- intended_dns (new static DNS)
- vlan_ip (new static ip address for main PoE NIC)
- vlan_subnet (new subnet mask for main PoE NIC)
- vlan_gateway (new default gateway for main PoE NIC)
- is_conference (flag indicating which settings the script should configure)
- user (admin username of AirMedia – script will create a user with this username, if needed)
- passwd (admin password of AirMedia – script will use to create user, if needed)
Github repo
No public repo, right now.
Code
# Crestron AirMedia API Config 0.9.4# Required libraries: os, requests, urllib3, pandas (all are installed with native python, but you still need import into script)import osimport requestsimport urllib3import pandas as pd# Disable error message for unsecure connectionurllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)# Get username from system and create filepath variablesuser = os.environ.get('USER', os.environ.get('USERNAME'))data_filepath = f"C:/Users/{user}/Downloads/am_api_template.csv"puf_filepath = f"C:/Users/{user}/Downloads/am3xxx_3.0700.0379.puf" # I just update this, as needed# Read am_api_template.csv into python as a list of key-value pairsd = pd.read_csv(data_filepath, header=0)data = []for row in d.index: i = {} for col in d.columns: i[col] = d.loc[row,col] data.append(i)del(d)# Config - loops through each entry in data variable's key-value pairs and configures the parameters listed in "iterator" variablefor am in data: target_ip = am["target_ip"] index_am = "https://" + target_ip + "/index_airmedia.html" index_banner = "https://" + target_ip + "/index_banner.html" url_create_user = "https://" + target_ip + "/createUser.html" url_login = "https://" + target_ip + "/userlogin.html" room_name = am["room_name"] am_name = (room_name + "-AM3200WF").upper().replace(" ", "").strip() intended_dns = am["intended_dns"] is_room_conference = am["is_conference"] intended_ip = am["vlan_ip"] intended_subnet = am["vlan_subnet"] intended_gateway = am["vlan_gateway"] am_user = am['user'] am_passwd = am['passwd'] # iterator is a list of tuples. each tuple consists of (0) a directory entry and (1) a json object with the data for that entry if is_room_conference == 0: iterator = [ ("/Device/NetworkAdapters/HostName", {"Device": {"NetworkAdapters": {"HostName": am_name}}}), ("/Device/AirMedia/ConnectionDisplayOptions/ShowConnectionInfo", {"Device": {"AirMedia": {"ConnectionDisplayOptions": {"ShowConnectionInfo": False}}}}), ("/Device/AirMedia/ConnectionDisplayOptions/ShowConnectionInfo", {"Device": {"AirMedia": {"ConnectionDisplayOptions": {"PersistConnectionInfo": False}}}}), ("/Device/AirMedia/ConnectionDisplayOptions/ShowAirplayInfo", {"Device": {"AirMedia": {"ConnectionDisplayOptions": {"ShowAirplayInfo": False}}}}), ("/Device/AirMedia/ConnectionDisplayOptions/ShowMiracastInfo", {"Device": {"AirMedia": {"ConnectionDisplayOptions": {"ShowMiracastInfo": False}}}}), ("/Device/Bluetooth/IsEnabled", {"Device": {"Bluetooth": {"IsEnabled": False}}}), ("/Device/App/Config/General/RoomName", {"Device": {"App": {"Config": {"General": {"RoomName": room_name}}}}}), ("/Device/App/Config/RuntimeSettings/ShowOverlay", {"Device": {"App": {"Config": {"RuntimeSettings": {"ShowOverlay": False}}}}}), ("/Device/App/Config/RuntimeSettings/PinPointUXFontSize", {"Device": {"App": {"Config": {"RuntimeSettings": {"PinPointUXFontSize": "Large"}}}}}), ("/Device/NetworkAdapters/DnsSettings/IPv4/StaticDns", {"Device": {"NetworkAdapters": {"DnsSettings": {"IPv4": {"StaticDns": [intended_dns]}}}}}), ("/Device/App/Config/RuntimeSettings/ShowCalendarClockOverlay", {"Device": {"App": {"Config": {"RuntimeSettings": {"ShowCalendarClockOverlay": False}}}}}), ("/Device/IpTableV2", {"Device": {"IpTableV2": {"EntriesCurrentKeyList": ["7"], "Entries": {"7": {"Address": "172.22.0.1", "IpId": "7", "Port": 41794, "ProgramInstanceId": ""}}}}}), ("/Device/SystemClock/Ntp/Server1/Address", {"Device": {"SystemClock": {"Ntp": {"Servers": {"Server1": {"Address": "10.40.8.1"}}}}}}), ("/Device/AirMedia/SystemMode", {"Device": {"AirMedia": {"SystemMode": "OptimizedForVideoQuality"}}}), ("/Device/NetworkAdapters/Adapters", {"Device": {"NetworkAdapters": {"Adapters": {"EthernetLan": {"IPv4": {"StaticAddresses": [{"Address": intended_ip, "SubnetMask": intended_subnet}], "StaticDefaultGateway": intended_gateway, "IsDhcpEnabled": False}}}}}}) ] else: iterator = [ ("/Device/NetworkAdapters/HostName", {"Device": {"NetworkAdapters": {"HostName": am_name}}}), ("/Device/AirMedia/ConnectionDisplayOptions/ShowConnectionInfo", {"Device": {"AirMedia": {"ConnectionDisplayOptions": {"ShowConnectionInfo": True}}}}), ("/Device/AirMedia/ConnectionDisplayOptions/ShowConnectionInfo", {"Device": {"AirMedia": {"ConnectionDisplayOptions": {"PersistConnectionInfo": False}}}}), ("/Device/AirMedia/ConnectionDisplayOptions/ShowAirplayInfo", {"Device": {"AirMedia": {"ConnectionDisplayOptions": {"ShowAirplayInfo": True}}}}), ("/Device/AirMedia/ConnectionDisplayOptions/ShowMiracastInfo", {"Device": {"AirMedia": {"ConnectionDisplayOptions": {"ShowMiracastInfo": True}}}}), ("/Device/Bluetooth/IsEnabled", {"Device": {"Bluetooth": {"IsEnabled": True}}}), ("/Device/App/Config/General/RoomName", {"Device": {"App": {"Config": {"General": {"RoomName": room_name}}}}}), ("/Device/App/Config/RuntimeSettings/ShowOverlay", {"Device": {"App": {"Config": {"RuntimeSettings": {"ShowOverlay": False}}}}}), ("/Device/App/Config/RuntimeSettings/PinPointUXFontSize", {"Device": {"App": {"Config": {"RuntimeSettings": {"PinPointUXFontSize": "Large"}}}}}), ("/Device/NetworkAdapters/DnsSettings/IPv4/StaticDns", {"Device": {"NetworkAdapters": {"DnsSettings": {"IPv4": {"StaticDns": [intended_dns]}}}}}), ("/Device/App/Config/RuntimeSettings/ShowCalendarClockOverlay", {"Device": {"App": {"Config": {"RuntimeSettings": {"ShowCalendarClockOverlay": False}}}}}), ("/Device/IpTableV2", {"Device": {"IpTableV2": {"EntriesCurrentKeyList": ["7"], "Entries": {"7": {"Address": "172.22.0.1", "IpId": "7", "Port": 41794, "ProgramInstanceId": ""}}}}}), ("/Device/SystemClock/Ntp/Server1/Address", {"Device": {"SystemClock": {"Ntp": {"Servers": {"Server1": {"Address": "10.40.8.1"}}}}}}), ("/Device/AirMedia/SystemMode", {"Device": {"AirMedia": {"SystemMode": "OptimizedForVideoQuality"}}}), ("/Device/NetworkAdapters/Adapters", {"Device": {"NetworkAdapters": {"Adapters": {"EthernetLan": {"IPv4": {"StaticAddresses": [{"Address": intended_ip, "SubnetMask": intended_subnet}], "StaticDefaultGateway": intended_gateway, "IsDhcpEnabled": False}}}}}}) ] # Set first-time admin user, if needed try: r = requests.Session() r.get("https://" + target_ip, verify=False) init_track_id = requests.utils.dict_from_cookiejar(r.cookies) auth_headers = {"Cookie": init_track_id['TRACKID'], "Host": target_ip, "Referer": "https://" + target_ip + "/"} r.get(index_am, verify=False, headers=auth_headers) auth_headers['Referer'] = index_am r.get(index_banner, verify=False, headers=auth_headers) auth_headers['Referer'] = index_banner r.get(url_create_user, verify=False, headers=auth_headers) auth_headers['Referer'] = url_create_user auth_headers['Origin'] = "https://" + target_ip r.post("https://" + target_ip + "/Device/Authentication", verify=False, headers=auth_headers, json={"Device": {"Authentication": {"AuthenticationState": {"AdminPassword": am_passwd, "AdminUsername": am_user, "IsEnabled": True}}}}) r.close() print(f"created user for {target_ip}") except: r.close() print(f"{target_ip} already had a user") # Settings Config s = requests.Session() s.get(url_login, verify=False) init_track_id = requests.utils.dict_from_cookiejar(s.cookies) post_headers = {"Cookie": next(iter(init_track_id)) + "=" + init_track_id['TRACKID'], "Origin": target_ip, "Referer": target_ip + "/userlogin.html"} post_body = f"login={am_user}&&passwd={am_passwd}" auth = s.post(url_login, verify=False, headers=post_headers, data=post_body) print("Configuring " + room_name) get_token = {"CREST-XSRF-TOKEN": auth.headers["CREST-XSRF-TOKEN"]} post_token = {"X-CREST-XSRF-TOKEN": auth.headers["CREST-XSRF-TOKEN"]} for i in iterator: r = s.post("https://" + target_ip + i[0], json = i[1], verify=False, headers=post_token) s.close() # Powershell script to handle firmware update and restart. It will trigger a possible firmware upgrade, if the version in your downloads folder is newer than that on your AirMedia. It will not check for completion of the upgrade. os.system(f"powershell -File C:/Users/{user}/Downloads/AirMediaAPI_Powershell.ps1 -target_ip {target_ip} -filepath {puf_filepath}") print("Completed " + room_name)print("All done. Wait for firmware update, if you think an AirMedia updated firmware (this tool passes the update-puf command, but does not check for completion).")
The python script executes a powershell script that covers firmware updates and device reboot. You can do this through the api, but I found powershell to be effective.
import-module pscrestron -usewindowspowershell# set target_ip and filepath as mandatory parameters when invoking this scriptparam( [Parameter(Mandatory=$true)] [string]$target_ip)param( [Parameter(Mandatory=$true)] [string]$filepath)# Update Firmwareupdate-puf -device $target_ip -path $filepath -secure -username "example" -password "example" -ShowProgress;# Rebootinvoke-crestroncommand -device $target_ip -secure -username "example" -password "example" -command "reboot";
Future changes
One change that I might implement for full release, is converting this to a web app, but I haven’t decided yet. I will probably wait until others in my org start using this, and get their feedback.
Python package for interacting with AirMedia API: forthcoming