Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Array handling: Align documentation to Broker/Server subset plus Workaround for Go Client to Server handling #619

Merged
merged 1 commit into from
Jul 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 22 additions & 27 deletions kuksa-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

```
Expand All @@ -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:

```
Expand All @@ -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
Expand Down
52 changes: 35 additions & 17 deletions kuksa-client/tests/test_datapoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"'
Expand All @@ -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
Expand All @@ -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

6 changes: 5 additions & 1 deletion kuksa_go_client/kuksa_client/ws.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -135,7 +139,7 @@ func (cc *KuksaClientCommWs) AuthorizeKuksaValConn(TokenOrTokenfile string) erro
}
tokenString = TokenOrTokenfile
}

log.Printf("Using token: %s", tokenString)

info, err := os.Stat(tokenString)
Expand Down
82 changes: 43 additions & 39 deletions kuksa_go_client/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading