Skip to content

Commit f5e3d0c

Browse files
committed
breaking change: allow to specify tags as numbers and overwrite data types.
- data type is not corrected/inferred anymore if specified
1 parent 177d32c commit f5e3d0c

File tree

12 files changed

+198
-136
lines changed

12 files changed

+198
-136
lines changed

.vscode/settings.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,5 @@
2121
"uncoveredBorderColor": ""
2222
},
2323
"go.coverOnSingleTest": true,
24-
"go.coverOnTestPackage": true,
25-
24+
"go.coverOnTestPackage": true
2625
}

README.md

Lines changed: 94 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Contributions are very welcome, see also the check [TODO](#TODO) or search in th
1616
## Usage
1717

1818
All arguments can also be set as `E3DC_` prefixed environment variable or in a config file like:
19+
1920
```
2021
host 127.0.0.1
2122
user myuser
@@ -27,88 +28,100 @@ request sent, is always last argument or can be piped through `stdin` as json st
2728

2829
See the examples below for more information.
2930

30-
3131
### Examples
3232

33-
***Note**: top element has always to be an array*
34-
35-
***Note**: these examples assume you have the default `.config` file setup or exported the environment variables.*
36-
37-
***Note**: all example use the default output format, check the help `./e3dc -help` for other formats.*
38-
39-
* Short tag only notation just request some information
40-
```sh
41-
./e3dc '[["EMS_REQ_START_MANUAL_CHARGE",0]]' | jq
42-
```
43-
Output:
44-
```json
45-
{
46-
"EMS_START_MANUAL_CHARGE": true
47-
}
48-
```
49-
50-
* Short tag only notation just request some information
51-
```sh
52-
./e3dc '["INFO_REQ_MAC_ADDRESS", "INFO_REQ_UTC_TIME"]' | jq
53-
```
54-
Output:
55-
```json
56-
{
57-
"INFO_MAC_ADDRESS": "00:00:00:00:00:00",
58-
"INFO_UTC_TIME": "1970-01-01T00:00:00.000000Z"
59-
}
60-
```
61-
62-
* Tuple notation of tag's to request information
63-
```sh
64-
./e3dc '[["INFO_REQ_MAC_ADDRESS"], ["INFO_REQ_UTC_TIME"]]' | jq
65-
```
66-
Output:
67-
```json
68-
{
69-
"INFO_MAC_ADDRESS": "00:00:00:00:00:00",
70-
"INFO_UTC_TIME": "1970-01-01T00:00:00.000000Z"
71-
}
72-
```
73-
74-
* Tuple notation of tag's and values to send information (data type is inferred by the tag)
75-
```sh
76-
./e3dc '[["EMS_REQ_START_MANUAL_CHARGE", 3000]]' | jq
77-
```
78-
Output:
79-
```json
80-
{
81-
"EMS_START_MANUAL_CHARGE": true
82-
}
83-
```
84-
* Tuple notation of tag's and values to send information (with explicit data type)
85-
86-
*Note: wrong data type is corrected by inferring it from the tag*
87-
```sh
88-
./e3dc '[["INFO_REQ_MAC_ADDRESS","None",""], ["INFO_REQ_UTC_TIME"]]' | jq
89-
```
90-
Output:
91-
```json
92-
{
93-
"INFO_MAC_ADDRESS": "00:00:00:00:00:00",
94-
"INFO_UTC_TIME": "1970-01-01T00:00:00.000000Z"
95-
}
96-
```
33+
**\*Note**: top element has always to be an array\*
34+
35+
**\*Note**: these examples assume you have the default `.config` file setup or exported the environment variables.\*
36+
37+
**\*Note**: all example use the default output format, check the help `./e3dc -help` for other formats.\*
38+
39+
- Short tag only notation just request some information
40+
41+
```sh
42+
./e3dc '[["EMS_REQ_START_MANUAL_CHARGE",0]]' | jq
43+
```
44+
45+
Output:
46+
47+
```json
48+
{
49+
"EMS_START_MANUAL_CHARGE": true
50+
}
51+
```
52+
53+
- Short tag only notation just request some information
54+
55+
```sh
56+
./e3dc '["INFO_REQ_MAC_ADDRESS", "INFO_REQ_UTC_TIME"]' | jq
57+
```
58+
59+
Output:
60+
61+
```json
62+
{
63+
"INFO_MAC_ADDRESS": "00:00:00:00:00:00",
64+
"INFO_UTC_TIME": "1970-01-01T00:00:00.000000Z"
65+
}
66+
```
67+
68+
- Tuple notation of tag's to request information
69+
70+
```sh
71+
./e3dc '[["INFO_REQ_MAC_ADDRESS"], ["INFO_REQ_UTC_TIME"]]' | jq
72+
```
73+
74+
Output:
75+
76+
```json
77+
{
78+
"INFO_MAC_ADDRESS": "00:00:00:00:00:00",
79+
"INFO_UTC_TIME": "1970-01-01T00:00:00.000000Z"
80+
}
81+
```
82+
83+
- Tuple notation of tag's and values to send information (data type is inferred by the tag)
84+
```sh
85+
./e3dc '[["EMS_REQ_START_MANUAL_CHARGE", 3000]]' | jq
86+
```
87+
Output:
88+
```json
89+
{
90+
"EMS_START_MANUAL_CHARGE": true
91+
}
92+
```
93+
- Tuple notation of tag's and values to send information (with explicit data type)
94+
95+
_Note: data type of the tag can be overriden by the user_
96+
97+
```sh
98+
./e3dc '[["INFO_REQ_MAC_ADDRESS","None",""], ["INFO_REQ_UTC_TIME"]]' | jq
99+
```
100+
101+
Output:
102+
103+
```json
104+
{
105+
"INFO_MAC_ADDRESS": "00:00:00:00:00:00",
106+
"INFO_UTC_TIME": "1970-01-01T00:00:00.000000Z"
107+
}
108+
```
97109

98110
## TODO
99-
- [ ] more testing
100-
- [ ] more documentation
101-
- [ ] check the generic data type logic (i.e. pointer vs value in interface), this should do a pro ;)
102-
- [ ] `cmd/e3dc`
103-
- [x] read config file (security)
104-
- [x] read from environment variables (security)
105-
- [x] streamline logging
106-
- [ ] optional experimental mode which accepts any custom message (including tag, datatype and value)
107-
- [ ] implement optional (by flag) pair output (same as the tuple input without datatype)
108-
- [ ] `rscp`
109-
- [ ] cleanup API
110-
- [ ] probably expose `Message` as interface and make the struct internal (would allow to move the complete cmd/e3dc specific json stuff out)
111-
- [ ] streamline logging
112-
- [ ] client: improve implementation to make it stable for keeping stable and connected when used in a service
113-
- [x] move `cmd/e3dc` specific json marshalling out of `rscp` to command line utility `cmd/e3dc`
114-
- [x] move `cmd/e3dc` specific json unmarshaling out of `rscp` to command line utility `cmd/e3dc`
111+
112+
- [ ] more testing
113+
- [ ] more documentation
114+
- [ ] check the generic data type logic (i.e. pointer vs value in interface), this should do a pro ;)
115+
- [ ] `cmd/e3dc`
116+
- [x] read config file (security)
117+
- [x] read from environment variables (security)
118+
- [x] streamline logging
119+
- [x] optional experimental mode which accepts any custom message (including tag, datatype and value)
120+
- [ ] implement optional (by flag) pair output (same as the tuple input without datatype)
121+
- [ ] `rscp`
122+
- [ ] cleanup API
123+
- [ ] probably expose `Message` as interface and make the struct internal (would allow to move the complete cmd/e3dc specific json stuff out)
124+
- [ ] streamline logging
125+
- [ ] client: improve implementation to make it stable for keeping stable and connected when used in a service
126+
- [x] move `cmd/e3dc` specific json marshalling out of `rscp` to command line utility `cmd/e3dc`
127+
- [x] move `cmd/e3dc` specific json unmarshaling out of `rscp` to command line utility `cmd/e3dc`

cmd/e3dc/json_helper.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ func isJSONString(d []byte) bool {
2222
return len(x) > 0 && x[0] == '"'
2323
}
2424

25+
func isJSONNumber(d []byte) bool {
26+
x := bytes.TrimLeft(d, " \t\r\n")
27+
return len(x) > 0 && x[0] >= 48 && x[0] <= 57
28+
}
29+
2530
func isJSONDataType(d []byte) (bool, *rscp.DataType) {
2631
if isJSONString(d) {
2732
s := new(rscp.DataType)

cmd/e3dc/json_input.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func unmarshalJSONValue(b []byte, m *rscp.Message) error {
2626
return nil
2727
}
2828

29-
//nolint:gomnd
29+
//nolint:gomnd,funlen
3030
func unmarshalJSONRequest(b []byte, m *rscp.Message) error {
3131
if isJSONEmpty(b) {
3232
return ErrInputInvalidTuple
@@ -78,6 +78,15 @@ func unmarshalJSONRequest(b []byte, m *rscp.Message) error {
7878
m.DataType = m.Tag.DataType()
7979
return nil
8080
}
81+
if isJSONNumber(b) {
82+
// parse tag
83+
if err := json.Unmarshal(b, &(m.Tag)); err != nil {
84+
return err
85+
}
86+
// infer data type
87+
m.DataType = m.Tag.DataType()
88+
return nil
89+
}
8190
// use Message default Unmarshal
8291
if err := json.Unmarshal(b, m); err != nil {
8392
return err

cmd/e3dc/json_input_test.go

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,21 @@ func Test_unmarshalJSONRequest(t *testing.T) {
3030
rscp.Message{Tag: rscp.INFO_REQ_UTC_TIME},
3131
false,
3232
},
33+
{`uint32 (valid known uint32 tag)`,
34+
`1`,
35+
rscp.Message{Tag: 1, DataType: rscp.Container},
36+
false,
37+
},
38+
{`uint32 (valid unknown uint32 tag)`,
39+
`0`,
40+
rscp.Message{Tag: 0},
41+
false,
42+
},
43+
{`string (invalid tag)`,
44+
`"INVALID_TAG"`,
45+
rscp.Message{},
46+
true,
47+
},
3348
{`tuple`,
3449
`["INFO_REQ_UTC_TIME"]`,
3550
rscp.Message{Tag: rscp.INFO_REQ_UTC_TIME},
@@ -65,18 +80,23 @@ func Test_unmarshalJSONRequest(t *testing.T) {
6580
rscp.Message{},
6681
true,
6782
},
68-
{`tuple (invalid tag)`,
69-
`[1]`,
70-
rscp.Message{},
71-
true,
83+
{`tuple (valid tag with datatype override)`,
84+
`[1, "None", null ]`,
85+
rscp.Message{Tag: rscp.RSCP_REQ_AUTHENTICATION, DataType: rscp.None, Value: nil},
86+
false,
7287
},
73-
{`tuple (invalid tag and datatype)`,
74-
`[1,1]`,
75-
rscp.Message{},
76-
true,
88+
{`tuple (valid known uint32 tag with datatype override)`,
89+
`[ 1, "None", null ]`,
90+
rscp.Message{Tag: 1, DataType: rscp.None, Value: nil},
91+
false,
92+
},
93+
{`tuple (valid unknown uint32 tag)`,
94+
`[ 0, "None", null ]`,
95+
rscp.Message{Tag: 0, DataType: rscp.None, Value: nil},
96+
false,
7797
},
78-
{`tuple (invalid tag, datatype and value)`,
79-
`[1,1,1]`,
98+
{`tuple (invalid string tag)`,
99+
`[ "INVALID_TAG" ]`,
80100
rscp.Message{},
81101
true,
82102
},
@@ -115,13 +135,23 @@ func Test_unmarshalJSONRequest(t *testing.T) {
115135
rscp.Message{Tag: rscp.BAT_REQ_DATA, DataType: rscp.Container, Value: []rscp.Message{{Tag: rscp.BAT_INDEX, DataType: rscp.UInt16, Value: uint16(0)}, {Tag: rscp.BAT_REQ_DEVICE_STATE}}},
116136
false,
117137
},
118-
{`object (invalid tag)`,
119-
`{ "Tag": 1 }`,
120-
rscp.Message{},
121-
true,
138+
{`object (valid tag with datatype override)`,
139+
`{ "Tag": 1, "DataType": "None", "Value": null }`,
140+
rscp.Message{Tag: rscp.RSCP_REQ_AUTHENTICATION, DataType: rscp.None, Value: nil},
141+
false,
122142
},
123-
{`string (invalid tag)`,
124-
`"INVALID_TAG"`,
143+
{`object (valid known uint32 tag with datatype override)`,
144+
`{ "Tag": 1, "DataType": "None", "Value": null }`,
145+
rscp.Message{Tag: 1, DataType: rscp.None, Value: nil},
146+
false,
147+
},
148+
{`object (valid unknown uint32 tag)`,
149+
`{ "Tag": 0, "DataType": "None", "Value": null }`,
150+
rscp.Message{Tag: 0, DataType: rscp.None, Value: nil},
151+
false,
152+
},
153+
{`object (invalid string tag)`,
154+
`{ "Tag": "INVALID_TAG" }`,
125155
rscp.Message{},
126156
true,
127157
},

rscp/message.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,6 @@ func (m *Message) validateResponse() error {
6666
//
6767
// must contain a valid tag and data type and the data type must match the value
6868
func (m *Message) validate() error {
69-
// check if message data type matches expected data type of the tag
70-
if !m.Tag.IsATag() {
71-
return fmt.Errorf("%s: %w", m.Tag, ErrValidTag)
72-
}
73-
// Note: see Issue https://github.com/spali/go-e3dc/issues/1
74-
//
75-
// else if m.isValidDataType() {
76-
// return fmt.Errorf("%s expects %s, got %s: %w", m.Tag, m.Tag.DataType(), m.DataType, ErrTagDataTypeMismatch)
77-
// }
7869
if !m.DataType.isValidValue(m.Value) {
7970
return fmt.Errorf("expected %T got %T : %w", m.DataType.newEmpty(0), m.Value, ErrDataTypeValueMismatch)
8071
}

rscp/message_json.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,16 +68,19 @@ func (m *Message) UnmarshalJSONValue(jm json.RawMessage) error {
6868

6969
// UnmarshalJSON unmarshals a message from json including nested messages in containers
7070
func (m *Message) UnmarshalJSON(b []byte) (err error) {
71+
const UnsetDataType = 0xF0
7172
// treat as Message, but we do not use Message type to prevent a stack overflow and copy the fields after unmarshalling
72-
jm := jsonMessage{}
73+
// also we use a non existing DataType default to detect if set in json or not
74+
jm := jsonMessage{DataType: UnsetDataType}
7375
if err := json.Unmarshal(b, &jm); err != nil {
7476
return fmt.Errorf("%w: %s", ErrJSONUnmarshal, err)
7577
}
76-
// Note: see Issue https://github.com/spali/go-e3dc/issues/1
77-
if jm.DataType == None && jm.DataType != jm.Tag.DataType() {
78-
jm.DataType = jm.Tag.DataType()
78+
m.Tag = jm.Tag
79+
if jm.DataType == UnsetDataType {
80+
m.DataType = m.Tag.DataType()
81+
} else {
82+
m.DataType = jm.DataType
7983
}
80-
m.Tag, m.DataType = jm.Tag, jm.DataType
8184
if err := m.UnmarshalJSONValue(jm.Value); err != nil {
8285
return fmt.Errorf("%w: %s", ErrJSONUnmarshal, err)
8386
}

rscp/message_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func Test_Message_validate(t *testing.T) {
5252
}{
5353
{"empty message",
5454
args{Message{}},
55-
ErrValidTag,
55+
nil,
5656
},
5757
{"message of type None",
5858
args{Message{Tag: INFO_REQ_UTC_TIME}},

rscp/request_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ func TestValidateRequests(t *testing.T) {
163163
}{
164164
{"empty message",
165165
args{[]Message{{}}},
166-
ErrValidTag,
166+
nil,
167167
},
168168
{"not a request",
169169
args{[]Message{{INFO_UTC_TIME, Timestamp, nil}}},

rscp/tag.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ type Tag uint32
66
// all tags as constant
77
//
88
//nolint:revive,stylecheck
9-
//go:generate go run github.com/alvaroloes/enumer -type=Tag -json
9+
//go:generate go run github.com/alvaroloes/enumer -type=Tag
1010
const (
1111
// Dieser TAG kapselt eine Authorisierungsanfrage an das S10.
1212
// Er enthält daher die Daten-Tags AUTHENTICATION_USER und AUTHENTICATION_PASSWORD

0 commit comments

Comments
 (0)