Ver Fonte

init commit (wip)

iot-audio test bench há 4 anos atrás
pai
commit
2554637cec
8 ficheiros alterados com 631 adições e 0 exclusões
  1. 189 0
      common.go
  2. 5 0
      go.mod
  3. 26 0
      go.sum
  4. 25 0
      handlers.go
  5. 16 0
      mac.go
  6. 39 0
      main.go
  7. 196 0
      mod/mdns/common.go
  8. 135 0
      mod/mdns/mdns.go

+ 189 - 0
common.go

@@ -0,0 +1,189 @@
+package main
+
+import (
+	"bufio"
+	"encoding/base64"
+	"errors"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"os"
+	"strconv"
+	"strings"
+	"time"
+)
+
+/*
+	Basic Response Functions
+
+	Send response with ease
+*/
+//Send text response with given w and message as string
+func sendTextResponse(w http.ResponseWriter, msg string) {
+	w.Write([]byte(msg))
+}
+
+//Send JSON response, with an extra json header
+func sendJSONResponse(w http.ResponseWriter, json string) {
+	w.Header().Set("Content-Type", "application/json")
+	w.Write([]byte(json))
+}
+
+func sendErrorResponse(w http.ResponseWriter, errMsg string) {
+	w.Header().Set("Content-Type", "application/json")
+	w.Write([]byte("{\"error\":\"" + errMsg + "\"}"))
+}
+
+func sendOK(w http.ResponseWriter) {
+	w.Header().Set("Content-Type", "application/json")
+	w.Write([]byte("\"OK\""))
+}
+
+/*
+	The paramter move function (mv)
+
+	You can find similar things in the PHP version of ArOZ Online Beta. You need to pass in
+	r (HTTP Request Object)
+	getParamter (string, aka $_GET['This string])
+
+	Will return
+	Paramter string (if any)
+	Error (if error)
+
+*/
+func mv(r *http.Request, getParamter string, postMode bool) (string, error) {
+	if postMode == false {
+		//Access the paramter via GET
+		keys, ok := r.URL.Query()[getParamter]
+
+		if !ok || len(keys[0]) < 1 {
+			//log.Println("Url Param " + getParamter +" is missing")
+			return "", errors.New("GET paramter " + getParamter + " not found or it is empty")
+		}
+
+		// Query()["key"] will return an array of items,
+		// we only want the single item.
+		key := keys[0]
+		return string(key), nil
+	} else {
+		//Access the parameter via POST
+		r.ParseForm()
+		x := r.Form.Get(getParamter)
+		if len(x) == 0 || x == "" {
+			return "", errors.New("POST paramter " + getParamter + " not found or it is empty")
+		}
+		return string(x), nil
+	}
+
+}
+
+func stringInSlice(a string, list []string) bool {
+	for _, b := range list {
+		if b == a {
+			return true
+		}
+	}
+	return false
+}
+
+func fileExists(filename string) bool {
+	_, err := os.Stat(filename)
+	if os.IsNotExist(err) {
+		return false
+	}
+	return true
+}
+
+func IsDir(path string) bool {
+	if fileExists(path) == false {
+		return false
+	}
+	fi, err := os.Stat(path)
+	if err != nil {
+		log.Fatal(err)
+		return false
+	}
+	switch mode := fi.Mode(); {
+	case mode.IsDir():
+		return true
+	case mode.IsRegular():
+		return false
+	}
+	return false
+}
+
+func inArray(arr []string, str string) bool {
+	for _, a := range arr {
+		if a == str {
+			return true
+		}
+	}
+	return false
+}
+
+func timeToString(targetTime time.Time) string {
+	return targetTime.Format("2006-01-02 15:04:05")
+}
+
+func IntToString(number int) string {
+	return strconv.Itoa(number)
+}
+
+func StringToInt(number string) (int, error) {
+	return strconv.Atoi(number)
+}
+
+func StringToInt64(number string) (int64, error) {
+	i, err := strconv.ParseInt(number, 10, 64)
+	if err != nil {
+		return -1, err
+	}
+	return i, nil
+}
+
+func Int64ToString(number int64) string {
+	convedNumber := strconv.FormatInt(number, 10)
+	return convedNumber
+}
+
+func GetUnixTime() int64 {
+	return time.Now().Unix()
+}
+
+func LoadImageAsBase64(filepath string) (string, error) {
+	if !fileExists(filepath) {
+		return "", errors.New("File not exists")
+	}
+	f, _ := os.Open(filepath)
+	reader := bufio.NewReader(f)
+	content, _ := ioutil.ReadAll(reader)
+	encoded := base64.StdEncoding.EncodeToString(content)
+	return string(encoded), nil
+}
+
+func PushToSliceIfNotExist(slice []string, newItem string) []string {
+	itemExists := false
+	for _, item := range slice {
+		if item == newItem {
+			itemExists = true
+		}
+	}
+
+	if !itemExists {
+		slice = append(slice, newItem)
+	}
+
+	return slice
+}
+
+func RemoveFromSliceIfExists(slice []string, target string) []string {
+	newSlice := []string{}
+	for _, item := range slice {
+		if item != target {
+			newSlice = append(newSlice, item)
+		}
+	}
+
+	return newSlice
+}
+

+ 5 - 0
go.mod

@@ -0,0 +1,5 @@
+module imuslab.com/hds/audio
+
+go 1.15
+
+require github.com/grandcat/zeroconf v1.0.0

+ 26 - 0
go.sum

@@ -0,0 +1,26 @@
+github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
+github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
+github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE=
+github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs=
+github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM=
+github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M=
+golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

+ 25 - 0
handlers.go

@@ -0,0 +1,25 @@
+package main
+
+type Status struct{
+	Playing bool //If the device is playing
+	PlayingFileName string //The playing filename
+	PlayingFileURL string //The playing URL
+	PlayingPosition int //The number of seconds that is currently playing
+	Volume int //The volume currently is playing at, in percentage
+	Status string //Status of the device, support {downloading, converting, playing, paused}
+}
+
+
+func handleIndex(w http.ResponseWriter, r *http.Request){
+	//Serve the index
+	sendOK(w)
+}
+
+func handleStatus(w http.ResponseWriter, r *http.Request){
+	
+}
+
+func handleEndpoints(w http.ResponseWriter, r *http.Request){
+
+}
+

+ 16 - 0
mac.go

@@ -0,0 +1,16 @@
+package main
+
+func getMacAddr() ([]string, error) {
+    ifas, err := net.Interfaces()
+    if err != nil {
+        return nil, err
+    }
+    var as []string
+    for _, ifa := range ifas {
+        a := ifa.HardwareAddr.String()
+        if a != "" {
+            as = append(as, a)
+        }
+    }
+    return as, nil
+}

+ 39 - 0
main.go

@@ -0,0 +1,39 @@
+package main
+
+/*
+	HDS-Audio
+	Author: tobychui
+
+	Experimental HDS based iot device for Audio playback in local area network
+*/
+
+import (
+	"flag"
+	"imuslab.com/hds/audio/mod/mdns"
+)
+
+var (
+	port = flag.Int("port", 12110)
+	MDNS mdns.MDNSHost
+)
+
+func main(){
+	//Start the MDNS broadcast
+	macAddr, _ := getMacAddr(port);
+
+	MDNS, err = mdns.NewMDNS(port, macAddr[0])
+	if err != nil{
+		panic(err)
+	}
+
+	//Register all required APIs for HDSv2
+	http.HandleFunc("/", handleIndex);
+	http.HandleFunc("/status", handleStatus);
+	http.HandleFunc("/eps", handleEndpoints);
+
+	//Start web server
+	err = http.ListenAndServe(":"+strconv.Itoa(*port), nil)
+	if err != nil{
+		panic(err)
+	}
+}

+ 196 - 0
mod/mdns/common.go

@@ -0,0 +1,196 @@
+package mdns
+
+import (
+	"os"
+    "log"
+	"net/http"
+	"strconv"
+	"strings"
+	"errors"
+	"encoding/base64"
+	"bufio"
+	"io/ioutil"
+	"time"
+)
+
+/*
+	SYSTEM COMMON FUNCTIONS
+
+	This is a system function that put those we usually use function but not belongs to
+	any module / system.
+
+	E.g. fileExists / IsDir etc
+
+*/
+
+/*
+	Basic Response Functions
+
+	Send response with ease
+*/
+//Send text response with given w and message as string
+func sendTextResponse(w http.ResponseWriter, msg string) {
+	w.Write([]byte(msg))
+}
+
+//Send JSON response, with an extra json header
+func sendJSONResponse(w http.ResponseWriter, json string) {
+	w.Header().Set("Content-Type", "application/json")
+	w.Write([]byte(json))
+}
+
+func sendErrorResponse(w http.ResponseWriter, errMsg string) {
+	w.Header().Set("Content-Type", "application/json")
+	w.Write([]byte("{\"error\":\"" + errMsg + "\"}"))
+}
+
+func sendOK(w http.ResponseWriter) {
+	w.Header().Set("Content-Type", "application/json")
+	w.Write([]byte("\"OK\""))
+}
+/*
+	The paramter move function (mv)
+
+	You can find similar things in the PHP version of ArOZ Online Beta. You need to pass in
+	r (HTTP Request Object)
+	getParamter (string, aka $_GET['This string])
+
+	Will return
+	Paramter string (if any)
+	Error (if error)
+
+*/
+func mv(r *http.Request, getParamter string, postMode bool) (string, error) {
+	if postMode == false {
+		//Access the paramter via GET
+		keys, ok := r.URL.Query()[getParamter]
+
+		if !ok || len(keys[0]) < 1 {
+			//log.Println("Url Param " + getParamter +" is missing")
+			return "", errors.New("GET paramter " + getParamter + " not found or it is empty")
+		}
+
+		// Query()["key"] will return an array of items,
+		// we only want the single item.
+		key := keys[0]
+		return string(key), nil
+	} else {
+		//Access the parameter via POST
+		r.ParseForm()
+		x := r.Form.Get(getParamter)
+		if len(x) == 0 || x == "" {
+			return "", errors.New("POST paramter " + getParamter + " not found or it is empty")
+		}
+		return string(x), nil
+	}
+
+}
+
+func stringInSlice(a string, list []string) bool {
+    for _, b := range list {
+        if b == a {
+            return true
+        }
+    }
+    return false
+}
+
+
+func fileExists(filename string) bool {
+    _, err := os.Stat(filename)
+    if os.IsNotExist(err) {
+        return false
+    }
+    return true
+}
+
+
+func IsDir(path string) bool{
+	if (fileExists(path) == false){
+		return false
+	}
+	fi, err := os.Stat(path)
+    if err != nil {
+        log.Fatal(err)
+        return false
+    }
+    switch mode := fi.Mode(); {
+    case mode.IsDir():
+        return true
+    case mode.IsRegular():
+        return false
+	}
+	return false
+}
+
+func inArray(arr []string, str string) bool {
+	for _, a := range arr {
+	   if a == str {
+		  return true
+	   }
+	}
+	return false
+ }
+
+ func timeToString(targetTime time.Time) string{
+	 return targetTime.Format("2006-01-02 15:04:05")
+ }
+
+ func IntToString(number int) string{
+	return strconv.Itoa(number)
+ }
+
+ func StringToInt(number string) (int, error){
+	return strconv.Atoi(number)
+ }
+
+ func StringToInt64(number string) (int64, error){
+	i, err := strconv.ParseInt(number, 10, 64)
+	if err != nil {
+		return -1, err
+	}
+	return i, nil
+ }
+
+ func Int64ToString(number int64) string{
+	convedNumber:=strconv.FormatInt(number,10)
+	return convedNumber
+ }
+
+ func GetUnixTime() int64{
+	return time.Now().Unix()
+ }
+
+ func LoadImageAsBase64(filepath string) (string, error){
+	if !fileExists(filepath){
+		return "", errors.New("File not exists")
+	}
+	f, _ := os.Open(filepath)
+    reader := bufio.NewReader(f)
+    content, _ := ioutil.ReadAll(reader)
+	encoded := base64.StdEncoding.EncodeToString(content)
+	return string(encoded), nil
+ }
+
+ //Get the IP address of the current authentication user
+func getUserIPAddr(w http.ResponseWriter, r *http.Request) {
+    requestPort,_ :=  mv(r, "port", false)
+    showPort := false;
+    if (requestPort == "true"){
+        //Show port as well
+        showPort = true;
+    }
+    IPAddress := r.Header.Get("X-Real-Ip")
+    if IPAddress == "" {
+        IPAddress = r.Header.Get("X-Forwarded-For")
+    }
+    if IPAddress == "" {
+        IPAddress = r.RemoteAddr
+    }
+    if (!showPort){
+        IPAddress = IPAddress[:strings.LastIndex(IPAddress, ":")]
+
+    }
+    w.Write([]byte(IPAddress))
+    return;
+}

+ 135 - 0
mod/mdns/mdns.go

