Writing a simple Garmin app for my watch
Posted by joseph on 01 Jun 2021 in Garmin-ConnectIQ
<style> kbd { color: black; padding: .1em .6em; font-size: 11px; background-color: #e4e6e8; border: 1px solid #9fa6ad; border-radius: 3px; box-shadow: 0 1px 1px rgba(12,13,14,0.15),inset 0 1px 0 0 white; white-space: nowrap; } md { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; } pre { background-color: rgba(255, 255, 255, 255); } p { white-space: normal; } </style> # What am I doing? I am going to be writing a [garmin](https://www.garmin.com/en-GB/ "garmin.com")-[connectiq](https://developer.garmin.com/ "developer.garmin.com") app for my smart watch for this specific app I will write a remote control for my family's new Denon AV amplifier (but this uses telnet so I wrote a simple [cgi-bin](https://www.techopedia.com/definition/5585/cgi-bin "techopedia.com") script to connect to the socket and send the commands). # How will it work? From my watch I will have to request my phone to make an HTTP request to my raspberry pi to tell it to open a socket on telnet on the amplifier and send the command. ```mermaid graph TD A[Watch] ==> |BluTooth| B[Phone] ==> |WiFi| C{Home Router} ==> |WiFi/ethernet| G{Raspberry Pi} ==> |WiFi/ethernet| I{Home Router} ==> |WiFi/ethernet| D{Amplifier} B --> F{Other WiFi} --> E(Public Domain) --> C ``` # Building the app ## Setup I first made a test app which would have three buttons each sending different requests to a cgi-script on my RPi to log them, when I got this working (which means copying and slightly editing their example code) I swiftly moved on to writing the cgi-script: ```python #!/usr/bin/python3 import cgi import socket import json map = { "power": { "on": "PWON", "off": "PWSTANDBY", "get": "PW?" }, "mute": { "on": "MUON", "off": "MUOFF", "get": "MU?" }, "volume": { "up": "MVUP", "down": "MVDOWN", "set": "MV{}", "get": "MV?" }, "sources": { "set": "SI{}", "get": "SI?", # "input-map": [ # {"Socket label": "SAT", "Plug": "Chromecast"}, # {"Socket label": "DVD", "Plug": "-"}, # {"Socket label": "BD", "Plug": "BluRay"}, # {"Socket label": "MPLAY", "Plug": "HDMI"}, # {"Socket label": "GAME", "Plug": "x1x"}, # {"Socket label": "AUX1", "Plug": "x360"} # ], "input-map": { "Chromecast": "SAT", "BluRay": "BD", "HDMI": "MPLAY", "x1x": "GAME", "x360": "AUX1", "BlueTooth": "NET", "TV": "TV", "PHONO": "PHONO", "TUNER": "TUNER", "SAT/CBL": "Chromecast", "BD": "BluRay", "MPLAY": "HDMI", "GAME": "x1x", "AUX1": "x360", "NET": "BlueTooth" } } } def telnet(data:str, extra=None): s = socket.socket() s.connect(("192.168.1.5", 23)) s.sendall(bytes(data.format(extra), "UTF-8")) o = str(s.recv(1024), 'UTF-8')[2:] s.close() return o data = cgi.FieldStorage() type = data["type"].value if type == "json": print('Content-type: application/json\n') o = {} s = socket.socket() s.connect(("192.168.1.5", 23)) s.sendall(bytes(map["mute"]["get"], "UTF-8")) o["mute"] = str(s.recv(1024), 'UTF-8')[2:] == "ON\r" s.sendall(bytes(map["sources"]["get"], "UTF-8")) o["socket"] = str(s.recv(1024), 'UTF-8')[2:-1] o["device"] = map["sources"]["input-map"][o["socket"]] s.sendall(bytes(map["volume"]["get"], "UTF-8")) s.recv(1024) volume = str(s.recv(1024), 'UTF-8') o["volume"] = float(volume[2:4] + "." + volume[4:]) print(json.dumps(o)) exit() print('Content-type: text/plain\n') function = data["function"].value try: extra = data["extra"].value if type == "sources": print(telnet(map[type][function], map["sources"]["input-map"][extra])) else: print(telnet(map[type][function], extra)) except: if function == "toggle": s = socket.socket() s.connect(("192.168.1.5", 23)) s.sendall(bytes(map[type]["get"], "UTF-8")) if str(s.recv(1024), "UTF-8")[2:] == "ON\r": s.sendall(bytes(map[type]["off"], "UTF-8")) else: s.sendall(bytes(map[type]["on"], "UTF-8")) print(str(s.recv(1024), "UTF-8")[2:]) s.close() else: print(telnet(map[type][function])) ``` It simply gets the url parameters converts them to a telnet command and sends it. ## Finalizing To finish I setup a series of menus on the app to allow control of volume (and mute), source and power. I then connected them to functions to send the requests and I was done. My source code can be found on my github as it is quite large with boilerplate.