Exemplo n.º 1
0
func POST__api__device__id(info *RestRequestInfo, sideEffect *RestSideEffects) (map[string]interface{}, RestError) {
	var err error

	device, restErr := getDeviceByIdString(info)
	if device == nil {
		return nil, restErr
	}

	// Check for SDDL doc.  If it doesn't exist, then create it.
	// TODO: should this only be done if the device is reporting?
	doc := device.SDDLDocument()
	if doc == nil {
		// Create SDDL for the device if it doesn't exist.
		// TODO: should this be automatically done by device.SDDLClass()?
		newDoc := sddl.Sys.NewEmptyDocument()
		err := device.SetSDDLDocument(newDoc)
		if err != nil {
			return nil, InternalServerError("Setting new SDDL document").Log()
		}
		doc = newDoc
	}

	// Parse payload
	for fieldName, value := range info.BodyObj {
		switch fieldName {
		case "friendly_name":
			friendlyName, ok := value.(string)
			if !ok {
				continue
			}
			device.SetName(friendlyName)
		case "location_note":
			locationNote, ok := value.(string)
			if !ok {
				continue
			}
			device.SetLocationNote(locationNote)
		case "var_decls":
			sddlJsonObj, ok := value.(map[string]interface{})
			if !ok {
				return nil, BadInputError("Expected object \"var_decls\"")
			}
			err = device.ExtendSDDL(sddlJsonObj)
			if err != nil {
				return nil, BadInputError(err.Error())
			}
		}
	}

	// Handle vars last
	for fieldName, value := range info.BodyObj {
		switch fieldName {
		case "vars":
			varsJsonObj, ok := value.(map[string]interface{})
			if !ok {
				return nil, BadInputError("Expected object \"vars\"")
			}
			for varName, valueJsonObj := range varsJsonObj {
				varDef, err := device.LookupVarDef(varName)
				if err != nil {
					canolog.Warn("Cloud variable not found: ", varName)
					/* TODO: Report warning in response*/
					continue
				}

				varVal, err := cloudvar.JsonToCloudVarValue(varDef, valueJsonObj)
				if err != nil {
					canolog.Warn("Cloud variable value parsing problem: ", varName, err)
					/* TODO: Report warning in response*/
					continue
				}
				device.InsertSample(varDef, time.Now(), varVal)
			}
		}
	}

	timestamps := info.Query["timestamps"]
	timestamp_type := "epoch_us"
	if timestamps != nil && timestamps[0] == "rfc3339" {
		timestamp_type = "rfc3339"
	}

	out, err := deviceToJsonObj(device, timestamp_type)
	if err != nil {
		return nil, InternalServerError("Generating JSON")
	}
	out["result"] = "ok"
	return out, nil
}
Exemplo n.º 2
0
// Process communication payload from device (via websocket. or REST)
//  {
//      "device_id" : "9dfe2a00-efe2-45f9-a84c-8afc69caf4e7",
//        "sddl" : {
//          "optional inbound bool onoff" : {}
//        },
//        "vars" : {
//            "temperature" : 38.0f;
//            "gps" : {
//                "latitude" : 38.0f;
//                "longitude" : 38.0f;
//            }
//        }
//    }
//  }
//
//  <conn> is an optional datalayer connection.  If provided, it is used.
//  Otherwise, a datalayer connection is opened by this routine.
//
//  <device> is the device that sent the communication.  If nil, then either
//  <deviceId> or, as a last resort, the payload's "device_id" will be used.
//
//  <deviceId> is a string device ID of the device that sent the communication.
//  This is ignored if <device> is not nil.  If nil, then the payload's
//  "device_id" will be used.
//
//  <secretKey> is the device's secret key. A secret key is required if
//  <device> is nil.  Either the value of <secretKey> or, as a last resort, the
//  payload's "secret_key" field will be used.
//
//  <payload> is a string containing the JSON payload.
func ProcessDeviceComm(
	cfg config.Config,
	conn datalayer.Connection,
	device datalayer.Device,
	deviceIdString string,
	secretKey string,
	payload string) ServiceResponse {
	var err error
	var out ServiceResponse
	var ok bool

	canolog.Info("ProcessDeviceComm STARTED")
	// If conn is nil, open a datalayer connection.
	if conn == nil {
		dl := cassandra_datalayer.NewDatalayer(cfg)
		conn, err = dl.Connect("canopy")
		if err != nil {
			return ServiceResponse{
				HttpCode: http.StatusInternalServerError,
				Err:      fmt.Errorf("Could not connect to database: %s", err),
				Response: `{"result" : "error", "error_type" : "could_not_connect_to_database"}`,
				Device:   nil,
			}
		}
		defer conn.Close()
	}

	// Parse JSON payload
	var payloadObj map[string]interface{}
	err = json.Unmarshal([]byte(payload), &payloadObj)
	if err != nil {
		return ServiceResponse{
			HttpCode: http.StatusBadRequest,
			Err:      fmt.Errorf("Error JSON decoding payload: %s", err),
			Response: `{"result" : "error", "error_type" : "decoding_paylaod"}`,
			Device:   nil,
		}
	}

	// Device can be provided to this routine in one of three ways:
	// 1) <device> parameter
	// 2) <deviceId> parameter
	// 3) "device_id" field in payload
	if device == nil && deviceIdString != "" {
		// Parse UUID
		uuid, err := gocql.ParseUUID(deviceIdString)
		if err != nil {
			return ServiceResponse{
				HttpCode: http.StatusBadRequest,
				Err:      fmt.Errorf("Invalid UUID %s: %s", deviceIdString, err),
				Response: `{"result" : "error", "error_type" : "device_uuid_required"}`,
				Device:   nil,
			}
		}

		// Get secret key from payload if necessary
		if secretKey == "" {
			secretKey, ok = payloadObj["secret_key"].(string)
			if !ok {
				return ServiceResponse{
					HttpCode: http.StatusBadRequest,
					Err:      fmt.Errorf("\"secret_key\" field must be string"),
					Response: `{"result" : "error", "error_type" : "bad_payload"}`,
					Device:   nil,
				}
			}
		}

		// lookup device
		device, err = conn.LookupDeviceVerifySecretKey(uuid, secretKey)
		if err != nil {
			return ServiceResponse{
				HttpCode: http.StatusInternalServerError,
				Err:      fmt.Errorf("Error looking up or verifying device: %s", err),
				Response: `{"result" : "error", "error_type" : "database_error"}`,
				Device:   nil,
			}
		}
	}

	// Is "device_id" provided in payload?
	_, ok = payloadObj["device_id"]
	if ok {
		deviceIdStringFromPayload, ok := payloadObj["device_id"].(string)
		if !ok {
			return ServiceResponse{
				HttpCode: http.StatusBadRequest,
				Err:      fmt.Errorf("\"device_id\" field must be string"),
				Response: `{"result" : "error", "error_type" : "bad_payload"}`,
				Device:   nil,
			}
		}

		// Parse UUID
		uuid, err := gocql.ParseUUID(deviceIdStringFromPayload)
		if err != nil {
			return ServiceResponse{
				HttpCode: http.StatusBadRequest,
				Err:      fmt.Errorf("Invalid UUID %s: %s", deviceIdStringFromPayload, err),
				Response: `{"result" : "error", "error_type" : "device_uuid_required"}`,
				Device:   nil,
			}
		}

		// Is <device> already set?
		// If not: set it.
		// If so: ensure consistency
		if device == nil {

			// Get secret key from payload if necessary
			if secretKey == "" {
				secretKey, ok = payloadObj["secret_key"].(string)
				if !ok {
					return ServiceResponse{
						HttpCode: http.StatusBadRequest,
						Err:      fmt.Errorf("\"secret_key\" field must be string"),
						Response: `{"result" : "error", "error_type" : "bad_payload"}`,
						Device:   nil,
					}
				}
			}

			// Lookup device
			device, err = conn.LookupDeviceVerifySecretKey(uuid, secretKey)
			if err != nil {
				return ServiceResponse{
					HttpCode: http.StatusInternalServerError,
					Err:      fmt.Errorf("Error looking up or verifying device: %s", err),
					Response: `{"result" : "error", "error_type" : "database_error"}`,
					Device:   nil,
				}
			}
		} else {
			if device.ID().String() != deviceIdStringFromPayload {
				return ServiceResponse{
					HttpCode: http.StatusBadRequest,
					Err:      fmt.Errorf("Inconsistent device ID: %s %s", device.ID().String(), deviceIdStringFromPayload),
					Response: `{"result" : "error", "error_type" : "bad_payload"}`,
					Device:   nil,
				}
			}
		}
	}

	// If device wasn't provided at all, throw error.
	if device == nil {
		return ServiceResponse{
			HttpCode: http.StatusBadRequest,
			Err:      fmt.Errorf("Device ID expected"),
			Response: `{"result" : "error", "error_type" : "bad_payload"}`,
			Device:   nil,
		}
	}
	out.Device = device

	device.UpdateLastActivityTime(nil)

	// If "sddl" is present, create new / reconfigure Cloud Variables.
	_, ok = payloadObj["sddl"]
	if ok {
		updateMap, ok := payloadObj["sddl"].(map[string]interface{})
		if !ok {
			return ServiceResponse{
				HttpCode: http.StatusBadRequest,
				Err:      fmt.Errorf("Expected object for \"sdd\" field"),
				Response: `{"result" : "error", "error_type" : "bad_payload"}`,
				Device:   nil,
			}
		}
		err = device.ExtendSDDL(updateMap)
		if err != nil {
			return ServiceResponse{
				HttpCode: http.StatusInternalServerError,
				Err:      fmt.Errorf("Error updating device's SDDL: %s", err),
				Response: `{"result" : "error", "error_type" : "database_error"}`,
				Device:   nil,
			}
		}
	}

	// If "vars" is present, update value of all Cloud Variables (creating new
	// Cloud Variables as necessary)
	doc := device.SDDLDocument()
	_, ok = payloadObj["vars"]
	canolog.Info("vars present:", ok)
	if ok {
		varsMap, ok := payloadObj["vars"].(map[string]interface{})
		if !ok {
			return ServiceResponse{
				HttpCode: http.StatusBadRequest,
				Err:      fmt.Errorf("Expected object for \"vars\" field"),
				Response: `{"result" : "error", "error_type" : "bad_payload"}`,
				Device:   nil,
			}
		}
		canolog.Info("varsMap: ", varsMap)
		for varName, value := range varsMap {
			varDef, err := doc.LookupVarDef(varName)
			// TODO: an error doesn't necessarily mean prop should be created?
			canolog.Info("Looking up property ", varName)
			if varDef == nil {
				// Property doesn't exist.  Add it.
				canolog.Info("Not found.  Add property ", varName)
				// TODO: What datatype?
				// TODO: What other parameters?
				varDef, err = doc.AddVarDef(varName, sddl.DATATYPE_FLOAT32)
				if err != nil {
					return ServiceResponse{
						HttpCode: http.StatusInternalServerError,
						Err:      fmt.Errorf("Error creating cloud variable %s: %s", varName, err),
						Response: `{"result" : "error", "error_type" : "database_error"}`,
						Device:   nil,
					}
				}

				// save modified SDDL
				// TODO: Save at the end?
				canolog.Info("SetSDDLDocument ", doc)
				err = device.SetSDDLDocument(doc)
				if err != nil {
					return ServiceResponse{
						HttpCode: http.StatusInternalServerError,
						Err:      fmt.Errorf("Error updating SDDL: %s", err),
						Response: `{"result" : "error", "error_type" : "database_error"}`,
						Device:   nil,
					}
				}
			}

			// Store property value.
			// Convert value datatype
			varVal, err := cloudvar.JsonToCloudVarValue(varDef, value)
			if err != nil {
				return ServiceResponse{
					HttpCode: http.StatusInternalServerError,
					Err:      fmt.Errorf("Error converting JSON to propertyValue: %s", err),
					Response: `{"result" : "error", "error_type" : "bad_payload"}`,
					Device:   nil,
				}
			}
			canolog.Info("InsertStample")
			err = device.InsertSample(varDef, time.Now(), varVal)
			if err != nil {
				return ServiceResponse{
					HttpCode: http.StatusInternalServerError,
					Err:      fmt.Errorf("Error inserting sample %s: %s", varName, err),
					Response: `{"result" : "error", "error_type" : "database_error"}`,
					Device:   nil,
				}
			}
		}
	}

	return ServiceResponse{
		HttpCode: http.StatusOK,
		Err:      nil,
		Response: `{"result" : "ok"}`,
		Device:   device,
	}
}