Пример #1
0
// logs is the logic implementation of the Logs page.
func logs(p *page.Params) {
	c := p.AppCtx

	// First get devices
	var devices []*ds.Device
	if devices, p.Err = cache.GetDevListForAccKey(c, p.Account.GetKey(c)); p.Err != nil {
		return
	}
	p.Custom["Devices"] = devices

	fv := p.Request.FormValue

	p.Custom["Before"] = fv("before")
	p.Custom["After"] = fv("after")
	p.Custom["SearchLoc"] = fv("loc")

	if fv("devID") == "" {
		// No device chosen yet
		return
	}

	var err error

	var devID int64
	if devID, err = strconv.ParseInt(string(fv("devID")), 10, 64); err != nil {
		p.ErrorMsg = "Invalid Device! Please select a Device from the list below."
		return
	}
	// Check if device is owned by the user:
	var dev *ds.Device
	for _, d := range devices {
		if d.KeyID == devID {
			dev = d
			p.Custom["Device"] = d
			break
		}
	}

	if dev == nil {
		p.ErrorMsg = "You do not have access to the specified Device! Please select a Device from the list below."
		return
	}

	// Parse filters:
	var before time.Time
	if fv("before") != "" {
		if before, err = p.ParseTime(timeLayout, strings.TrimSpace(fv("before"))); err != nil {
			p.ErrorMsg = template.HTML(`Invalid <span class="highlight">Before</span>!`)
			return
		}
		// Add 1 second to the parsed time because fraction of a second is not parsed but exists,
		// so this new time will also include records which has the same time up to the second part and has millisecond part too.
		before = before.Add(time.Second)
	}
	var after time.Time
	if fv("after") != "" {
		if after, err = p.ParseTime(timeLayout, strings.TrimSpace(fv("after"))); err != nil {
			p.ErrorMsg = template.HTML(`Invalid <span class="highlight">After</span>!`)
			return
		}
	}
	var searchLoc appengine.GeoPoint
	areaCode := int64(-1)
	if dev.Indexed() && fv("loc") != "" {
		// GPS coordinates; lat must be in range -90..90, lng must be in range -180..180
		baseErr := template.HTML(`Invalid <span class="highlight">Location</span>!`)

		var coords = strings.Split(strings.TrimSpace(fv("loc")), ",")
		if len(coords) != 2 {
			p.ErrorMsg = baseErr
			return
		}
		searchLoc.Lat, err = strconv.ParseFloat(coords[0], 64)
		if err != nil {
			p.ErrorMsg = baseErr
			return
		}
		searchLoc.Lng, err = strconv.ParseFloat(coords[1], 64)
		if err != nil {
			p.ErrorMsg = baseErr
			return
		}
		if !searchLoc.Valid() {
			p.ErrorMsg = template.HTML(`Invalid <span class="highlight">Location</span> specified by latitude and longitude! Valid range: [-90, 90] latitude and [-180, 180] longitude`)
			return
		}
		areaCode = AreaCodeForGeoPt(dev.AreaSize, searchLoc.Lat, searchLoc.Lng)
	}
	var page int

	cursorsString := fv("cursors")
	var cursors = strings.Split(cursorsString, ";")[1:] // Split always returns at least 1 element (and we use semicolon separator before cursors)

	// Form values
	if fv("page") == "" {
		page = 1
	} else {
		page, err = strconv.Atoi(fv("page"))
		if err != nil || page < 1 {
			page = 1
		}
		if page > len(cursors) { // If page is provided, so are (should be) the cursors
			page = len(cursors)
		}
	}

	switch {
	case fv("submitFirstPage") != "":
		page = 1
	case fv("submitPrevPage") != "":
		if page > 1 {
			page--
		}
	case fv("submitNextPage") != "":
		page++
	}

	pageSize := p.Account.GetLogsPageSize()

	if ps := fv("pageSize"); ps != "" && ps != strconv.Itoa(pageSize) {
		// Page size has been changed (on Settings page), drop cursors, render page 1
		page = 1
		cursorsString = ""
		cursors = make([]string, 0, 1)
	}

	// 'ts all good, proceed with the query:
	q := datastore.NewQuery(ds.ENameGPS).Filter(ds.PNameDevKeyID+"=", devID)
	if !before.IsZero() {
		q = q.Filter(ds.PNameCreated+"<", before)
	}
	if !after.IsZero() {
		q = q.Filter(ds.PNameCreated+">", after)
	}
	if areaCode >= 0 {
		q = q.Filter(ds.PNameAreaCodes+"=", areaCode)
	}
	q = q.Order("-" + ds.PNameCreated).Limit(pageSize)

	var records = make([]*ds.GPS, 0, pageSize)

	// If there is a cursor, set it.
	// Page - cursor index mapping:     cursors[page-2]
	//     1st page: no cursor, 2nd page: cursors[0], 3nd page: cursors[1], ...
	if page > 1 && page <= len(cursors)+1 {
		var cursor datastore.Cursor
		if cursor, p.Err = datastore.DecodeCursor(cursors[page-2]); p.Err != nil {
			return
		}
		q = q.Start(cursor)
	}

	// Iterate over the results:
	t := q.Run(c)
	for {
		r := new(ds.GPS)
		_, err := t.Next(r)
		if err == datastore.Done {
			break
		}
		if err != nil {
			// Datastore error
			p.Err = err
			return
		}
		records = append(records, r)
		r.Dd = -1 // For now, will be set if applicable
		if r.Track() {
			// Check the previous (in time) record and calculate distance.
			// If previous is not a Track, check the one before that etc.
			for i := len(records) - 2; i >= 0; i-- {
				if prev := records[i]; prev.Track() {
					prev.Dd = Distance(r.GeoPoint.Lat, r.GeoPoint.Lng, prev.GeoPoint.Lat, prev.GeoPoint.Lng)
					prev.Dt = prev.Created.Sub(r.Created)
					break
				}
			}
		}
	}

	if len(records) == 0 {
		// End of list reached, disable Next page button:
		p.Custom["EndOfList"] = true
	}

	if page == 1 || page > len(cursors) {
		// Get updated cursor and store it for next page:
		var cursor datastore.Cursor
		if cursor, p.Err = t.Cursor(); p.Err != nil {
			return
		}
		cursorString := cursor.String()
		if page == 1 {
			// If new records were inserted, they appear on the first page in which case
			// the cursor for the 2nd page changes (and all other cursors will change).
			// In this case drop all the cursors:
			if len(cursors) > 0 && cursors[0] != cursorString {
				cursorsString = ""
				cursors = make([]string, 0, 1)
			}
		} else {
			// When end of list is reached, the same cursor will be returned
			if len(records) == 0 && page == len(cursors)+1 && cursors[page-2] == cursorString {
				// Add 1 extra, empty page, but not more.
				if page > 2 && cursors[page-3] == cursorString {
					// An extra, empty page has already been added, do not add more:
					page--
				}
			}
		}

		if page > len(cursors) {
			cursors = append(cursors, cursorString)
			cursorsString += ";" + cursorString
		}
	}

	// Calculate labels: '1'..'9' then 'A'...
	for i, lbl := len(records)-1, '1'; i >= 0; i-- {
		if r := records[i]; r.Track() {
			r.Label = lbl
			if lbl == '9' {
				lbl = 'A' - 1
			}
			lbl++
		}
	}

	p.Custom["CursorList"] = cursors
	p.Custom["Cursors"] = cursorsString

	p.Custom["Page"] = page
	p.Custom["PageSize"] = pageSize
	p.Custom["RecordOffset"] = (page-1)*pageSize + 1
	p.Custom["Records"] = records
	if p.Mobile {
		p.Custom["MapWidth"], p.Custom["MapHeight"] = p.Account.GetMobMapPrevSize()
		p.Custom["MapImgFormat"] = p.Account.GetMobMapImgFormat()
	} else {
		p.Custom["MapWidth"], p.Custom["MapHeight"] = p.Account.GetMapPrevSize()
	}
	p.Custom["APIKey"] = "AIzaSyCEU_tZ1n0-mMg4woGKIfPqdbi0leSKvjg"
	p.Custom["AllMarkers"] = allMarkers(records)

	if len(records) == 0 {
		if page == 1 {
			if before.IsZero() && after.IsZero() && areaCode < 0 {
				p.Custom["PrintNoRecordsForDev"] = true
			} else {
				p.Custom["PrintNoMatchForFilters"] = true
			}
		} else {
			p.Custom["PrintNoMoreRecords"] = true
		}
	}
}
Пример #2
0
// checkAlert checks the specified alert.
func checkAlert(c appengine.Context, a *ds.Alert, accKeyID int64) {
	const alertDurationMin = 5
	const alertDuration = alertDurationMin * time.Minute

	// Get latest car GPS records
	carRecords, err := getDevRecords(c, a.CarDevID)
	if err != nil {
		return
	}

	// Check if car GPS records are received properly:
	if time.Since(carRecords[0].Created) > alertDuration {
		c.Warningf("No car GPS records found in the last %d minutes!", alertDurationMin)
		sendAlert(c, accKeyID, "Car GPS device gone dark!", carGoneDarkAlertMail)
		return
	}

	if a.PersMobDevID == 0 {
		c.Debugf("Car GPS records found in the last 5 minutes. No Personal Mobile device specified.")
		return
	}

	carMoved := devMoved(carRecords)
	if !carMoved {
		// Nothing more to do if car is not moving
		c.Infof("Car is not moving. Ok.")
		return
	}

	c.Infof("Car is moving!")

	// Get latest personal mobile GPS records
	persMobRecords, err := getDevRecords(c, a.PersMobDevID)
	if err != nil {
		return
	}

	// Check if personal mobile GPS records are received properly:
	if time.Since(persMobRecords[0].Created) > alertDuration {
		c.Warningf("No personal mobile GPS records found in the last %d minutes!", alertDurationMin)
		sendAlert(c, accKeyID, "Car is moving without you!", carMovingWithoutYouMail)
		return
	}

	persMobMoved := devMoved(persMobRecords)

	// Do not draw fast conclusion here if personal mobile is not moving,
	// it might be GPS tracking was just turned on and we don't have 2 track records yet
	// or they are not at great distance. But if we don't even have 1 track record,
	// that's hijacking-suspicious:

	if persMobMoved {
		c.Infof("Personal mobile is also moving!")
	} else {
		c.Infof("Personal mobile is NOT moving!")
	}

	var pg1 *ds.GPS
	for _, r := range persMobRecords {
		if r.Track() {
			pg1 = r
			break
		}
	}

	if pg1 == nil || time.Since(pg1.Created) > alertDuration {
		// Car is moving and we don't have recent track record from personal mobile!
		c.Warningf("No personal mobile GPS track record found in the last %d minutes!", alertDurationMin)
		sendAlert(c, accKeyID, "Car is moving without you!", carMovingWithoutYouMail)
		return
	}

	// Check distance:
	// We have a track record for both devices for sure (because both moved which also ensures having at least 2!).
	var cg1, cg2 *ds.GPS
	for _, r := range carRecords {
		if r.Track() {
			if cg1 == nil {
				cg1 = r
			} else if cg2 == nil {
				cg2 = r
				break
			}
		}
	}

	cg1.Dd = logic.Distance(cg2.GeoPoint.Lat, cg2.GeoPoint.Lng, cg1.GeoPoint.Lat, cg1.GeoPoint.Lng) // [m]
	cg1.Dt = cg1.Created.Sub(cg2.Created)                                                           // duration
	cv := float64(cg1.Dd) / cg1.Dt.Seconds()                                                        // Car speed [m/s]
	cpdt := math.Abs(cg1.Created.Sub(pg1.Created).Seconds())                                        // [s]
	dist := logic.Distance(cg1.GeoPoint.Lat, cg1.GeoPoint.Lng, pg1.GeoPoint.Lat, pg1.GeoPoint.Lng)

	c.Debugf("Car movement speed: %.1f km/h", cv*3.6)
	c.Debugf("Delta T between latest Car and PersMob GPS records: %d s", int64(cpdt))
	c.Debugf("Car - PersMob distance: %d m", dist)

	var alertMargin int64 = 500 // [m]
	// Increase alert margin based on the movement speed of the car and the delta time between
	// the last car and personal mobile GPS records.
	// Also if this delta time is greater, accuracy decreases/drops.
	// So also increase alert margin based on delta time: 6 meters for every second.
	// (It is an effect like increasing car speed by 6 m/s = 21.6 km/h.)
	// BE RESTRICTIVE: Only do this correction if personal mobile is also moving!
	// If not, do not let the car get far away (if for example pers mob is not moving,
	// the car could get kilometers away before alert would be sent).
	if persMobMoved {
		alertMargin += int64(cv*cpdt + cpdt*6)
	}
	c.Debugf("Using alert margin distance: %d m", alertMargin)

	if dist > alertMargin {
		c.Warningf("Personal mobile is not moving together with car!")
		sendAlert(c, accKeyID, "Car is moving without you!", carMovingWithoutYouMail)
		return
	}
	c.Infof("They are moving together. Ok.")
}