@@ -0,0 +1,135 @@
+package mdns
+
+import (
+	"context"
+	"log"
+	"net"
+	"strings"
+	"time"
+
+	"github.com/grandcat/zeroconf"
+)
+
+type MDNSHost struct {
+	MDNS *zeroconf.Server
+	Host *NetworkHost
+}
+
+type NetworkHost struct {
+	HostName     string
+	Port         int
+	IPv4         []net.IP
+	Domain       string
+	Model        string
+	UUID         string
+	Vendor       string
+	BuildVersion string
+	MinorVersion string
+}
+
+func NewMDNS(port int, uuid string) (*MDNSHost, error) {
+	//Register the mds services
+	//server, err := zeroconf.Register("ArOZ", "_http._tcp", "local.", *listen_port, []string{"version_build=" + build_version, "version_minor=" + internal_version, "vendor=" + deviceVendor, "model=" + deviceModel, "uuid=" + deviceUUID, "domain=aroz.online"}, nil)
+	server, err := zeroconf.Register("hds-audio", "_http._tcp", "local.", port, []string{"version_build=1.0", "version_minor=0.00", "vendor=HomeDynamic Project", "model=Audio Device", "uuid=" + uuid, "domain=hds.arozos.com", "protocol=hdsv2"}, nil)
+	if err != nil {
+		return &MDNSHost{}, err
+	}
+
+	return &MDNSHost{
+		MDNS: server,
+		Host: &config,
+	}, nil
+}
+
+func (m *MDNSHost) Close() {
+	if m != nil {
+		m.MDNS.Shutdown()
+	}
+
+}
+
+//Scan with given timeout and domain filter. Use m.Host.Domain for scanning similar typed devices
+func (m *MDNSHost) Scan(timeout int, domainFilter string) []*NetworkHost {
+	// Discover all services on the network (e.g. _workstation._tcp)
+	resolver, err := zeroconf.NewResolver(nil)
+	if err != nil {
+		log.Fatalln("Failed to initialize resolver:", err.Error())
+	}
+
+	entries := make(chan *zeroconf.ServiceEntry)
+	//Create go routine  to wait for the resolver
+
+	discoveredHost := []*NetworkHost{}
+
+	go func(results <-chan *zeroconf.ServiceEntry) {
+		for entry := range results {
+			if domainFilter == "" {
+
+				//This is a ArOZ Online Host
+				//Split the required information out of the text element
+				TEXT := entry.Text
+				properties := map[string]string{}
+				for _, v := range TEXT {
+					kv := strings.Split(v, "=")
+					if len(kv) == 2 {
+						properties[kv[0]] = kv[1]
+					}
+				}
+
+				//log.Println(properties)
+				discoveredHost = append(discoveredHost, &NetworkHost{
+					HostName:     entry.HostName,
+					Port:         entry.Port,
+					IPv4:         entry.AddrIPv4,
+					Domain:       properties["domain"],
+					Model:        properties["model"],
+					UUID:         properties["uuid"],
+					Vendor:       properties["vendor"],
+					BuildVersion: properties["version_build"],
+					MinorVersion: properties["version_minor"],
+				})
+
+			} else {
+				if stringInSlice("domain="+domainFilter, entry.Text) {
+					//This is a ArOZ Online Host
+					//Split the required information out of the text element
+					TEXT := entry.Text
+					properties := map[string]string{}
+					for _, v := range TEXT {
+						kv := strings.Split(v, "=")
+						if len(kv) == 2 {
+							properties[kv[0]] = kv[1]
+						}
+					}
+
+					//log.Println(properties)
+					discoveredHost = append(discoveredHost, &NetworkHost{
+						HostName:     entry.HostName,
+						Port:         entry.Port,
+						IPv4:         entry.AddrIPv4,
+						Domain:       properties["domain"],
+						Model:        properties["model"],
+						UUID:         properties["uuid"],
+						Vendor:       properties["vendor"],
+						BuildVersion: properties["version_build"],
+						MinorVersion: properties["version_minor"],
+					})
+
+				}
+			}
+
+		}
+	}(entries)
+
+	//Resolve each of the mDNS and pipe it back to the log functions
+	ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(timeout))
+	defer cancel()
+	err = resolver.Browse(ctx, "_http._tcp", "local.", entries)
+	if err != nil {
+		log.Fatalln("Failed to browse:", err.Error())
+	}
+
+	<-ctx.Done()
+
+	return discoveredHost
+}