github.com/aarzilli/tools@v0.0.0-20151123112009-0d27094f75e0/foscam/foscam.go (about)

     1  // Package foscam allows setting and
     2  // clearing alarm mode of such cam via http.
     3  package foscam
     4  
     5  import (
     6  	"encoding/xml"
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"math"
    11  	"net/http"
    12  	"strings"
    13  	"time"
    14  
    15  	_ "net/http/pprof"
    16  
    17  	"github.com/pbberlin/tools/appengine/util_appengine"
    18  	"github.com/pbberlin/tools/net/http/htmlfrag"
    19  	"github.com/pbberlin/tools/net/http/loghttp"
    20  	"github.com/pbberlin/tools/net/http/tplx"
    21  	"github.com/pbberlin/tools/stringspb"
    22  	"github.com/pbberlin/tools/util"
    23  
    24  	"appengine"
    25  	"appengine/urlfetch"
    26  )
    27  
    28  var spf func(format string, a ...interface{}) string = fmt.Sprintf
    29  var wpf func(w io.Writer, format string, a ...interface{}) (int, error) = fmt.Fprintf
    30  
    31  var serveraddress string = `header of ["SERVER_ADDR"]`
    32  var pos_01 int = strings.Index(serveraddress, "192.")
    33  
    34  var dns_router string = "see func init"
    35  var dns_cam string = "see func init"
    36  
    37  const (
    38  	debug               = false
    39  	credentialsExcluded = "&usr=[a...n]&pwd=[pb...05]"
    40  	path_get_alarm      = "/cgi-bin/CGIProxy.fcgi?cmd=getMotionDetectConfig"
    41  	path_set_alarm      = "/cgi-bin/CGIProxy.fcgi?cmd=setMotionDetectConfig"
    42  
    43  	path_snap_config     = "/cgi-bin/CGIProxy.fcgi?cmd=setSnapConfig&snapPicQuality=0&saveLocation=2"
    44  	path_snap_retrieval  = "/cgi-bin/CGIProxy.fcgi?cmd=snapPicture2" + credentials
    45  	path_video_retrieval = "/cgi-bin/CGIStream.cgi?cmd=GetMJStream" + credentials
    46  
    47  	path_get_log = "/cgi-bin/CGIProxy.fcgi?cmd=getLog&count=20&offset=0"
    48  )
    49  
    50  // we have to upper case of fields to really get the values - to annoyed to think why
    51  type CGI_Result struct {
    52  	Result          string `xml:"result"`
    53  	IsEnable        string `xml:"isEnable"`
    54  	Linkage         string `xml:"linkage"`
    55  	SnapInterval    string `xml:"snapInterval"`
    56  	Sensitivity     string `xml:"sensitivity"`
    57  	TriggerInterval string `xml:"triggerInterval"`
    58  	Schedule0       string `xml:"schedule0"`
    59  	Schedule1       string `xml:"schedule1"`
    60  	Schedule2       string `xml:"schedule2"`
    61  	Schedule3       string `xml:"schedule3"`
    62  	Schedule4       string `xml:"schedule4"`
    63  	Schedule5       string `xml:"schedule5"`
    64  	Schedule6       string `xml:"schedule6"`
    65  	Area0           string `xml:"area0"`
    66  	Area1           string `xml:"area1"`
    67  	Area2           string `xml:"area2"`
    68  	Area3           string `xml:"area3"`
    69  	Area4           string `xml:"area4"`
    70  	Area5           string `xml:"area5"`
    71  	Area6           string `xml:"area6"`
    72  	Area7           string `xml:"area7"`
    73  	Area8           string `xml:"area8"`
    74  	Area9           string `xml:"area9"`
    75  	Log0            string `xml:"log0"`
    76  	Log1            string `xml:"log1"`
    77  	Log2            string `xml:"log2"`
    78  	Log3            string `xml:"log3"`
    79  	Log4            string `xml:"log4"`
    80  	Log5            string `xml:"log5"`
    81  	Log6            string `xml:"log6"`
    82  	Log7            string `xml:"log7"`
    83  	Log8            string `xml:"log8"`
    84  	Log9            string `xml:"log9"`
    85  	// XMLName  xml.Name `xml:"account"`
    86  }
    87  
    88  func urlParamTS() string {
    89  	ts := time.Now().UnixNano()
    90  	return spf("%v", ts)
    91  }
    92  
    93  func makeRequest(w http.ResponseWriter, r *http.Request, path string) CGI_Result {
    94  
    95  	c := appengine.NewContext(r)
    96  	client := urlfetch.Client(c)
    97  
    98  	url_exe := spf(`http://%s%s%s&ts=%s`, dns_cam, path, credentials, urlParamTS())
    99  	url_dis := spf(`http://%s%s&ts=%s`, dns_cam, path, urlParamTS())
   100  	wpf(w, "<div style='font-size:10px; line-height:11px;'>requesting %v<br></div>\n", url_dis)
   101  	resp1, err := client.Get(url_exe)
   102  	loghttp.E(w, r, err, false)
   103  
   104  	bcont, err := ioutil.ReadAll(resp1.Body)
   105  	defer resp1.Body.Close()
   106  	loghttp.E(w, r, err, false)
   107  
   108  	cgiRes := CGI_Result{}
   109  	xmlerr := xml.Unmarshal(bcont, &cgiRes)
   110  	loghttp.E(w, r, xmlerr, false)
   111  
   112  	if cgiRes.Result != "0" {
   113  		wpf(w, "<b>RESPONSE shows bad mood:</b><br>\n")
   114  		psXml := stringspb.IndentedDump(cgiRes)
   115  		dis := strings.Trim(psXml, "{}")
   116  		wpf(w, "<pre style='font-size:10px;line-height:11px;'>%v</pre>", dis)
   117  	}
   118  
   119  	if debug {
   120  		scont := string(bcont)
   121  		wpf(w, "<pre style='font-size:10px;line-height:11px;'>%v</pre>", scont)
   122  	}
   123  
   124  	return cgiRes
   125  
   126  }
   127  
   128  func imageRetrieve(w http.ResponseWriter, r *http.Request) {
   129  
   130  	makeRequest(w, r, path_snap_config)
   131  	wpf(w, "<img src='http://%s%s' width='60%' /><br>", dns_cam, path_snap_retrieval)
   132  
   133  }
   134  
   135  func logRetrieve(w http.ResponseWriter, r *http.Request) {
   136  
   137  	cgiRes := makeRequest(w, r, path_get_log)
   138  
   139  	sl := []string{cgiRes.Log0, cgiRes.Log1, cgiRes.Log2, cgiRes.Log3, cgiRes.Log4,
   140  		cgiRes.Log5, cgiRes.Log6, cgiRes.Log7, cgiRes.Log8, cgiRes.Log9}
   141  
   142  	for _, v := range sl {
   143  		sl1 := strings.Split(v, "%2B")
   144  		if len(sl1) < 4 {
   145  			continue
   146  		}
   147  
   148  		// 		 time+user+ip+logID
   149  		unixTS := sl1[0]
   150  		usr := sl1[1]
   151  		ip := sl1[2]
   152  		eventId := sl1[3]
   153  		eventDesc := ""
   154  		switch eventId {
   155  		case "0":
   156  			eventDesc = "Power On"
   157  		case "1":
   158  			eventDesc = "Motion Alarm"
   159  		case "3":
   160  			eventDesc = "Login"
   161  		case "4":
   162  			eventDesc = "Logout"
   163  		case "5":
   164  			eventDesc = "Offline"
   165  		default:
   166  			eventDesc = "unkown event id: " + eventId
   167  		}
   168  		_, _, _ = eventDesc, usr, ip
   169  
   170  		ts := util.TimeFromUnix(unixTS)
   171  		tsf := ts.Format("2.1.2006 15:04:05")
   172  
   173  		tn := time.Now()
   174  		since := tn.Sub(ts)
   175  		iHours := int(math.Floor(since.Hours()))
   176  		iMinutes := util.Round(since.Minutes()) - iHours*60
   177  
   178  		if eventId == "1" {
   179  			wpf(w, "Last Alarm <b>%3vhrs %2vmin</b> ago (%v)<br>\n", iHours, iMinutes, tsf)
   180  			break
   181  		}
   182  	}
   183  
   184  }
   185  
   186  func foscamStatus(w http.ResponseWriter, r *http.Request, m map[string]interface{}) {
   187  
   188  	htmlfrag.SetNocacheHeaders(w)
   189  	w.Header().Set("Content-Type", "text/html; charset=utf-8")
   190  
   191  	logRetrieve(w, r)
   192  
   193  	cgiRes := makeRequest(w, r, path_get_alarm)
   194  
   195  	psXml := stringspb.IndentedDump(cgiRes)
   196  	dis := strings.Trim(psXml, "{}")
   197  	dis = strings.Replace(dis, "\t", "", -1)
   198  	dis = strings.Replace(dis, " ", "", -1)
   199  	dis = strings.Replace(dis, "\"", "", -1)
   200  	dis = strings.Replace(dis, "\n", " ", -1)
   201  	dis = strings.Replace(dis, "Area0", "\nArea0", -1)
   202  	dis = strings.Replace(dis, "Schedule0", "\nSchedule0", -1)
   203  	dis = strings.Replace(dis, "Log0", "\nLog0", -1)
   204  	wpf(w, "<pre style='font-size:10px;line-height:11px;'>%v</pre>", dis)
   205  
   206  	if cgiRes.IsEnable == "0" {
   207  		wpf(w, "Status <b>DISabled</b><br><br>\n")
   208  	} else {
   209  		wpf(w, "Status <b>ENabled</b><br><br>\n")
   210  	}
   211  
   212  	imageRetrieve(w, r)
   213  
   214  }
   215  
   216  func foscamToggle(w http.ResponseWriter, r *http.Request, m map[string]interface{}) {
   217  
   218  	htmlfrag.SetNocacheHeaders(w)
   219  	w.Header().Set("Content-Type", "text/html; charset=utf-8")
   220  
   221  	ssecs := r.FormValue("sleep")
   222  	if ssecs != "" {
   223  		secs := util.Stoi(ssecs)
   224  		wpf(w, "sleeping %v secs ... <br><br>\n", secs)
   225  		time.Sleep(time.Duration(secs) * time.Second)
   226  	}
   227  
   228  	prevStat := makeRequest(w, r, path_get_alarm)
   229  
   230  	wpf(w, "||%s||<br>\n", prevStat.IsEnable)
   231  	if strings.TrimSpace(prevStat.IsEnable) == "0" {
   232  		prevStat.IsEnable = "1"
   233  	} else {
   234  		prevStat.IsEnable = "0"
   235  	}
   236  	prevStat.Area0 = "255"
   237  	prevStat.Area1 = "255"
   238  	prevStat.Area2 = "255"
   239  	prevStat.Area3 = "255"
   240  	prevStat.Area4 = "255"
   241  	prevStat.Area5 = "255"
   242  	prevStat.Area6 = "255"
   243  	prevStat.Area7 = "255"
   244  	prevStat.Area8 = "255"
   245  	prevStat.Area9 = "255"
   246  
   247  	// ugly: XML dump to query string
   248  	s2 := spf("%+v", prevStat)
   249  	s2 = strings.Trim(s2, "{}")
   250  	s2 = strings.Replace(s2, ":", "=", -1)
   251  	s2 = strings.Replace(s2, " ", "&", -1)
   252  
   253  	// even worse: we have to lower the case again
   254  	pairs := strings.Split(s2, "&")
   255  	recombined := ""
   256  	for i, v := range pairs {
   257  		fchar := v[:1]
   258  		fchar = strings.ToLower(fchar)
   259  		recombined += fchar + v[1:]
   260  		if i < len(pairs)-1 {
   261  			recombined += "&"
   262  		}
   263  	}
   264  
   265  	wpf(w, "<pre>")
   266  	// disS2 := stringspb.Breaker(s2, 50)
   267  	// for _, v := range disS2 {
   268  	// 	wpf(w, "%v\n", v)
   269  	// }
   270  	disRecombined := stringspb.Breaker(recombined, 50)
   271  	for _, v := range disRecombined {
   272  		wpf(w, "%v\n", v)
   273  	}
   274  	wpf(w, "</pre>")
   275  	// wpf(w, "<pre>%v</pre>\n", recombined)
   276  
   277  	toggleRes := makeRequest(w, r, path_set_alarm+"&"+recombined)
   278  	if toggleRes.Result == "0" {
   279  		wpf(w, "<br>end foscam toggle - success<br>\n")
   280  		if prevStat.IsEnable == "0" {
   281  			wpf(w, "<b>DISabled</b><br>\n")
   282  		} else {
   283  			wpf(w, "<b>ENabled</b><br>\n")
   284  		}
   285  	}
   286  
   287  }
   288  
   289  func foscamWatch(w http.ResponseWriter, r *http.Request, m map[string]interface{}) {
   290  
   291  	htmlfrag.SetNocacheHeaders(w)
   292  	w.Header().Set("Content-Type", "text/html; charset=utf-8")
   293  
   294  	wpf(w, tplx.ExecTplHelper(tplx.Head, map[string]interface{}{"HtmlTitle": "Foscam live watch"}))
   295  
   296  	/*
   297  
   298  		There is no way to access a real video stream.
   299  		Thus we use this suggestion: http://foscam.us/forum/post43654.html#p43654
   300  
   301  	*/
   302  	str := `<img 
   303  	width='640' 
   304  	src="http://` + dns_cam + `/CGIProxy.fcgi?cmd=snapPicture2&usr=visitor&pwd=visitor&t=" 
   305  	onload='setTimeout(function() {src = src.substring(0, (src.lastIndexOf("t=")+2))+(new Date()).getTime()}, 1000)' 
   306  	onerror='setTimeout(function() {src = src.substring(0, (src.lastIndexOf("t=")+2))+(new Date()).getTime()}, 5000)' 
   307  	alt='' />`
   308  	w.Write([]byte(str))
   309  
   310  	w.Write([]byte(tplx.Foot))
   311  
   312  }
   313  
   314  func init() {
   315  
   316  	if util_appengine.IsLocalEnviron() {
   317  		dns_router = "192.168.1.1"
   318  		dns_cam = "192.168.1.4:8081"
   319  	} else {
   320  		dns_router = "ds7934.myfoscam.org"
   321  		dns_cam = "ds7934.myfoscam.org:8081"
   322  	}
   323  
   324  	http.HandleFunc("/foscam-status", loghttp.Adapter(foscamStatus))
   325  	http.HandleFunc("/foscam-toggle", loghttp.Adapter(foscamToggle))
   326  	http.HandleFunc("/foscam-watch", loghttp.Adapter(foscamWatch))
   327  
   328  }