diff --git a/kuksa-client/README.md b/kuksa-client/README.md index f64e11041..029319dd6 100644 --- a/kuksa-client/README.md +++ b/kuksa-client/README.md @@ -183,7 +183,7 @@ This is an example showing how some of the commands can be used: ### Syntax for specifying data in the command line interface -Values used as argument to for example `setValue` shall match the type given. Quotes (single and double) are +Values used as argument to for example `setValue` shall match the type given. Quotes (single and double) are generally not needed, except in a few special cases. A few valid examples on setting float is shown below: ``` @@ -195,6 +195,10 @@ setValue Vehicle.Speed '45.2' For strings escaped quotes are needed if you want quotes to be sent to Server/Databroker, like if you want to store `Almost "red"` as value. Alternatively you can use outer single quotes and inner double quotes. +*NOTE: KUKSA Server and Databroker currently handle (escaped) quotes in strings differently!* +*The behavior described below is in general correct for KUKSA Databroker, but result may be different if interacting with KUKSA Server!* +*For consistent behavior it is recommended not to include (escaped) quotes in strings, except when needed to separate values* + The two examples below are equal: ``` @@ -221,37 +225,28 @@ setValue Vehicle.Cabin.Light.InteractiveLightBar.Effect "Almost green" setValue Vehicle.Cabin.Light.InteractiveLightBar.Effect 'Almost green ' ``` +It is possible to set array values. In general the value should be a valid JSON representation of the array. +For maximum compatibility for both KUKSA Server and KUKSA Databroker the following recommendations applies: -It is possible to set array values. Setting a string array with simple identifiers is not a problem. Also not if they -contain blanks - -``` -// Array with two elements -setValue Vehicle.OBD.DTCList [abc,def] -// Array with two elements, "hello there" and "def" -setValue Vehicle.OBD.DTCList [hello there,def] -``` - -Setting values that includes comma or quotation marks is more tricky, as the shell argument parser handling affects -how they are interpreted. The recommended approach is to have outer quotes of one type and user inner quotes of the -other type. - -Example 1: First item should be `hello, there` +* Always use single quotes around the array value. For some cases, like if there is no blanks or comma in the value, it is not needed, but it is good practice. +* Always use double quotes around string values. +* Never use single quotes inside string values +* Double quotes inside string values are allowed but must be escaped (`\"`) -``` -setValue Vehicle.OBD.DTCList '[ "hello, there",def]' -``` +Some examples supported by both KUKSA databroker and KUKSA Server are shown below -Example 2: First item should be `hello, "there"` +Setting a string array in KUKSA Databroker with simple identifiers is not a problem. +Also not if they contain blanks ``` -setValue Vehicle.OBD.DTCList '["hello, \"there\"",def]' -``` - -Example 3: First item should be `hello, 'there'` - -``` -setValue Vehicle.OBD.DTCList "['hello, \'there\'',def]" +// Array with two string elements +setValue Vehicle.OBD.DTCList '["abc","def"]' +// Array with two int elements (Note no quotes) +setValue Vehicle.SomeInt '[123,456]' +// Array with two elements, "hello there" and "def" +setValue Vehicle.OBD.DTCList '["hello there","def"]' +// Array with doubl quotes in string value; hello "there" +setValue Vehicle.OBD.DTCList '["hello, \"there\"","def"]' ``` ### Updating VSS Structure diff --git a/kuksa-client/tests/test_datapoint.py b/kuksa-client/tests/test_datapoint.py index ac0934d83..77a7eace2 100644 --- a/kuksa-client/tests/test_datapoint.py +++ b/kuksa-client/tests/test_datapoint.py @@ -19,124 +19,142 @@ # For simple strings like abd it is optional to quote them ("abc") or not (abc) # Quotes are needed if you have commas ("ab, c") # If you have duoble quotes in strings you must escape them +# +# Note that KUKSA Server has different rules, there the payload must be valid JSON, +# so not all tests shown below are recommended as they cannot be used for KUKSA Server + def test_array_parse_no_quote(): """ No need for quotes just because you have a blank """ test_str = r'[say hello, abc]' - my_array = list(Datapoint.cast_array_values(Datapoint.cast_str,test_str)) + my_array = list(Datapoint.cast_array_values(Datapoint.cast_str, test_str)) assert len(my_array) == 2 assert my_array[0] == "say hello" assert my_array[1] == "abc" + def test_array_parse_no_inside_quote(): """Quotes are OK""" test_str = r'["say hello","abc"]' - my_array = list(Datapoint.cast_array_values(Datapoint.cast_str,test_str)) + my_array = list(Datapoint.cast_array_values(Datapoint.cast_str, test_str)) assert len(my_array) == 2 assert my_array[0] == "say hello" assert my_array[1] == "abc" + def test_array_parse_no_inside_quote_single(): """Quotes are OK""" test_str = "['say hello','abc']" - my_array = list(Datapoint.cast_array_values(Datapoint.cast_str,test_str)) + my_array = list(Datapoint.cast_array_values(Datapoint.cast_str, test_str)) assert len(my_array) == 2 assert my_array[0] == "say hello" assert my_array[1] == "abc" + def test_array_parse_double_quote(): test_str = r'["say \"hello\"","abc"]' - my_array = list(Datapoint.cast_array_values(Datapoint.cast_str,test_str)) + my_array = list(Datapoint.cast_array_values(Datapoint.cast_str, test_str)) assert len(my_array) == 2 assert my_array[0] == "say \"hello\"" assert my_array[1] == "abc" + def test_array_parse_single_quote(): test_str = r'[say \'hello\',abc]' - my_array = list(Datapoint.cast_array_values(Datapoint.cast_str,test_str)) + my_array = list(Datapoint.cast_array_values(Datapoint.cast_str, test_str)) assert len(my_array) == 2 assert my_array[0] == "say 'hello'" assert my_array[1] == "abc" + def test_array_parse_comma(): test_str = r'["say, hello","abc"]' - my_array = list(Datapoint.cast_array_values(Datapoint.cast_str,test_str)) + my_array = list(Datapoint.cast_array_values(Datapoint.cast_str, test_str)) assert len(my_array) == 2 assert my_array[0] == r'say, hello' assert my_array[1] == "abc" + def test_array_square(): """No problem having square brackets as part of strings""" test_str = r'[say hello[], abc]' - my_array = list(Datapoint.cast_array_values(Datapoint.cast_str,test_str)) + my_array = list(Datapoint.cast_array_values(Datapoint.cast_str, test_str)) assert len(my_array) == 2 assert my_array[0] == "say hello[]" assert my_array[1] == "abc" + def test_array_empty_string_quoted(): test_str = r'["", abc]' - my_array = list(Datapoint.cast_array_values(Datapoint.cast_str,test_str)) + my_array = list(Datapoint.cast_array_values(Datapoint.cast_str, test_str)) assert len(my_array) == 2 assert my_array[0] == "" assert my_array[1] == "abc" + def test_array_empty_string_not_quoted(): """In this case the first item is ignored""" test_str = r'[, abc]' - my_array = list(Datapoint.cast_array_values(Datapoint.cast_str,test_str)) + my_array = list(Datapoint.cast_array_values(Datapoint.cast_str, test_str)) assert len(my_array) == 1 assert my_array[0] == "abc" + def test_double_comma(): """In this case the middle item is ignored""" test_str = r'[def,, abc]' - my_array = list(Datapoint.cast_array_values(Datapoint.cast_str,test_str)) + my_array = list(Datapoint.cast_array_values(Datapoint.cast_str, test_str)) assert len(my_array) == 2 assert my_array[0] == "def" assert my_array[1] == "abc" + def test_quotes_in_string_values(): """Escaped double quotes, so in total 4 items""" test_str = r'["dtc1, dtc2", dtc3, \" dtc4, dtc4\"]' - my_array = list(Datapoint.cast_array_values(Datapoint.cast_str,test_str)) + my_array = list(Datapoint.cast_array_values(Datapoint.cast_str, test_str)) assert len(my_array) == 4 assert my_array[0] == "dtc1, dtc2" assert my_array[1] == "dtc3" assert my_array[2] == "\" dtc4" assert my_array[3] == "dtc4\"" + def test_quotes_in_string_values_2(): """Doubee quotes in double quotes so in total three values""" test_str = "['dtc1, dtc2', dtc3, \" dtc4, dtc4\"]" - my_array = list(Datapoint.cast_array_values(Datapoint.cast_str,test_str)) + my_array = list(Datapoint.cast_array_values(Datapoint.cast_str, test_str)) assert len(my_array) == 3 assert my_array[0] == 'dtc1, dtc2' assert my_array[1] == "dtc3" assert my_array[2] == " dtc4, dtc4" + def test_int_no_quote(): test_str = r'[123,456]' - my_array = list(Datapoint.cast_array_values(int,test_str)) + my_array = list(Datapoint.cast_array_values(int, test_str)) assert len(my_array) == 2 assert my_array[0] == 123 assert my_array[1] == 456 + def test_int_quote(): """Quoting individual int values is not allowed""" test_str = r'["123","456"]' with pytest.raises(ValueError): - my_array = list(Datapoint.cast_array_values(int,test_str)) - + list(Datapoint.cast_array_values(int, test_str)) + def test_float_no_quote(): test_str = r'[123,456.23]' - my_array = list(Datapoint.cast_array_values(float,test_str)) + my_array = list(Datapoint.cast_array_values(float, test_str)) assert len(my_array) == 2 assert my_array[0] == 123 assert my_array[1] == 456.23 + def test_cast_str(): """Unquoted quotation marks shall be removed, quoted kept without quotes""" test_str = r'"say hello"' @@ -146,6 +164,7 @@ def test_cast_str(): test_str = r'say "hello"' assert Datapoint.cast_str(test_str) == r'say "hello"' + def test_cast_bool(): assert Datapoint.cast_bool("true") is True assert Datapoint.cast_bool("True") is True @@ -160,4 +179,3 @@ def test_cast_bool(): assert Datapoint.cast_bool("Ja") is True assert Datapoint.cast_bool("Nein") is True assert Datapoint.cast_bool("Doch") is True - diff --git a/kuksa_go_client/kuksa_client/ws.go b/kuksa_go_client/kuksa_client/ws.go index 536f6e19e..6f3966cf6 100644 --- a/kuksa_go_client/kuksa_client/ws.go +++ b/kuksa_go_client/kuksa_client/ws.go @@ -116,6 +116,10 @@ func (cc *KuksaClientCommWs) SetValueFromKuksaVal(path string, value string, att req.Set("action", "set") req.Set("path", path) req.Set("attribute", attr) + // Note: Line below currently gives problems if value is a string representation of a JSON object + // (array and theoretically also struct) + // The Set method handles it as a string, which the gives problems at the server side which expects for example + // a JSON array rather than a string containing an array req.Set(attr, value) _, err := cc.communicationHandler(req) @@ -135,7 +139,7 @@ func (cc *KuksaClientCommWs) AuthorizeKuksaValConn(TokenOrTokenfile string) erro } tokenString = TokenOrTokenfile } - + log.Printf("Using token: %s", tokenString) info, err := os.Stat(tokenString) diff --git a/kuksa_go_client/main.go b/kuksa_go_client/main.go index 808cd788c..de28f684e 100644 --- a/kuksa_go_client/main.go +++ b/kuksa_go_client/main.go @@ -85,53 +85,57 @@ func main() { } } - err = backend.SetValueFromKuksaVal("Vehicle.OBD.DTCList", "[dtc1, dtc2, dtc3]", "value") - if err != nil { - log.Printf("Set Value Error: %v", err) - } else { - log.Printf("Vehicle.OBD.DTCList Set: [dtc1, dtc2, dtc3]") - } + // Go client does not support setting of array values for Websocket + // Reason is SetValueFromKuksaVal where we set the JSON array we get as onput as string, + // so it gets quoted and considered as a string on server side and cause error + if *protocol == "grpc" { + err = backend.SetValueFromKuksaVal("Vehicle.OBD.DTCList", "[dtc1, dtc2, dtc3]", "value") + if err != nil { + log.Printf("Set Value Error: %v", err) + } else { + log.Printf("Vehicle.OBD.DTCList Set: [dtc1, dtc2, dtc3]") + } - values, err = backend.GetValueFromKuksaVal("Vehicle.OBD.DTCList", "value") - if err != nil { - log.Printf("Get Value Error: %v", err) - } else { - for _, value := range values { - if *protocol == "grpc" { - log.Println("Vehicle.OBD.DTCList: " + value.(*v1.DataEntry).String()) - } else { - log.Println("Vehicle.OBD.DTCList: " + value.(string)) + values, err = backend.GetValueFromKuksaVal("Vehicle.OBD.DTCList", "value") + if err != nil { + log.Printf("Get Value Error: %v", err) + } else { + for _, value := range values { + if *protocol == "grpc" { + log.Println("Vehicle.OBD.DTCList: " + value.(*v1.DataEntry).String()) + } else { + log.Println("Vehicle.OBD.DTCList: " + value.(string)) + } } } - } - // set string with "" and \" - // Expected result is 4 items in the list - // dtc1, dtc2 - // dtc2 - // dtc3, dtc3 - // dtc4 - var valstr = "['dtc1, dtc2', dtc2, \"dtc3, dtc3\", dtc4]" - err = backend.SetValueFromKuksaVal("Vehicle.OBD.DTCList", valstr, "value") - if err != nil { - log.Printf("Set Value Error: %v", err) - } else { - log.Printf("Vehicle.OBD.DTCList Set: " + valstr) - } + // set string with "" and \" + // Expected result is 4 items in the list + // dtc1, dtc2 + // dtc2 + // dtc3, dtc3 + // dtc4 + var valstr = "['dtc1, dtc2', dtc2, \"dtc3, dtc3\", dtc4]" + err = backend.SetValueFromKuksaVal("Vehicle.OBD.DTCList", valstr, "value") + if err != nil { + log.Printf("Set Value Error: %v", err) + } else { + log.Printf("Vehicle.OBD.DTCList Set: " + valstr) + } - values, err = backend.GetValueFromKuksaVal("Vehicle.OBD.DTCList", "value") - if err != nil { - log.Printf("Get Value Error: %v", err) - } else { - for _, value := range values { - if *protocol == "grpc" { - log.Println("Vehicle.OBD.DTCList: " + value.(*v1.DataEntry).String()) - } else { - log.Println("Vehicle.OBD.DTCList: " + value.(string)) + values, err = backend.GetValueFromKuksaVal("Vehicle.OBD.DTCList", "value") + if err != nil { + log.Printf("Get Value Error: %v", err) + } else { + for _, value := range values { + if *protocol == "grpc" { + log.Println("Vehicle.OBD.DTCList: " + value.(*v1.DataEntry).String()) + } else { + log.Println("Vehicle.OBD.DTCList: " + value.(string)) + } } } } - err = backend.SetValueFromKuksaVal("Vehicle.ADAS.ABS.IsEnabled", "true", "targetValue") if err != nil { log.Printf("Set Value Error: %v", err)