In most homes today we have some home computer for the kids, some laptops, and several tablets/phones. Now just try to get them all to press the volume-down buttons when you want to rest… How about a self-discovering, distributed service for controlling volume around the house?… Did I hear someone saying: “Give us some snazzy UI to remote control our kids with”? your wish is my command.

Every now and then, I get an itch to write a code project, which would act as a testing ground for new tech, and expose me to problems and ideas I wouldn’t otherwise have explored. I really like Golang’s ability to present a small, self contained service, which includes a REST backend & Web-server in one smallish exe file (and a public dir for html/js), I also really liked VueJS for the same kind of simplicity, It has no problem having just one html file for everything. I wanted to test this notion by building a small home project, I also got to explore network service-discovery, just to spice it up. I might later take this experiment into go-mobile or progressive web apps.

The complete source code for this project can be found on my github page.

Service discovery options:

SSDP: one of the original “simple” protocols that was invented along with the internet itself, based on UDP multicast, very easy to implement and incorporated into upnp, but rather overused and might be blocked by home router configuration. The go implementation only had 15 stars, hmm..

ZeroConf: zconf uses Multicast DNS, the go implementation has 140 stars, go-mdns (hashicorp) has 450 , this sounds more like mainstream code to me. mDNS is also based on UDP multicast (bi-directional this time), it is said to be more resilient to common home network mis-configurations. Both are equally non-secure, but we are talking about home networks...

I would have gone with zeroconf, but after playing with it (guide), it seems to require installing a Bonjour client, or applying some reg hack to disable a service that is listening on the mDNS default ports. I was naively thinking that the whole idea of zeroconf was ZERO configuration. So I ended up opting for some SSDP Simplicity.

import (

ssdp "github.com/koron/go-ssdp"

) hname, err := os.Hostname() //advertizing the service

ad, err := ssdp.Advertise(

"urn:schemas-upnp-org:service:KidControl:1", // send as "ST"

"id:"+hname, // send as "USN"

"http://"+myIp+":7777/", // send as "LOCATION"

"ssdp for KidControl", // send as "SERVER"

3600) // send as "maxAge" in "CACHE-CONTROL"

if err != nil {

logger.Error("Error advertising ssdp: ", err)

} //searching for instances on the network

svcList,err := ssdp.Search("urn:schemas-upnp-org:service:KidControl:1", 5, "")

After settling that debate, let’s go on to writing the actual service. we’ll use go-volume to control volume Cross-OS, on windows we should make sure we are not in a remote session (that doesn’t work). Laying down some boilerplate server code, for get/set vol REST API, and serving up a public web root:

import (

gmux "github.com/gorilla/mux"

)



mux := gmux.NewRouter() //restfull API:

mux.HandleFunc("/set-volume", setVolumeOnMachine).Queries("machine", "{machine}")

mux.HandleFunc("/set-volume", setVolume)

mux.HandleFunc("/get-volume", getVolumeOnMachine).Queries("machine", "{machine}")

mux.HandleFunc("/get-volume", getVolume)

mux.HandleFunc("/configuration", sendConfig) //web server:

mux.PathPrefix("/").Handler(http.FileServer(http.Dir("./public"))) //run the Http listener:

err := http.ListenAndServe(":8080", mux)

if err != nil {

fmt.Print("lisgtening error: ", err)

}

Now on to the JS side for a nice web UI. Since I am not a web designer, and have no aspiration to be one, I’ll just pickup some nice css suite (e.g.: bootstrap) to take care of responsive tables and rounded buttons for me.

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" >

The old Vue / Angular / React debate:

Since we will have a dynamic repeating part, for controlling the volume in each discovered machine, we need some framework to generate dynamic HTML tags and bind them to the data we get from the server.

This is where VueJS comes in, now you say: “It could have been any other JS framework why pick vue? why not Angular v1.5/v6, ReactJS etc…” Well, these other frameworks normally require a considerable installation, along with some UI build process and some time spent learning the specifics of their not-so-intuitive operation. Digging around the network for minimal angular/react demos I did find some, but they are either old or hacky, and not recommended for production. Vue is extremely easy to integrate, just like bootstrap, you drop a “script src” link into the project and you are ready to start coding with Vue without a need for a build chain (no webpack, pre-compilation steps, plugins, etc.) and this is official and recommended on their main page. “OK then, this Vue thing sounds like good fun. Sign me up!”

Let there be Vue:

<script src="https://unpkg.com/vue"></script> <div class="form-group m-3" id="vueApp">

<h3>Change Volume</h3> <!--we start using vue, and attach it to the 'vueApp' ID later-->

<div v-for="m in machines" class="row">

<div class="col-md-1">

<b>{{m.name}}</b>

</div>

<div class="col-md-2">

<input type="range" v-on:change="changeVolume(m.volume,m.name)" min="0" max="100" v-model="m.volume" id="volumeSlider"></input>

</div>

<div class="col-md-1">

<input type="text" v-on:change="changeVolume(m.volume,m.name)" id="volumeText" v-model="m.volume"></input>

</div>

</div>

Some JQuery ajax functions to connect VueJS to our REST API:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script>

//the JS part of vue, this is where we attach to the tags

var appData = new Vue({

el: '#vueApp',

data: {

machines: [{

name: "localhost",

volume: 100

},

]

},

mounted: function () {

//the "on document ready" function

var self = this;

$.get("http://" + window.location.host + "/configuration", function (data) {

let d = JSON.parse(data)

self.machines = d.machines;

});

},

methods: {

changeVolume: function (slideValue, machine) {

let url = "http://" + window.location.host + "/set-volume?machine=" + machine $.ajax({

url: url,

data: JSON.stringify({

"volume": slideValue,

}),

dataType: 'json',

type: 'POST',

contentType: 'application/json',

success: response => console.log(response),

error: e => console.log(e)

});

}

}

}) </script>

Presto! Go around running it on a number of computers around the house (Win/Linux/Mac), afterwards connecting to any one of them will give you something like this display:

Playing around with the volume around the house was never so easy!!

Now there is a shiny new webapp to shush the kid’s youtube streams with. It was also a ton of fun writing the application… I love simplicity when it comes to code, I think most programmers do, it is one of the reasons Node, JSON and JS are so successful. There is something magical about starting up easy, gaining complexity only when asked for, which makes it all seem like a kids construction game. In my opinion, frameworks with steep learning curves are very rarely justified, this drive towards simplicity is why developers keep rebuilding applications and frameworks from scratch, always thinking: “there has to be an easier way to accomplish the task”.