225 lines
5.4 KiB
Go
225 lines
5.4 KiB
Go
package redis
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/gomodule/redigo/redis"
|
|
"src.dualinventive.com/go/dinet/ditime"
|
|
"src.dualinventive.com/go/dinet/rpc"
|
|
"src.dualinventive.com/go/lib/dilog"
|
|
)
|
|
|
|
type redisResultValueItem struct {
|
|
rpc.ResultValueItem
|
|
LastUpdate ditime.Time `json:"last_update"`
|
|
}
|
|
|
|
// marshalResult converts the result object to JSON for redis. It adds last_update to the JSON string
|
|
func marshalResult(result interface{}) ([]byte, error) {
|
|
var values interface{}
|
|
|
|
switch res := result.(type) {
|
|
case map[string]interface{}:
|
|
res["last_update"] = ditime.Now()
|
|
values = res
|
|
|
|
case rpc.ResultValueItem:
|
|
nRes := redisResultValueItem{res, ditime.Now()}
|
|
values = nRes
|
|
default:
|
|
return nil, errors.New("unknown marshal result type")
|
|
}
|
|
return json.Marshal(values)
|
|
}
|
|
|
|
// processMsgResult converts the result to JSON and writes it to redis. Id is used for key calculation
|
|
// the caller can use e.g sensor item uid: sensor:10:data.
|
|
func (r *Redis) processMsgResult(logger dilog.Logger, key, field string, result interface{}) error {
|
|
value, err := marshalResult(result)
|
|
if err != nil {
|
|
return fmt.Errorf("json marshal failed: %v", err)
|
|
}
|
|
|
|
err = r.write(logger, key, field, value)
|
|
if err != nil {
|
|
return fmt.Errorf("redis write failed: %v", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// processGeneralClassMethod process all messages that don't have a specific result object
|
|
func (r *Redis) processGeneralClassMethod(logger dilog.Logger, m *rpc.Msg, valueItems []map[string]interface{}) error {
|
|
newUIDFields := make([]string, len(valueItems))
|
|
key := keyName(m)
|
|
if key == "" {
|
|
logger.Warning("processGeneralClassMethod keyName is empty")
|
|
return nil
|
|
}
|
|
|
|
logger = logger.WithField("key", key)
|
|
|
|
for i, result := range valueItems {
|
|
var time ditime.Time
|
|
if _, ok := result["time"].(ditime.Time); !ok {
|
|
// Add server time when not existing in the message
|
|
time = m.Time
|
|
result["time"] = time
|
|
}
|
|
var id uint16
|
|
// Add id if exist
|
|
if uid, ok := result["uid"]; ok {
|
|
if fuid, ok := uid.(float64); ok {
|
|
id = uint16(fuid)
|
|
}
|
|
}
|
|
if rid, ok := result["id"]; ok {
|
|
if fid, ok := rid.(float64); ok {
|
|
id = uint16(fid)
|
|
}
|
|
}
|
|
newUIDFields[i] = strconv.Itoa(int(id))
|
|
|
|
field := fieldName(m, id)
|
|
if !r.IsNew(logger, key, field, time) {
|
|
logger.WithFields(dilog.Fields{
|
|
"field": field,
|
|
"time": time,
|
|
}).Debug("not new")
|
|
continue
|
|
}
|
|
err := r.processMsgResult(logger, key, field, result)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r.UpdateCache(key, field, time)
|
|
}
|
|
if err := r.removeOldInfoFields(m, newUIDFields); err != nil {
|
|
logger.WithError(err).Error("error removing info fields")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// processSensorData process all sensor data messages
|
|
func (r *Redis) processSensorData(logger dilog.Logger, m *rpc.Msg, result []rpc.ResultValueItem) error {
|
|
key := keyName(m)
|
|
if key == "" {
|
|
logger.Warning("processSensorData keyName is empty")
|
|
return nil
|
|
}
|
|
|
|
logger = logger.WithField("key", key)
|
|
|
|
for _, sensorItem := range result {
|
|
field := fieldName(m, sensorItem.UID)
|
|
|
|
// Should not happen, but just in case
|
|
if sensorItem.Time == 0 {
|
|
sensorItem.Time = m.Time
|
|
}
|
|
|
|
if !r.IsNew(logger, key, field, sensorItem.Time) {
|
|
logger.WithFields(dilog.Fields{
|
|
"field": field,
|
|
"time": sensorItem.Time,
|
|
}).Debug("not new")
|
|
continue
|
|
}
|
|
|
|
if sensorItem.UID == rpc.SensorGPS.UID() {
|
|
r.processGPSData(logger, m, sensorItem)
|
|
}
|
|
|
|
err := r.processMsgResult(logger, key, field, sensorItem)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r.UpdateCache(key, field, sensorItem.Time)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// processGPSData grabs the GPS data and send it to redis
|
|
func (r *Redis) processGPSData(logger dilog.Logger, m *rpc.Msg, sensorItem rpc.ResultValueItem) {
|
|
// Grab GPS
|
|
rawValue, ok := sensorItem.Value.(*json.RawMessage)
|
|
if ok {
|
|
gpsData := rpc.GPSSensorData{}
|
|
if err := json.Unmarshal(*rawValue, &gpsData); err != nil {
|
|
logger.WithField("data", *rawValue).Error("Invalid GPS data")
|
|
} else if err := r.writeSensorGPS(m, gpsData); err != nil {
|
|
logger.WithError(err).Error("cannot writeGPS data")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (r *Redis) removeOldInfoFields(m *rpc.Msg, uids []string) error {
|
|
// Skip non reply messages
|
|
if m.Type != rpc.MsgTypeReply {
|
|
return nil
|
|
}
|
|
// Skip non info messages
|
|
if m.Method() != "info" {
|
|
return nil
|
|
}
|
|
class := m.Class()
|
|
|
|
hKey := keyName(m)
|
|
if hKey == "" {
|
|
return fmt.Errorf("keyName is empty")
|
|
}
|
|
|
|
c, err := r.getConn()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer func() {
|
|
closeerr := c.Close()
|
|
// we are closing the connection so it is too late for errors.
|
|
_ = closeerr
|
|
}()
|
|
|
|
keys, err := redis.Strings(c.Do("HKEYS", hKey))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
rKeys := make([]interface{}, 0, len(uids)+1)
|
|
rKeys = append(rKeys, hKey)
|
|
for _, k := range keys {
|
|
if removeInfoField(uids, class, k) {
|
|
// Append missing key
|
|
rKeys = append(rKeys, k)
|
|
}
|
|
}
|
|
if len(rKeys) > 1 {
|
|
// Remove missing keys
|
|
_, err = c.Do("HDEL", rKeys...)
|
|
}
|
|
return err
|
|
}
|
|
|
|
// removeInfoField returns true when the given key is an info field of class 'class' and contains none of the given ids
|
|
func removeInfoField(uids []string, class rpc.Class, key string) bool {
|
|
p := strings.SplitN(key, ":", 3)
|
|
// Skip keys that don't contains id's
|
|
if len(p) != 3 {
|
|
return false
|
|
}
|
|
// Skip non info keys
|
|
if p[0] != string(class) || p[2] != "info" {
|
|
return false
|
|
}
|
|
|
|
for _, i := range uids {
|
|
if i == p[1] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|