From 635cbff18806cee2b303feae690561341da64e4b Mon Sep 17 00:00:00 2001 From: Ulf Bjorkengren Date: Tue, 19 Mar 2024 15:41:35 +0100 Subject: [PATCH] Simulator option added to feederv2 template. Signed-off-by: Ulf Bjorkengren --- feeder/feeder-template/feederv2/feederv2.go | 79 ++++++++++++++++++- feeder/feeder-template/feederv2/tripdata.json | 9 +++ tutorial/content/feeder/_index.md | 10 +++ 3 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 feeder/feeder-template/feederv2/tripdata.json diff --git a/feeder/feeder-template/feederv2/feederv2.go b/feeder/feeder-template/feederv2/feederv2.go index 6475fba1..111a80a6 100644 --- a/feeder/feeder-template/feederv2/feederv2.go +++ b/feeder/feeder-template/feederv2/feederv2.go @@ -30,6 +30,19 @@ type DomainData struct { Value string } +type DataItem struct { + Path string `json:"path"` + Dp []DpItem `json:"dp"` +} + +type DpItem struct { + Ts string `json:"ts"` + Value string `json:"value"` +} + +var tripData []DataItem +var simulatedSource string + type FeederMap struct { MapIndex uint16 Name string @@ -247,6 +260,7 @@ func initVehicleInterfaceMgr(fMap []FeederMap, inputChan chan DomainData, output var simCtx simulateDataCtx simCtx.RandomSim = true simCtx.Fmap = fMap + dpIndex := 0 for { select { case outData := <-outputChan: @@ -257,8 +271,17 @@ func initVehicleInterfaceMgr(fMap []FeederMap, inputChan chan DomainData, output simCtx.Iteration = 0 default: - time.Sleep(3 * time.Second) // not to overload input channel - inputChan <- simulateInput(&simCtx) // simulating signals read from the vehicle interface + if simulatedSource == "internal" { + time.Sleep(3 * time.Second) // not to overload input channel + inputChan <- simulateInput(&simCtx) // simulating signals read from the vehicle interface + } else { + time.Sleep(1 * time.Second) // set to the tripdata "time base" + dataPoint := getSimulatedDataPoints(dpIndex) + for i := 0; i < len(dataPoint); i++ { + inputChan <- dataPoint[i] + } + dpIndex = incDpIndex(dpIndex) + } } } } @@ -308,6 +331,41 @@ func getRandomVssfMapIndex(fMap []FeederMap) int { return signalIndex } +func readSimulatedData(fname string) []DataItem { + if !fileExists(fname) { + utils.Error.Printf("readSimulatedData: The file %s does not exist.", fname) + return nil + } + data, err := os.ReadFile(fname) + if err != nil { + utils.Error.Printf("readSimulatedData:Error reading %s: %s", fname, err) + return nil + } + err = json.Unmarshal([]byte(data), &tripData) + if err != nil { + utils.Error.Printf("readSimulatedData:Error unmarshal json=%s", err) + return nil + } + return tripData +} + +func getSimulatedDataPoints(dpIndex int) []DomainData { + dataPoint := make([]DomainData, len(tripData)) + for i := 0 ; i < len(tripData); i++ { + dataPoint[i].Name = tripData[i].Path + dataPoint[i].Value = tripData[i].Dp[dpIndex].Value + } + return dataPoint +} + +func incDpIndex(index int) int { + index++ + if index == len(tripData[0].Dp) { + return 0 + } + return index +} + func convertDomainData(north2SouthConv bool, inData DomainData, feederMap []FeederMap) DomainData { var outData DomainData matchIndex := sort.Search(len(feederMap), func(i int) bool { return feederMap[i].Name >= inData.Name }) @@ -395,6 +453,8 @@ func main() { Required: false, Help: "changes log output level", Default: "info"}) + simSource := parser.Selector("i", "simsource", []string{"vssjson", "internal"}, &argparse.Options{Required: false, + Help: "Simulator source must be either vssjson, or internal", Default: "internal"}) // "vehiclejson" could be added for non-converted simulator data stateDB := parser.Selector("d", "statestorage", []string{"sqlite", "redis", "memcache", "none"}, &argparse.Options{Required: false, Help: "Statestorage must be either sqlite, redis, memcache, or none", Default: "redis"}) dbFile := parser.String("f", "dbfile", &argparse.Options{ @@ -407,6 +467,7 @@ func main() { utils.Error.Print(parser.Usage(err)) } stateDbType = *stateDB + simulatedSource = *simSource utils.InitLog("feeder-log.txt", "./logs", *logFile, *logLevel) @@ -463,6 +524,13 @@ func main() { utils.Info.Printf("Initializing the feeder for mapping file %s.", *mapFile) feederMap := readFeederMap(*mapFile) + if simulatedSource != "internal" { + tripData = readSimulatedData("tripdata.json") + if len(tripData) == 0 { + utils.Error.Printf("Tripdata file not found.") + os.Exit(1) + } + } scalingDataList = readscalingDataList(*sclDataFile) go initVSSInterfaceMgr(vssInputChan, vssOutputChan) go initVehicleInterfaceMgr(feederMap, vehicleInputChan, vehicleOutputChan) @@ -472,7 +540,12 @@ func main() { case vssInData := <-vssInputChan: vehicleOutputChan <- convertDomainData(true, vssInData, feederMap) case vehicleInData := <-vehicleInputChan: - vssOutputChan <- convertDomainData(false, vehicleInData, feederMap) + if simulatedSource != "vssjson" { + vssOutputChan <- convertDomainData(false, vehicleInData, feederMap) + } else { +//utils.Info.Printf("simulatedDataPoints:Path=%s, Value=%s", vehicleInData.Name, vehicleInData.Value) + vssOutputChan <- vehicleInData // conversion not needed + } } } } diff --git a/feeder/feeder-template/feederv2/tripdata.json b/feeder/feeder-template/feederv2/tripdata.json new file mode 100644 index 00000000..a04ff411 --- /dev/null +++ b/feeder/feeder-template/feederv2/tripdata.json @@ -0,0 +1,9 @@ +[ + +{ "path": "Vehicle.TraveledDistance", "dp": [ { "ts": "2020-04-15T13:37:00Z", "value": "1000.0" }, { "ts": "2020-04-15T13:37:05Z", "value": "1000.0" }, { "ts": "2020-04-15T13:37:10Z", "value": "1001.0" }, { "ts": "2020-04-15T13:37:15Z ", "value": "1001.0" } ]}, + +{ "path": "Vehicle.CurrentLocation.Longitude", "dp": [ { "ts": "2020-04-15T13:37:00Z", "value": "56.02024719364729" }, { "ts": "2020-04-15T13:37:00Z", "value": "56.02233748493814" }, { "ts": "2020-04-15T13:37:00Z", "value": "56.02565421151008" }, { "ts": "2020-04-15T13:37:05Z", "value": "56.02823595803967" } ]}, + +{ "path": " Vehicle.CurrentLocation.Latitude " , "dp": [ { "ts": "2020-04-15T13:37:00Z", "value": "12.599927977070497" }, { "ts": "2020-04-15T13:37:00Z", "value": "12.601058355542794" }, { "ts": "2020-04-15T13:37:00Z", "value": "12.602554268256588" }, { "ts": "2020-04-15T13:37:05Z", "value": "12.603616368784676" } ]} + +] diff --git a/tutorial/content/feeder/_index.md b/tutorial/content/feeder/_index.md index 7a9122d8..f7ea493b 100644 --- a/tutorial/content/feeder/_index.md +++ b/tutorial/content/feeder/_index.md @@ -44,3 +44,13 @@ where the Vehicle interface is implemented to connect to a RemotiveLabs broker. There is also an External Vehicle Interface Client [EVIC](https://github.com/covesa/vissr/tree/master/feeder/feeder-evic) feeder that enables the interface client to be implemented in a separate executable. + +## Simulated vehicle data sources +The feederv2 template contains two different simulation mechanisms that are selected via the command line configuration parameters. + +The one configured by "internal" uses the conversion instructions data as input for which signals to simulate, which are then randomly selected and set to random values. + +The other configured by "vssjson" tries to read the file "tripdata.json", which must have a format as found in the example file. s seen in that file it contains an array of signal path names, and for each signal it contains an array of datapoints, i.e. timestamps and values. The data points are replayed at a constant frequency of 1 Hz. To change the frequency the time.Sleep input in the code must be changed and recompiled. This sets the rquirement on the data point arrays that their values must have been captured at this frequency, or recalculated to this frequency. Each data point array must have the same length. The simulator waps around and starts again from the beginning after reaching the end. + +The signal names must be VSS paths as they are not processed by the conversion engine. +Extending the model to instead expect "vehicle domain signals" (like CAN signal data) should be a simple coding exercise for anyone preferring that.