├── LICENSE
├── README.md
├── mhplayer
├── http.go
├── record.go
└── static
│ ├── demo.css
│ ├── index.html
│ ├── index.html.txt
│ ├── indexnew.html
│ └── indexnew0.html
├── mhrecorder
├── main.go
└── static
│ ├── demo.css
│ └── index.html
├── mhserver
├── client.go
├── config.go
├── crontab
├── globals.go
├── home.html
├── hub.go
├── main.go
├── processmsg.go
├── scheduler.go
└── view
│ ├── cameras.html
│ ├── config
│ ├── Hello.json
│ ├── New.json
│ ├── connected.json
│ ├── dirtest.json
│ ├── eastshoplightoff.json
│ ├── eastshoplighton.json
│ ├── lunch.json
│ ├── motiondetectoff.json
│ ├── motiondetecton.json
│ ├── mute.json
│ ├── nightmodeoff.json
│ ├── nightmodeon.json
│ ├── northshoplightoff.json
│ ├── northshoplighton.json
│ ├── note.json
│ ├── restartclient.json
│ ├── setshopthermostat.json
│ ├── southshoplightoff.json
│ ├── southshoplighton.json
│ ├── unmute.json
│ ├── volume-100.json
│ ├── volume-25.json
│ ├── volume-50.json
│ ├── volume-75.json
│ └── volume.json
│ ├── control.html
│ ├── css
│ ├── bulma-switch.min.css
│ ├── img
│ │ └── jsoneditor-icons.png
│ ├── jsoneditor.css
│ └── style.css
│ ├── index.html
│ ├── peer.html
│ └── scripts
│ ├── control.js
│ ├── img
│ └── jsoneditor-icons.png
│ ├── jsoneditor.js
│ ├── jstree.js
│ ├── peer.js
│ ├── seq.js
│ └── single.js
├── mycam
├── README.md
├── fifo.go
├── http.go.txt
├── main.go
├── recorder.go
├── rtc.go
├── rtsp2webrtc.go
├── static
│ ├── demo.css
│ └── index.html
└── webrtc.png
└── pivoice
├── main.go
└── picgotts.go
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 machinesworking
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # myhome
2 | A Home grown security system using Google Go, Raspberry Pi, and hacked Wizecams using
3 | Dafang hacks at https://github.com/Dafang-Hacks/rootfs
4 |
5 | The system is intentionally local only .....no cloud
6 |
7 | Audible Notifications use picotts on wireless pi zeros
8 |
9 | Lights are controlled using hacked sonoff switches
10 |
11 | A thermostat is built from an esp8266
12 |
13 | My Home server is written in go and uses websockets for device communications https://github.com/gorilla/websocket
14 |
15 | Control is via web page using json messages.
16 |
17 | Configuration is also a web page.
18 |
19 | All devices are discovered automatically by the server connection and the control page is built dynamically.
20 |
21 | The Wizecams use onboard RtsptoWebrtc from https://github.com/deepch/RTSPtoWebRTC
22 |
23 | Which uses https://github.com/pions/webrtc for the webrtc peer written entirely in Go.
24 |
25 | The cameras can be viewed in a web page using the supplied html and javascript using webrtc.
26 |
27 | Disclaimer: This is my first attempt at this kind of project. Use at your own risk. I will be adding more documentation to the code as I clean it up.
28 |
--------------------------------------------------------------------------------
/mhplayer/http.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "net/http"
7 | "os"
8 |
9 | "encoding/base64"
10 | "encoding/json"
11 |
12 | "github.com/gorilla/mux"
13 | "github.com/pions/webrtc"
14 | "github.com/pions/webrtc/pkg/ice"
15 | )
16 |
17 | //var DataChanelTest chan<- media.RTCSample
18 |
19 | //var rawrtpchan chan<- *rtp.Packet
20 | var namechan = make(chan string)
21 | var playctrlchan = make(chan string)
22 | var vp8Track *webrtc.RTCTrack
23 |
24 | func main() {
25 | dir, _ := os.Getwd()
26 |
27 | fmt.Printf("CWD: %s\n", dir)
28 | r := mux.NewRouter()
29 | r.HandleFunc("/receive", home)
30 | r.HandleFunc("/list", list)
31 | r.HandleFunc("/play", play)
32 | r.HandleFunc("/stop", stop)
33 |
34 | r.PathPrefix("/").Handler(http.StripPrefix("/", http.FileServer(http.Dir("static/"))))
35 | go playgob(playctrlchan)
36 | go func() {
37 | err := http.ListenAndServe(":8181", r)
38 | if err != nil {
39 | }
40 | }()
41 | select {}
42 | }
43 |
44 | func list(w http.ResponseWriter, r *http.Request) {
45 | data := r.FormValue("data")
46 | list, err := IOReadDir(data)
47 | if err != nil {
48 | fmt.Println("list failed: " + err.Error())
49 |
50 | }
51 | filelist, err := json.Marshal(list)
52 | if err != nil {
53 | fmt.Println("Error: " + err.Error())
54 | }
55 | w.Write(filelist)
56 | }
57 |
58 | func play(w http.ResponseWriter, r *http.Request) {
59 |
60 | data := r.FormValue("data")
61 | namechan <- data
62 | }
63 | func stop(w http.ResponseWriter, r *http.Request) {
64 |
65 | playctrlchan <- "stop"
66 | }
67 | func home(w http.ResponseWriter, r *http.Request) {
68 | w.Header().Set("Access-Control-Allow-Origin", "*")
69 | data := r.FormValue("data")
70 | sd, err := base64.StdEncoding.DecodeString(data)
71 |
72 | if err != nil {
73 | log.Println(err)
74 | return
75 | }
76 | webrtc.RegisterDefaultCodecs()
77 | peerConnection, err := webrtc.New(webrtc.RTCConfiguration{
78 | IceServers: []webrtc.RTCIceServer{
79 | {
80 | URLs: []string{"stun:stun.l.google.com:19302"},
81 | },
82 | },
83 | })
84 | if err != nil {
85 | panic(err)
86 | }
87 | peerConnection.OnICEConnectionStateChange(func(connectionState ice.ConnectionState) {
88 | fmt.Printf("Connection State has changed %s \n", connectionState.String())
89 | })
90 | vp8Track, err = peerConnection.NewRTCSampleTrack(webrtc.DefaultPayloadTypeH264, "video", "pion2")
91 |
92 | if err != nil {
93 | log.Println(err)
94 | return
95 | }
96 | _, err = peerConnection.AddTrack(vp8Track)
97 | if err != nil {
98 | log.Println(err)
99 | return
100 | }
101 | offer := webrtc.RTCSessionDescription{
102 | Type: webrtc.RTCSdpTypeOffer,
103 | Sdp: string(sd),
104 | }
105 | if err := peerConnection.SetRemoteDescription(offer); err != nil {
106 | log.Println(err)
107 | return
108 | }
109 | answer, err := peerConnection.CreateAnswer(nil)
110 | if err != nil {
111 | log.Println(err)
112 | return
113 | }
114 | w.Write([]byte(base64.StdEncoding.EncodeToString([]byte(answer.Sdp))))
115 | // DataChanelTest = vp8Track.Samples
116 |
117 | }
118 |
--------------------------------------------------------------------------------
/mhplayer/record.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/gob"
5 | "fmt"
6 | "io/ioutil"
7 | "os"
8 | "time"
9 |
10 | "github.com/pions/webrtc/pkg/media"
11 | )
12 |
13 | // This saves the gob from the time the the motion signal is sent until it times out
14 | func savegob(rtspchan chan media.RTCSample, savectrlchan chan string, devicename string) {
15 | var path = "static/clips/" + devicename
16 | var file *os.File
17 | var enc *gob.Encoder
18 | var state = "stopped"
19 | defer func() {
20 | file.Close()
21 | }()
22 |
23 | for {
24 | select {
25 | case gob := <-rtspchan:
26 | if state == "started" {
27 | enc.Encode(gob)
28 | }
29 | case ctrl := <-savectrlchan:
30 | switch ctrl {
31 |
32 | case "stop":
33 | state = "stopped"
34 |
35 | case "start":
36 | file, err := os.Create(path + "/" + devicename + time.Now().Format(time.RFC3339) + ".gob")
37 | if err != nil {
38 | fmt.Printf("Could not create file: %s", err.Error())
39 | return
40 | }
41 | // fmt.Println("Received start and created file for ")
42 | enc = gob.NewEncoder(file)
43 | state = "started"
44 | case "destroy":
45 | return
46 |
47 | }
48 | }
49 | }
50 |
51 | }
52 |
53 | // playgob plays the recorded gob to the browser
54 | func playgob(playctrlchan chan string) {
55 | var state = "stopped"
56 | var file *os.File
57 | var path = "static/clips/"
58 | var decoder *gob.Decoder
59 | defer func() {
60 | file.Close()
61 | }()
62 | var err error
63 | ticker := time.NewTicker(time.Millisecond * 25)
64 |
65 | // var cnt = 0
66 | for {
67 | select {
68 | case filename := <-namechan:
69 | if state == "playing" {
70 | fmt.Println("Canceled Play")
71 | file.Close()
72 | state = "stopped"
73 | }
74 | file, err = os.Open(path + filename)
75 | if err != nil {
76 | fmt.Printf("Could not open file: %s", err.Error())
77 | continue
78 | }
79 | decoder = gob.NewDecoder(file)
80 |
81 | fmt.Println("Changed state to playing for file: " + filename)
82 | state = "playing"
83 | case ctrl := <-playctrlchan:
84 | if ctrl == "stop" {
85 | if state == "playing" {
86 | fmt.Println("Canceled Play")
87 | file.Close()
88 | state = "stopped"
89 |
90 | }
91 | }
92 | case <-ticker.C:
93 | if state == "playing" {
94 | goblet := new(media.RTCSample)
95 |
96 | err = decoder.Decode(goblet)
97 | if err != nil {
98 | if err.Error() == "EOF" {
99 | file.Seek(0, 0)
100 | fmt.Println("Looping")
101 | decoder = gob.NewDecoder(file)
102 |
103 | } else {
104 | fmt.Printf("Decode GOB Error: %s", err.Error())
105 | continue
106 | }
107 |
108 | }
109 |
110 | vp8Track.Samples <- *goblet
111 | }
112 | }
113 | }
114 |
115 | }
116 |
117 | func IOReadDir(root string) ([]string, error) {
118 | var files []string
119 | var path = "static/clips/"
120 | fileInfo, err := ioutil.ReadDir(path + root)
121 | if err != nil {
122 | return files, err
123 | }
124 | for _, file := range fileInfo {
125 | files = append(files, file.Name())
126 | }
127 | return files, nil
128 | }
129 |
--------------------------------------------------------------------------------
/mhplayer/static/demo.css:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/mhplayer/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Browser base64 Session Description
4 | Golang base64 Session Description:
5 |
6 |
7 |
8 |
11 |
12 |
13 |
83 |
--------------------------------------------------------------------------------
/mhplayer/static/index.html.txt:
--------------------------------------------------------------------------------
1 |
2 |
3 | Browser base64 Session Description
4 | Golang base64 Session Description:
5 |
6 |
7 |
8 |
57 |
--------------------------------------------------------------------------------
/mhplayer/static/indexnew.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Browser base64 Session Description
4 | Golang base64 Session Description:
5 |
6 |
7 |
8 |
15 |
16 |
17 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
110 |
--------------------------------------------------------------------------------
/mhplayer/static/indexnew0.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Browser base64 Session Description
4 | Golang base64 Session Description:
5 |
6 |
7 |
8 |
15 |
16 |
17 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
118 |
--------------------------------------------------------------------------------
/mhrecorder/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "encoding/gob"
6 | "fmt"
7 | "io"
8 | "log"
9 | "net"
10 | "os"
11 | "strings"
12 | "sync"
13 |
14 | "github.com/pions/webrtc/pkg/media"
15 | "github.com/pkg/errors"
16 | )
17 |
18 | const (
19 | // Port is the port number that the server listens to.
20 | Port = ":61000"
21 | )
22 |
23 | /*
24 | ## Incoming connections
25 |
26 | Preparing for incoming data is a bit more involved. According to our ad-hoc
27 | protocol, we receive the name of a command terminated by `\n`, followed by data.
28 | The nature of the data depends on the respective command. To handle this, we
29 | create an `Endpoint` object with the following properties:
30 |
31 | * It allows to register one or more handler functions, where each can handle a
32 | particular command.
33 | * It dispatches incoming commands to the associated handler based on the commands
34 | name.
35 |
36 | */
37 |
38 | // HandleFunc is a function that handles an incoming command.
39 | // It receives the open connection wrapped in a `ReadWriter` interface.
40 | type HandleFunc func(*bufio.ReadWriter)
41 |
42 | var file *os.File
43 | var sample media.RTCSample
44 | var path = "static/clips/"
45 |
46 | // Endpoint provides an endpoint to other processess
47 | // that they can send data to.
48 | type Endpoint struct {
49 | listener net.Listener
50 | handler map[string]HandleFunc
51 |
52 | // Maps are not threadsafe, so we need a mutex to control access.
53 | m sync.RWMutex
54 | }
55 |
56 | // NewEndpoint creates a new endpoint. To keep things simple,
57 | // the endpoint listens on a fixed port number.
58 | func NewEndpoint() *Endpoint {
59 | // Create a new Endpoint with an empty list of handler funcs.
60 | return &Endpoint{
61 | handler: map[string]HandleFunc{},
62 | }
63 | }
64 |
65 | // AddHandleFunc adds a new function for handling incoming data.
66 | func (e *Endpoint) AddHandleFunc(name string, f HandleFunc) {
67 | e.m.Lock()
68 | e.handler[name] = f
69 | e.m.Unlock()
70 | }
71 |
72 | // Listen starts listening on the endpoint port on all interfaces.
73 | // At least one handler function must have been added
74 | // through AddHandleFunc() before.
75 | func (e *Endpoint) Listen() error {
76 | var err error
77 | e.listener, err = net.Listen("tcp", Port)
78 | if err != nil {
79 | return errors.Wrapf(err, "Unable to listen on port %s\n", Port)
80 | }
81 | log.Println("Listen on", e.listener.Addr().String())
82 | for {
83 | log.Println("Accept a connection request.")
84 | conn, err := e.listener.Accept()
85 | if err != nil {
86 | log.Println("Failed accepting a connection request:", err)
87 | continue
88 | }
89 | log.Println("Handle incoming messages.")
90 | go e.handleMessages(conn)
91 | }
92 | }
93 |
94 | // handleMessages reads the connection up to the first newline.
95 | // Based on this string, it calls the appropriate HandleFunc.
96 | func (e *Endpoint) handleMessages(conn net.Conn) {
97 | // Wrap the connection into a buffered reader for easier reading.
98 | rw := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
99 | defer conn.Close()
100 |
101 | // Read from the connection until EOF. Expect a command name as the
102 | // next input. Call the handler that is registered for this command.
103 | for {
104 | log.Print("Receive command '")
105 | cmd, err := rw.ReadString('\n')
106 | switch {
107 | case err == io.EOF:
108 | log.Println("Reached EOF - close this connection.\n ---")
109 | return
110 | case err != nil:
111 | log.Println("\nError reading command. Got: '"+cmd+"'\n", err)
112 | return
113 | }
114 | // Trim the request string - ReadString does not strip any newlines.
115 | cmd = strings.Trim(cmd, "\n ")
116 | log.Println(cmd + "'")
117 |
118 | // Fetch the appropriate handler function from the 'handler' map and call it.
119 | e.m.RLock()
120 | handleCommand, ok := e.handler[cmd]
121 | e.m.RUnlock()
122 | if !ok {
123 | log.Println("Command '" + cmd + "' is not registered.")
124 | return
125 | }
126 | handleCommand(rw)
127 | }
128 | }
129 |
130 | /* Now let's create two handler functions. The easiest case is where our
131 | ad-hoc protocol only sends string data.
132 |
133 | The second handler receives and processes a struct that was sent as GOB data.
134 | */
135 |
136 | // handleStrings handles the "STRING" request.
137 |
138 | func handleOpen(rw *bufio.ReadWriter) {
139 | // Receive a string.
140 |
141 | log.Print("Receive STRING message:")
142 | s, err := rw.ReadString('\n')
143 | if err != nil {
144 | log.Println("Cannot read from connection.\n", err)
145 | }
146 | s = strings.Trim(s, "\n ")
147 | log.Println(s)
148 | file, err = os.Create(path + s)
149 | if err != nil {
150 | log.Printf("Could not create file: %s", err.Error())
151 | _, err = rw.WriteString("Error\n")
152 | if err != nil {
153 | log.Println("Cannot write to connection.\n", err)
154 | }
155 | err = rw.Flush()
156 | if err != nil {
157 | log.Println("Flush failed.", err)
158 | }
159 | return
160 | }
161 |
162 | _, err = rw.WriteString("continue\n")
163 | if err != nil {
164 | log.Println("Cannot write to connection.\n", err)
165 | }
166 | err = rw.Flush()
167 | if err != nil {
168 | log.Println("Flush failed.", err)
169 | }
170 |
171 | }
172 |
173 | func handleRecord(rw *bufio.ReadWriter) {
174 | log.Print("Recording GOB data:")
175 | // sample := make([]byte, 1024)
176 | dec := gob.NewDecoder(rw)
177 | enc := gob.NewEncoder(file)
178 | goblet := new(media.RTCSample)
179 | for {
180 |
181 | err := dec.Decode(goblet)
182 | if err != nil {
183 | file.Sync()
184 | file.Close()
185 | fmt.Println("Closing reader")
186 | return
187 | }
188 |
189 | //_, err = file.Write(sample)
190 | err = enc.Encode(goblet)
191 | if err != nil {
192 | file.Sync()
193 | file.Close()
194 | fmt.Println("Closing writer")
195 | return
196 | }
197 | }
198 | }
199 |
200 | /*
201 | ## The client and server functions
202 |
203 | With all this in place, we can now set up client and server functions.
204 |
205 | The client function connects to the server and sends STRING and GOB requests.
206 |
207 | The server starts listening for requests and triggers the appropriate handlers.
208 | */
209 |
210 | // server listens for incoming requests and dispatches them to
211 | // registered handler functions.
212 | func server() error {
213 | endpoint := NewEndpoint()
214 |
215 | // Add the handle funcs.
216 | endpoint.AddHandleFunc("OPEN", handleOpen)
217 | endpoint.AddHandleFunc("RECORD", handleRecord)
218 |
219 | // Start listening.
220 | return endpoint.Listen()
221 | }
222 |
223 | /*
224 | ## Main
225 |
226 | Main starts either a client or a server, depending on whether the `connect`
227 | flag is set. Without the flag, the process starts as a server, listening
228 | for incoming requests. With the flag the process starts as a client and connects
229 | to the host specified by the flag value.
230 |
231 | Try "localhost" or "127.0.0.1" when running both processes on the same machine.
232 |
233 | */
234 |
235 | // main
236 | func main() {
237 |
238 | err := server()
239 | if err != nil {
240 | log.Println("Error:", errors.WithStack(err))
241 | }
242 |
243 | log.Println("Server done.")
244 | }
245 |
246 | // The Lshortfile flag includes file name and line number in log messages.
247 | func init() {
248 | log.SetFlags(log.Lshortfile)
249 | }
250 |
--------------------------------------------------------------------------------
/mhrecorder/static/demo.css:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/mhrecorder/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Myhome Recorder
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Browser base64 Session Description
16 | Golang base64 Session Description:
17 |
18 |
19 |
20 |
27 |
28 |
29 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
136 |
137 |
138 |
--------------------------------------------------------------------------------
/mhserver/client.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "log"
6 | "net"
7 | "net/http"
8 | "time"
9 |
10 | "github.com/gorilla/websocket"
11 | )
12 |
13 | const (
14 | // Time allowed to write a message to the peer.
15 | writeWait = 2 * time.Second
16 |
17 | // Time allowed to read the next pong message from the peer.
18 | pongWait = 60 * time.Second
19 |
20 | // Send pings to peer with this period. Must be less than pongWait.
21 | pingPeriod = (pongWait * 9) / 10
22 |
23 | // Maximum message size allowed from peer.
24 | maxMessageSize = 10000
25 | )
26 |
27 | var (
28 | newline = []byte{'\n'}
29 | space = []byte{' '}
30 | )
31 |
32 | var upgrader = websocket.Upgrader{
33 | ReadBufferSize: 4096,
34 | WriteBufferSize: 4096,
35 | }
36 |
37 | // readPump pumps messages from the websocket connection to the hub.
38 | //
39 | // The application runs readPump in a per-connection goroutine. The application
40 | // ensures that there is at most one reader on a connection by executing all
41 | // reads from this goroutine.
42 | func (c *Client) readPump() {
43 | defer func() {
44 | log.Println("closing websocket read")
45 | ti := time.Now()
46 | note := rawmsg{Type: "notification", Value: ti.Format(time.Stamp) + ": " + c.device.Desc + " has disconnected"}
47 |
48 | jnote, err := json.Marshal(note)
49 | if err != nil {
50 | log.Printf("Marshal error: %s", err.Error())
51 | }
52 | mesg := message{Type: "control", Targets: []string{"text"}, Msg: jnote}
53 | c.hub.broadcast <- mesg
54 |
55 | c.hub.unregister <- c
56 | c.conn.Close()
57 | }()
58 | c.conn.SetReadLimit(maxMessageSize)
59 | c.conn.SetReadDeadline(time.Now().Add(pongWait))
60 | c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
61 |
62 | for {
63 | mtype, msg, err := c.conn.ReadMessage()
64 | if err != nil {
65 | if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
66 | log.Printf("error: %v", err)
67 | }
68 | break
69 | }
70 |
71 | if mtype == websocket.TextMessage {
72 | var messg message
73 | err = json.Unmarshal(msg, &messg)
74 | if err != nil {
75 | log.Printf("unmarshal error: %s", err.Error())
76 | }
77 | c.msg = &messg
78 | processchan <- c
79 |
80 | }
81 | }
82 | }
83 |
84 | // writePump pumps messages from the hub to the websocket connection.
85 | //
86 | // A goroutine running writePump is started for each connection. The
87 | // application ensures that there is at most one writer to a connection by
88 | // executing all writes from this goroutine.
89 | func (c *Client) writePump() {
90 | ticker := time.NewTicker(pingPeriod)
91 | defer func() {
92 | log.Println("closing websocket write")
93 | ticker.Stop()
94 | c.conn.Close()
95 | }()
96 | for {
97 | select {
98 | case msg, ok := <-c.send:
99 | c.conn.SetWriteDeadline(time.Now().Add(writeWait))
100 | if !ok {
101 | log.Println("hub closed channel") // The hub closed the channel.
102 | ti := time.Now()
103 | note := rawmsg{Type: "notification", Value: ti.Format(time.Stamp) + ": " + c.device.Desc + " was disconnected by the Hub "}
104 |
105 | jnote, err := json.Marshal(note)
106 | if err != nil {
107 | log.Printf("Marshal error: %s", err.Error())
108 | }
109 | mesg := message{Type: "control", Targets: []string{"text"}, Msg: jnote}
110 | c.hub.broadcast <- mesg
111 | c.conn.WriteMessage(websocket.CloseMessage, []byte{})
112 | return
113 | }
114 | w, err := c.conn.NextWriter(websocket.TextMessage)
115 | if err != nil {
116 | log.Printf("couldn't get next writer: %s \n", err.Error())
117 | return
118 | }
119 |
120 | msgstr, _ := json.Marshal(msg)
121 |
122 | // websocket.WriteJSON(c.conn, msg)
123 | w.Write(msgstr)
124 |
125 | // Add queued chat messages to the current websocket message.
126 | n := len(c.send)
127 | for i := 0; i < n; i++ {
128 |
129 | websocket.WriteJSON(c.conn, <-c.send)
130 | }
131 |
132 | if err := w.Close(); err != nil {
133 | return
134 | }
135 |
136 | case <-ticker.C:
137 | c.conn.SetWriteDeadline(time.Now().Add(writeWait))
138 |
139 | if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
140 | log.Println("couldn't send ping")
141 | return
142 | }
143 | // default:
144 | // fmt.Println("dropped message")
145 | }
146 | }
147 | }
148 |
149 | // serveWs handles websocket requests from the peer.
150 | func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) {
151 | log.Println(r)
152 | conn, err := upgrader.Upgrade(w, r, nil)
153 | if err != nil {
154 |
155 | log.Println(err)
156 | return
157 | }
158 | host, _, _ := net.SplitHostPort(r.RemoteAddr)
159 |
160 | device := &device{Type: "", Name: "", Desc: "", State: "", Address: host}
161 | client := &Client{hub: hub, conn: conn, send: make(chan message), device: device}
162 | client.hub.register <- client
163 |
164 | // Allow collection of memory referenced by the caller by doing all work in
165 | // new goroutines.
166 | go client.writePump()
167 | go client.readPump()
168 |
169 | }
170 |
--------------------------------------------------------------------------------
/mhserver/config.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io/ioutil"
7 | "log"
8 | "strings"
9 |
10 | "os"
11 | )
12 |
13 | //load the configuration
14 | func loadnodes(nodesFile string) ([]byte, error) {
15 |
16 | //read the entire file into var body
17 | body, err := ioutil.ReadFile(msgfilepath + "/" + nodesFile)
18 |
19 | if err != nil {
20 | fmt.Printf("Couldn't load nodes file\r\n")
21 | return nil, err
22 | }
23 |
24 | return body, nil
25 | }
26 |
27 | func loadmsgnames() (*message, error) {
28 | files := make(map[string]json.RawMessage)
29 | // names := []string{}
30 | //read the configs in the config folder
31 | fileinfos, err := ioutil.ReadDir(msgfilepath)
32 | if err != nil {
33 | fmt.Printf("Couldn't load config file names file\r\n")
34 | return nil, err
35 | }
36 |
37 | // list only the JSON files...no folders
38 | for _, info := range fileinfos {
39 | if info.IsDir() {
40 | continue
41 | }
42 | if strings.HasSuffix(info.Name(), ".json") {
43 | //names = append(names, info.Name())
44 | txt, _ := loadnodes(info.Name())
45 |
46 | files[info.Name()] = txt
47 | }
48 | }
49 | //create the map for the JSON message
50 |
51 | msg := new(message)
52 | msg.Type = "control"
53 | ctrl := rawmsg{Type: "msglist", Value: files}
54 |
55 | ctrlstr, err := json.Marshal(ctrl)
56 | if err != nil {
57 | fmt.Printf("could not marshal controls: %s", err.Error())
58 | return nil, err
59 | }
60 |
61 | msg.Targets = []string{"text", "hmi"}
62 | msg.Msg = ctrlstr
63 |
64 | return msg, nil
65 | }
66 |
67 | //Save a changed Config file
68 | func saveNodes(nodesFile string, msg interface{}) {
69 |
70 | data, err := json.Marshal(msg)
71 | if err != nil {
72 | log.Printf("Could not save file: %s", nodesFile)
73 | }
74 |
75 | //write the File to the correct path
76 | fmt.Printf("Saving file %s with %s\n", msgfilepath+"/"+nodesFile, data)
77 | err = ioutil.WriteFile(msgfilepath+"/"+nodesFile, data, os.FileMode.Perm(0666))
78 | if err != nil {
79 | fmt.Printf("failed to save message: %s", err.Error())
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/mhserver/crontab:
--------------------------------------------------------------------------------
1 | 30 * * * * * * printme
2 |
--------------------------------------------------------------------------------
/mhserver/globals.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 |
6 | "github.com/gorilla/websocket"
7 | "github.com/pions/webrtc"
8 | )
9 |
10 | var msgfilepath = "view/config"
11 | var processchan = make(chan *Client)
12 | var answerchan = make(chan webrtc.RTCSessionDescription)
13 |
14 | // Client is a middleman between the websocket connection and the hub.
15 | type Client struct {
16 | hub *Hub
17 |
18 | // The websocket connection.
19 | conn *websocket.Conn
20 |
21 | // Buffered channel of outbound messages.
22 | send chan message
23 | msg *message
24 | device *device
25 | }
26 |
27 | type device struct {
28 | Type string
29 | Name string
30 | Desc string
31 | State string
32 | Address string
33 | }
34 |
35 | type command struct {
36 | Name string
37 | Value map[string]interface{}
38 | }
39 | type message struct {
40 | Type string
41 | Targets []string
42 | //Conn *websocket.Conn
43 | Msg json.RawMessage
44 | }
45 | type rawmsg struct {
46 | Type string
47 | Value interface{}
48 | }
49 |
--------------------------------------------------------------------------------
/mhserver/home.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Chat Example
5 |
53 |
90 |
91 |
92 |
93 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/mhserver/hub.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | )
6 |
7 | // Hub maintains the set of active clients and broadcasts messages to the
8 | // clients.
9 | type Hub struct {
10 | // Registered clients.
11 | clients map[*Client]bool
12 |
13 | // Inbound messages from the clients.
14 | broadcast chan message
15 | process chan *Client
16 |
17 | // Register requests from the clients.
18 | register chan *Client
19 |
20 | // Unregister requests from clients.
21 | unregister chan *Client
22 | }
23 |
24 | func newHub() *Hub {
25 | return &Hub{
26 | broadcast: make(chan message),
27 | process: make(chan *Client),
28 | register: make(chan *Client),
29 | unregister: make(chan *Client),
30 | clients: make(map[*Client]bool),
31 | }
32 | }
33 |
34 | func (h *Hub) run() {
35 | for {
36 | select {
37 | case client := <-h.register:
38 | h.clients[client] = true
39 | case client := <-h.unregister:
40 | if _, ok := h.clients[client]; ok {
41 | delete(h.clients, client)
42 | close(client.send)
43 | }
44 | case msg := <-h.broadcast:
45 | // log.Println("broadcasting")
46 | for client := range h.clients {
47 | // log.Printf("found %s\n", client.device.Name)
48 | for _, targ := range msg.Targets {
49 | if client.device.Type == targ || client.device.Name == targ {
50 | select {
51 | case client.send <- msg:
52 | default:
53 | log.Printf("Hub closing client %s\n", client.device.Name)
54 | close(client.send)
55 | delete(h.clients, client)
56 | }
57 | }
58 | }
59 | }
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/mhserver/main.go:
--------------------------------------------------------------------------------
1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package main
6 |
7 | import (
8 | "flag"
9 | "fmt"
10 | "log"
11 | "net/http"
12 | )
13 |
14 | var addr = flag.String("addr", ":8080", "http service address")
15 |
16 | func serveHome(w http.ResponseWriter, r *http.Request) {
17 | log.Println(r.URL)
18 | if r.URL.Path != "/" {
19 | http.Error(w, "Not found", http.StatusNotFound)
20 | return
21 | }
22 | if r.Method != "GET" {
23 | http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
24 | return
25 | }
26 | http.ServeFile(w, r, "home.html")
27 | }
28 |
29 | func main() {
30 | flag.Parse()
31 | hub := newHub()
32 | go hub.run()
33 | go processmsg()
34 | go http.Handle("/", http.FileServer(http.Dir("view/")))
35 | go scheduler()
36 |
37 | http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
38 | serveWs(hub, w, r)
39 | })
40 | fmt.Println("starting server")
41 | err := http.ListenAndServe(*addr, nil)
42 | if err != nil {
43 | log.Fatal("ListenAndServe: ", err)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/mhserver/processmsg.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "log"
7 | "os"
8 | "time"
9 |
10 | uuid "github.com/satori/go.uuid"
11 | )
12 |
13 | func processmsg() {
14 | for {
15 | select {
16 | case c := <-processchan:
17 | if c.msg.Type == "status" {
18 | // update device info with status msg
19 | var dev device
20 |
21 | err := json.Unmarshal(c.msg.Msg, &dev)
22 | if err != nil {
23 | log.Printf("unmarshal error: %s", err.Error())
24 | }
25 | dev.Address = c.device.Address
26 | c.device = &dev
27 |
28 | ti := time.Now()
29 | //send notification to browsers of status
30 | note := rawmsg{Type: "notification", Value: ti.Format(time.Stamp) + ": " + c.device.Desc + " connected"}
31 | jnote, err := json.Marshal(note)
32 | if err != nil {
33 | log.Printf("Marshal error: %s", err.Error())
34 | }
35 |
36 | // load the saved message names to the browsers
37 | mesg := message{Type: "control", Targets: []string{"text"}, Msg: jnote}
38 | c.hub.broadcast <- mesg
39 | if c.device.Type == "text" {
40 | msglist, _ := loadmsgnames()
41 | c.send <- *msglist
42 |
43 | ms := new(message)
44 | ms.Type = "control"
45 | var devs []string
46 | for client := range c.hub.clients {
47 | if client.device.Type == "camera" {
48 | devs = append(devs, client.device.Name)
49 | }
50 | }
51 | note := rawmsg{Type: "peerlist", Value: devs}
52 |
53 | ms.Msg, _ = json.Marshal(note)
54 | c.send <- *ms
55 | id, _ := uuid.NewV4()
56 | c.device.Name = id.String()
57 | note = rawmsg{Type: "id", Value: id}
58 | ms.Msg, _ = json.Marshal(note)
59 | c.send <- *ms
60 |
61 | }
62 |
63 | // send new camera control to the recorder
64 | if c.device.Type == "camera" {
65 | recnote := rawmsg{Type: "create", Value: c.device}
66 | jrecnote, err := json.Marshal(recnote)
67 | if err != nil {
68 | log.Printf("Marshal error: %s", err.Error())
69 | }
70 | recmesg := message{Type: "control", Targets: []string{"recorder"}, Msg: jrecnote}
71 | c.hub.broadcast <- recmesg
72 | }
73 | // if the device is the recorder send currently connected cameras
74 | if c.device.Type == "recorder" {
75 | for client := range c.hub.clients {
76 | log.Printf("found %s\n", client.device.Name)
77 |
78 | if client.device.Type == "camera" {
79 | recnote := rawmsg{Type: "create", Value: client.device}
80 | jrecnote, err := json.Marshal(recnote)
81 | if err != nil {
82 | log.Printf("Marshal error: %s", err.Error())
83 | }
84 | recmesg := message{Type: "control", Targets: []string{"recorder"}, Msg: jrecnote}
85 | c.send <- recmesg
86 |
87 | }
88 |
89 | }
90 | }
91 |
92 | }
93 |
94 | if c.msg.Type == "motion" {
95 |
96 | val := new(rawmsg)
97 | err := json.Unmarshal(c.msg.Msg, val)
98 | if err != nil {
99 | fmt.Printf("failed to decode motion\n")
100 |
101 | }
102 | // send motion start to the device node in the recorder
103 | if val.Value.(string) == "on" {
104 | rctrl := rawmsg{Type: "start", Value: c.device}
105 | jrctrl, _ := json.Marshal(rctrl)
106 |
107 | rmesg := message{Type: "control", Targets: []string{"recorder"}, Msg: jrctrl}
108 | c.hub.broadcast <- rmesg
109 | // send motion start to angelos and browsers
110 | note := rawmsg{Type: "notification", Value: "motion detected " + c.device.Desc}
111 | jnote, _ := json.Marshal(note)
112 | mesg := message{Type: "control", Targets: []string{"hmi", "text"}, Msg: jnote}
113 | c.hub.broadcast <- mesg
114 |
115 | }
116 | //same as above for off command
117 | if val.Value.(string) == "off" {
118 | note := rawmsg{Type: "stop", Value: c.device}
119 | jnote, _ := json.Marshal(note)
120 |
121 | mesg := message{Type: "control", Targets: []string{"recorder"}, Msg: jnote}
122 | c.hub.broadcast <- mesg
123 | /*
124 | note1 := rawmsg{Type: "notification", Value: "no motion " + c.device.Desc}
125 | jnote1, _ := json.Marshal(note1)
126 | mesg1 := message{Type: "control", Targets: []string{"hmi", "text"}, Msg: jnote1}
127 | c.hub.broadcast <- mesg1
128 | */
129 | }
130 |
131 | }
132 | if c.msg.Type == "state" {
133 |
134 | val := new(rawmsg)
135 | err := json.Unmarshal(c.msg.Msg, val)
136 | if err != nil {
137 | fmt.Printf("failed to decode state\n")
138 |
139 | }
140 |
141 | note := rawmsg{Type: "notification", Value: c.device.Desc + "is " + val.Value.(string)}
142 | jnote, _ := json.Marshal(note)
143 |
144 | mesg := message{Type: "control", Targets: []string{"hmi", "text"}, Msg: jnote}
145 | c.hub.broadcast <- mesg
146 |
147 | }
148 | //forward a control message
149 | if c.msg.Type == "control" {
150 | c.hub.broadcast <- *c.msg
151 |
152 | }
153 |
154 | if c.msg.Type == "command" {
155 | //get the command
156 |
157 | var cmd command
158 | err := json.Unmarshal(c.msg.Msg, &cmd)
159 | if err != nil {
160 | fmt.Printf("Could not unmarshal %v\n", cmd)
161 | }
162 |
163 | switch cmd.Name {
164 |
165 | case "savemsg":
166 | // save a new message from browser
167 | log.Printf("Message Value : %v", cmd)
168 | var filename = cmd.Value["Name"].(string)
169 |
170 | var data = cmd.Value["Value"]
171 |
172 | saveNodes(filename, data)
173 | msglist, err := loadmsgnames()
174 | if err != nil {
175 | fmt.Printf("Could not load config list %s", err.Error())
176 | }
177 | c.send <- *msglist
178 |
179 | // delete a message
180 | case "deletemsg":
181 | filename := cmd.Value["filename"].(string)
182 | // conn := msg["connection"].(*websocket.Conn)
183 | info, err := os.Lstat(msgfilepath + "/" + filename)
184 | if err != nil {
185 | fmt.Println("Can't stat config file")
186 | } else {
187 | if !info.IsDir() {
188 | os.Remove(msgfilepath + "/" + filename)
189 | msglist, err := loadmsgnames()
190 | if err != nil {
191 | fmt.Printf("Could not load config list %s", err.Error())
192 | }
193 | c.send <- *msglist
194 | }
195 | }
196 |
197 | case "loadmsg":
198 |
199 | // load a message and send to browser
200 | msg := new(message)
201 | var ctrl rawmsg
202 | msg.Type = "control"
203 | ctrl.Type = "configuration"
204 | name := cmd.Value["filename"].(string)
205 | log.Printf("loading filename %s\n", name)
206 | body, _ := loadnodes(name)
207 |
208 | err = json.Unmarshal(body, &ctrl.Value)
209 | if err != nil {
210 | fmt.Printf("could not unmarshal body: %s\n", err.Error())
211 | }
212 | msg.Msg, err = json.Marshal(ctrl)
213 | if err != nil {
214 | fmt.Printf("could not marshal control value: %s\n", err.Error())
215 | }
216 | // send the message to the browser
217 | select {
218 | case c.send <- *msg:
219 | default:
220 | close(c.send)
221 | delete(c.hub.clients, c)
222 | }
223 | //send the message from the browser
224 | case "sendmsg":
225 | msg := new(message)
226 |
227 | name := cmd.Value["filename"].(string)
228 | log.Printf("loading filename %s\n", name)
229 | localmsg, _ := loadnodes(name)
230 |
231 | log.Printf("Sending Message: %s", localmsg)
232 | json.Unmarshal(localmsg, msg)
233 |
234 | select {
235 | case c.hub.broadcast <- *msg:
236 | default:
237 | close(c.send)
238 | delete(c.hub.clients, c)
239 | }
240 | case "listdevices":
241 | // list all currently connected devices
242 |
243 | ms := new(message)
244 | ms.Type = "control"
245 | var devs []device
246 | // ti := time.Now()
247 | for client := range c.hub.clients {
248 | if "all" == cmd.Value["Type"].(string) || cmd.Value["Type"].(string) == client.device.Type {
249 | devs = append(devs, *client.device)
250 | }
251 | }
252 | note := rawmsg{Type: "devicelist", Value: devs}
253 |
254 | ms.Msg, _ = json.Marshal(note)
255 | c.send <- *ms
256 | fmt.Printf("Sending device list to %s\n", c.device.Name)
257 | }
258 |
259 | }
260 |
261 | }
262 | }
263 | }
264 |
--------------------------------------------------------------------------------
/mhserver/scheduler.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "log"
7 | "os"
8 | "reflect"
9 | "strings"
10 | "time"
11 |
12 | "github.com/fsnotify/fsnotify"
13 | "github.com/gorhill/cronexpr"
14 | )
15 |
16 | type schedule struct {
17 | expression *cronexpr.Expression
18 | cmd string
19 | timer *time.Timer
20 | }
21 |
22 | var filename string
23 |
24 | var todos []schedule
25 | var cases []reflect.SelectCase
26 |
27 | func scheduler() {
28 | filename = "crontab"
29 | loadSchedule(filename)
30 |
31 | }
32 |
33 | func looper() {
34 |
35 | watcher, err := fsnotify.NewWatcher()
36 | if err != nil {
37 | log.Fatal(err)
38 | }
39 | defer watcher.Close()
40 |
41 | err = watcher.Add(filename)
42 | if err != nil {
43 | log.Fatal(err)
44 | }
45 |
46 | cases[len(cases)-1] = reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(watcher.Events)}
47 |
48 | for {
49 |
50 | chosen, value, _ := reflect.Select(cases)
51 | if chosen == len(cases)-1 {
52 | event := reflect.Value(value).Interface().(fsnotify.Event)
53 |
54 | if event.Op&fsnotify.Write == fsnotify.Write {
55 | log.Println("modified file:", event.Name)
56 | break
57 | }
58 |
59 | } else {
60 | sched := todos[chosen]
61 | fmt.Println(sched.cmd)
62 | nextTime := sched.expression.Next(time.Now())
63 | duration := nextTime.Sub(time.Now())
64 | sched.timer.Reset(duration)
65 | }
66 |
67 | }
68 | log.Println("reloaded schedule")
69 | loadSchedule(filename)
70 | }
71 |
72 | func loadSchedule(localname string) {
73 |
74 | reader, err := os.Open(localname)
75 | if err != nil {
76 | log.Printf("Could not open crontab file %s", err.Error())
77 | return
78 | }
79 | todos = nil
80 | cases = nil
81 |
82 | scanner := bufio.NewScanner(reader)
83 | for scanner.Scan() {
84 | fmt.Println(scanner.Text()) // Println will add back the final '\n'
85 | fields := strings.Fields(scanner.Text())
86 | cmd := fields[7]
87 | expr, err := cronexpr.Parse(scanner.Text())
88 | if err != nil {
89 | log.Printf("crontab parser error: %q", err.Error())
90 | continue
91 |
92 | }
93 | nextTime := expr.Next(time.Now())
94 | duration := nextTime.Sub(time.Now())
95 | timer := time.NewTimer(duration)
96 | sched := schedule{expression: expr, cmd: cmd, timer: timer}
97 | todos = append(todos, sched)
98 |
99 | }
100 |
101 | if err := scanner.Err(); err != nil {
102 | fmt.Fprintln(os.Stderr, "reading standard input:", err)
103 | return
104 | }
105 |
106 | cases = make([]reflect.SelectCase, len(todos)+1)
107 | for i, ch := range todos {
108 | cases[i] = reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(ch.timer.C)}
109 | }
110 |
111 | looper()
112 | }
113 |
--------------------------------------------------------------------------------
/mhserver/view/cameras.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Cameras
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | My Camera Monitoring
18 |
19 |
20 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/mhserver/view/config/Hello.json:
--------------------------------------------------------------------------------
1 | {"Msg":{"Type":"notification","Value":"Just saying hello"},"Targets":["hmi"],"Type":"control"}
--------------------------------------------------------------------------------
/mhserver/view/config/New.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/mhserver/view/config/connected.json:
--------------------------------------------------------------------------------
1 | {"Msg":{"Name":"listdevices","Value":{"Type":"all"}},"Targets":["text"],"Type":"command"}
2 |
--------------------------------------------------------------------------------
/mhserver/view/config/dirtest.json:
--------------------------------------------------------------------------------
1 | {"Msg":{"Type":"oscommand","Value": {"cmd":"ls", "args":["-l", "-a", "-i"]}},"Targets":["camera"],"Type":"control"}
2 |
--------------------------------------------------------------------------------
/mhserver/view/config/eastshoplightoff.json:
--------------------------------------------------------------------------------
1 | {"Msg":{"Name":"mode","Value":"off"},"Targets":["eastshoplight"],"Type":"control"}
--------------------------------------------------------------------------------
/mhserver/view/config/eastshoplighton.json:
--------------------------------------------------------------------------------
1 | {"Msg":{"Name":"mode","Value":"on"},"Targets":["eastshoplight"],"Type":"control"}
--------------------------------------------------------------------------------
/mhserver/view/config/lunch.json:
--------------------------------------------------------------------------------
1 | {"Msg":{"Type":"notification","Value":"is it time for lunch?"},"Targets":["hmi"],"Type":"control"}
2 |
--------------------------------------------------------------------------------
/mhserver/view/config/motiondetectoff.json:
--------------------------------------------------------------------------------
1 | {"Msg":{"Type":"oscommand","Value":"setconf -k m -v -1"},"Targets":["camera"],"Type":"control"}
2 |
--------------------------------------------------------------------------------
/mhserver/view/config/motiondetecton.json:
--------------------------------------------------------------------------------
1 | {"Msg":{"Type":"oscommand","Value":"setconf -k m -v 4"},"Targets":["camera"],"Type":"control"}
2 |
--------------------------------------------------------------------------------
/mhserver/view/config/mute.json:
--------------------------------------------------------------------------------
1 | {"Msg":{"Type":"sound","Value":"off"},"Targets":["hmi"],"Type":"control"}
2 |
--------------------------------------------------------------------------------
/mhserver/view/config/nightmodeoff.json:
--------------------------------------------------------------------------------
1 | {"Msg":{"Type":"nightmode","Value":"off"},"Targets":["camera"],"Type":"control"}
--------------------------------------------------------------------------------
/mhserver/view/config/nightmodeon.json:
--------------------------------------------------------------------------------
1 | {"Msg":{"Type":"nightmode","Value":"on"},"Targets":["camera"],"Type":"control"}
--------------------------------------------------------------------------------
/mhserver/view/config/northshoplightoff.json:
--------------------------------------------------------------------------------
1 | {"Msg":{"Name":"mode","Value":"off"},"Targets":["northshoplight"],"Type":"control"}
--------------------------------------------------------------------------------
/mhserver/view/config/northshoplighton.json:
--------------------------------------------------------------------------------
1 | {"Msg":{"Name":"mode","Value":"on"},"Targets":["northshoplight"],"Type":"control"}
--------------------------------------------------------------------------------
/mhserver/view/config/note.json:
--------------------------------------------------------------------------------
1 | {"Msg":{"Type":"notification","Value":"Just saying hello"},"Targets":["hmi","text"],"Type":"control"}
--------------------------------------------------------------------------------
/mhserver/view/config/restartclient.json:
--------------------------------------------------------------------------------
1 | {"Msg":{"Type":"oscommand","Value": {"cmd":"/usr/scripts/restart-client.sh", "args":[]}},"Targets":["camera"],"Type":"control"}
2 |
--------------------------------------------------------------------------------
/mhserver/view/config/setshopthermostat.json:
--------------------------------------------------------------------------------
1 | {"Msg":{"Name":"set","Value":"75"},"Targets":["shopthermostat"],"Type":"control"}
2 |
--------------------------------------------------------------------------------
/mhserver/view/config/southshoplightoff.json:
--------------------------------------------------------------------------------
1 | {"Msg":{"Name":"mode","Value":"off"},"Targets":["southshoplight"],"Type":"control"}
--------------------------------------------------------------------------------
/mhserver/view/config/southshoplighton.json:
--------------------------------------------------------------------------------
1 | {"Msg":{"Name":"mode","Value":"on"},"Targets":["southshoplight"],"Type":"control"}
--------------------------------------------------------------------------------
/mhserver/view/config/unmute.json:
--------------------------------------------------------------------------------
1 | {"Msg":{"Type":"sound","Value":"on"},"Targets":["hmi"],"Type":"control"}
--------------------------------------------------------------------------------
/mhserver/view/config/volume-100.json:
--------------------------------------------------------------------------------
1 | {"Msg":{"Type":"volume","Value":"100%"},"Targets":["hmi"],"Type":"control"}
2 |
--------------------------------------------------------------------------------
/mhserver/view/config/volume-25.json:
--------------------------------------------------------------------------------
1 | {"Msg":{"Type":"volume","Value":"25%"},"Targets":["hmi"],"Type":"control"}
2 |
--------------------------------------------------------------------------------
/mhserver/view/config/volume-50.json:
--------------------------------------------------------------------------------
1 | {"Msg":{"Type":"volume","Value":"50%"},"Targets":["hmi"],"Type":"control"}
2 |
--------------------------------------------------------------------------------
/mhserver/view/config/volume-75.json:
--------------------------------------------------------------------------------
1 | {"Msg":{"Type":"volume","Value":"75%"},"Targets":["hmi"],"Type":"control"}
2 |
--------------------------------------------------------------------------------
/mhserver/view/config/volume.json:
--------------------------------------------------------------------------------
1 | {"Msg":{"Type":"volume","Value":"50%"},"Targets":["hmi"],"Type":"control"}
--------------------------------------------------------------------------------
/mhserver/view/control.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | My Home
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | My Home Control Panel
21 |
22 |
23 |
24 |
25 |
26 |
27 | Lights
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | Cameras
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | HMI
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | Misc
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/mhserver/view/css/bulma-switch.min.css:
--------------------------------------------------------------------------------
1 | @-webkit-keyframes spinAround{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes spinAround{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.switch[type=checkbox]{outline:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;display:inline-block;position:absolute;opacity:0}.switch[type=checkbox]:focus+label::after,.switch[type=checkbox]:focus+label::before,.switch[type=checkbox]:focus+label:after,.switch[type=checkbox]:focus+label:before{outline:1px dotted #b5b5b5}.switch[type=checkbox][disabled]{cursor:not-allowed}.switch[type=checkbox][disabled]+label{opacity:.5}.switch[type=checkbox][disabled]+label::before,.switch[type=checkbox][disabled]+label:before{opacity:.5}.switch[type=checkbox][disabled]+label::after,.switch[type=checkbox][disabled]+label:after{opacity:.5}.switch[type=checkbox][disabled]+label:hover{cursor:not-allowed}.switch[type=checkbox]+label{position:relative;display:initial;font-size:1rem;line-height:initial;padding-left:3.5rem;padding-top:.2rem;cursor:pointer}.switch[type=checkbox]+label::before,.switch[type=checkbox]+label:before{position:absolute;display:block;top:0;left:0;width:3rem;height:1.5rem;border:.1rem solid transparent;border-radius:4px;background:#b5b5b5;content:''}.switch[type=checkbox]+label::after,.switch[type=checkbox]+label:after{display:block;position:absolute;top:.25rem;left:.25rem;width:1rem;height:1rem;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);border-radius:4px;background:#fff;-webkit-transition:all .25s ease-out;transition:all .25s ease-out;content:''}.switch[type=checkbox].is-rtl+label{padding-left:0;padding-right:3.5rem}.switch[type=checkbox].is-rtl+label::before,.switch[type=checkbox].is-rtl+label:before{left:auto;right:0}.switch[type=checkbox].is-rtl+label::after,.switch[type=checkbox].is-rtl+label:after{left:auto;right:1.625rem}.switch[type=checkbox]:checked+label::before,.switch[type=checkbox]:checked+label:before{background:#00d1b2}.switch[type=checkbox]:checked+label::after{left:1.625rem}.switch[type=checkbox]:checked.is-rtl+label::after,.switch[type=checkbox]:checked.is-rtl+label:after{left:auto;right:.25rem}.switch[type=checkbox].is-outlined+label::before,.switch[type=checkbox].is-outlined+label:before{background-color:transparent;border-color:#b5b5b5}.switch[type=checkbox].is-outlined+label::after,.switch[type=checkbox].is-outlined+label:after{background:#b5b5b5}.switch[type=checkbox].is-outlined:checked+label::before,.switch[type=checkbox].is-outlined:checked+label:before{background-color:transparent;border-color:#00d1b2}.switch[type=checkbox].is-outlined:checked+label::after,.switch[type=checkbox].is-outlined:checked+label:after{background:#00d1b2}.switch[type=checkbox].is-thin+label::before,.switch[type=checkbox].is-thin+label:before{top:.54545rem;height:.375rem}.switch[type=checkbox].is-thin+label::after,.switch[type=checkbox].is-thin+label:after{-webkit-box-shadow:0 0 3px #7a7a7a;box-shadow:0 0 3px #7a7a7a}.switch[type=checkbox].is-rounded+label::before,.switch[type=checkbox].is-rounded+label:before{border-radius:24px}.switch[type=checkbox].is-rounded+label::after,.switch[type=checkbox].is-rounded+label:after{border-radius:50%}.switch[type=checkbox].is-small+label{position:relative;display:initial;font-size:.75rem;line-height:initial;padding-left:2.75rem;padding-top:.2rem;cursor:pointer}.switch[type=checkbox].is-small+label::before,.switch[type=checkbox].is-small+label:before{position:absolute;display:block;top:0;left:0;width:2.25rem;height:1.125rem;border:.1rem solid transparent;border-radius:4px;background:#b5b5b5;content:''}.switch[type=checkbox].is-small+label::after,.switch[type=checkbox].is-small+label:after{display:block;position:absolute;top:.25rem;left:.25rem;width:.625rem;height:.625rem;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);border-radius:4px;background:#fff;-webkit-transition:all .25s ease-out;transition:all .25s ease-out;content:''}.switch[type=checkbox].is-small.is-rtl+label{padding-left:0;padding-right:2.75rem}.switch[type=checkbox].is-small.is-rtl+label::before,.switch[type=checkbox].is-small.is-rtl+label:before{left:auto;right:0}.switch[type=checkbox].is-small.is-rtl+label::after,.switch[type=checkbox].is-small.is-rtl+label:after{left:auto;right:1.25rem}.switch[type=checkbox].is-small:checked+label::before,.switch[type=checkbox].is-small:checked+label:before{background:#00d1b2}.switch[type=checkbox].is-small:checked+label::after{left:1.25rem}.switch[type=checkbox].is-small:checked.is-rtl+label::after,.switch[type=checkbox].is-small:checked.is-rtl+label:after{left:auto;right:.25rem}.switch[type=checkbox].is-small.is-outlined+label::before,.switch[type=checkbox].is-small.is-outlined+label:before{background-color:transparent;border-color:#b5b5b5}.switch[type=checkbox].is-small.is-outlined+label::after,.switch[type=checkbox].is-small.is-outlined+label:after{background:#b5b5b5}.switch[type=checkbox].is-small.is-outlined:checked+label::before,.switch[type=checkbox].is-small.is-outlined:checked+label:before{background-color:transparent;border-color:#00d1b2}.switch[type=checkbox].is-small.is-outlined:checked+label::after,.switch[type=checkbox].is-small.is-outlined:checked+label:after{background:#00d1b2}.switch[type=checkbox].is-small.is-thin+label::before,.switch[type=checkbox].is-small.is-thin+label:before{top:.40909rem;height:.28125rem}.switch[type=checkbox].is-small.is-thin+label::after,.switch[type=checkbox].is-small.is-thin+label:after{-webkit-box-shadow:0 0 3px #7a7a7a;box-shadow:0 0 3px #7a7a7a}.switch[type=checkbox].is-small.is-rounded+label::before,.switch[type=checkbox].is-small.is-rounded+label:before{border-radius:24px}.switch[type=checkbox].is-small.is-rounded+label::after,.switch[type=checkbox].is-small.is-rounded+label:after{border-radius:50%}.switch[type=checkbox].is-medium+label{position:relative;display:initial;font-size:1.25rem;line-height:initial;padding-left:4.25rem;padding-top:.2rem;cursor:pointer}.switch[type=checkbox].is-medium+label::before,.switch[type=checkbox].is-medium+label:before{position:absolute;display:block;top:0;left:0;width:3.75rem;height:1.875rem;border:.1rem solid transparent;border-radius:4px;background:#b5b5b5;content:''}.switch[type=checkbox].is-medium+label::after,.switch[type=checkbox].is-medium+label:after{display:block;position:absolute;top:.25rem;left:.25rem;width:1.375rem;height:1.375rem;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);border-radius:4px;background:#fff;-webkit-transition:all .25s ease-out;transition:all .25s ease-out;content:''}.switch[type=checkbox].is-medium.is-rtl+label{padding-left:0;padding-right:4.25rem}.switch[type=checkbox].is-medium.is-rtl+label::before,.switch[type=checkbox].is-medium.is-rtl+label:before{left:auto;right:0}.switch[type=checkbox].is-medium.is-rtl+label::after,.switch[type=checkbox].is-medium.is-rtl+label:after{left:auto;right:2rem}.switch[type=checkbox].is-medium:checked+label::before,.switch[type=checkbox].is-medium:checked+label:before{background:#00d1b2}.switch[type=checkbox].is-medium:checked+label::after{left:2rem}.switch[type=checkbox].is-medium:checked.is-rtl+label::after,.switch[type=checkbox].is-medium:checked.is-rtl+label:after{left:auto;right:.25rem}.switch[type=checkbox].is-medium.is-outlined+label::before,.switch[type=checkbox].is-medium.is-outlined+label:before{background-color:transparent;border-color:#b5b5b5}.switch[type=checkbox].is-medium.is-outlined+label::after,.switch[type=checkbox].is-medium.is-outlined+label:after{background:#b5b5b5}.switch[type=checkbox].is-medium.is-outlined:checked+label::before,.switch[type=checkbox].is-medium.is-outlined:checked+label:before{background-color:transparent;border-color:#00d1b2}.switch[type=checkbox].is-medium.is-outlined:checked+label::after,.switch[type=checkbox].is-medium.is-outlined:checked+label:after{background:#00d1b2}.switch[type=checkbox].is-medium.is-thin+label::before,.switch[type=checkbox].is-medium.is-thin+label:before{top:.68182rem;height:.46875rem}.switch[type=checkbox].is-medium.is-thin+label::after,.switch[type=checkbox].is-medium.is-thin+label:after{-webkit-box-shadow:0 0 3px #7a7a7a;box-shadow:0 0 3px #7a7a7a}.switch[type=checkbox].is-medium.is-rounded+label::before,.switch[type=checkbox].is-medium.is-rounded+label:before{border-radius:24px}.switch[type=checkbox].is-medium.is-rounded+label::after,.switch[type=checkbox].is-medium.is-rounded+label:after{border-radius:50%}.switch[type=checkbox].is-large+label{position:relative;display:initial;font-size:1.5rem;line-height:initial;padding-left:5rem;padding-top:.2rem;cursor:pointer}.switch[type=checkbox].is-large+label::before,.switch[type=checkbox].is-large+label:before{position:absolute;display:block;top:0;left:0;width:4.5rem;height:2.25rem;border:.1rem solid transparent;border-radius:4px;background:#b5b5b5;content:''}.switch[type=checkbox].is-large+label::after,.switch[type=checkbox].is-large+label:after{display:block;position:absolute;top:.25rem;left:.25rem;width:1.75rem;height:1.75rem;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);border-radius:4px;background:#fff;-webkit-transition:all .25s ease-out;transition:all .25s ease-out;content:''}.switch[type=checkbox].is-large.is-rtl+label{padding-left:0;padding-right:5rem}.switch[type=checkbox].is-large.is-rtl+label::before,.switch[type=checkbox].is-large.is-rtl+label:before{left:auto;right:0}.switch[type=checkbox].is-large.is-rtl+label::after,.switch[type=checkbox].is-large.is-rtl+label:after{left:auto;right:2.375rem}.switch[type=checkbox].is-large:checked+label::before,.switch[type=checkbox].is-large:checked+label:before{background:#00d1b2}.switch[type=checkbox].is-large:checked+label::after{left:2.375rem}.switch[type=checkbox].is-large:checked.is-rtl+label::after,.switch[type=checkbox].is-large:checked.is-rtl+label:after{left:auto;right:.25rem}.switch[type=checkbox].is-large.is-outlined+label::before,.switch[type=checkbox].is-large.is-outlined+label:before{background-color:transparent;border-color:#b5b5b5}.switch[type=checkbox].is-large.is-outlined+label::after,.switch[type=checkbox].is-large.is-outlined+label:after{background:#b5b5b5}.switch[type=checkbox].is-large.is-outlined:checked+label::before,.switch[type=checkbox].is-large.is-outlined:checked+label:before{background-color:transparent;border-color:#00d1b2}.switch[type=checkbox].is-large.is-outlined:checked+label::after,.switch[type=checkbox].is-large.is-outlined:checked+label:after{background:#00d1b2}.switch[type=checkbox].is-large.is-thin+label::before,.switch[type=checkbox].is-large.is-thin+label:before{top:.81818rem;height:.5625rem}.switch[type=checkbox].is-large.is-thin+label::after,.switch[type=checkbox].is-large.is-thin+label:after{-webkit-box-shadow:0 0 3px #7a7a7a;box-shadow:0 0 3px #7a7a7a}.switch[type=checkbox].is-large.is-rounded+label::before,.switch[type=checkbox].is-large.is-rounded+label:before{border-radius:24px}.switch[type=checkbox].is-large.is-rounded+label::after,.switch[type=checkbox].is-large.is-rounded+label:after{border-radius:50%}.switch[type=checkbox].is-white:checked+label::before,.switch[type=checkbox].is-white:checked+label:before{background:#fff}.switch[type=checkbox].is-white.is-outlined:checked+label::before,.switch[type=checkbox].is-white.is-outlined:checked+label:before{background-color:transparent;border-color:#fff!important}.switch[type=checkbox].is-white.is-outlined:checked+label::after,.switch[type=checkbox].is-white.is-outlined:checked+label:after{background:#fff}.switch[type=checkbox].is-white.is-thin.is-outlined+label::after,.switch[type=checkbox].is-white.is-thin.is-outlined+label:after{-webkit-box-shadow:none;box-shadow:none}.switch[type=checkbox].is-unchecked-white+label::before,.switch[type=checkbox].is-unchecked-white+label:before{background:#fff}.switch[type=checkbox].is-unchecked-white.is-outlined+label::before,.switch[type=checkbox].is-unchecked-white.is-outlined+label:before{background-color:transparent;border-color:#fff!important}.switch[type=checkbox].is-unchecked-white.is-outlined+label::after,.switch[type=checkbox].is-unchecked-white.is-outlined+label:after{background:#fff}.switch[type=checkbox].is-black:checked+label::before,.switch[type=checkbox].is-black:checked+label:before{background:#0a0a0a}.switch[type=checkbox].is-black.is-outlined:checked+label::before,.switch[type=checkbox].is-black.is-outlined:checked+label:before{background-color:transparent;border-color:#0a0a0a!important}.switch[type=checkbox].is-black.is-outlined:checked+label::after,.switch[type=checkbox].is-black.is-outlined:checked+label:after{background:#0a0a0a}.switch[type=checkbox].is-black.is-thin.is-outlined+label::after,.switch[type=checkbox].is-black.is-thin.is-outlined+label:after{-webkit-box-shadow:none;box-shadow:none}.switch[type=checkbox].is-unchecked-black+label::before,.switch[type=checkbox].is-unchecked-black+label:before{background:#0a0a0a}.switch[type=checkbox].is-unchecked-black.is-outlined+label::before,.switch[type=checkbox].is-unchecked-black.is-outlined+label:before{background-color:transparent;border-color:#0a0a0a!important}.switch[type=checkbox].is-unchecked-black.is-outlined+label::after,.switch[type=checkbox].is-unchecked-black.is-outlined+label:after{background:#0a0a0a}.switch[type=checkbox].is-light:checked+label::before,.switch[type=checkbox].is-light:checked+label:before{background:#f5f5f5}.switch[type=checkbox].is-light.is-outlined:checked+label::before,.switch[type=checkbox].is-light.is-outlined:checked+label:before{background-color:transparent;border-color:#f5f5f5!important}.switch[type=checkbox].is-light.is-outlined:checked+label::after,.switch[type=checkbox].is-light.is-outlined:checked+label:after{background:#f5f5f5}.switch[type=checkbox].is-light.is-thin.is-outlined+label::after,.switch[type=checkbox].is-light.is-thin.is-outlined+label:after{-webkit-box-shadow:none;box-shadow:none}.switch[type=checkbox].is-unchecked-light+label::before,.switch[type=checkbox].is-unchecked-light+label:before{background:#f5f5f5}.switch[type=checkbox].is-unchecked-light.is-outlined+label::before,.switch[type=checkbox].is-unchecked-light.is-outlined+label:before{background-color:transparent;border-color:#f5f5f5!important}.switch[type=checkbox].is-unchecked-light.is-outlined+label::after,.switch[type=checkbox].is-unchecked-light.is-outlined+label:after{background:#f5f5f5}.switch[type=checkbox].is-dark:checked+label::before,.switch[type=checkbox].is-dark:checked+label:before{background:#363636}.switch[type=checkbox].is-dark.is-outlined:checked+label::before,.switch[type=checkbox].is-dark.is-outlined:checked+label:before{background-color:transparent;border-color:#363636!important}.switch[type=checkbox].is-dark.is-outlined:checked+label::after,.switch[type=checkbox].is-dark.is-outlined:checked+label:after{background:#363636}.switch[type=checkbox].is-dark.is-thin.is-outlined+label::after,.switch[type=checkbox].is-dark.is-thin.is-outlined+label:after{-webkit-box-shadow:none;box-shadow:none}.switch[type=checkbox].is-unchecked-dark+label::before,.switch[type=checkbox].is-unchecked-dark+label:before{background:#363636}.switch[type=checkbox].is-unchecked-dark.is-outlined+label::before,.switch[type=checkbox].is-unchecked-dark.is-outlined+label:before{background-color:transparent;border-color:#363636!important}.switch[type=checkbox].is-unchecked-dark.is-outlined+label::after,.switch[type=checkbox].is-unchecked-dark.is-outlined+label:after{background:#363636}.switch[type=checkbox].is-primary:checked+label::before,.switch[type=checkbox].is-primary:checked+label:before{background:#00d1b2}.switch[type=checkbox].is-primary.is-outlined:checked+label::before,.switch[type=checkbox].is-primary.is-outlined:checked+label:before{background-color:transparent;border-color:#00d1b2!important}.switch[type=checkbox].is-primary.is-outlined:checked+label::after,.switch[type=checkbox].is-primary.is-outlined:checked+label:after{background:#00d1b2}.switch[type=checkbox].is-primary.is-thin.is-outlined+label::after,.switch[type=checkbox].is-primary.is-thin.is-outlined+label:after{-webkit-box-shadow:none;box-shadow:none}.switch[type=checkbox].is-unchecked-primary+label::before,.switch[type=checkbox].is-unchecked-primary+label:before{background:#00d1b2}.switch[type=checkbox].is-unchecked-primary.is-outlined+label::before,.switch[type=checkbox].is-unchecked-primary.is-outlined+label:before{background-color:transparent;border-color:#00d1b2!important}.switch[type=checkbox].is-unchecked-primary.is-outlined+label::after,.switch[type=checkbox].is-unchecked-primary.is-outlined+label:after{background:#00d1b2}.switch[type=checkbox].is-link:checked+label::before,.switch[type=checkbox].is-link:checked+label:before{background:#3273dc}.switch[type=checkbox].is-link.is-outlined:checked+label::before,.switch[type=checkbox].is-link.is-outlined:checked+label:before{background-color:transparent;border-color:#3273dc!important}.switch[type=checkbox].is-link.is-outlined:checked+label::after,.switch[type=checkbox].is-link.is-outlined:checked+label:after{background:#3273dc}.switch[type=checkbox].is-link.is-thin.is-outlined+label::after,.switch[type=checkbox].is-link.is-thin.is-outlined+label:after{-webkit-box-shadow:none;box-shadow:none}.switch[type=checkbox].is-unchecked-link+label::before,.switch[type=checkbox].is-unchecked-link+label:before{background:#3273dc}.switch[type=checkbox].is-unchecked-link.is-outlined+label::before,.switch[type=checkbox].is-unchecked-link.is-outlined+label:before{background-color:transparent;border-color:#3273dc!important}.switch[type=checkbox].is-unchecked-link.is-outlined+label::after,.switch[type=checkbox].is-unchecked-link.is-outlined+label:after{background:#3273dc}.switch[type=checkbox].is-info:checked+label::before,.switch[type=checkbox].is-info:checked+label:before{background:#209cee}.switch[type=checkbox].is-info.is-outlined:checked+label::before,.switch[type=checkbox].is-info.is-outlined:checked+label:before{background-color:transparent;border-color:#209cee!important}.switch[type=checkbox].is-info.is-outlined:checked+label::after,.switch[type=checkbox].is-info.is-outlined:checked+label:after{background:#209cee}.switch[type=checkbox].is-info.is-thin.is-outlined+label::after,.switch[type=checkbox].is-info.is-thin.is-outlined+label:after{-webkit-box-shadow:none;box-shadow:none}.switch[type=checkbox].is-unchecked-info+label::before,.switch[type=checkbox].is-unchecked-info+label:before{background:#209cee}.switch[type=checkbox].is-unchecked-info.is-outlined+label::before,.switch[type=checkbox].is-unchecked-info.is-outlined+label:before{background-color:transparent;border-color:#209cee!important}.switch[type=checkbox].is-unchecked-info.is-outlined+label::after,.switch[type=checkbox].is-unchecked-info.is-outlined+label:after{background:#209cee}.switch[type=checkbox].is-success:checked+label::before,.switch[type=checkbox].is-success:checked+label:before{background:#23d160}.switch[type=checkbox].is-success.is-outlined:checked+label::before,.switch[type=checkbox].is-success.is-outlined:checked+label:before{background-color:transparent;border-color:#23d160!important}.switch[type=checkbox].is-success.is-outlined:checked+label::after,.switch[type=checkbox].is-success.is-outlined:checked+label:after{background:#23d160}.switch[type=checkbox].is-success.is-thin.is-outlined+label::after,.switch[type=checkbox].is-success.is-thin.is-outlined+label:after{-webkit-box-shadow:none;box-shadow:none}.switch[type=checkbox].is-unchecked-success+label::before,.switch[type=checkbox].is-unchecked-success+label:before{background:#23d160}.switch[type=checkbox].is-unchecked-success.is-outlined+label::before,.switch[type=checkbox].is-unchecked-success.is-outlined+label:before{background-color:transparent;border-color:#23d160!important}.switch[type=checkbox].is-unchecked-success.is-outlined+label::after,.switch[type=checkbox].is-unchecked-success.is-outlined+label:after{background:#23d160}.switch[type=checkbox].is-warning:checked+label::before,.switch[type=checkbox].is-warning:checked+label:before{background:#ffdd57}.switch[type=checkbox].is-warning.is-outlined:checked+label::before,.switch[type=checkbox].is-warning.is-outlined:checked+label:before{background-color:transparent;border-color:#ffdd57!important}.switch[type=checkbox].is-warning.is-outlined:checked+label::after,.switch[type=checkbox].is-warning.is-outlined:checked+label:after{background:#ffdd57}.switch[type=checkbox].is-warning.is-thin.is-outlined+label::after,.switch[type=checkbox].is-warning.is-thin.is-outlined+label:after{-webkit-box-shadow:none;box-shadow:none}.switch[type=checkbox].is-unchecked-warning+label::before,.switch[type=checkbox].is-unchecked-warning+label:before{background:#ffdd57}.switch[type=checkbox].is-unchecked-warning.is-outlined+label::before,.switch[type=checkbox].is-unchecked-warning.is-outlined+label:before{background-color:transparent;border-color:#ffdd57!important}.switch[type=checkbox].is-unchecked-warning.is-outlined+label::after,.switch[type=checkbox].is-unchecked-warning.is-outlined+label:after{background:#ffdd57}.switch[type=checkbox].is-danger:checked+label::before,.switch[type=checkbox].is-danger:checked+label:before{background:#ff3860}.switch[type=checkbox].is-danger.is-outlined:checked+label::before,.switch[type=checkbox].is-danger.is-outlined:checked+label:before{background-color:transparent;border-color:#ff3860!important}.switch[type=checkbox].is-danger.is-outlined:checked+label::after,.switch[type=checkbox].is-danger.is-outlined:checked+label:after{background:#ff3860}.switch[type=checkbox].is-danger.is-thin.is-outlined+label::after,.switch[type=checkbox].is-danger.is-thin.is-outlined+label:after{-webkit-box-shadow:none;box-shadow:none}.switch[type=checkbox].is-unchecked-danger+label::before,.switch[type=checkbox].is-unchecked-danger+label:before{background:#ff3860}.switch[type=checkbox].is-unchecked-danger.is-outlined+label::before,.switch[type=checkbox].is-unchecked-danger.is-outlined+label:before{background-color:transparent;border-color:#ff3860!important}.switch[type=checkbox].is-unchecked-danger.is-outlined+label::after,.switch[type=checkbox].is-unchecked-danger.is-outlined+label:after{background:#ff3860}
2 |
--------------------------------------------------------------------------------
/mhserver/view/css/img/jsoneditor-icons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/machinesworking/myhome/f587956724c2c14a66511f97694a69508b8b5651/mhserver/view/css/img/jsoneditor-icons.png
--------------------------------------------------------------------------------
/mhserver/view/css/jsoneditor.css:
--------------------------------------------------------------------------------
1 | .jsoneditor .field,
2 | .jsoneditor .value,
3 | .jsoneditor .readonly {
4 | border: 1px solid transparent;
5 | min-height: 16px;
6 | min-width: 32px;
7 | padding: 2px;
8 | margin: 1px;
9 | word-wrap: break-word;
10 | float: left;
11 | }
12 |
13 | /* adjust margin of p elements inside editable divs, needed for Opera, IE */
14 |
15 | .jsoneditor .field p,
16 | .jsoneditor .value p {
17 | margin: 0;
18 | }
19 |
20 | .jsoneditor .value {
21 | word-break: break-word;
22 | }
23 |
24 | .jsoneditor .readonly {
25 | min-width: 16px;
26 | color: gray;
27 | }
28 |
29 | .jsoneditor .empty {
30 | border-color: lightgray;
31 | border-style: dashed;
32 | border-radius: 2px;
33 | }
34 |
35 | .jsoneditor .field.empty {
36 | background-image: url("img/jsoneditor-icons.png");
37 | background-position: 0 -144px;
38 | }
39 |
40 | .jsoneditor .value.empty {
41 | background-image: url("img/jsoneditor-icons.png");
42 | background-position: -48px -144px;
43 | }
44 |
45 | .jsoneditor .value.url {
46 | color: green;
47 | text-decoration: underline;
48 | }
49 |
50 | .jsoneditor a.value.url:hover,
51 | .jsoneditor a.value.url:focus {
52 | color: red;
53 | }
54 |
55 | .jsoneditor .separator {
56 | padding: 3px 0;
57 | vertical-align: top;
58 | color: gray;
59 | }
60 |
61 | .jsoneditor .field[contenteditable=true]:focus,
62 | .jsoneditor .field[contenteditable=true]:hover,
63 | .jsoneditor .value[contenteditable=true]:focus,
64 | .jsoneditor .value[contenteditable=true]:hover,
65 | .jsoneditor .field.highlight,
66 | .jsoneditor .value.highlight {
67 | background-color: #FFFFAB;
68 | border: 1px solid yellow;
69 | border-radius: 2px;
70 | }
71 |
72 | .jsoneditor .field.highlight-active,
73 | .jsoneditor .field.highlight-active:focus,
74 | .jsoneditor .field.highlight-active:hover,
75 | .jsoneditor .value.highlight-active,
76 | .jsoneditor .value.highlight-active:focus,
77 | .jsoneditor .value.highlight-active:hover {
78 | background-color: #ffee00;
79 | border: 1px solid #ffc700;
80 | border-radius: 2px;
81 | }
82 |
83 | .jsoneditor div.tree button {
84 | width: 24px;
85 | height: 24px;
86 | padding: 0;
87 | margin: 0;
88 | border: none;
89 | cursor: pointer;
90 | background: transparent url("img/jsoneditor-icons.png");
91 | }
92 |
93 | .jsoneditor div.tree button.collapsed {
94 | background-position: 0 -48px;
95 | }
96 |
97 | .jsoneditor div.tree button.expanded {
98 | background-position: 0 -72px;
99 | }
100 |
101 | .jsoneditor div.tree button.contextmenu {
102 | background-position: -48px -72px;
103 | }
104 |
105 | .jsoneditor div.tree button.contextmenu:hover,
106 | .jsoneditor div.tree button.contextmenu:focus,
107 | .jsoneditor div.tree button.contextmenu.selected {
108 | background-position: -48px -48px;
109 | }
110 |
111 | .jsoneditor div.tree *:focus {
112 | outline: none;
113 | }
114 |
115 | .jsoneditor div.tree button:focus {
116 | /* TODO: nice outline for buttons with focus
117 | outline: #97B0F8 solid 2px;
118 | box-shadow: 0 0 8px #97B0F8;
119 | */
120 | background-color: #f5f5f5;
121 | outline: #e5e5e5 solid 1px;
122 | }
123 |
124 | .jsoneditor div.tree button.invisible {
125 | visibility: hidden;
126 | background: none;
127 | }
128 |
129 | .jsoneditor {
130 | color: #1A1A1A;
131 | border: 1px solid #97B0F8;
132 | -moz-box-sizing: border-box;
133 | -webkit-box-sizing: border-box;
134 | box-sizing: border-box;
135 | width: 100%;
136 | height: 100%;
137 | overflow: auto;
138 | position: relative;
139 | padding: 0;
140 | line-height: 100%;
141 | }
142 |
143 | .jsoneditor div.tree table.tree {
144 | border-collapse: collapse;
145 | border-spacing: 0;
146 | width: 100%;
147 | margin: 0;
148 | }
149 |
150 | .jsoneditor div.outer {
151 | width: 100%;
152 | height: 100%;
153 | margin: -35px 0 0 0;
154 | padding: 35px 0 0 0;
155 | -moz-box-sizing: border-box;
156 | -webkit-box-sizing: border-box;
157 | box-sizing: border-box;
158 | overflow: hidden;
159 | }
160 |
161 | .jsoneditor div.tree {
162 | width: 100%;
163 | height: 100%;
164 | position: relative;
165 | overflow: auto;
166 | }
167 |
168 | .jsoneditor textarea.text {
169 | width: 100%;
170 | height: 100%;
171 | margin: 0;
172 | -moz-box-sizing: border-box;
173 | -webkit-box-sizing: border-box;
174 | box-sizing: border-box;
175 | border: none;
176 | background-color: white;
177 | resize: none;
178 | }
179 |
180 | .jsoneditor tr.highlight {
181 | background-color: #FFFFAB;
182 | }
183 |
184 | .jsoneditor div.tree button.dragarea {
185 | background: url("img/jsoneditor-icons.png") -72px -72px;
186 | cursor: move;
187 | }
188 |
189 | .jsoneditor div.tree button.dragarea:hover,
190 | .jsoneditor div.tree button.dragarea:focus {
191 | background-position: -72px -48px;
192 | }
193 |
194 | .jsoneditor tr,
195 | .jsoneditor th,
196 | .jsoneditor td {
197 | padding: 0;
198 | margin: 0;
199 | }
200 |
201 | .jsoneditor td {
202 | vertical-align: top;
203 | }
204 |
205 | .jsoneditor td.tree {
206 | vertical-align: top;
207 | }
208 |
209 | .jsoneditor .field,
210 | .jsoneditor .value,
211 | .jsoneditor td,
212 | .jsoneditor th,
213 | .jsoneditor textarea {
214 | font-family: droid sans mono, consolas, monospace, courier new, courier, sans-serif;
215 | font-size: 10pt;
216 | color: #1A1A1A;
217 | }
218 | /* ContextMenu - main menu */
219 |
220 | .jsoneditor-contextmenu {
221 | position: absolute;
222 | z-index: 99999;
223 | }
224 |
225 | .jsoneditor-contextmenu ul {
226 | position: relative;
227 | left: 0;
228 | top: 0;
229 | width: 124px;
230 | background: white;
231 | border: 1px solid #d3d3d3;
232 | box-shadow: 2px 2px 12px rgba(128, 128, 128, 0.3);
233 | list-style: none;
234 | margin: 0;
235 | padding: 0;
236 | }
237 |
238 | .jsoneditor-contextmenu ul li button {
239 | padding: 0;
240 | margin: 0;
241 | width: 124px;
242 | height: 24px;
243 | border: none;
244 | cursor: pointer;
245 | color: #4d4d4d;
246 | background: transparent;
247 | line-height: 26px;
248 | text-align: left;
249 | }
250 |
251 | /* Fix button padding in firefox */
252 |
253 | .jsoneditor-contextmenu ul li button::-moz-focus-inner {
254 | padding: 0;
255 | border: 0;
256 | }
257 |
258 | .jsoneditor-contextmenu ul li button:hover,
259 | .jsoneditor-contextmenu ul li button:focus {
260 | color: #1a1a1a;
261 | background-color: #f5f5f5;
262 | outline: none;
263 | }
264 |
265 | .jsoneditor-contextmenu ul li button.default {
266 | width: 92px;
267 | }
268 |
269 | .jsoneditor-contextmenu ul li button.expand {
270 | float: right;
271 | width: 32px;
272 | height: 24px;
273 | border-left: 1px solid #e5e5e5;
274 | }
275 |
276 | .jsoneditor-contextmenu div.icon {
277 | float: left;
278 | width: 24px;
279 | height: 24px;
280 | border: none;
281 | padding: 0;
282 | margin: 0;
283 | background-image: url("img/jsoneditor-icons.png");
284 | }
285 |
286 | .jsoneditor-contextmenu ul li button div.expand {
287 | float: right;
288 | width: 24px;
289 | height: 24px;
290 | padding: 0;
291 | margin: 0 4px 0 0;
292 | background: url("img/jsoneditor-icons.png") 0 -72px;
293 | opacity: 0.4;
294 | }
295 |
296 | .jsoneditor-contextmenu ul li button:hover div.expand,
297 | .jsoneditor-contextmenu ul li button:focus div.expand,
298 | .jsoneditor-contextmenu ul li.selected div.expand,
299 | .jsoneditor-contextmenu ul li button.expand:hover div.expand,
300 | .jsoneditor-contextmenu ul li button.expand:focus div.expand {
301 | opacity: 1;
302 | }
303 |
304 | .jsoneditor-contextmenu .separator {
305 | height: 0;
306 | border-top: 1px solid #e5e5e5;
307 | padding-top: 5px;
308 | margin-top: 5px;
309 | }
310 |
311 | .jsoneditor-contextmenu button.remove > .icon {
312 | background-position: -24px -24px;
313 | }
314 |
315 | .jsoneditor-contextmenu button.remove:hover > .icon,
316 | .jsoneditor-contextmenu button.remove:focus > .icon {
317 | background-position: -24px 0;
318 | }
319 |
320 | .jsoneditor-contextmenu button.append > .icon {
321 | background-position: 0 -24px;
322 | }
323 |
324 | .jsoneditor-contextmenu button.append:hover > .icon,
325 | .jsoneditor-contextmenu button.append:focus > .icon {
326 | background-position: 0 0;
327 | }
328 |
329 | .jsoneditor-contextmenu button.insert > .icon {
330 | background-position: 0 -24px;
331 | }
332 |
333 | .jsoneditor-contextmenu button.insert:hover > .icon,
334 | .jsoneditor-contextmenu button.insert:focus > .icon {
335 | background-position: 0 0;
336 | }
337 |
338 | .jsoneditor-contextmenu button.duplicate > .icon {
339 | background-position: -48px -24px;
340 | }
341 |
342 | .jsoneditor-contextmenu button.duplicate:hover > .icon,
343 | .jsoneditor-contextmenu button.duplicate:focus > .icon {
344 | background-position: -48px 0;
345 | }
346 |
347 | .jsoneditor-contextmenu button.sort-asc > .icon {
348 | background-position: -168px -24px;
349 | }
350 |
351 | .jsoneditor-contextmenu button.sort-asc:hover > .icon,
352 | .jsoneditor-contextmenu button.sort-asc:focus > .icon {
353 | background-position: -168px 0;
354 | }
355 |
356 | .jsoneditor-contextmenu button.sort-desc > .icon {
357 | background-position: -192px -24px;
358 | }
359 |
360 | .jsoneditor-contextmenu button.sort-desc:hover > .icon,
361 | .jsoneditor-contextmenu button.sort-desc:focus > .icon {
362 | background-position: -192px 0;
363 | }
364 |
365 | /* ContextMenu - sub menu */
366 |
367 | .jsoneditor-contextmenu ul li .selected {
368 | background-color: #D5DDF6;
369 | }
370 |
371 | .jsoneditor-contextmenu ul li {
372 | overflow: hidden;
373 | }
374 |
375 | .jsoneditor-contextmenu ul li ul {
376 | display: none;
377 | position: relative;
378 | left: -10px;
379 | top: 0;
380 | border: none;
381 | box-shadow: inset 0 0 10px rgba(128, 128, 128, 0.5);
382 | padding: 0 10px;
383 | /* TODO: transition is not supported on IE8-9 */
384 | -webkit-transition: all 0.3s ease-out;
385 | -moz-transition: all 0.3s ease-out;
386 | -o-transition: all 0.3s ease-out;
387 | transition: all 0.3s ease-out;
388 | }
389 |
390 |
391 |
392 | .jsoneditor-contextmenu ul li ul li button {
393 | padding-left: 24px;
394 | }
395 |
396 | .jsoneditor-contextmenu ul li ul li button:hover,
397 | .jsoneditor-contextmenu ul li ul li button:focus {
398 | background-color: #f5f5f5;
399 | }
400 |
401 | .jsoneditor-contextmenu button.type-string > .icon {
402 | background-position: -144px -24px;
403 | }
404 |
405 | .jsoneditor-contextmenu button.type-string:hover > .icon,
406 | .jsoneditor-contextmenu button.type-string:focus > .icon,
407 | .jsoneditor-contextmenu button.type-string.selected > .icon {
408 | background-position: -144px 0;
409 | }
410 |
411 | .jsoneditor-contextmenu button.type-auto > .icon {
412 | background-position: -120px -24px;
413 | }
414 |
415 | .jsoneditor-contextmenu button.type-auto:hover > .icon,
416 | .jsoneditor-contextmenu button.type-auto:focus > .icon,
417 | .jsoneditor-contextmenu button.type-auto.selected > .icon {
418 | background-position: -120px 0;
419 | }
420 |
421 | .jsoneditor-contextmenu button.type-object > .icon {
422 | background-position: -72px -24px;
423 | }
424 |
425 | .jsoneditor-contextmenu button.type-object:hover > .icon,
426 | .jsoneditor-contextmenu button.type-object:focus > .icon,
427 | .jsoneditor-contextmenu button.type-object.selected > .icon {
428 | background-position: -72px 0;
429 | }
430 |
431 | .jsoneditor-contextmenu button.type-array > .icon {
432 | background-position: -96px -24px;
433 | }
434 |
435 | .jsoneditor-contextmenu button.type-array:hover > .icon,
436 | .jsoneditor-contextmenu button.type-array:focus > .icon,
437 | .jsoneditor-contextmenu button.type-array.selected > .icon {
438 | background-position: -96px 0;
439 | }
440 |
441 | .jsoneditor-contextmenu button.type-modes > .icon {
442 | background-image: none;
443 | width: 6px;
444 | }
445 | .jsoneditor .menu {
446 | width: 100%;
447 | height: 35px;
448 | padding: 2px;
449 | margin: 0;
450 | overflow: hidden;
451 | -moz-box-sizing: border-box;
452 | -webkit-box-sizing: border-box;
453 | box-sizing: border-box;
454 | color: #1A1A1A;
455 | background-color: #D5DDF6;
456 | border-bottom: 1px solid #97B0F8;
457 | }
458 |
459 | .jsoneditor .menu button {
460 | width: 26px;
461 | height: 26px;
462 | margin: 2px;
463 | padding: 0;
464 | border-radius: 2px;
465 | border: 1px solid #aec0f8;
466 | background: #e3eaf6 url("img/jsoneditor-icons.png");
467 | color: #4D4D4D;
468 | opacity: 0.8;
469 | font-family: arial, sans-serif;
470 | font-size: 10pt;
471 | float: left;
472 | }
473 |
474 | .jsoneditor .menu button:hover {
475 | background-color: #f0f2f5;
476 | }
477 |
478 | .jsoneditor .menu button:focus,
479 | .jsoneditor .menu button:active {
480 | background-color: #ffffff;
481 | }
482 |
483 | .jsoneditor .menu button:disabled {
484 | background-color: #e3eaf6;
485 | }
486 |
487 | .jsoneditor .menu button.collapse-all {
488 | background-position: 0 -96px;
489 | }
490 |
491 | .jsoneditor .menu button.expand-all {
492 | background-position: 0 -120px;
493 | }
494 |
495 | .jsoneditor .menu button.undo {
496 | background-position: -24px -96px;
497 | }
498 |
499 | .jsoneditor .menu button.undo:disabled {
500 | background-position: -24px -120px;
501 | }
502 |
503 | .jsoneditor .menu button.redo {
504 | background-position: -48px -96px;
505 | }
506 |
507 | .jsoneditor .menu button.redo:disabled {
508 | background-position: -48px -120px;
509 | }
510 |
511 | .jsoneditor .menu button.compact {
512 | background-position: -72px -96px;
513 | }
514 |
515 | .jsoneditor .menu button.format {
516 | background-position: -72px -120px;
517 | }
518 |
519 | .jsoneditor .menu button.modes {
520 | background-image: none;
521 | width: auto;
522 | padding-left: 6px;
523 | padding-right: 6px;
524 | }
525 |
526 | .jsoneditor .menu button.separator {
527 | margin-left: 10px;
528 | }
529 |
530 | .jsoneditor .menu a {
531 | font-family: arial, sans-serif;
532 | font-size: 10pt;
533 | color: #97B0F8;
534 | vertical-align: middle;
535 | }
536 |
537 | .jsoneditor .menu a:hover {
538 | color: red;
539 | }
540 |
541 | .jsoneditor .menu a.poweredBy {
542 | font-size: 8pt;
543 | position: absolute;
544 | right: 0;
545 | top: 0;
546 | padding: 10px;
547 | }
548 |
549 | /* TODO: css for button:disabled is not supported by IE8 */
550 | .jsoneditor .search input,
551 | .jsoneditor .search .results {
552 | font-family: arial, sans-serif;
553 | font-size: 10pt;
554 | color: #1A1A1A;
555 | background: transparent;
556 | /* For Firefox */
557 | }
558 |
559 | .jsoneditor .search {
560 | position: absolute;
561 | right: 2px;
562 | top: 2px;
563 | }
564 |
565 | .jsoneditor .search .frame {
566 | border: 1px solid #97B0F8;
567 | background-color: white;
568 | padding: 0 2px;
569 | margin: 0;
570 | }
571 |
572 | .jsoneditor .search .frame table {
573 | border-collapse: collapse;
574 | }
575 |
576 | .jsoneditor .search input {
577 | width: 120px;
578 | border: none;
579 | outline: none;
580 | margin: 1px;
581 | }
582 |
583 | .jsoneditor .search .results {
584 | color: #4d4d4d;
585 | padding-right: 5px;
586 | line-height: 24px;
587 | }
588 |
589 | .jsoneditor .search button {
590 | width: 16px;
591 | height: 24px;
592 | padding: 0;
593 | margin: 0;
594 | border: none;
595 | background: url("img/jsoneditor-icons.png");
596 | vertical-align: top;
597 | }
598 |
599 | .jsoneditor .search button:hover {
600 | background-color: transparent;
601 | }
602 |
603 | .jsoneditor .search button.refresh {
604 | width: 18px;
605 | background-position: -99px -73px;
606 | }
607 |
608 | .jsoneditor .search button.next {
609 | cursor: pointer;
610 | background-position: -124px -73px;
611 | }
612 |
613 | .jsoneditor .search button.next:hover {
614 | background-position: -124px -49px;
615 | }
616 |
617 | .jsoneditor .search button.previous {
618 | cursor: pointer;
619 | background-position: -148px -73px;
620 | margin-right: 2px;
621 | }
622 |
623 | .jsoneditor .search button.previous:hover {
624 | background-position: -148px -49px;
625 | }
--------------------------------------------------------------------------------
/mhserver/view/css/style.css:
--------------------------------------------------------------------------------
1 |
2 | .switch {
3 | position: relative;
4 | display: inline-block;
5 | width: 60px;
6 | height: 34px;
7 | }
8 |
9 | .switch input {
10 | opacity: 0;
11 | width: 0;
12 | height: 0;
13 | }
14 |
15 | .slider {
16 | position: absolute;
17 | cursor: pointer;
18 | top: 0;
19 | left: 0;
20 | right: 0;
21 | bottom: 0;
22 | background-color: #ccc;
23 | -webkit-transition: .4s;
24 | transition: .4s;
25 | }
26 |
27 | .slider:before {
28 | position: absolute;
29 | content: "";
30 | height: 26px;
31 | width: 26px;
32 | left: 4px;
33 | bottom: 4px;
34 | background-color: white;
35 | -webkit-transition: .4s;
36 | transition: .4s;
37 | }
38 |
39 | input:checked + .slider {
40 | background-color: #2196F3;
41 | }
42 |
43 | input:focus + .slider {
44 | box-shadow: 0 0 1px #2196F3;
45 | }
46 |
47 | input:checked + .slider:before {
48 | -webkit-transform: translateX(26px);
49 | -ms-transform: translateX(26px);
50 | transform: translateX(26px);
51 | }
52 |
53 | /* Rounded sliders */
54 | .slider.round {
55 | border-radius: 34px;
56 | }
57 |
58 | .slider.round:before {
59 | border-radius: 50%;
60 | }
61 |
62 |
63 |
64 |
65 | .seqconfig {
66 | position: absolute;
67 | right: 10px;
68 | top: 5px;
69 | width: 600px;
70 | bottom: 5px;
71 | }
72 | .seqmenu {
73 | position: absolute;
74 | left: 5px;
75 | top: 5px;
76 | right: 650px;
77 | bottom: 5px;
78 |
79 | }
80 | #headerdiv{
81 | width: 100%
82 | }
83 | .seqcontainer{
84 | position: relative;
85 | height: 500px;
86 | display: none;
87 | }
88 |
89 | #remoteVideos{
90 | width:1300px;
91 | overflow: auto;
92 |
93 | }
94 |
95 | #statusDiv{
96 | width:800px;
97 | Height:500px;
98 | overflow: auto;
99 | }
100 | #msgtext{
101 | visibility: hidden
102 | }
103 | #saveButton, #loadButton, #selectButton{
104 | background-color: #ccc;
105 | -moz-border-radius: 5px;
106 | -webkit-border-radius: 5px;
107 | border-radius:6px;
108 | color: #fff;
109 | }
110 |
111 |
112 |
113 |
114 | #shutdownDlgButton{
115 | background-color: #df6666;
116 | width: 300px;
117 | height: 300px;
118 | -moz-border-radius: 150px;
119 | -webkit-border-radius: 150px;
120 | border-radius:150px;
121 | color: #fff;
122 | }
123 | #continueShutdownButton{
124 | background-color: #ccc;
125 | width: 250px;
126 | height: 250px;
127 | -moz-border-radius: 125px;
128 | -webkit-border-radius: 125px;
129 | border-radius:125px;
130 | color: #fff;
131 | }
132 |
133 | #cancelShutdownButton{
134 | background-color: #ccc;
135 | width: 250px;
136 | height: 250px;
137 | -moz-border-radius: 125px;
138 | -webkit-border-radius: 125px;
139 | border-radius:125px;
140 | color: #000000;
141 | }
142 |
143 | #shutdownDlgButton:hover , #continueShutdownButton:hover, #abortShutdownButton:hover{
144 | border: none;
145 | background:red;
146 | box-shadow: 0px 0px 1px #777;
147 |
148 |
149 | }
150 | #cancelShutdownButton:hover{
151 | border: none;
152 | background:yellow;
153 | box-shadow: 0px 0px 1px #777;
154 |
155 |
156 | }
157 |
158 | #saveButton:hover, #loadButton:hover, #selectButton:hover {
159 | border: none;
160 | background:blue;
161 | box-shadow: 0px 0px 1px #777;
162 |
163 |
164 | }
165 |
166 | #abortButton{
167 | background-color: #ccc;
168 | width: 100px;
169 | height: 100px;
170 | -moz-border-radius: 50px;
171 | -webkit-border-radius: 50px;
172 | border-radius:50px;
173 | color: #000000;
174 | }
175 |
176 |
177 | body {
178 | font: 10.5pt arial;
179 | color: #4d4d4d;
180 | line-height: 150%;
181 | width: 800px;
182 | }
183 |
184 | code {
185 | background-color: #f5f5f5;
186 | }
187 |
188 | #jsoneditor {
189 | width: 600px;
190 | height: 500px;
191 | }
192 |
193 |
194 |
195 | .overlay {
196 | display: none;
197 | position: fixed;
198 | top: 0;
199 | left: 0;
200 | height: 100%;
201 | width: 100%;
202 | background-color: rgba(0,0,0,0.3);
203 | z-index: 10;
204 | }
205 | #abortDialog {
206 | display: none;
207 | opacity: 0.7;
208 | width: 120px;
209 | height: 100px;
210 | left: 660px;
211 | position: fixed;
212 | background-color: #bfbfbf;
213 | border-radius: 5px;
214 | text-align: center;
215 | }
216 | .modal {
217 |
218 | width: 600px;
219 | height: 400px;
220 | /* line-height: 200px;*/
221 | position: fixed;
222 | top: 50%;
223 | left: 50%;
224 | margin-top: -200px;
225 | margin-left: -300px;
226 | background-color: #bfbfbf;
227 | border-radius: 5px;
228 | text-align: center;
229 | z-index: 11; /* 1px higher than the overlay layer */
230 | }
231 |
232 | .statusDiv{
233 | margin-right: 10px;
234 | margin-left: 10px;
235 | margin-top: 10px;
236 | margin-bottom: 5px;
237 | padding: 0px;
238 | height: 82%;
239 | width: 100%;
240 | overflow: auto;
241 | text-align: left;
242 | font-size:85%;
243 | font-family: serif;
244 | border-style: solid;
245 | border-width: 2px;
246 | }
247 | .vidiv{
248 |
249 | overflow: auto;
250 | font-size:85%;
251 | font-family: serif;
252 | border-style: solid;
253 | border-width: 2px;
254 | }
255 |
--------------------------------------------------------------------------------
/mhserver/view/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Myhome Config
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | My home monitoring system
19 |
28 |
29 |
30 |
31 |
50 |
51 |
52 |
53 | Status
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/mhserver/view/peer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Browser base64 Session Description
5 | Golang base64 Session Description:
6 |
7 |
8 |
9 |
58 |
--------------------------------------------------------------------------------
/mhserver/view/scripts/control.js:
--------------------------------------------------------------------------------
1 | var editor=null;
2 | var ws;
3 | var configlist;
4 | var container;
5 | var editor;
6 | var statusdiv;
7 | var editdiv;
8 | var list;
9 | var currentConfig=null;
10 | var msgtext;
11 | var peers=[{}];
12 | var localid;
13 | var alllights = {Type:"light", Name: "light", Desc: "all lights", State: "online", Address:"all"};
14 | var controlmsgs;
15 |
16 | //var shutdowndlg;
17 | //var shutdowndlgbutton;
18 |
19 |
20 |
21 | if ("WebSocket" in window) {
22 |
23 |
24 | function init(){
25 |
26 |
27 | var MESSAGE_SERVER = (location.protocol == 'https:' ? 'wss' : 'ws') + '://'+ document.domain +':8080/ws';
28 | ws = new WebSocket(MESSAGE_SERVER);
29 | // ws = new WebSocket("ws://172.20.3.50:8080/ws");
30 |
31 |
32 | ws.onopen = function(event) {
33 | //msgarea.nodeValue="connected to server ";
34 |
35 | var msg = {Type:"status", Msg:{ Name: "unknown", Type: "text", Desc: location.hostname, State: "online"}};
36 | ws.send(JSON.stringify(msg));
37 | var item = document.createElement("div");
38 | item.innerHTML = "Status sent";
39 | appendLog(item);
40 | addctrllist("camera");
41 | addctrllist("hmi");
42 |
43 |
44 |
45 | };
46 |
47 | ws.onerror = function(event) {
48 |
49 | var item = document.createElement("div");
50 | item.innerHTML = "Websocket error";
51 | appendLog(item);
52 |
53 |
54 |
55 | };
56 |
57 | ws.onmessage = function(event) {
58 |
59 |
60 | var packet = JSON.parse(event.data);
61 |
62 | if(packet.Type == "control"){
63 | if(packet.Msg === null)
64 | return;
65 |
66 | /*
67 |
68 | if(packet.Msg.Type == "notification"){
69 | status = packet.Msg.Value;
70 |
71 | var div = document.createElement("Div");
72 | div.innerHTML = status;
73 | statusdiv.appendChild(div);
74 | statusDiv.scrollTop = statusDiv.scrollHeight;
75 | }
76 | */
77 | if(packet.Msg.Type == "id"){
78 | localid = packet.Msg.Value;
79 | var msg1 = {Msg: {Name: "listdevices", Value: {Type: "all"}}, Targets: ["hmi"], Type: "command"};
80 | ws.send(JSON.stringify(msg1));
81 | }
82 |
83 |
84 | if(packet.Msg.Type == "devicelist" ){
85 |
86 |
87 | packet.Msg.Value.push({Type:"light", Name: "light", Desc: "all lights", State: "online", Address:"all"});
88 | packet.Msg.Value.push({Type:"camera", Name: "camera", Desc: "all cameras", State: "online", Address:"all"});
89 | packet.Msg.Value.push({Type:"hmi", Name: "hmi", Desc: "all HMIs", State: "online", Address:"all"});
90 |
91 |
92 |
93 |
94 | for(cnt=0; cnt < packet.Msg.Value.length; ++cnt){
95 |
96 | var dev = packet.Msg.Value[cnt];
97 | var devtype = dev.Type;
98 | if(devtype === "light" || devtype === "camera" || devtype==="hmi"){
99 | adddev(dev);
100 | } else {
101 | adddevmisc
102 | }
103 |
104 | }
105 | }
106 |
107 | if(packet.Msg.Type == "msglist" ){
108 | controlmsgs = packet.Msg.Value;
109 | camctrllist = document.getElementById("camerasel");
110 | hmictrllist = document.getElementById("hmisel");
111 | while (camctrllist.firstChild) {
112 | camctrllist.removeChild(camctrllist.firstChild);
113 |
114 | }
115 | while (hmictrllist.firstChild) {
116 | hmictrllist.removeChild(hmictrllist.firstChild);
117 |
118 | }
119 |
120 |
121 | Object.keys(packet.Msg.Value).forEach(function(key){
122 | if(packet.Msg.Value[key].Targets != null){
123 |
124 | if(packet.Msg.Value[key].Targets[0]==="camera"){
125 | var option = document.createElement("option");
126 | option.innerHTML=key.substring(0, key.indexOf(".json"));
127 | option.value=key;
128 | camctrllist.appendChild(option);
129 |
130 | }
131 | if(packet.Msg.Value[key].Targets[0]==="hmi"){
132 | var option = document.createElement("option");
133 | option.innerHTML=key.substring(0, key.indexOf(".json"));
134 | option.value=key;
135 | hmictrllist.appendChild(option);
136 | }
137 | }
138 | });
139 |
140 |
141 | }
142 |
143 | if (packet.Msg.Type == "configuration"){
144 | //editor.set(packet.Msg.Value);
145 | // send the control
146 | }
147 |
148 | }
149 |
150 |
151 | }; // end of onmessage
152 |
153 | ws.onclose = function(event) {
154 | var item = document.createElement("div");
155 | item.innerHTML = "Websocket has been closed";
156 | appendLog(item);
157 | };
158 |
159 | }; // end of init
160 |
161 | function error(err) { console.log("crap")}
162 |
163 | function appendLog(item) {
164 | var doScroll = log.scrollTop > log.scrollHeight - log.clientHeight - 1;
165 | log.appendChild(item);
166 | if (doScroll) {
167 | log.scrollTop = log.scrollHeight - log.clientHeight;
168 | }
169 | }
170 |
171 |
172 |
173 | function sendSwitchControl(ev){
174 | devicetype = ev.target.dataset.devtype;
175 |
176 | var checked="off";
177 | if(ev.target.checked)
178 | checked = "on";
179 | // gang the switches
180 | if(ev.target.id == devicetype){
181 | devs = document.getElementById(devicetype+"s").querySelectorAll("input")
182 | Object.keys(devs).forEach(function(key){
183 | devs[key].checked = ev.target.checked;
184 | });
185 |
186 |
187 | }
188 |
189 | switch(ev.target.dataset.devtype){
190 |
191 | case "light":
192 |
193 | ws.send(JSON.stringify({Msg:{Name:"mode",Value:checked},Targets:[ev.target.name],Type:"control"}));
194 |
195 | break;
196 |
197 |
198 | case "camera":
199 | /*
200 | sel = document.getElementById("camerasel");
201 | msgname=sel.options[sel.selectedIndex].text;
202 | msg = controlmsgs[msgname+".json"];
203 | msg.Targets=[ev.target.name];
204 | ws.send(JSON.stringify(msg));
205 |
206 | devs = document.getElementById(devicetype+"s").querySelectorAll("input")
207 | Object.keys(devs).forEach(function(key){
208 | devs[key].checked = 0;
209 | });
210 | */
211 | break;
212 |
213 | case "hmi":
214 | // ws.send(JSON.stringify({Msg:{Name:"mode",Value:checked},Targets:[ev.target.name],Type:"control"}));
215 |
216 | break;
217 |
218 | case "misc":
219 | // ws.send(JSON.stringify({Msg:{Name:"mode",Value:checked},Targets:[ev.target.name],Type:"control"}));
220 |
221 | break;
222 |
223 |
224 | }
225 | }
226 |
227 |
228 |
229 | function sendSubmitControl(ev){
230 |
231 |
232 |
233 | switch(ev.target.dataset.devtype){
234 |
235 |
236 | case "camera":
237 | sel = document.getElementById("camerasel");
238 | msgname=sel.options[sel.selectedIndex].text;
239 | msg = controlmsgs[msgname+".json"];
240 | devices = [];
241 | devs = document.getElementById(devicetype+"s").querySelectorAll("input")
242 | Object.keys(devs).forEach(function(key){
243 | devname = devs[key].name
244 | if(devs[key].checked){
245 | devices.push(devname);
246 | if(devname === "camera")
247 | devices=["camera"];
248 | }
249 |
250 | devs[key].checked = 0;
251 |
252 |
253 | });
254 | msg.Targets=devices;
255 | ws.send(JSON.stringify(msg));
256 |
257 | break;
258 |
259 | case "hmi":
260 | sel = document.getElementById("hmisel");
261 | msgname=sel.options[sel.selectedIndex].text;
262 | msg = controlmsgs[msgname+".json"];
263 | devices = [];
264 | devs = document.getElementById(devicetype+"s").querySelectorAll("input")
265 | Object.keys(devs).forEach(function(key){
266 | devname = devs[key].name
267 | if(devs[key].checked){
268 | devices.push(devname);
269 | if(devname === "hmi")
270 | devices=["hmi"];
271 | }
272 |
273 | devs[key].checked = 0;
274 |
275 |
276 | });
277 | msg.Targets=devices;
278 | ws.send(JSON.stringify(msg));
279 |
280 | break;
281 |
282 | case "misc":
283 | // ws.send(JSON.stringify({Msg:{Name:"mode",Value:checked},Targets:[ev.target.name],Type:"control"}));
284 |
285 | break;
286 |
287 |
288 | }
289 |
290 | }
291 |
292 |
293 | function addctrllist(devtype){
294 | var dvdiv = document.getElementById(devtype+"s");
295 | var div = document.createElement("div");
296 | div.className = "field";
297 | var inp = document.createElement("select");
298 | inp.id = devtype+"sel";
299 | inp.dataset.devtype=devtype;
300 |
301 |
302 | var el = document.createElement("button");
303 | el.dataset.devtype=devtype;
304 | el.addEventListener("click", sendSubmitControl);
305 | el.innerHTML="Submit";
306 |
307 |
308 | div.appendChild(inp);
309 | div.appendChild(el);
310 | dvdiv.appendChild(div);
311 | }
312 |
313 |
314 |
315 | function adddev(dev){
316 | var devdiv = document.getElementById(dev.Type+"s");
317 | if (devdiv === null)
318 | devdiv = document.getElementById("miscs");
319 | var div = document.createElement("div");
320 | div.className = "field";
321 | var inp = document.createElement("input");
322 | inp.type = "checkbox";
323 | inp.id = dev.Name;
324 | inp.name = dev.Name;
325 | inp.innerHTML = dev.Desc;
326 | inp.className = "switch";
327 | inp.checked = "";
328 | inp.addEventListener("click", sendSwitchControl);
329 | inp.dataset.devtype=dev.Type;
330 | var lbl = document.createElement("label");
331 | lbl.htmlFor = dev.Name;
332 | lbl.innerHTML=dev.Desc;
333 | div.appendChild(inp);
334 | div.appendChild(lbl);
335 | devdiv.appendChild(div);
336 | }
337 |
338 |
339 |
340 | function adddevmisc(dev){}
341 |
342 | function sendmsg(){
343 | var name = configlist.options[configlist.selectedIndex].text;
344 |
345 | var msg = editor.get();
346 |
347 | if(name == "note.json")
348 | msg.Msg.Value = msgtext.value;
349 |
350 |
351 | ws.send(JSON.stringify(msg));
352 |
353 | }
354 |
355 | function loadConfig(){
356 | var name = editconfiglist.options[editconfiglist.selectedIndex].text;
357 |
358 | var msg = {Type: "command", Msg:{Name: "loadmsg", Value: {filename: name}}};
359 | ws.send(JSON.stringify(msg));
360 | }
361 |
362 |
363 |
364 |
365 | }
366 |
--------------------------------------------------------------------------------
/mhserver/view/scripts/img/jsoneditor-icons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/machinesworking/myhome/f587956724c2c14a66511f97694a69508b8b5651/mhserver/view/scripts/img/jsoneditor-icons.png
--------------------------------------------------------------------------------
/mhserver/view/scripts/peer.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | function MyPeer(peername, ws){
3 |
4 | this.localname = peername;
5 |
6 |
7 | let pc = new RTCPeerConnection({
8 | iceServers: [
9 | {
10 | urls: 'stun:stun.l.google.com:19302'
11 | }
12 | ]
13 | })
14 | let log = msg => {
15 | // document.getElementById('vstat').innerHTML += peername+": "+msg + '
'
16 | console.log(msg);
17 | }
18 |
19 | pc.ontrack = function (event) {
20 | var el = document.createElement(event.track.kind)
21 | el.srcObject = event.streams[0]
22 | el.autoplay = true
23 | el.controls = true
24 | el.width = "640"
25 | el.height = "480"
26 | document.getElementById(peername).appendChild(el)
27 | }
28 |
29 | pc.oniceconnectionstatechange = e => log(pc.iceConnectionState)
30 | pc.onicecandidate = event => {
31 | if(event.candidate === null){
32 | var msg = new Object();
33 | msg.Type = "control";
34 | msg.Targets = [peername];
35 | msg.Msg = {Type: "offer", Value: {from: localid, data: pc.localDescription.sdp}};
36 | ws.send(JSON.stringify(msg));
37 | }
38 | }
39 | console.log("creating offer")
40 | pc.createOffer({offerToReceiveVideo: true, offerToReceiveAudio: true}).then(d => pc.setLocalDescription(d)).catch(log)
41 | return pc;
42 | /*
43 | pc.startsess = function(d){
44 | try {
45 | pc.setRemoteDescription(new RTCSessionDescription({type: 'answer', sdp: d}))
46 | } catch (e) {
47 | alert(e)
48 | }
49 | }
50 | */
51 | }
52 |
--------------------------------------------------------------------------------
/mhserver/view/scripts/seq.js:
--------------------------------------------------------------------------------
1 | var editor=null;
2 | var ws;
3 | var configlist;
4 | var container;
5 | var editor;
6 | var statusdiv;
7 | var editdiv;
8 | var list;
9 | var currentConfig=null;
10 | var msgtext;
11 | var peers=[{}];
12 | var localid;
13 |
14 | //var shutdowndlg;
15 | //var shutdowndlgbutton;
16 | var nightmodestate = "off";
17 | var options = {
18 | mode: 'tree',
19 | modes: ['code', 'form', 'text', 'tree', 'view'], // allowed modes
20 | error: function (err) {
21 | alert(err.toString());
22 | }
23 | };
24 |
25 |
26 |
27 | if ("WebSocket" in window) {
28 |
29 |
30 | function init(){
31 | editconfiglist = document.getElementById("editConfigSelect");
32 | configlist = document.getElementById("configSelect");
33 | container = document.getElementById("jsoneditor");
34 | msgtext = document.getElementById("msgtext");
35 |
36 |
37 | statusdiv = document.getElementById("statusDiv");
38 |
39 |
40 | status = document.getElementById("status");
41 |
42 | editdiv = document.getElementById("editDiv");
43 |
44 | editor = new JSONEditor(container, options);
45 |
46 |
47 | configlist.addEventListener("change", function() {
48 | var name = configlist.options[configlist.selectedIndex].text;
49 | currentConfig = name;
50 |
51 |
52 | if(name == "note.json")
53 | msgtext.style.visibility="visible";
54 | else
55 | msgtext.style.visibility="hidden";
56 |
57 | var msg = {Type: "command", Msg:{Name: "loadmsg", Value: {filename: name}}};
58 |
59 |
60 | ws.send(JSON.stringify(msg));
61 |
62 | });
63 |
64 | editconfiglist.addEventListener("change", function() {
65 | loadConfig();
66 | });
67 |
68 |
69 | var MESSAGE_SERVER = (location.protocol == 'https:' ? 'wss' : 'ws') + '://'+ document.domain +':8080/ws';
70 | ws = new WebSocket(MESSAGE_SERVER);
71 | // ws = new WebSocket("ws://172.20.3.50:8080/ws");
72 |
73 |
74 | ws.onopen = function(event) {
75 | //msgarea.nodeValue="connected to server ";
76 |
77 | var msg = {Type:"status", Msg:{ Name: "unknown", Type: "text", Desc: location.hostname, State: "online"}};
78 | ws.send(JSON.stringify(msg));
79 |
80 |
81 | };
82 |
83 | ws.onerror = function(event) {
84 | };
85 |
86 | ws.onmessage = function(event) {
87 |
88 |
89 | var packet = JSON.parse(event.data);
90 |
91 | if(packet.Type == "control"){
92 | if(packet.Msg.Type == "notification"){
93 | status = packet.Msg.Value;
94 |
95 | var div = document.createElement("Div");
96 | div.innerHTML = status;
97 | statusdiv.appendChild(div);
98 | statusDiv.scrollTop = statusDiv.scrollHeight;
99 | }
100 |
101 | if(packet.Msg.Type == "id"){
102 | localid = packet.Msg.Value;
103 | }
104 |
105 |
106 | if(packet.Msg.Type == "devicelist" ){
107 |
108 |
109 | for(cnt=0; cnt < packet.Msg.Value.length; ++cnt){
110 | var div = document.createElement("Div");
111 | div.innerHTML = packet.Msg.Value[cnt].Desc +" "+packet.Msg.Value[cnt].State;
112 | statusdiv.appendChild(div);
113 | statusDiv.scrollTop = statusDiv.scrollHeight;
114 | }
115 |
116 |
117 | }
118 | /*
119 | if(packet.Msg.Type == "peerlist" ){
120 | var peercnt = packet.Msg.Value.length;
121 | for(cnt=0; cnt < peercnt; ++cnt){
122 | var peername = packet.Msg.Value[cnt];
123 | console.log("creating connection for "+peername);
124 | var peer = new MyPeer( peername, ws );
125 | peers[cnt]={sessionid: peername, peer: peer};
126 | }
127 | }
128 |
129 | if(packet.Msg.Type == "answer"){
130 |
131 | for(var z=0; zClosing connection to security server";
194 | };
195 |
196 | }; // end of init
197 |
198 | function error(err) { console.log("crap")}
199 |
200 |
201 | function clearLog(){
202 | while (statusdiv.firstChild) {
203 | statusdiv.removeChild(statusdiv.firstChild);
204 |
205 | }
206 | }
207 | function saveConfig(){
208 |
209 | var name = editconfiglist.options[editconfiglist.selectedIndex].text;
210 |
211 | if(name=="New.json"){
212 | do{
213 | name = prompt("Please Enter A File Name",".json");
214 | }while(!(name != "New.json" && name != "" && name.lastIndexOf(".json") != name.length-6 && name.length >=7))
215 |
216 | }
217 |
218 | var data = editor.get();
219 |
220 | var msg = {Type: "command", Msg:{Name: "savemsg", Value:{Name: name, Value: data}}};
221 | ws.send(JSON.stringify(msg));
222 | }
223 |
224 | function loadConfig(){
225 | var name = editconfiglist.options[editconfiglist.selectedIndex].text;
226 |
227 | var msg = {Type: "command", Msg:{Name: "loadmsg", Value: {filename: name}}};
228 | ws.send(JSON.stringify(msg));
229 | }
230 |
231 | function doneEditing(){
232 | var editdiv = document.getElementById("editDiv");
233 | editDiv.style.display="none";
234 | }
235 |
236 | function deleteConfig(){
237 | var name = editconfiglist.options[editconfiglist.selectedIndex].text;
238 | var msg = {Type: "command", Msg:{Name: "deletemsg", Value:{filename: name}}};
239 | ws.send(JSON.stringify(msg));
240 | }
241 |
242 |
243 | function editConfig(){
244 |
245 | editDiv.style.display="block";
246 | }
247 |
248 |
249 |
250 |
251 | function sendmsg(){
252 | var name = configlist.options[configlist.selectedIndex].text;
253 |
254 | var msg = editor.get();
255 |
256 | if(name == "note.json")
257 | msg.Msg.Value = msgtext.value;
258 |
259 |
260 | ws.send(JSON.stringify(msg));
261 |
262 | }
263 | }
264 |
--------------------------------------------------------------------------------
/mhserver/view/scripts/single.js:
--------------------------------------------------------------------------------
1 | var ws;
2 | var peers=[{}];
3 | var localid;
4 |
5 |
6 |
7 |
8 | if ("WebSocket" in window) {
9 |
10 |
11 | function init(){
12 |
13 | var MESSAGE_SERVER = (location.protocol == 'https:' ? 'wss' : 'ws') + '://'+ document.domain +':8080/ws';
14 | ws = new WebSocket(MESSAGE_SERVER);
15 | // ws = new WebSocket("ws://172.20.3.50:8080/ws");
16 |
17 |
18 | ws.onopen = function(event) {
19 | //msgarea.nodeValue="connected to server ";
20 |
21 | var msg = {Type:"status", Msg:{ Name: "unknown", Type: "text", Desc: location.hostname, State: "online"}};
22 | ws.send(JSON.stringify(msg));
23 |
24 |
25 | };
26 |
27 | ws.onerror = function(event) {
28 | };
29 |
30 | ws.onmessage = function(event) {
31 |
32 |
33 | var packet = JSON.parse(event.data);
34 |
35 | if(packet.Type == "control"){
36 |
37 |
38 | if(packet.Msg.Type == "id"){
39 | localid = packet.Msg.Value;
40 | }
41 |
42 |
43 | if(packet.Msg.Type == "peerlist" ){
44 | var peercnt = packet.Msg.Value.length;
45 | for(cnt=0; cnt < peercnt; ++cnt){
46 | var peername = packet.Msg.Value[cnt];
47 | console.log("creating connection for "+peername);
48 |
49 | var peer = new MyPeer( peername, ws );
50 | peers[cnt]={sessionid: peername, peer: peer};
51 | }
52 | var tableelt = document.getElementById("videotable");
53 | var pcnt=0;
54 | for (var irow=0; irow<2; irow++) {
55 | var row = tableelt.insertRow(0);
56 | for (var icol=0; icol<2; icol++) {
57 | var vidiv = document.createElement("div");
58 | vidiv.id = peers[pcnt].sessionid;
59 | vidiv.innerHTML = vidiv.id+'
';
60 | vidiv.className = 'vidiv';
61 | ++pcnt;
62 | row.insertCell(0).appendChild(vidiv);
63 | }
64 | }
65 |
66 |
67 | }
68 | if(packet.Msg.Type == "answer"){
69 |
70 | for(var z=0; zClosing connection to security server";
91 | console.log("Websocket closed")
92 |
93 | };
94 |
95 | }; // end of init
96 |
97 | function error(err) { console.log("crap")}
98 |
99 |
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/mycam/README.md:
--------------------------------------------------------------------------------
1 | # RTSPtoWebRTC
2 |
3 | this sample usage Pion WebRTC https://github.com/pions/webrtc stream RTSP camera to browser
4 |
5 | 1) go get github.com/deepch/RTSPtoWebRTC
6 | 2) cd to github.com/deepch/RTSPtoWebRTC
7 | 3) go run *.go
8 | 4) open browser http://127.0.0.1:8080
9 |
10 | you can edit url := "rtsp://admin:123456@171.25.232.42:1554/mpeg4cif" to any you stream
11 |
--------------------------------------------------------------------------------
/mycam/fifo.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "encoding/json"
6 | "fmt"
7 | "log"
8 | "os"
9 | "syscall"
10 | )
11 |
12 | var pipeFile = "/tmp/wspipe.tmp"
13 |
14 | func fifo() {
15 |
16 | os.Remove(pipeFile)
17 | err := syscall.Mkfifo(pipeFile, 0666)
18 | if err != nil {
19 | log.Fatal("Make named pipe file error:", err)
20 | }
21 |
22 | go writeMsg()
23 | defer func() {
24 | fmt.Printf("closing fifo")
25 | }()
26 | fmt.Println("open a named pipe file for read.")
27 | file, err := os.OpenFile(pipeFile, os.O_CREATE|os.O_RDWR, os.ModeNamedPipe)
28 | if err != nil {
29 | log.Fatal("Open named pipe file error:", err)
30 | }
31 |
32 | reader := bufio.NewReader(file)
33 |
34 | for {
35 | line, err := reader.ReadBytes('\n')
36 | if err != nil {
37 | fmt.Println("Error Named Pipe")
38 |
39 | continue
40 | }
41 |
42 | fmt.Printf("Received from pipe: %s", line)
43 | raw := new(rawmsg)
44 | err = json.Unmarshal(line, raw)
45 | if err != nil {
46 | fmt.Printf("failed to decode motion\n")
47 |
48 | }
49 | if raw.Value.(string) == "on" {
50 | fmt.Println("starting recorder")
51 | // fmt.Printf("starting record: %s\n", time.Now().Format(time.RFC3339Nano))
52 | go record()
53 | }
54 | if raw.Value.(string) == "off" {
55 | fmt.Println("sending record cancel")
56 | cancelRecordChan <- true
57 | }
58 |
59 | msg := new(message)
60 | msg.Type = "motion"
61 | msg.Msg = line
62 | // fmt.Println("sending msg to writemsg")
63 | messageChan <- *msg
64 | // fmt.Println("sent msg to writemsg")
65 |
66 | }
67 | }
68 |
69 | func writeMsg() {
70 |
71 | defer func() {
72 | fmt.Printf("closing writeMsg\n")
73 | }()
74 | for {
75 | select {
76 |
77 | // drain the channel
78 | case msg := <-messageChan:
79 | if c != nil {
80 | if online {
81 | fmt.Println("sending msg to server")
82 |
83 | err := c.WriteJSON(msg)
84 | fmt.Println("sent msg to server")
85 |
86 | if err != nil {
87 | fmt.Printf("Error while sending: %s ", err.Error())
88 | }
89 | }
90 | }
91 | case <-cancelwriteChan:
92 | return
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/mycam/http.go.txt:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "net/http"
7 |
8 | "encoding/base64"
9 |
10 | "github.com/gorilla/mux"
11 | "github.com/pions/webrtc"
12 | "github.com/pions/webrtc/pkg/ice"
13 | "github.com/pions/webrtc/pkg/media"
14 | uuid "github.com/satori/go.uuid"
15 | )
16 |
17 | // DataChanelTest sample data channel
18 | var sampleChannels = make(map[uuid.UUID]chan<- media.RTCSample)
19 | var cancelchan = make(chan bool)
20 |
21 | // start the http server
22 | func StartHTTPServer() {
23 | r := mux.NewRouter()
24 | r.HandleFunc("/receive", HTTPHome)
25 | r.PathPrefix("/").Handler(http.StripPrefix("/", http.FileServer(http.Dir("static/"))))
26 |
27 | go func() {
28 | err := http.ListenAndServe(":8181", r)
29 | if err != nil {
30 | }
31 | }()
32 | select {
33 | case <-cancelchan:
34 | return
35 | }
36 | }
37 | func HTTPHome(w http.ResponseWriter, r *http.Request) {
38 | w.Header().Set("Access-Control-Allow-Origin", "*")
39 | data := r.FormValue("data")
40 | sd, err := base64.StdEncoding.DecodeString(data)
41 | if err != nil {
42 | log.Println(err)
43 | return
44 | }
45 | var u1 uuid.UUID
46 |
47 | webrtc.RegisterDefaultCodecs()
48 | peerConnection, err := webrtc.New(webrtc.RTCConfiguration{
49 | IceServers: []webrtc.RTCIceServer{
50 | {
51 | URLs: []string{"stun:stun.l.google.com:19302"},
52 | },
53 | },
54 | })
55 | if err != nil {
56 | panic(err)
57 | }
58 |
59 | peerConnection.OnICEConnectionStateChange(func(connectionState ice.ConnectionState) {
60 |
61 | fmt.Printf("Connection State has changed %s \n", connectionState.String())
62 | if connectionState.String() == "Disconnected" {
63 | sampleChannels[u1] = nil
64 | fmt.Printf("removed session %s\n", u1.String())
65 | }
66 |
67 | })
68 | vp8Track, err := peerConnection.NewRTCTrack(webrtc.DefaultPayloadTypeH264, "video", "pion2")
69 | if err != nil {
70 | log.Println(err)
71 | return
72 | }
73 | _, err = peerConnection.AddTrack(vp8Track)
74 | if err != nil {
75 | log.Println(err)
76 | return
77 | }
78 | offer := webrtc.RTCSessionDescription{
79 | Type: webrtc.RTCSdpTypeOffer,
80 | Sdp: string(sd),
81 | }
82 | if err := peerConnection.SetRemoteDescription(offer); err != nil {
83 | log.Println(err)
84 | return
85 | }
86 | answer, err := peerConnection.CreateAnswer(nil)
87 | if err != nil {
88 | log.Println(err)
89 | return
90 | }
91 |
92 | w.Write([]byte(base64.StdEncoding.EncodeToString([]byte(answer.Sdp))))
93 | u1 = uuid.Must(uuid.NewV4())
94 | sampleChannels[u1] = vp8Track.Samples
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/mycam/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "flag"
6 | "fmt"
7 | "log"
8 | "net/url"
9 | "os/exec"
10 | "time"
11 |
12 | "github.com/gorilla/websocket"
13 | )
14 |
15 | type device struct {
16 | Type string
17 | Name string
18 | Desc string
19 | State string
20 | }
21 |
22 | type rawmsg struct {
23 | Type string
24 | Value interface{}
25 | }
26 |
27 | type message struct {
28 | Type string
29 | Targets []string
30 | Msg json.RawMessage
31 | }
32 |
33 | func check(e error) {
34 | if e != nil {
35 | fmt.Println(e.Error())
36 | }
37 | }
38 |
39 | var messageChan = make(chan message)
40 | var cancelwriteChan = make(chan bool)
41 | var cancelholdChan = make(chan bool)
42 | var cancelChan = make(chan bool)
43 | var cancelRecordChan = make(chan bool)
44 | var c *websocket.Conn
45 | var err error
46 | var devicedesc, devicename *string
47 | var online bool
48 |
49 | func main() {
50 |
51 | devicename = flag.String("name", "testcam", " -name The name of this camera")
52 | devicedesc = flag.String("desc", "test camera", " -desc The description of this camera")
53 | wsServer := flag.String("server", "192.168.50.13:8080", "-server The webserver to contact")
54 |
55 | flag.Parse()
56 |
57 | // sig := make(chan os.Signal, 1)
58 | // signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
59 |
60 | // go StartHTTPServer()
61 | go rtsp2webrtc()
62 | go fifo()
63 | u := url.URL{Scheme: "ws", Host: *wsServer, Path: "/ws"}
64 |
65 | Start:
66 | online = false
67 | log.Printf("connecting to %s", u.String())
68 |
69 | c, _, err = websocket.DefaultDialer.Dial(u.String(), nil)
70 | if err != nil {
71 | online = false
72 | time.Sleep(10 * time.Second)
73 | goto Start
74 | }
75 | dv := device{Type: "camera", Name: *devicename, Desc: *devicedesc, State: "online"}
76 | c.SetReadLimit(10000)
77 | dev, _ := json.Marshal(dv)
78 | status := message{Type: "status", Msg: dev}
79 | err = c.WriteJSON(&status)
80 |
81 | if err != nil {
82 | fmt.Println("I am sorry but I cannot send to the server.")
83 | online = false
84 | time.Sleep(10 * time.Second)
85 | goto Start
86 |
87 | }
88 | fmt.Println("Sent online status")
89 | online = true
90 | msg := new(message)
91 |
92 | for {
93 | err = c.ReadJSON(&msg)
94 | if err != nil {
95 | online = false
96 | fmt.Println("I am sorry but I cannot receive from the server.")
97 | time.Sleep(10 * time.Second)
98 | goto Start
99 | }
100 |
101 | switch msg.Type {
102 |
103 | case "control":
104 | var ctrl = new(rawmsg)
105 |
106 | err := json.Unmarshal(msg.Msg, &ctrl)
107 | if err != nil {
108 | fmt.Printf("failed to decode control: %s\n", err.Error())
109 |
110 | }
111 |
112 | switch ctrl.Type {
113 | case "nightmode":
114 | cmd := exec.Command("/usr/scripts/nightmode.sh", ctrl.Value.(string))
115 | cmd.Run()
116 |
117 | case "offer":
118 | fmt.Println("received offer")
119 | session := ctrl.Value.(map[string]interface{})
120 | sdp := session["data"].(string)
121 | from := session["from"].(string)
122 | go Rtc(sdp, from)
123 | case "oscommand":
124 |
125 | oscmd := ctrl.Value.(map[string]interface{})
126 | command := oscmd["cmd"].(string)
127 | args := oscmd["args"].([]interface{})
128 | argstr := make([]string, len(args))
129 | for k, v := range args {
130 | argstr[k] = v.(string)
131 | }
132 |
133 | cmd := exec.Command(command, argstr...)
134 | fmt.Printf("Command: %v", cmd)
135 | err := cmd.Start()
136 | log.Printf("Command finished with error: %v", err)
137 | }
138 | }
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/mycam/recorder.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "encoding/gob"
6 | "fmt"
7 | "log"
8 | "net"
9 | "os"
10 | "time"
11 |
12 | "github.com/pions/webrtc/pkg/media"
13 | "github.com/pkg/errors"
14 | uuid "github.com/satori/go.uuid"
15 | )
16 |
17 | func record() error {
18 | var u1 uuid.UUID
19 | var address = "192.168.50.13:61000"
20 | var path = *devicename + "/" + *devicename + time.Now().Format(time.RFC3339) + ".gob\n"
21 | fmt.Printf("Openning: %s\n", time.Now().Format(time.RFC3339Nano))
22 | rw, err := open(address)
23 | if err != nil {
24 | return errors.Wrap(err, "Can't open recorder at "+address)
25 |
26 | }
27 |
28 | /*
29 | rw, err := openfile(path)
30 | if err != nil {
31 | fmt.Printf("couldn't open file")
32 | return errors.Wrap(err, "Can't open recorder at "+address)
33 |
34 | }
35 | */
36 | defer func() {
37 |
38 | fmt.Println("Closing recorder")
39 |
40 | }()
41 |
42 | rw.WriteString("OPEN\n")
43 | if err != nil {
44 | return errors.Wrap(err, "Could not send the STRING command ")
45 | }
46 | rw.WriteString(path)
47 | if err != nil {
48 | return errors.Wrap(err, "Could not send the STRING request ")
49 | }
50 |
51 | err = rw.Flush()
52 | if err != nil {
53 | return errors.Wrap(err, "Flush failed.")
54 | }
55 |
56 | // Read the reply.
57 | log.Println("Read the reply.")
58 | response, err := rw.ReadString('\n')
59 | if err != nil {
60 | return errors.Wrap(err, "Client: Failed to read the reply: '"+response+"'")
61 | }
62 |
63 | if response != "continue\n" {
64 |
65 | return errors.Wrap(nil, "STRING request: got a response: "+response)
66 |
67 | }
68 | log.Println("continuing")
69 | rw.WriteString("RECORD\n")
70 | if err != nil {
71 | return errors.Wrap(err, "Could not send the STRING command ")
72 | }
73 | err = rw.Flush()
74 | if err != nil {
75 | return errors.Wrap(err, "Flush failed.")
76 | }
77 |
78 | u1 = uuid.Must(uuid.NewV4())
79 | mychan := make(chan media.RTCSample)
80 | m.RLock()
81 | sampleChannels[u1] = mychan
82 | m.RUnlock()
83 | fmt.Println("added samples recorder channel " + u1.String())
84 |
85 | enc := gob.NewEncoder(rw)
86 | for {
87 | select {
88 | case quit := <-cancelRecordChan:
89 | if quit == true {
90 | fmt.Println("Canceling record")
91 | err = rw.Flush()
92 | if err != nil {
93 | return errors.Wrap(err, "Flush failed.")
94 | }
95 | m.RLock()
96 | delete(sampleChannels, u1)
97 | m.RUnlock()
98 | fmt.Printf("deleted %s \n", u1.String())
99 | return errors.Wrap(nil, "recording canceled")
100 | }
101 | case sample := <-mychan:
102 | enc.Encode(sample)
103 | }
104 | }
105 | }
106 |
107 | // return errors.Wrap(nil, "recording completed")
108 |
109 | func open(addr string) (*bufio.ReadWriter, error) {
110 | // Dial the remote process.
111 | // Note that the local port is chosen on the fly. If the local port
112 | // must be a specific one, use DialTCP() instead.
113 | log.Println("Dial " + addr)
114 | conn, err := net.Dial("tcp", addr)
115 | if err != nil {
116 | return nil, errors.Wrap(err, "Dialing "+addr+" failed")
117 | }
118 | return bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)), nil
119 | }
120 | func openfile(filename string) (*bufio.ReadWriter, error) {
121 | file, err := os.Create(filename)
122 | if err != nil {
123 | fmt.Printf("Could not create file: %s", err.Error())
124 | return nil, err
125 | }
126 | return bufio.NewReadWriter(bufio.NewReader(file), bufio.NewWriter(file)), nil
127 |
128 | }
129 |
--------------------------------------------------------------------------------
/mycam/rtc.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "log"
7 | "sync"
8 |
9 | "github.com/pions/webrtc"
10 | "github.com/pions/webrtc/pkg/ice"
11 | "github.com/pions/webrtc/pkg/media"
12 | uuid "github.com/satori/go.uuid"
13 | )
14 |
15 | var m sync.RWMutex
16 |
17 | // DataChanelTest sample data channel
18 | var sampleChannels = make(map[uuid.UUID]chan<- media.RTCSample)
19 | var cancelchan = make(chan bool)
20 |
21 | func Rtc(sd string, id string) {
22 |
23 | var u1 uuid.UUID
24 |
25 | webrtc.RegisterDefaultCodecs()
26 | peerConnection, err := webrtc.New(webrtc.RTCConfiguration{
27 | IceServers: []webrtc.RTCIceServer{
28 | {
29 | URLs: []string{"stun:stun.l.google.com:19302"},
30 | },
31 | },
32 | })
33 | if err != nil {
34 | panic(err)
35 | }
36 |
37 | peerConnection.OnICEConnectionStateChange(func(connectionState ice.ConnectionState) {
38 |
39 | fmt.Printf("Connection State has changed %s \n", connectionState.String())
40 | if connectionState.String() == "Disconnected" {
41 | delete(sampleChannels, u1)
42 |
43 | fmt.Printf("removed session %s\n", u1.String())
44 | return
45 | }
46 |
47 | })
48 | vp8Track, err := peerConnection.NewRTCTrack(webrtc.DefaultPayloadTypeH264, "video", "pion2")
49 | if err != nil {
50 | log.Println(err)
51 | return
52 | }
53 | _, err = peerConnection.AddTrack(vp8Track)
54 | if err != nil {
55 | log.Println(err)
56 | return
57 | }
58 |
59 | offer := webrtc.RTCSessionDescription{
60 | Type: webrtc.RTCSdpTypeOffer,
61 | Sdp: sd,
62 | }
63 |
64 | if err := peerConnection.SetRemoteDescription(offer); err != nil {
65 | log.Println(err)
66 | return
67 | }
68 | answer, err := peerConnection.CreateAnswer(nil)
69 | if err != nil {
70 | log.Println(err)
71 | return
72 | }
73 |
74 | // fmt.Printf("answer: %v", answer)
75 | // send the answer back
76 | msg := new(message)
77 | msg.Type = "control"
78 | msg.Targets = append(msg.Targets, id)
79 | rwmsg := rawmsg{Type: "answer", Value: rawmsg{Type: *devicename, Value: answer}}
80 | msg.Msg, err = json.Marshal(rwmsg)
81 | if err != nil {
82 | fmt.Printf("Could not marshal answer: %s", err.Error())
83 | return
84 | }
85 | messageChan <- *msg
86 |
87 | /*
88 | mssg, err := json.Marshal(msg)
89 | if err != nil {
90 | fmt.Printf("mssg: %s", err.Error())
91 | }
92 |
93 | c.WriteMessage(1, []byte(mssg))
94 | */
95 | u1 = uuid.Must(uuid.NewV4())
96 | m.RLock()
97 | sampleChannels[u1] = vp8Track.Samples
98 | m.RUnlock()
99 | fmt.Println("added samples channel " + u1.String())
100 | <-cancelChan
101 | fmt.Println("rtc completed")
102 | }
103 |
--------------------------------------------------------------------------------
/mycam/rtsp2webrtc.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "time"
7 |
8 | rtsp "github.com/deepch/sample_rtsp"
9 | "github.com/pions/webrtc/pkg/media"
10 | )
11 |
12 | var (
13 | VideoWidth int
14 | VideoHeight int
15 | )
16 |
17 | func rtsp2webrtc() {
18 |
19 | url := "rtsp://localhost:8554/unicast"
20 | sps := []byte{}
21 | pps := []byte{}
22 | fuBuffer := []byte{}
23 | count := 0
24 | Client := rtsp.RtspClientNew()
25 | Client.Debug = false
26 | syncCount := 0
27 | preTS := 0
28 |
29 | defer func() {
30 | // cancelchan <- true
31 | Client.Close()
32 | }()
33 |
34 | writeNALU := func(sync bool, ts int, payload []byte) {
35 |
36 | var sample = media.RTCSample{Data: payload, Samples: uint32(ts - preTS)}
37 | for key := range sampleChannels {
38 |
39 | if sampleChannels[key] != nil && preTS != 0 {
40 | select {
41 | case sampleChannels[key] <- sample:
42 | default:
43 |
44 | }
45 | }
46 | preTS = ts
47 | }
48 | }
49 | handleNALU := func(nalType byte, payload []byte, ts int64) {
50 | if nalType == 7 {
51 | if len(sps) == 0 {
52 | sps = payload
53 | }
54 | // writeNALU(true, int(ts), payload)
55 | } else if nalType == 8 {
56 | if len(pps) == 0 {
57 | pps = payload
58 | }
59 | // writeNALU(true, int(ts), payload)
60 | } else if nalType == 5 {
61 | syncCount++
62 | lastkeys := append([]byte("\000\000\001"+string(sps)+"\000\000\001"+string(pps)+"\000\000\001"), payload...)
63 |
64 | writeNALU(true, int(ts), lastkeys)
65 | } else {
66 | if syncCount > 0 {
67 | writeNALU(false, int(ts), payload)
68 | }
69 | }
70 | }
71 | if err := Client.Open(url); err != nil {
72 | fmt.Println("[RTSP] Error", err)
73 | } else {
74 | for {
75 | select {
76 | case <-Client.Signals:
77 | fmt.Println("Exit signals by rtsp")
78 | sps = []byte{}
79 | pps = []byte{}
80 | fuBuffer = []byte{}
81 | count = 0
82 | Client = rtsp.RtspClientNew()
83 | Client.Debug = false
84 | syncCount = 0
85 | preTS = 0
86 | RETRY1:
87 | if err := Client.Open(url); err != nil {
88 | fmt.Println("[RTSP] Error", err)
89 | time.Sleep(time.Second * 10)
90 | goto RETRY1
91 | }
92 |
93 | case <-cancelchan:
94 | Client.Close()
95 | case data := <-Client.Outgoing:
96 | count += len(data)
97 | //fmt.Println("recive rtp packet size", len(data), "recive all packet size", count)
98 | if data[0] == 36 && data[1] == 0 {
99 | cc := data[4] & 0xF
100 | rtphdr := 12 + cc*4
101 | ts := (int64(data[8]) << 24) + (int64(data[9]) << 16) + (int64(data[10]) << 8) + (int64(data[11]))
102 | packno := (int64(data[6]) << 8) + int64(data[7])
103 | if false {
104 | log.Println("packet num", packno)
105 | }
106 | nalType := data[4+rtphdr] & 0x1F
107 | if nalType >= 1 && nalType <= 23 {
108 | handleNALU(nalType, data[4+rtphdr:], ts)
109 | } else if nalType == 28 {
110 | isStart := data[4+rtphdr+1]&0x80 != 0
111 | isEnd := data[4+rtphdr+1]&0x40 != 0
112 | nalType := data[4+rtphdr+1] & 0x1F
113 | nal := data[4+rtphdr]&0xE0 | data[4+rtphdr+1]&0x1F
114 | if isStart {
115 | fuBuffer = []byte{0}
116 | }
117 | fuBuffer = append(fuBuffer, data[4+rtphdr+2:]...)
118 | if isEnd {
119 | fuBuffer[0] = nal
120 | handleNALU(nalType, fuBuffer, ts)
121 | }
122 | }
123 | } else if data[0] == 36 && data[1] == 2 {
124 | //cc := data[4] & 0xF
125 | //rtphdr := 12 + cc*4
126 | //payload := data[4+rtphdr+4:]
127 | }
128 | }
129 | }
130 | }
131 | Client.Close()
132 | }
133 |
--------------------------------------------------------------------------------
/mycam/static/demo.css:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/mycam/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Browser base64 Session Description
4 | Golang base64 Session Description:
5 |
6 |
7 |
8 |
63 |
--------------------------------------------------------------------------------
/mycam/webrtc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/machinesworking/myhome/f587956724c2c14a66511f97694a69508b8b5651/mycam/webrtc.png
--------------------------------------------------------------------------------
/pivoice/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2013 IBM Corp.
3 | *
4 | * All rights reserved. This program and the accompanying materials
5 | * are made available under the terms of the Eclipse Public License v1.0
6 | * which accompanies this distribution, and is available at
7 | * http://www.eclipse.org/legal/epl-v10.html
8 | *
9 | * Contributors:
10 | * Seth Hoenig
11 | * Allan Stockdill-Mander
12 | * Mike Robertson
13 | */
14 |
15 | package main
16 |
17 | import (
18 | "encoding/json"
19 | "flag"
20 | "fmt"
21 | "log"
22 | "net/url"
23 | "os/exec"
24 | "time"
25 |
26 | "github.com/gorilla/websocket"
27 | )
28 |
29 | type device struct {
30 | Type string
31 | Name string
32 | Desc string
33 | State string
34 | }
35 |
36 | type rawmsg struct {
37 | Type string
38 | Value interface{}
39 | }
40 |
41 | type message struct {
42 | Type string
43 | Msg json.RawMessage
44 | }
45 |
46 | func check(e error) {
47 | if e != nil {
48 | fmt.Println(e.Error())
49 | }
50 | }
51 |
52 | func main() {
53 | angelos := flag.String("name", "angelos10", " -name The name of this Angelos")
54 | devicedesc := flag.String("desc", "angelos Ten", " -desc The description of this Angelos")
55 |
56 | wsServer := flag.String("server", "192.168.50.13:8080", "-server The webserver to contact")
57 | audioPath := flag.String("apath", "/home/pi/audio", "-apath pathtoaudio")
58 |
59 | flag.Parse()
60 | speech := Speech{Folder: *audioPath, Language: "en"}
61 | // origin := "http://" + *angelos + ".local/"
62 |
63 | // sig := make(chan os.Signal, 1)
64 | // signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
65 | Start:
66 |
67 | log.SetFlags(0)
68 |
69 | // interrupt := make(chan os.Signal, 1)
70 | // signal.Notify(interrupt, os.Interrupt)
71 |
72 | u := url.URL{Scheme: "ws", Host: *wsServer, Path: "/ws"}
73 | log.Printf("connecting to %s", u.String())
74 |
75 | c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
76 | if err != nil {
77 |
78 | speech.Speak("I am sorry but I cannot connect to the server.")
79 |
80 | // log. Fatal(err)
81 | log.Printf("dial: %s", err.Error())
82 |
83 | time.Sleep(10 * time.Second)
84 | goto Start
85 |
86 | }
87 | defer c.Close()
88 |
89 | //done := make(chan struct{})
90 |
91 | dv := device{Type: "hmi", Name: *angelos, Desc: *devicedesc, State: "online"}
92 | dev, _ := json.Marshal(dv)
93 | status := message{Type: "status", Msg: dev}
94 | err = c.WriteJSON(&status)
95 |
96 | // err = websocket.JSON.Send(ws, &status)
97 | if err != nil {
98 |
99 | speech.Speak("I am sorry but I cannot send to the server.")
100 |
101 | time.Sleep(10 * time.Second)
102 |
103 | goto Start
104 |
105 | }
106 |
107 | msg := new(message)
108 | for {
109 | log.Println("waiting for message from server")
110 | err = c.ReadJSON(&msg)
111 | // fmt.Printf("%v", msg)
112 | // err = websocket.JSON.Receive(ws, &rcvbuf)
113 | if err != nil {
114 |
115 | speech.Speak("I am sorry but I cannot receive from the server.")
116 |
117 | time.Sleep(10 * time.Second)
118 |
119 | goto Start
120 | }
121 | switch msg.Type {
122 | case "control":
123 | var ctrl = new(rawmsg)
124 |
125 | err := json.Unmarshal(msg.Msg, &ctrl)
126 | if err != nil {
127 | fmt.Printf("failed to decode control: %s\n", err.Error())
128 |
129 | }
130 |
131 | switch ctrl.Type {
132 |
133 | case "sound":
134 | if ctrl.Value.(string) == "off" {
135 | speech.Speak("Sound will be set to " + ctrl.Value.(string))
136 | }
137 | cmd := exec.Command("amixer", "set", "'Master'", ctrl.Value.(string))
138 | cmd.Run()
139 | if ctrl.Value == "on" {
140 | speech.Speak("Sound will be set to " + ctrl.Value.(string))
141 | }
142 |
143 | case "volume":
144 | cmd := exec.Command("amixer", "set", "'Master'", ctrl.Value.(string))
145 | cmd.Run()
146 | speech.Speak("Volume has been set to " + ctrl.Value.(string))
147 |
148 | case "notification":
149 |
150 | err = speech.Speak(ctrl.Value.(string))
151 |
152 | if err != nil {
153 | fmt.Printf("speak returned error: %s", err.Error())
154 | continue
155 | }
156 |
157 | }
158 | }
159 |
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/pivoice/picgotts.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "net/http"
7 | "net/url"
8 | "os"
9 | "os/exec"
10 | )
11 |
12 | /**
13 | * Required:
14 | * - mplayer
15 | *
16 | * Use:
17 | *
18 | * speech := htgotts.Speech{Folder: "audio", Language: "en"}
19 | */
20 |
21 | // Speech struct
22 | type Speech struct {
23 | Folder string
24 | Language string
25 | }
26 |
27 | // Speak downloads speech and plays it using aplay
28 | func (speech *Speech) Speak(text string) error {
29 |
30 | fileName := speech.Folder + "/" + text + ".wav"
31 |
32 | var err error
33 | if err = speech.createFolderIfNotExists(speech.Folder); err != nil {
34 | return err
35 | }
36 | if err = speech.createIfNotExists(fileName, text); err != nil {
37 | return err
38 | }
39 |
40 | return speech.play(fileName)
41 | }
42 |
43 | /**
44 | * Create the folder if does not exists.
45 | */
46 | func (speech *Speech) createFolderIfNotExists(folder string) error {
47 | dir, err := os.Open(folder)
48 | if os.IsNotExist(err) {
49 | fmt.Printf("creating directory\n")
50 |
51 | return os.MkdirAll(folder, 0700)
52 | }
53 |
54 | dir.Close()
55 | // fmt.Printf("directory exists\n")
56 |
57 | return nil
58 | }
59 |
60 | func (speech *Speech) createIfNotExists(fileName string, text string) error {
61 |
62 | if _, err := os.Stat(fileName); os.IsNotExist(err) {
63 | // fmt.Printf("Creating new voice file: %s", fileName)
64 | pico2wave := exec.Command("pico2wave", "-w", fileName, text)
65 | pico2wave.Run()
66 | }
67 |
68 | // fmt.Printf("Voice file exists: %s", fileName)
69 |
70 | return nil
71 | }
72 |
73 | /**
74 | * Download the voice file if does not exists.
75 | */
76 | func (speech *Speech) downloadIfNotExists(fileName string, text string) error {
77 | f, err := os.Open(fileName)
78 | if err != nil {
79 | url := fmt.Sprintf("http://translate.google.com/translate_tts?ie=UTF-8&total=1&idx=0&textlen=32&client=tw-ob&q=%s&tl=%s", url.QueryEscape(text), speech.Language)
80 | response, err := http.Get(url)
81 | if err != nil {
82 | return err
83 | }
84 | defer response.Body.Close()
85 |
86 | output, err := os.Create(fileName)
87 | if err != nil {
88 | return err
89 | }
90 |
91 | _, err = io.Copy(output, response.Body)
92 | return err
93 | }
94 |
95 | f.Close()
96 | return nil
97 | }
98 |
99 | /**
100 | * Play voice file.
101 | */
102 | func (speech *Speech) play(fileName string) error {
103 | // fmt.Printf("Playing %s\n", fileName)
104 | aplay := exec.Command("/usr/bin/aplay", fileName)
105 | return aplay.Run()
106 | }
107 |
--------------------------------------------------------------------------------