diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000..f0fa22eb --- /dev/null +++ b/.flake8 @@ -0,0 +1,3 @@ +[flake8] +max-line-length = 100 +ignore = E722 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 98240553..d139e7c9 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,8 @@ /cc_library/.idea __pycache__/ **/.idea +**/pkg +**/bin +**/src/* +!**/src/sciler */debug.log diff --git a/.travis.yml b/.travis.yml index ea561f43..c3b9f98f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,8 +12,7 @@ matrix: # https://medium.com/faun/configuring-travis-ci-for-angular-application-34afee1715f - language: node_js - node_js: - - "12.13.1" + node_js: 12.13.1 services: - xvfb dist: xenial @@ -33,12 +32,15 @@ matrix: chrome: stable - language: go - go_import_path: github.com/IssaHanou/BEP_1920_Q2 + go: 1.13.4 + go_import_path: github.com/IssaHanou/BEP_1920_Q2/back-end before_install: - - go get github.com/stretchr/testify/assert - go get -u golang.org/x/lint/golint + - export GOPATH=$TRAVIS_BUILD_DIR/back-end + - cd back-end/src/sciler + - go get github.com/stretchr/testify/assert script: - - cd back-end - - golint -set_exit_status ./.. - - go test -v ./... - - go build + - cd ../.. + - golint -set_exit_status sciler/... + - go test -v sciler/... + - go build sciler diff --git a/back-end/.gitignore b/back-end/.gitignore new file mode 100644 index 00000000..8b9c0447 --- /dev/null +++ b/back-end/.gitignore @@ -0,0 +1,2 @@ +/bin +/pkg \ No newline at end of file diff --git a/back-end/.idea/vcs.xml b/back-end/.idea/vcs.xml deleted file mode 100644 index 6c0b8635..00000000 --- a/back-end/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/back-end/README.md b/back-end/README.md index eb797851..63f194e6 100644 --- a/back-end/README.md +++ b/back-end/README.md @@ -2,7 +2,11 @@ The back-end is written in Go. Tools used are go fmt, golint and Testify. External imports that need to be retrieved should be added in `.travis.yml` in the `before_script`. - +Configure (project) GOPATH to /back-end folder in /BEP_1920_Q2. +When using Goland uncheck `Use GOPATH that is defined in system environment` and check `Index entire GOPATH` + +run `go get ./...` in the `sciler` folder to go get all dependencies + ### Setup gofmt and golint to run automatically: - install golint: `go get -u golang.org/x/lint/golint` - natigate to Settings > Tools > File Watchers diff --git a/back-end/back-end.exe b/back-end/back-end.exe new file mode 100644 index 00000000..0cb76b4e Binary files /dev/null and b/back-end/back-end.exe differ diff --git a/back-end/config/config.go b/back-end/config/config.go deleted file mode 100644 index 5d01b784..00000000 --- a/back-end/config/config.go +++ /dev/null @@ -1,40 +0,0 @@ -package config - -import ( - "encoding/json" - "fmt" - "strings" -) - -// General is a struct that describes the configurations of an escape room -type General struct { - Name string `json:"name"` - Duration string `json:"duration"` -} - -// GetFromJSON takes in json with general info of escape room. -func GetFromJSON(input []byte) string { - var data General - err := json.Unmarshal(input, &data) - if err != nil { - _ = fmt.Errorf(err.Error()) - } - return "Escape room " + data.Name + " should be solved within " + - formatDuration(data.Duration) -} - -//Takes in duration string in format hh:mm:ss -func formatDuration(duration string) string { - vars := strings.Split(duration, ":") - var result string - if vars[0] != "00" { - result += vars[0] + " hours and" - } - if vars[1] != "00" { - result += vars[1] + " minutes" - } - if vars[2] != "00" { - result += " and " + vars[2] + " seconds" - } - return result -} diff --git a/back-end/config/config_test.go b/back-end/config/config_test.go deleted file mode 100644 index fa938b07..00000000 --- a/back-end/config/config_test.go +++ /dev/null @@ -1,16 +0,0 @@ -package config - -import ( - "github.com/stretchr/testify/assert" - "testing" -) - -func TestGetFromJson(t *testing.T) { - json := []byte(`{ - "name": "My Awesome Escape", - "duration": "00:30:00" - }`) - result := GetFromJSON(json) - expected := "Escape room My Awesome Escape should be solved within 30 minutes" - assert.Equal(t, result, expected, "JSON should be properly converted to string") -} diff --git a/back-end/main.go b/back-end/main.go deleted file mode 100644 index 70af8c4b..00000000 --- a/back-end/main.go +++ /dev/null @@ -1,15 +0,0 @@ -package main - -import ( - "fmt" - "github.com/IssaHanou/BEP_1920_Q2/back-end/config" -) - -func main() { - fmt.Println("Starting server") - data := []byte(`{ - "name": "My Awesome Escape", - "duration": "00:30:00" - }`) - fmt.Println(config.GetFromJSON(data)) -} \ No newline at end of file diff --git a/back-end/resources/room_config.json b/back-end/resources/room_config.json index c8fe0c8b..0ff6b3d6 100644 --- a/back-end/resources/room_config.json +++ b/back-end/resources/room_config.json @@ -13,52 +13,52 @@ "redSwitch": "boolean", "orangeSwitch": "boolean", "greenSwitch": "boolean", - "slider1": "integer", - "slider2": "integer", - "slider3": "integer", + "slider1": "numeric", + "slider2": "numeric", + "slider3": "numeric", "mainSwitch": "boolean" }, "output": { "greenLight1": { "type": "string", "instruction": { - "interval": "integer", - "delay": "integer" + "interval": "numeric", + "delay": "numeric" } }, "greenLight2": { "type": "string", "instruction": { - "interval": "integer", - "delay": "integer" + "interval": "numeric", + "delay": "numeric" } }, "greenLight3": { "type": "string", "instruction": { - "interval": "integer", - "delay": "integer" + "interval": "numeric", + "delay": "numeric" } }, "redLight1": { "type": "string", "instruction": { - "interval": "integer", - "delay": "integer" + "interval": "numeric", + "delay": "numeric" } }, - "redLight2": { + "redLight2": { "type": "string", "instruction": { - "interval": "integer", - "delay": "integer" + "interval": "numeric", + "delay": "numeric" } }, - "redLight3": { + "redLight3": { "type": "string", "instruction": { - "interval": "integer", - "delay": "integer" + "interval": "numeric", + "delay": "numeric" } } } @@ -67,12 +67,21 @@ "id": "telephone", "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", "input": { - "turningWheel": "array" + "turningWheel": "num-array" }, "output": { "audio": "string", "ringTone": "string" } + }, + { + "id": "door1", + "description": "De deur kan open en dicht zijn en neemt geen input.", + "input": { + }, + "output": { + "open": "boolean" + } } ], "puzzles": [ @@ -86,11 +95,12 @@ "conditions": [ { "type": "device", - "id": "telphone", + "type_id": "telephone", "constraints": [ { - "comparison": "eq", - "turningWheel": [ + "comp": "eq", + "component_id": "turningWheel", + "value": [ 0, 1, 2, @@ -101,7 +111,7 @@ "actions": [ { "type": "device", - "id": "controlBoard", + "type_id": "controlBoard", "message": { "output": { "greenLight1": { @@ -141,28 +151,26 @@ "conditions": [ { "type": "device", - "id": "controlBoard", + "type_id": "controlBoard", "constraints": [ { "component_id": "mainSwitch", - "comparison": "eq", + "comp": "eq", + "value": true + }, + { + "component_id": "greenSwitch", + "comp": "eq", + "value": true + }, + { + "component_id": "redSwitch", + "comp": "eq", "value": true }, - [ - { - "component_id": "greenSwitch", - "comparison": "eq", - "value": true - }, - { - "component_id": "redSwitch", - "comparison": "eq", - "value": true - } - ], { "component_id": "slider2", - "comparison": "lt", + "comp": "lt", "value": 30 } ] @@ -171,7 +179,7 @@ "actions": [ { "type": "device", - "id": "telephone", + "type_id": "telephone", "message": { "output": { "audio": "audio_1" @@ -180,7 +188,7 @@ }, { "type": "device", - "id": "controlBoard", + "type_id": "controlBoard", "message": { "output": { "greenLight1": false, @@ -211,10 +219,10 @@ "conditions": [ { "type": "timer", - "id": "telephone", + "type_id": "timer1", "constraints": [ { - "comparison": "eq", + "comp": "eq", "value": "00:01:00" } ] @@ -223,9 +231,10 @@ "actions": [ { "type": "device", - "id": "telephone", + "type_id": "telephone", "message": { "output": { + "ringTone": "standard", "audio": "audio_1" } } @@ -233,6 +242,49 @@ ] } ] + }, + { + "name": "Stop", + "rules": [ + { + "id": "flipSwitch", + "description": "Als de knop omgaat, stopt het spel: tijd stopt en deur gaat open.", + "limit": 1, + "conditions": [ + { + "type": "device", + "type_id": "controlBoard", + "constraints": [ + { + "component_id": "mainSwitch", + "comp": "eq", + "value": true + } + ] + } + ], + "actions": [ + { + "type": "timer", + "type_id": "timer1", + "message": { + "output": { + "instruction": "stop" + } + } + }, + { + "type": "device", + "type_id": "door1", + "message": { + "output": { + "open": true + } + } + } + ] + } + ] } ] } \ No newline at end of file diff --git a/back-end/resources/room_manual.md b/back-end/resources/room_manual.md index e4cfff4f..7b3265ff 100644 --- a/back-end/resources/room_manual.md +++ b/back-end/resources/room_manual.md @@ -26,7 +26,7 @@ This will be a list of all devices in the room. Each device is defined as a JSON - `id`: this is the id of a device. Write it in camelCase, e.g. "controlBoard". - `description`: this is optional and can contain more information about the device. This can be displayed in the front-end, so should be readable and in Dutch. - `input`: defines type of values to be expected as input. The keys are component ids and values are types of input (in string format). - Examples are: "string", "boolean", "array", "integer" or a custom name. + Possible types are: "string", "boolean", "numeric", "string-array", "bool-array", "num-array" or a custom name. - `output`: defines type of values to be expected as output. The keys are component ids and the value is a map with a `type` property and an optional `instruction`, which defines a map with custom instruction for the device. @@ -52,20 +52,22 @@ Rules are defined by: - `description`: this is optional and can contain more information about the rule. This can be displayed in the front-end, so should be readable and in Dutch. - `limit`: this sets the number of times this rule can be triggered. -- `conditions`: this is an array of conditions. By putting several constraints in an array within the conditions array, they will be treated as OR conditions. +- `conditions`: this is an array of conditions. By putting several constraints in an array within the conditions array, they will be treated as OR conditions. Rule/combination pairs must be unique. - `type`: this can `rule`, `timer` or `device`. - - `id`: this will be the id of a timer, rule or device, depending on the type. - - `constraints`: this is an array of constraints. By putting several constraints in an array within the constrains array, they will be treated as OR constraints. + - `type_id`: this will be the id of a timer, rule or device, depending on the type. + - `constraints`: this is an array of constraints. - - `comparison`: this can be "eq", "lt", "gt", "cont" (contains), "lte", "gte" - - `value`: this is the value on which the comparison is made. + - `comp`: this is the type of comparison and can be "eq", "lt", "gt", "contains" (contains), "lte", "gte" + - `value`: this is the value on which the comparison is made. This should be in the same type as specified in the input of the device. + If it has custom input, then enter value in preferred type and deal with it on the client. + In case of "timer" type, it should be in the format "hh:mm:ss" - `component_id`: in the case of "device" type, this is the id of the component it triggers. + In case of "timer" type, this is non-existent. - `actions`: this is an array of actions: - `type`: this can be `device` or `timer` - - `id`: the id of device or timer, depending on type respectively - - `message`: this defines the output message sent. In case of device type, the message contains parameter `output`. - The output defines the type of values to be expected as output. The keys are component ids - and the value is a map with a `type` property and an optional `instruction`, which defines a map with custom instruction for the device. + - `type_id`: the id of device or timer, depending on type respectively + - `message`: this defines the `output` message sent. The output defines the type of values to be expected as output. + In case of device type, the keys are component ids and the value is a type. An additional "instruction" property may be defined. In the case of timer, the message should have `instruction` specified as `stop` or `subtract`, in the latter case, a `value` should also be passed. diff --git a/back-end/resources/testing/testCheckActionComponentType.json b/back-end/resources/testing/testCheckActionComponentType.json new file mode 100644 index 00000000..a80f3e97 --- /dev/null +++ b/back-end/resources/testing/testCheckActionComponentType.json @@ -0,0 +1,45 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "ledLights", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "light": "boolean" + }, + "output": { + "color": "string" + } + } + ], + "general_events": [ + { + "name": "Telefoon puzzels", + "rules": [ + { + "id": "correctSequence", + "description": "De juiste volgorde van cijfers moet gedraaid worden.", + "limit": 1, + "conditions": [ + ], + "actions": [ + { + "type": "device", + "type_id": "ledLights", + "message": { + "output": { + "color": 0 + } + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/back-end/resources/testing/testComponentActionNotPresent.json b/back-end/resources/testing/testComponentActionNotPresent.json new file mode 100644 index 00000000..83a6779f --- /dev/null +++ b/back-end/resources/testing/testComponentActionNotPresent.json @@ -0,0 +1,57 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "ledLights", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "light": "boolean" + }, + "output": { + "color": "string" + } + } + ], + "puzzles": [ + { + "name": "Telefoon puzzels", + "rules": [ + { + "id": "correctSequence", + "description": "De juiste volgorde van cijfers moet gedraaid worden.", + "limit": 1, + "conditions": [ + { + "type": "device", + "type_id": "ledLights", + "constraints": [ + { + "comp": "eq", + "component_id": "light", + "value": true + } + ] + } + ], + "actions": [ + { + "type": "device", + "type_id": "ledLights", + "message": { + "output": { + "light": "green" + } + } + + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/back-end/resources/testing/testComponentConstraintNotPresent.json b/back-end/resources/testing/testComponentConstraintNotPresent.json new file mode 100644 index 00000000..5cf651bb --- /dev/null +++ b/back-end/resources/testing/testComponentConstraintNotPresent.json @@ -0,0 +1,56 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "ledLights", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "light": "boolean" + }, + "output": { + "color": "string" + } + } + ], + "puzzles": [ + { + "name": "Telefoon puzzels", + "rules": [ + { + "id": "correctSequence", + "description": "De juiste volgorde van cijfers moet gedraaid worden.", + "limit": 1, + "conditions": [ + { + "type": "device", + "type_id": "ledLights", + "constraints": [ + { + "comp": "eq", + "component_id": "color", + "value": true + } + ] + } + ], + "actions": [ + { + "type": "device", + "type_id": "ledLights", + "message": { + "output": { + "color": "green" + } + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/back-end/resources/testing/testDeviceActionNotPresent.json b/back-end/resources/testing/testDeviceActionNotPresent.json new file mode 100644 index 00000000..076da362 --- /dev/null +++ b/back-end/resources/testing/testDeviceActionNotPresent.json @@ -0,0 +1,56 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "ledLights", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "light": "boolean" + }, + "output": { + "color": "string" + } + } + ], + "puzzles": [ + { + "name": "Telefoon puzzels", + "rules": [ + { + "id": "correctSequence", + "description": "De juiste volgorde van cijfers moet gedraaid worden.", + "limit": 1, + "conditions": [ + { + "type": "device", + "type_id": "ledLights", + "constraints": [ + { + "comp": "eq", + "component_id": "light", + "value": true + } + ] + } + ], + "actions": [ + { + "type": "device", + "type_id": "telephone", + "message": { + "output": { + "color": "green" + } + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/back-end/resources/testing/testDeviceConstraintNotPresent.json b/back-end/resources/testing/testDeviceConstraintNotPresent.json new file mode 100644 index 00000000..9ae39f6b --- /dev/null +++ b/back-end/resources/testing/testDeviceConstraintNotPresent.json @@ -0,0 +1,57 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "ledLights", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "light": "boolean" + }, + "output": { + "color": "string" + } + } + ], + "puzzles": [ + { + "name": "Telefoon puzzels", + "rules": [ + { + "id": "correctSequence", + "description": "De juiste volgorde van cijfers moet gedraaid worden.", + "limit": 1, + "conditions": [ + { + "type": "device", + "type_id": "telephone", + "constraints": [ + { + "comp": "eq", + "component_id": "color", + "value": true + } + ] + } + ], + "actions": [ + { + "type": "device", + "type_id": "ledLights", + "message": { + "output": { + "color": "green" + } + } + + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/back-end/resources/testing/testDeviceCustomType.json b/back-end/resources/testing/testDeviceCustomType.json new file mode 100644 index 00000000..dc1a20a0 --- /dev/null +++ b/back-end/resources/testing/testDeviceCustomType.json @@ -0,0 +1,47 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "telephone", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "turningWheel": "my-type" + }, + "output": { + "audio": "string" + } + } + ], + "puzzles": [ + { + "name": "Telefoon puzzels", + "rules": [ + { + "id": "correctSequence", + "description": "De juiste volgorde van cijfers moet gedraaid worden.", + "limit": 1, + "conditions": [ + { + "type": "device", + "type_id": "telephone", + "constraints": [ + { + "comp": "eq", + "component_id": "turningWheel", + "value": { + "my_key": "my_value" + } + } + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/back-end/resources/testing/testDurationError.json b/back-end/resources/testing/testDurationError.json new file mode 100644 index 00000000..8c4345f3 --- /dev/null +++ b/back-end/resources/testing/testDurationError.json @@ -0,0 +1,8 @@ +{ + "general": { + "name": "Escape X", + "duration": 30, + "host": "192.0.0.84", + "port": 1883 + } +} \ No newline at end of file diff --git a/back-end/resources/testing/testIncorrectTypeAction.json b/back-end/resources/testing/testIncorrectTypeAction.json new file mode 100644 index 00000000..e2c57429 --- /dev/null +++ b/back-end/resources/testing/testIncorrectTypeAction.json @@ -0,0 +1,45 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "ledLights", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "light": "boolean" + }, + "output": { + "color": "string" + } + } + ], + "puzzles": [ + { + "name": "Telefoon puzzels", + "rules": [ + { + "id": "correctSequence", + "description": "De juiste volgorde van cijfers moet gedraaid worden.", + "limit": 1, + "conditions": [ + ], + "actions": [ + { + "type": "my-type", + "type_id": "color", + "message": { + "output": { + "color": "green" + } + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/back-end/resources/testing/testIncorrectTypeCondition.json b/back-end/resources/testing/testIncorrectTypeCondition.json new file mode 100644 index 00000000..1e021ce6 --- /dev/null +++ b/back-end/resources/testing/testIncorrectTypeCondition.json @@ -0,0 +1,45 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "ledLights", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "light": "boolean" + }, + "output": { + "color": "string" + } + } + ], + "puzzles": [ + { + "name": "Telefoon puzzels", + "rules": [ + { + "id": "correctSequence", + "description": "De juiste volgorde van cijfers moet gedraaid worden.", + "limit": 1, + "conditions": [ + { + "type": "mytype", + "type_id": "timer1", + "constraints": [ + { + "comp": "eq", + "value": "30:00" + } + ] + } + ], + "actions": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/back-end/resources/testing/testNoComponentIDBoolArray.json b/back-end/resources/testing/testNoComponentIDBoolArray.json new file mode 100644 index 00000000..109ec594 --- /dev/null +++ b/back-end/resources/testing/testNoComponentIDBoolArray.json @@ -0,0 +1,57 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "ledLights", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "light": "boolean" + }, + "output": { + "color": "string" + } + } + ], + "puzzles": [ + { + "name": "Telefoon puzzels", + "rules": [ + { + "id": "correctSequence", + "description": "De juiste volgorde van cijfers moet gedraaid worden.", + "limit": 1, + "conditions": [ + { + "type": "device", + "type_id": "ledLights", + "constraints": [ + { + "comp": "eq", + "component_id": "light", + "value": true + } + ] + } + ], + "actions": [ + { + "type": "device", + "type_id": "ledLights", + "message": { + "output": { + "color": "green" + } + } + + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/back-end/resources/testing/test_config.json b/back-end/resources/testing/test_config.json new file mode 100644 index 00000000..1d02af75 --- /dev/null +++ b/back-end/resources/testing/test_config.json @@ -0,0 +1,350 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "controlBoard", + "description": "Control board with three switches, three slides with lights and one main switch.", + "input": { + "redSwitch": "boolean", + "orangeSwitch": "boolean", + "greenSwitch": "boolean", + "slider1": "numeric", + "slider2": "numeric", + "slider3": "numeric", + "mainSwitch": "boolean" + }, + "output": { + "greenLight1": { + "type": "string", + "instruction": { + "interval": "numeric", + "delay": "numeric" + } + }, + "greenLight2": { + "type": "string", + "instruction": { + "interval": "numeric", + "delay": "numeric" + } + }, + "greenLight3": { + "type": "string", + "instruction": { + "interval": "numeric", + "delay": "numeric" + } + }, + "redLight1": { + "type": "string", + "instruction": { + "interval": "numeric", + "delay": "numeric" + } + }, + "redLight2": { + "type": "string", + "instruction": { + "interval": "numeric", + "delay": "numeric" + } + }, + "redLight3": { + "type": "string", + "instruction": { + "interval": "numeric", + "delay": "numeric" + } + } + } + }, + { + "id": "telephone", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "turningWheel": "num-array" + }, + "output": { + "audio": "string", + "ringTone": "string" + } + }, + { + "id": "testDevice", + "description": "Device with all types of input for testing", + "input": { + "string": "string", + "bool": "bool", + "numeric": "numeric", + "num-array": "num-array", + "bool-array": "bool-array", + "string-array": "string-array", + "custom": "custom", + "timer": "timer" + } + } + ], + "puzzles": [ + { + "name": "Telefoon puzzels", + "rules": [ + { + "id": "correctSequence", + "description": "De juiste volgorde van cijfers moet gedraaid worden.", + "limit": 1, + "conditions": [ + { + "type": "device", + "type_id": "telephone", + "constraints": [ + { + "comp": "eq", + "component_id": "turningWheel", + "value": [ + 0, + 1, + 2, + 7 + ] + } + ], + "actions": [ + { + "type": "device", + "type_id": "controlBoard", + "message": { + "output": { + "greenLight1": { + "instruction": "blink", + "interval": 0.5, + "delay": 0 + }, + "greenLight2": false, + "greenLight3": false, + "redLight1": false, + "redLight2": false, + "redLight3": { + "instruction": "blink", + "interval": 0.5, + "delay": 0 + } + } + } + } + ] + } + ] + } + ], + "hints": [ + "De knop verzend jouw volgorde", + "Heb je al even gewacht?" + ] + }, + { + "name": "Control puzzel", + "rules": [ + { + "id": "controlSwitch", + "description": "Als de grote switch wordt geflipt, worden alle condities gecheckt (tweede schuif en groene of rode switch aan) en gaat het lichtje branden en de telefoon ringt.", + "limit": 1, + "conditions": [ + { + "type": "device", + "type_id": "controlBoard", + "constraints": [ + { + "component_id": "mainSwitch", + "comp": "eq", + "value": true + }, + { + "component_id": "greenSwitch", + "comp": "eq", + "value": true + }, + { + "component_id": "redSwitch", + "comp": "eq", + "value": true + }, + { + "component_id": "slider2", + "comp": "lt", + "value": 30 + } + ] + } + ], + "actions": [ + { + "type": "device", + "type_id": "telephone", + "message": { + "output": { + "audio": "audio_1" + } + } + }, + { + "type": "device", + "type_id": "controlBoard", + "message": { + "output": { + "greenLight1": false, + "greenLight2": true, + "greenLight3": false, + "redLight1": false, + "redLight2": false, + "redLight3": false + } + } + } + ] + } + ], + "hints": [ + "Zet de schuiven nauwkeurig" + ] + }, + { + "name": "Test puzzel", + "rules": [ + { + "id": "testCondition", + "description": "Condition with all component type constraints for testing", + "limit": 1, + "conditions": [ + { + "type": "device", + "type_id": "testDevice", + "constraints": [ + { + "comp": "contains", + "value": "mystring", + "component_id": "string" + }, + { + "comp": "eq", + "value": [ + "mystring1", + "mystring2" + ], + "component_id": "string-array" + }, + { + "comp": "lte", + "value": 2.5, + "component_id": "numeric" + }, + { + "comp": "contains", + "value": [ + 0, + -1, + 0.5, + 25, + 9.12 + ], + "component_id": "num-array" + }, + { + "comp": "contains", + "value": [ + true, + false, + true + ], + "component_id": "bool-array" + }, + { + "comp": "gt", + "value": { + "instruction": "test" + }, + "component_id": "custom" + } + ] + } + ], + "actions": [], + "hints": [] + } + ] + } + ], + "general_events": [ + { + "name": "Start", + "rules": [ + { + "id": "telephoneRings", + "description": "Als het spel start, moet de telefoon na 1 minuut ringen.", + "limit": 1, + "conditions": [ + { + "type": "timer", + "type_id": "timer1", + "constraints": [ + { + "comp": "eq", + "value": "00:01:00" + } + ] + } + ], + "actions": [ + { + "type": "device", + "type_id": "telephone", + "message": { + "output": { + "audio": "audio_1" + } + } + } + ] + } + ] + }, + { + "name": "Stop", + "rules": [ + { + "id": "flipSwitch", + "description": "Als de knop omgaat, stopt het spel.", + "limit": 1, + "conditions": [ + { + "type": "device", + "type_id": "controlBoard", + "constraints": [ + { + "comp": "eq", + "value": true, + "component_id": "mainSwitch" + } + ] + } + ], + "actions": [ + { + "type": "timer", + "type_id": "timer1", + "message": { + "output": { + "instruction": "stop" + } + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/back-end/resources/testing/timer/testTimerIncorrectInstruction.json b/back-end/resources/testing/timer/testTimerIncorrectInstruction.json new file mode 100644 index 00000000..c4b5e049 --- /dev/null +++ b/back-end/resources/testing/timer/testTimerIncorrectInstruction.json @@ -0,0 +1,46 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "ledLights", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "light": "boolean" + }, + "output": { + "color": "string" + } + } + ], + "puzzles": [ + { + "name": "Telefoon puzzels", + "rules": [ + { + "id": "correctSequence", + "description": "De juiste volgorde van cijfers moet gedraaid worden.", + "limit": 1, + "conditions": [ + ], + "actions": [ + { + "type": "timer", + "type_id": "timer1", + "message": { + "output": { + "instruction": "test", + "value": "Test" + } + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/back-end/resources/testing/timer/testTimerIncorrectSubtractValue.json b/back-end/resources/testing/timer/testTimerIncorrectSubtractValue.json new file mode 100644 index 00000000..9f508095 --- /dev/null +++ b/back-end/resources/testing/timer/testTimerIncorrectSubtractValue.json @@ -0,0 +1,46 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "ledLights", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "light": "boolean" + }, + "output": { + "color": "string" + } + } + ], + "puzzles": [ + { + "name": "Telefoon puzzels", + "rules": [ + { + "id": "correctSequence", + "description": "De juiste volgorde van cijfers moet gedraaid worden.", + "limit": 1, + "conditions": [ + ], + "actions": [ + { + "type": "timer", + "type_id": "timer1", + "message": { + "output": { + "instruction": "subtract", + "value": true + } + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/back-end/resources/testing/timer/testTimerInvalidSubtractValue.json b/back-end/resources/testing/timer/testTimerInvalidSubtractValue.json new file mode 100644 index 00000000..05bf2272 --- /dev/null +++ b/back-end/resources/testing/timer/testTimerInvalidSubtractValue.json @@ -0,0 +1,46 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "ledLights", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "light": "boolean" + }, + "output": { + "color": "string" + } + } + ], + "puzzles": [ + { + "name": "Telefoon puzzels", + "rules": [ + { + "id": "correctSequence", + "description": "De juiste volgorde van cijfers moet gedraaid worden.", + "limit": 1, + "conditions": [ + ], + "actions": [ + { + "type": "timer", + "type_id": "timer1", + "message": { + "output": { + "instruction": "subtract", + "value": "test" + } + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/back-end/resources/testing/timer/testTimerNoInstruction.json b/back-end/resources/testing/timer/testTimerNoInstruction.json new file mode 100644 index 00000000..b3796e26 --- /dev/null +++ b/back-end/resources/testing/timer/testTimerNoInstruction.json @@ -0,0 +1,45 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "ledLights", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "light": "boolean" + }, + "output": { + "color": "string" + } + } + ], + "puzzles": [ + { + "name": "Telefoon puzzels", + "rules": [ + { + "id": "correctSequence", + "description": "De juiste volgorde van cijfers moet gedraaid worden.", + "limit": 1, + "conditions": [ + ], + "actions": [ + { + "type": "timer", + "type_id": "timer1", + "message": { + "output": { + "value": "Test" + } + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/back-end/resources/testing/timer/testTimerNoSubtractValue.json b/back-end/resources/testing/timer/testTimerNoSubtractValue.json new file mode 100644 index 00000000..012e7b94 --- /dev/null +++ b/back-end/resources/testing/timer/testTimerNoSubtractValue.json @@ -0,0 +1,46 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "ledLights", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "light": "boolean" + }, + "output": { + "color": "string" + } + } + ], + "puzzles": [ + { + "name": "Telefoon puzzels", + "rules": [ + { + "id": "correctSequence", + "description": "De juiste volgorde van cijfers moet gedraaid worden.", + "limit": 1, + "conditions": [ + ], + "actions": [ + { + "type": "timer", + "type_id": "timer1", + "message": { + "output": { + "instruction": "subtract", + "random": "lol" + } + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/back-end/resources/testing/wrong-types/testBoolArrayIncorrectType.json b/back-end/resources/testing/wrong-types/testBoolArrayIncorrectType.json new file mode 100644 index 00000000..3e58551c --- /dev/null +++ b/back-end/resources/testing/wrong-types/testBoolArrayIncorrectType.json @@ -0,0 +1,46 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "ledLights", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "light": "bool-array" + }, + "output": { + "color": "string" + } + } + ], + "puzzles": [ + { + "name": "Telefoon puzzels", + "rules": [ + { + "id": "correctSequence", + "description": "De juiste volgorde van cijfers moet gedraaid worden.", + "limit": 1, + "conditions": [ + { + "type": "device", + "type_id": "ledLights", + "constraints": [ + { + "comp": "eq", + "value": [30], + "component_id": "light" + } + ] + } + ], + "actions": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/back-end/resources/testing/wrong-types/testBoolArrayIncorrectType2.json b/back-end/resources/testing/wrong-types/testBoolArrayIncorrectType2.json new file mode 100644 index 00000000..475f68c4 --- /dev/null +++ b/back-end/resources/testing/wrong-types/testBoolArrayIncorrectType2.json @@ -0,0 +1,46 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "ledLights", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "light": "bool-array" + }, + "output": { + "color": "string" + } + } + ], + "puzzles": [ + { + "name": "Telefoon puzzels", + "rules": [ + { + "id": "correctSequence", + "description": "De juiste volgorde van cijfers moet gedraaid worden.", + "limit": 1, + "conditions": [ + { + "type": "device", + "type_id": "ledLights", + "constraints": [ + { + "comp": "eq", + "value": 30, + "component_id": "light" + } + ] + } + ], + "actions": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/back-end/resources/testing/wrong-types/testBooleanIncorrectType.json b/back-end/resources/testing/wrong-types/testBooleanIncorrectType.json new file mode 100644 index 00000000..cbbc39b1 --- /dev/null +++ b/back-end/resources/testing/wrong-types/testBooleanIncorrectType.json @@ -0,0 +1,46 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "ledLights", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "light": "boolean" + }, + "output": { + "color": "string" + } + } + ], + "puzzles": [ + { + "name": "Telefoon puzzels", + "rules": [ + { + "id": "correctSequence", + "description": "De juiste volgorde van cijfers moet gedraaid worden.", + "limit": 1, + "conditions": [ + { + "type": "device", + "type_id": "ledLights", + "constraints": [ + { + "comp": "eq", + "value": 30, + "component_id": "light" + } + ] + } + ], + "actions": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/back-end/resources/testing/wrong-types/testDeviceInputWrongTypeError.json b/back-end/resources/testing/wrong-types/testDeviceInputWrongTypeError.json new file mode 100644 index 00000000..51537684 --- /dev/null +++ b/back-end/resources/testing/wrong-types/testDeviceInputWrongTypeError.json @@ -0,0 +1,21 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "telephone", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "turningWheel": 0 + }, + "output": { + "audio": "audio1", + "ringTone": "string" + } + } + ] +} \ No newline at end of file diff --git a/back-end/resources/testing/wrong-types/testDeviceOutputWrongTypeError.json b/back-end/resources/testing/wrong-types/testDeviceOutputWrongTypeError.json new file mode 100644 index 00000000..18b9b686 --- /dev/null +++ b/back-end/resources/testing/wrong-types/testDeviceOutputWrongTypeError.json @@ -0,0 +1,21 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "telephone", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "turningWheel": "int-array" + }, + "output": { + "audio": 0, + "ringTone": "string" + } + } + ] +} \ No newline at end of file diff --git a/back-end/resources/testing/wrong-types/testIncorrectTypeComparison.json b/back-end/resources/testing/wrong-types/testIncorrectTypeComparison.json new file mode 100644 index 00000000..ef167a4c --- /dev/null +++ b/back-end/resources/testing/wrong-types/testIncorrectTypeComparison.json @@ -0,0 +1,46 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "ledLights", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "light": "boolean" + }, + "output": { + "color": "string" + } + } + ], + "puzzles": [ + { + "name": "Telefoon puzzels", + "rules": [ + { + "id": "correctSequence", + "description": "De juiste volgorde van cijfers moet gedraaid worden.", + "limit": 1, + "conditions": [ + { + "type": "device", + "type_id": "ledLights", + "constraints": [ + { + "comp": 30, + "value": "30:00", + "component_id": "light" + } + ] + } + ], + "actions": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/back-end/resources/testing/wrong-types/testIncorrectTypeComponentID.json b/back-end/resources/testing/wrong-types/testIncorrectTypeComponentID.json new file mode 100644 index 00000000..5a651dfc --- /dev/null +++ b/back-end/resources/testing/wrong-types/testIncorrectTypeComponentID.json @@ -0,0 +1,46 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "ledLights", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "light": "boolean" + }, + "output": { + "color": "string" + } + } + ], + "puzzles": [ + { + "name": "Telefoon puzzels", + "rules": [ + { + "id": "correctSequence", + "description": "De juiste volgorde van cijfers moet gedraaid worden.", + "limit": 1, + "conditions": [ + { + "type": "device", + "type_id": "ledLights", + "constraints": [ + { + "comp": "eq", + "value": "30:00", + "component_id": 30 + } + ] + } + ], + "actions": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/back-end/resources/testing/wrong-types/testInvalidTypeComparison.json b/back-end/resources/testing/wrong-types/testInvalidTypeComparison.json new file mode 100644 index 00000000..737504c7 --- /dev/null +++ b/back-end/resources/testing/wrong-types/testInvalidTypeComparison.json @@ -0,0 +1,45 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "ledLights", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "light": "boolean" + }, + "output": { + "color": "string" + } + } + ], + "puzzles": [ + { + "name": "Telefoon puzzels", + "rules": [ + { + "id": "correctSequence", + "description": "De juiste volgorde van cijfers moet gedraaid worden.", + "limit": 1, + "conditions": [ + { + "type": "timer", + "type_id": "timer1", + "constraints": [ + { + "comp": "test", + "value": "30:00" + } + ] + } + ], + "actions": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/back-end/resources/testing/wrong-types/testNumArrayIncorrectType.json b/back-end/resources/testing/wrong-types/testNumArrayIncorrectType.json new file mode 100644 index 00000000..d7d8bdf9 --- /dev/null +++ b/back-end/resources/testing/wrong-types/testNumArrayIncorrectType.json @@ -0,0 +1,46 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "ledLights", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "light": "num-array" + }, + "output": { + "color": "string" + } + } + ], + "puzzles": [ + { + "name": "Telefoon puzzels", + "rules": [ + { + "id": "correctSequence", + "description": "De juiste volgorde van cijfers moet gedraaid worden.", + "limit": 1, + "conditions": [ + { + "type": "device", + "type_id": "ledLights", + "constraints": [ + { + "comp": "eq", + "value": [true], + "component_id": "light" + } + ] + } + ], + "actions": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/back-end/resources/testing/wrong-types/testNumArrayIncorrectType2.json b/back-end/resources/testing/wrong-types/testNumArrayIncorrectType2.json new file mode 100644 index 00000000..5fdfdcd3 --- /dev/null +++ b/back-end/resources/testing/wrong-types/testNumArrayIncorrectType2.json @@ -0,0 +1,46 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "ledLights", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "light": "num-array" + }, + "output": { + "color": "string" + } + } + ], + "puzzles": [ + { + "name": "Telefoon puzzels", + "rules": [ + { + "id": "correctSequence", + "description": "De juiste volgorde van cijfers moet gedraaid worden.", + "limit": 1, + "conditions": [ + { + "type": "device", + "type_id": "ledLights", + "constraints": [ + { + "comp": "eq", + "value": "test", + "component_id": "light" + } + ] + } + ], + "actions": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/back-end/resources/testing/wrong-types/testNumericIncorrectType.json b/back-end/resources/testing/wrong-types/testNumericIncorrectType.json new file mode 100644 index 00000000..32d0ea3e --- /dev/null +++ b/back-end/resources/testing/wrong-types/testNumericIncorrectType.json @@ -0,0 +1,46 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "ledLights", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "light": "numeric" + }, + "output": { + "color": "string" + } + } + ], + "puzzles": [ + { + "name": "Telefoon puzzels", + "rules": [ + { + "id": "correctSequence", + "description": "De juiste volgorde van cijfers moet gedraaid worden.", + "limit": 1, + "conditions": [ + { + "type": "device", + "type_id": "ledLights", + "constraints": [ + { + "comp": "eq", + "value": "30", + "component_id": "light" + } + ] + } + ], + "actions": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/back-end/resources/testing/wrong-types/testStringArrayIncorrectType.json b/back-end/resources/testing/wrong-types/testStringArrayIncorrectType.json new file mode 100644 index 00000000..ded6b4c7 --- /dev/null +++ b/back-end/resources/testing/wrong-types/testStringArrayIncorrectType.json @@ -0,0 +1,46 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "ledLights", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "light": "string-array" + }, + "output": { + "color": "string" + } + } + ], + "puzzles": [ + { + "name": "Telefoon puzzels", + "rules": [ + { + "id": "correctSequence", + "description": "De juiste volgorde van cijfers moet gedraaid worden.", + "limit": 1, + "conditions": [ + { + "type": "device", + "type_id": "ledLights", + "constraints": [ + { + "comp": "eq", + "value": [30], + "component_id": "light" + } + ] + } + ], + "actions": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/back-end/resources/testing/wrong-types/testStringArrayIncorrectType2.json b/back-end/resources/testing/wrong-types/testStringArrayIncorrectType2.json new file mode 100644 index 00000000..6e924e8a --- /dev/null +++ b/back-end/resources/testing/wrong-types/testStringArrayIncorrectType2.json @@ -0,0 +1,46 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "ledLights", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "light": "string-array" + }, + "output": { + "color": "string" + } + } + ], + "puzzles": [ + { + "name": "Telefoon puzzels", + "rules": [ + { + "id": "correctSequence", + "description": "De juiste volgorde van cijfers moet gedraaid worden.", + "limit": 1, + "conditions": [ + { + "type": "device", + "type_id": "ledLights", + "constraints": [ + { + "comp": "eq", + "value": 30, + "component_id": "light" + } + ] + } + ], + "actions": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/back-end/resources/testing/wrong-types/testStringIncorrectType.json b/back-end/resources/testing/wrong-types/testStringIncorrectType.json new file mode 100644 index 00000000..90f15bb7 --- /dev/null +++ b/back-end/resources/testing/wrong-types/testStringIncorrectType.json @@ -0,0 +1,46 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "ledLights", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "light": "string" + }, + "output": { + "color": "string" + } + } + ], + "puzzles": [ + { + "name": "Telefoon puzzels", + "rules": [ + { + "id": "correctSequence", + "description": "De juiste volgorde van cijfers moet gedraaid worden.", + "limit": 1, + "conditions": [ + { + "type": "device", + "type_id": "ledLights", + "constraints": [ + { + "comp": "eq", + "value": 30, + "component_id": "light" + } + ] + } + ], + "actions": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/back-end/resources/testing/wrong-types/testTimerIncorrectPattern.json b/back-end/resources/testing/wrong-types/testTimerIncorrectPattern.json new file mode 100644 index 00000000..7c16c715 --- /dev/null +++ b/back-end/resources/testing/wrong-types/testTimerIncorrectPattern.json @@ -0,0 +1,45 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "ledLights", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "light": "boolean" + }, + "output": { + "color": "string" + } + } + ], + "general_events": [ + { + "name": "Telefoon puzzels", + "rules": [ + { + "id": "correctSequence", + "description": "De juiste volgorde van cijfers moet gedraaid worden.", + "limit": 1, + "conditions": [ + { + "type": "timer", + "type_id": "timer1", + "constraints": [ + { + "comp": "eq", + "value": "30:00" + } + ] + } + ], + "actions": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/back-end/resources/testing/wrong-types/testTimerIncorrectType.json b/back-end/resources/testing/wrong-types/testTimerIncorrectType.json new file mode 100644 index 00000000..215fd469 --- /dev/null +++ b/back-end/resources/testing/wrong-types/testTimerIncorrectType.json @@ -0,0 +1,45 @@ +{ + "general": { + "name": "Escape X", + "duration": "00:30:00", + "host": "192.0.0.84", + "port": 1883 + }, + "devices": [ + { + "id": "ledLights", + "description": "The telephone can ring and display a message. It will also record the numbers turned, and send these as sequence", + "input": { + "light": "boolean" + }, + "output": { + "color": "string" + } + } + ], + "puzzles": [ + { + "name": "Telefoon puzzels", + "rules": [ + { + "id": "correctSequence", + "description": "De juiste volgorde van cijfers moet gedraaid worden.", + "limit": 1, + "conditions": [ + { + "type": "timer", + "type_id": "timer1", + "constraints": [ + { + "comp": "eq", + "value": 30 + } + ] + } + ], + "actions": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/back-end/src/sciler/communication/communication.go b/back-end/src/sciler/communication/communication.go new file mode 100644 index 00000000..4b9f1b42 --- /dev/null +++ b/back-end/src/sciler/communication/communication.go @@ -0,0 +1,78 @@ +package communication + +import ( + "errors" + "fmt" + mqtt "github.com/eclipse/paho.mqtt.golang" + logger "github.com/sirupsen/logrus" + "time" +) + +// Communicator is a type that maintains communication with the front-end and the client computers. +type Communicator struct { + client mqtt.Client + topicsOfInterest []string +} + +// NewCommunicator is a constructor for a Communicator +func NewCommunicator(host string, port int, topicsOfInterest []string) *Communicator { + opts := mqtt.NewClientOptions() + opts.AddBroker(fmt.Sprintf("%s://%s:%d", "tcp", host, port)) + opts.SetClientID("back-end") + opts.SetConnectionLostHandler(onConnectionLost) + client := mqtt.NewClient(opts) + return &Communicator{client, topicsOfInterest} +} + +// Start is a function that will start the communication by connecting to the broker and subscribing to all topics of interest +func (communicator *Communicator) Start(handler mqtt.MessageHandler) { + _ = action(communicator.client.Connect, "connect", -1) + topics := make(map[string]byte) + for _, topic := range communicator.topicsOfInterest { + topics[topic] = byte(0) + } + _ = action(func() mqtt.Token { + return communicator.client.SubscribeMultiple(topics, handler) + }, "subscribing", -1) +} + +func onConnectionLost(client mqtt.Client, e error) { + logger.Warn(fmt.Sprintf("Connection lost : %v", e)) + if client.IsConnected() { + client.Disconnect(500) + } + // TODO reconnect +} + +// Publish is a method that will send a message to a specific topic +func (communicator *Communicator) Publish(topic string, message string, retrials int) { + err := action(func() mqtt.Token { + return communicator.client.Publish(topic, byte(0), false, message) + }, "publish", retrials) + if err == errors.New("action failed") { + // TODO reconnect + } +} + +// action is a function that will execute a communication action to the broker +// actionType is the description of the action in one word for logging +// retrials is the maximum number of times the action re-executed when failing, when retrials < 0, it is tried forever +func action(action func() mqtt.Token, actionType string, retrials int) error { + for i := 0; i < retrials || retrials < 0; i++ { + token := action() + var err error = nil + if token.Wait() && token.Error() != nil { + logger.Warnf("Fail to %s, %v", actionType, token.Error()) + time.Sleep(1 * time.Second) + + logger.Infof("Retry %d to %s", i+1, actionType) + err = errors.New("action eventually successful") + continue + } else { + logger.Infof("%s successful!", actionType) + return err + } + } + logger.Errorf("All retries to %s failed, giving up :(", actionType) + return errors.New("action failed") +} diff --git a/back-end/src/sciler/communication/communication_test.go b/back-end/src/sciler/communication/communication_test.go new file mode 100644 index 00000000..21117015 --- /dev/null +++ b/back-end/src/sciler/communication/communication_test.go @@ -0,0 +1,173 @@ +package communication + +import ( + "errors" + "fmt" + "github.com/eclipse/paho.mqtt.golang" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "testing" + "time" +) + +func TestNewCommunicator(t *testing.T) { + type args struct { + host string + port int + topicsOfInterest []string + } + + optionsLocal := mqtt.NewClientOptions() + optionsLocal.AddBroker(fmt.Sprintf("%s://%s:%d", "tcp", "localhost", 1883)) + optionsLocal.SetClientID("back-end") + optionsLocal.SetConnectionLostHandler(onConnectionLost) + + tests := []struct { + name string + args args + want *Communicator + }{ + { + name: "two topics", + args: args{ + host: "localhost", + port: 1883, + topicsOfInterest: []string{"test", "back-end"}, + }, + want: &Communicator{ + client: mqtt.NewClient(optionsLocal), + topicsOfInterest: []string{"test", "back-end"}, + }, + }, + { + name: "no topics", + args: args{ + host: "localhost", + port: 1883, + topicsOfInterest: nil, + }, + want: &Communicator{ + client: mqtt.NewClient(optionsLocal), + topicsOfInterest: nil, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := NewCommunicator(tt.args.host, tt.args.port, tt.args.topicsOfInterest) + assert.Equal(t, got.topicsOfInterest, tt.want.topicsOfInterest) + }) + } +} + +type TokenMockSuccess struct { + mock.Mock +} + +func (t TokenMockSuccess) Wait() bool { + return false +} + +func (t TokenMockSuccess) WaitTimeout(time.Duration) bool { + return true +} + +func (t TokenMockSuccess) Error() error { + return nil +} + +type TokenMockFailure struct { + mock.Mock +} + +func (t TokenMockFailure) Wait() bool { + return true +} + +func (t TokenMockFailure) WaitTimeout(time.Duration) bool { + return true +} + +func (t TokenMockFailure) Error() error { + return errors.New("testing error of TokenMockFailure") +} + +type ClientMock struct { + mock.Mock +} + +func (c ClientMock) IsConnected() bool { + return true +} + +func (c ClientMock) IsConnectionOpen() bool { + return true +} + +func (c ClientMock) Connect() mqtt.Token { + args := c.Called() + return args.Get(0).(mqtt.Token) +} + +func (c ClientMock) Disconnect(quiesce uint) { + return +} + +func (c ClientMock) Publish(topic string, qos byte, retained bool, payload interface{}) mqtt.Token { + args := c.Called(topic, qos, retained, payload) + return args.Get(0).(mqtt.Token) +} + +func (c ClientMock) Subscribe(topic string, qos byte, callback mqtt.MessageHandler) mqtt.Token { + return new(TokenMockSuccess) +} + +func (c ClientMock) SubscribeMultiple(filters map[string]byte, callback mqtt.MessageHandler) mqtt.Token { + return new(TokenMockSuccess) +} + +func (c ClientMock) Unsubscribe(topics ...string) mqtt.Token { + return new(TokenMockSuccess) +} + +func (c ClientMock) AddRoute(topic string, callback mqtt.MessageHandler) { + return +} + +func (c ClientMock) OptionsReader() mqtt.ClientOptionsReader { + return *new(mqtt.ClientOptionsReader) +} + +func TestCommunicator_Start(t *testing.T) { + client := new(ClientMock) + communicator := Communicator{ + client: client, + topicsOfInterest: []string{"back-end", "test"}, + } + + client.On("Connect").Return(new(TokenMockSuccess)).Once() + communicator.Start(func(client mqtt.Client, message mqtt.Message) {}) + client.AssertExpectations(t) +} + +func TestCommunicator_Publish(t *testing.T) { + client := new(ClientMock) + communicator := Communicator{ + client: client, + topicsOfInterest: []string{"back-end", "test"}, + } + client.On("Publish", "test", byte(0), false, "json").Return(new(TokenMockSuccess)).Once() + communicator.Publish("test", "json", 3) + client.AssertExpectations(t) +} + +func TestCommunicator_PublishFailure(t *testing.T) { + client := new(ClientMock) + communicator := Communicator{ + client: client, + topicsOfInterest: []string{"back-end", "test"}, + } + client.On("Publish", "test", byte(0), false, "json").Return(new(TokenMockFailure)).Times(3) + communicator.Publish("test", "json", 3) + client.AssertExpectations(t) +} diff --git a/back-end/src/sciler/config/configHandler.go b/back-end/src/sciler/config/configHandler.go new file mode 100644 index 00000000..96b67cf7 --- /dev/null +++ b/back-end/src/sciler/config/configHandler.go @@ -0,0 +1,201 @@ +package config + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "reflect" + "regexp" +) + +// ReadFile reads filename and call readJSON on contents. +func ReadFile(filename string) WorkingConfig { + dat, err := ioutil.ReadFile(filename) + if err != nil { + panic(errors.New("Could not read file " + filename).Error()) + } + config := ReadJSON(dat) + return config +} + +// ReadJSON transforms json file into config object. +func ReadJSON(input []byte) WorkingConfig { + var config ReadConfig + jsonErr := json.Unmarshal(input, &config) + if jsonErr != nil { + panic(jsonErr.Error()) + } + newConfig, configErr := generateDataStructures(config) + if configErr != nil { + panic(configErr.Error()) + } + return newConfig +} + +// Creates additional structures: forms device and rule maps; +// and maps for actions and constraints (retrieved from puzzles and general events), with condition pointer as key +func generateDataStructures(readConfig ReadConfig) (WorkingConfig, error) { + var config WorkingConfig + // Copy information from read config to working config. + config.General = readConfig.General + config.Puzzles = readConfig.Puzzles + config.GeneralEvents = readConfig.GeneralEvents + config.Devices = make(map[string]Device) + for _, d := range readConfig.Devices { + config.Devices[d.ID] = d + } + + // Create additional data structures. + config.Rules = make(map[string]Rule) + config.ActionMap = make(map[string][]Action) + config.ConstraintMap = make(map[string]map[string][]interface{}) + for i, p := range config.Puzzles { + for j, r := range p.Rules { + config.Rules[r.ID] = r + actions, err := checkActions(config.Devices, r.Actions) + if err != nil { + return config, err + } + for k, c := range r.Conditions { + // Set both original pointer's and current pointer's conditions + config.Puzzles[i].Rules[j].Conditions[k].RuleID = r.ID + c.RuleID = r.ID + config.ActionMap[c.GetID()] = actions + constraints, err := makeConstraints(config.Devices, c) + if err != nil { + return config, err + } + config.ConstraintMap[c.GetID()] = constraints + } + } + } + for i, e := range config.GeneralEvents { + for j, r := range e.Rules { + config.Rules[r.ID] = r + actions, err := checkActions(config.Devices, r.Actions) + if err != nil { + return config, err + } + for k, c := range r.Conditions { + // Set both original pointer's and current pointer's conditions + config.GeneralEvents[i].Rules[j].Conditions[k].RuleID = r.ID + c.RuleID = r.ID + config.ActionMap[c.GetID()] = actions + constraints, err := makeConstraints(config.Devices, c) + if err != nil { + return config, err + } + config.ConstraintMap[c.GetID()] = constraints + } + } + } + return config, nil +} + +func checkActions(devices map[string]Device, actions []Action) ([]Action, error) { + for _, a := range actions { + output := a.Message.Output + if a.Type == "timer" { + instruction, ok := output["instruction"] + if !ok { + return actions, errors.New("timer should have an instruction defined") + } + if instruction != "stop" && instruction != "subtract" { + return actions, errors.New("timer should have an instruction defined, which is either stop or subtract") + } + if instruction == "subtract" { + value, ok2 := output["value"] + if !ok2 { + return actions, errors.New("timer with subtract instruction should have value") + } + if reflect.TypeOf(value).Kind() != reflect.String { + return actions, errors.New("timer with subtract instruction should have value in string format") + } + var rgxPat = regexp.MustCompile(`^[0-9]{2}:[0-9]{2}:[0-9]{2}$`) + if !rgxPat.MatchString(value.(string)) { + return actions, errors.New(value.(string) + " did not match pattern 'hh:mm:ss'") + } + } + } else if a.Type == "device" { + device, ok := devices[a.TypeID] + if !ok { + return actions, errors.New("device with id " + a.TypeID + " not found in map") + } + for key, value := range output { + expectedType, ok2 := device.Output[key] + if !ok2 { + return actions, errors.New("component id: " + key + " not found in device input") + } + // value should be of type specified in device output + err := CheckComponentType(expectedType, value) + if err != nil { + return actions, err + } + } + } else { + return actions, errors.New("invalid type of action: " + a.Type) + } + } + return actions, nil +} + +// CheckComponentType checks if value is of componentType and returns error if not. +func CheckComponentType(componentType interface{}, value interface{}) error { + switch componentType { + case "string": + if reflect.TypeOf(value).Kind() != reflect.String { + return errors.New("Value was not of type string: " + fmt.Sprint(value)) + } + case "numeric": + if reflect.TypeOf(value).Kind() != reflect.Float64 { + return errors.New("Value was not of type numeric: " + fmt.Sprint(value)) + } + case "boolean": + if reflect.TypeOf(value).Kind() != reflect.Bool { + return errors.New("Value was not of type boolean: " + fmt.Sprint(value)) + } + case "num-array": + if reflect.TypeOf(value).Kind() != reflect.Slice { + return errors.New("Value was not of type array: " + fmt.Sprint(value)) + } + array := value.([]interface{}) + for i := range array { + if reflect.TypeOf(array[i]).Kind() != reflect.Float64 { + return errors.New("Value was not of type num-array: " + fmt.Sprint(value)) + } + } + case "string-array": + if reflect.TypeOf(value).Kind() != reflect.Slice { + return errors.New("Value was not of type array: " + fmt.Sprint(value)) + } + array := value.([]interface{}) + for i := range array { + if reflect.TypeOf(array[i]).Kind() != reflect.String { + return errors.New("Value was not of type string-array: " + fmt.Sprint(value)) + } + } + case "bool-array": + if reflect.TypeOf(value).Kind() != reflect.Slice { + return errors.New("Value was not of type array: " + fmt.Sprint(value)) + } + array := value.([]interface{}) + for i := range array { + if reflect.TypeOf(array[i]).Kind() != reflect.Bool { + return errors.New("Value was not of type bool-array: " + fmt.Sprint(value)) + } + } + default: + // Value is of custom type, must be checked at client computer level + return nil + } + return nil +} + +//TODO handling multiple OR/AND - new issue +//TODO front-end - new issue +//TODO multiple errors? +//TODO check device present +//TODO check component present +//TODO check output types +//TODO catch non-existing keys in json diff --git a/back-end/src/sciler/config/configTypes.go b/back-end/src/sciler/config/configTypes.go new file mode 100644 index 00000000..3445bcca --- /dev/null +++ b/back-end/src/sciler/config/configTypes.go @@ -0,0 +1,88 @@ +package config + +import ( + "crypto/sha512" + "encoding/base64" + "fmt" +) + +// ReadConfig specifies all configuration elements of an escape room. +type ReadConfig struct { + General General `json:"general"` + Devices []Device `json:"devices"` + Puzzles []Puzzle `json:"puzzles"` + GeneralEvents []GeneralEvent `json:"general_events"` +} + +// General is a struct that describes the configurations of an escape room. +type General struct { + Name string `json:"name"` + Duration string `json:"duration"` + Host string `json:"host"` + Port int `json:"port"` +} + +// Device is a struct that describes the configurations of a device in the room. +type Device struct { + ID string `json:"id"` + Description string `json:"description"` + Input map[string]string `json:"input"` + Output OutputObject `json:"output"` +} + +// Puzzle is a struct that describes contents of a puzzle. +type Puzzle struct { + Name string `json:"name"` + Rules []Rule `json:"rules"` + Hints []string `json:"hints"` +} + +// GeneralEvent defines a general event, like start. +type GeneralEvent struct { + Name string `json:"name"` + Rules []Rule `json:"rules"` +} + +// Rule is a struct that describes how action flow is handled in the escape room. +type Rule struct { + ID string `json:"id"` + Description string `json:"description"` + Limit int `json:"limit"` + Conditions []Condition `json:"conditions"` + Actions []Action `json:"actions"` +} + +// Condition is a struct that determines when rules are fired. +type Condition struct { + Type string `json:"type"` + TypeID string `json:"type_id"` + Constraints []ConstraintInfo `json:"constraints"` + RuleID string +} + +// ConstraintInfo is a general map allowing to read input constraints, which are later parsed to real constraint objects. +type ConstraintInfo map[string]interface{} + +// GetID returns hash of condition, limited to the first 24 characters +func (condition *Condition) GetID() string { + hasher := sha512.New() + toHash := condition.RuleID + condition.TypeID + fmt.Sprint(condition.Constraints) + hasher.Write([]byte(toHash)) + hash := base64.URLEncoding.EncodeToString(hasher.Sum(nil)) + return hash[0:24] +} + +// Action is a struct that determines what happens when a rule is fired. +type Action struct { + Type string `json:"type"` + TypeID string `json:"type_id"` + Message ActionMessage `json:"message"` +} + +// ActionMessage can be sent across clients of the brokers. +type ActionMessage struct { + Output OutputObject `json:"output"` +} + +// OutputObject contains a map defining either input or output. +type OutputObject map[string]interface{} diff --git a/back-end/src/sciler/config/configTypes_test.go b/back-end/src/sciler/config/configTypes_test.go new file mode 100644 index 00000000..4b5b3b22 --- /dev/null +++ b/back-end/src/sciler/config/configTypes_test.go @@ -0,0 +1,13 @@ +package config + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestCondition_GetID(t *testing.T) { + constraint := ConstraintInfo{"comp": "eq", "value": 4, "component_id": "turn"} + cond := Condition{"device", "telephone", []ConstraintInfo{constraint}, "ruleID"} + cond2 := Condition{"device", "telephone", []ConstraintInfo{constraint}, "ruleID"} + assert.Equal(t, cond.GetID(), cond2.GetID(), "IDs of conditions with same values should be equal") +} diff --git a/back-end/src/sciler/config/constraintHandler.go b/back-end/src/sciler/config/constraintHandler.go new file mode 100644 index 00000000..b4ee903a --- /dev/null +++ b/back-end/src/sciler/config/constraintHandler.go @@ -0,0 +1,228 @@ +package config + +import ( + "errors" + "fmt" + "reflect" + "regexp" +) + +// Forms constraint to processed constraint, which includes type. Value is also checked to be of that type. +func makeConstraints(devices map[string]Device, condition Condition) (map[string][]interface{}, error) { + var constraints = make(map[string][]interface{}) + for _, cons := range condition.Constraints { + // If the cons is enforced on a device, then the cons value type must match the input type. + if condition.Type == "device" { + generalConstraint, constraintError := createConstraint(cons, true) + if constraintError != nil { + return constraints, constraintError + } + device, ok := devices[condition.TypeID] + if !ok { + return constraints, errors.New("device with id " + condition.TypeID + " not found in map") + } + newConstraint, constraintType, err := checkDeviceType(cons, generalConstraint, device) + if err != nil { + return constraints, err + } + constraints[constraintType] = append(constraints[constraintType], newConstraint) + } else + // If the cons is enforced on a timer, then the cons value type must be in format "hh:mm:ss". + if condition.Type == "timer" { + generalConstraint, constraintError := createConstraint(cons, false) + if constraintError != nil { + return constraints, constraintError + } + if reflect.TypeOf(cons["value"]).Kind() != reflect.String { + return constraints, errors.New(fmt.Sprint(cons["value"]) + " cannot be cast to string, for time constraint") + } + var rgxPat = regexp.MustCompile(`^[0-9]{2}:[0-9]{2}:[0-9]{2}$`) + if !rgxPat.MatchString(cons["value"].(string)) { + return constraints, errors.New(fmt.Sprint(cons["value"]) + " did not match pattern 'hh:mm:ss'") + } + constraints["timer"] = append(constraints["timer"], + ConstraintTimer{generalConstraint, cons["value"].(string)}) + } else { + return constraints, errors.New("invalid type of condition: " + condition.Type) + } + } + return constraints, nil +} + +// Checks constraint value to be of type, retrieved from device input. +func checkDeviceType(constraint ConstraintInfo, generalConstraint Constraint, device Device) (interface{}, string, error) { + componentType, ok := device.Input[constraint["component_id"].(string)] + if !ok { + return Constraint{}, "", errors.New("component id: " + constraint["component_id"].(string) + " not found in device input") + } + err := CheckComponentType(componentType, constraint["value"]) + if err != nil { + return Constraint{}, "", err + } + switch componentType { + case "string": + return ConstraintString{generalConstraint, constraint["value"].(string)}, "string", nil + case "numeric": + return ConstraintNumeric{generalConstraint, constraint["value"].(float64)}, "numeric", nil + case "boolean": + return ConstraintBool{generalConstraint, constraint["value"].(bool)}, "boolean", nil + case "num-array": + array := constraint["value"].([]interface{}) + var newArray []float64 + for i := range array { + newArray = append(newArray, array[i].(float64)) + } + return ConstraintNumericArray{generalConstraint, newArray}, "num-array", nil + case "string-array": + array := constraint["value"].([]interface{}) + var newArray []string + for i := range array { + newArray = append(newArray, array[i].(string)) + } + return ConstraintStringArray{generalConstraint, newArray}, "string-array", nil + case "bool-array": + array := constraint["value"].([]interface{}) + var newArray []bool + for i := range array { + newArray = append(newArray, array[i].(bool)) + } + return ConstraintBoolArray{generalConstraint, newArray}, "bool-array", nil + default: + // Value is of custom type, must be checked at client computer level + return ConstraintCustomType{generalConstraint, constraint["value"]}, "custom", nil + } +} + +// Check if constraint can be constructed, with string comparison and string component id. +// includeCompo specifies whether to include the component id in the constraint, which is not the case for timer type. +func createConstraint(constraint ConstraintInfo, includeComponentID bool) (Constraint, error) { + var err error + if reflect.TypeOf(constraint["comp"]).Kind() != reflect.String { + return Constraint{}, errors.New("comparison '" + fmt.Sprint(constraint["comp"]) + "' cannot be cast to string") + } + if !checkComparison(constraint["comp"].(string)) { + return Constraint{}, errors.New("comparison should be 'eq', 'lt', 'lte', 'gt', 'gte' or 'contains'") + } + if includeComponentID { + if reflect.TypeOf(constraint["component_id"]).Kind() != reflect.String { + return Constraint{}, errors.New("component_id " + fmt.Sprint(constraint["component_id"]) + " cannot be cast to string") + } + return Constraint{constraint["comp"].(string), constraint["component_id"].(string)}, err + } + return Constraint{constraint["comp"].(string), ""}, err +} + +// Check if comparison is valid +func checkComparison(comparison string) bool { + possibleComparisons := []string{"eq", "lt", "lte", "gt", "gte", "contains"} + for _, ex := range possibleComparisons { + if ex == comparison { + return true + } + } + return false +} + +// GetConstraintTimer struct object from map +func GetConstraintTimer(config WorkingConfig, conditionID string) []ConstraintTimer { + var constraintArray []ConstraintTimer + if config.ConstraintMap[conditionID] == nil { + panic(errors.New("Condition ID: " + conditionID + " not in constraint map").Error()) + } + var resultArray []interface{} = config.ConstraintMap[conditionID]["timer"] + for i := range resultArray { + constraintArray = append(constraintArray, resultArray[i].(ConstraintTimer)) + } + return constraintArray +} + +// GetConstraintBool struct object from map +func GetConstraintBool(config WorkingConfig, conditionID string) []ConstraintBool { + var constraintArray []ConstraintBool + if config.ConstraintMap[conditionID] == nil { + panic(errors.New("Condition ID: " + conditionID + " not in constraint map").Error()) + } + var resultArray []interface{} = config.ConstraintMap[conditionID]["boolean"] + for i := range resultArray { + constraintArray = append(constraintArray, resultArray[i].(ConstraintBool)) + } + return constraintArray +} + +// GetConstraintString struct object from map +func GetConstraintString(config WorkingConfig, conditionID string) []ConstraintString { + var constraintArray []ConstraintString + if config.ConstraintMap[conditionID] == nil { + panic(errors.New("Condition ID: " + conditionID + " not in constraint map").Error()) + } + var resultArray []interface{} = config.ConstraintMap[conditionID]["string"] + for i := range resultArray { + constraintArray = append(constraintArray, resultArray[i].(ConstraintString)) + } + return constraintArray +} + +// GetConstraintNumeric struct object from map +func GetConstraintNumeric(config WorkingConfig, conditionID string) []ConstraintNumeric { + var constraintArray []ConstraintNumeric + if config.ConstraintMap[conditionID] == nil { + panic(errors.New("Condition ID: " + conditionID + " not in constraint map").Error()) + } + var resultArray []interface{} = config.ConstraintMap[conditionID]["numeric"] + for i := range resultArray { + constraintArray = append(constraintArray, resultArray[i].(ConstraintNumeric)) + } + return constraintArray +} + +// GetConstraintStringArray struct object from map +func GetConstraintStringArray(config WorkingConfig, conditionID string) []ConstraintStringArray { + var constraintArray []ConstraintStringArray + if config.ConstraintMap[conditionID] == nil { + panic(errors.New("Condition ID: " + conditionID + " not in constraint map").Error()) + } + var resultArray []interface{} = config.ConstraintMap[conditionID]["string-array"] + for i := range resultArray { + constraintArray = append(constraintArray, resultArray[i].(ConstraintStringArray)) + } + return constraintArray +} + +// GetConstraintBoolArray struct object from map +func GetConstraintBoolArray(config WorkingConfig, conditionID string) []ConstraintBoolArray { + var constraintArray []ConstraintBoolArray + if config.ConstraintMap[conditionID] == nil { + panic(errors.New("Condition ID: " + conditionID + " not in constraint map").Error()) + } + var resultArray []interface{} = config.ConstraintMap[conditionID]["bool-array"] + for i := range resultArray { + constraintArray = append(constraintArray, resultArray[i].(ConstraintBoolArray)) + } + return constraintArray +} + +// GetConstraintNumArray struct object from map +func GetConstraintNumArray(config WorkingConfig, conditionID string) []ConstraintNumericArray { + var constraintArray []ConstraintNumericArray + if config.ConstraintMap[conditionID] == nil { + panic(errors.New("Condition ID: " + conditionID + " not in constraint map").Error()) + } + var resultArray []interface{} = config.ConstraintMap[conditionID]["num-array"] + for i := range resultArray { + constraintArray = append(constraintArray, resultArray[i].(ConstraintNumericArray)) + } + return constraintArray +} + +// GetConstraintCustomType struct object from map +func GetConstraintCustomType(config WorkingConfig, conditionID string) []ConstraintCustomType { + var constraintArray []ConstraintCustomType + if config.ConstraintMap[conditionID] == nil { + panic(errors.New("Condition ID: " + conditionID + " not in constraint map").Error()) + } + var resultArray []interface{} = config.ConstraintMap[conditionID]["custom"] + for i := range resultArray { + constraintArray = append(constraintArray, resultArray[i].(ConstraintCustomType)) + } + return constraintArray +} diff --git a/back-end/src/sciler/config/errorConfig_test.go b/back-end/src/sciler/config/errorConfig_test.go new file mode 100644 index 00000000..0859f473 --- /dev/null +++ b/back-end/src/sciler/config/errorConfig_test.go @@ -0,0 +1,289 @@ +package config + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +/////////////////////////////////// Test errors +func TestDurationError(t *testing.T) { + filename := "../../../resources/testing/testDurationError.json" + assert.PanicsWithValue(t, + "json: cannot unmarshal number into Go struct field General.general.duration of type string", + func() { ReadFile(filename) }, + "Incorrect json (duration in int) should panic") +} + +func TestFileError(t *testing.T) { + filename := "missing.json" + assert.PanicsWithValue(t, + "Could not read file missing.json", + func() { ReadFile(filename) }, + "Could not find json file") +} + +/////////////////////////////////// Wrong reference to device +func TestDeviceInputWrongTypeError(t *testing.T) { + filename := "../../../resources/testing/wrong-types/testDeviceInputWrongTypeError.json" + assert.PanicsWithValue(t, + "json: cannot unmarshal number into Go struct field Device.devices.input of type string", + func() { ReadFile(filename) }, + "Incorrect json (no input type in string format) should panic") +} + +// Currently not relevant as device output is allowed to take in a map of type/instruction +//func TestDeviceOutputWrongTypeError(t *testing.T) { +// filename := "../../../resources/testing/wrong-types/testDeviceOutputWrongTypeError.json" +// assert.PanicsWithValue(t, +// "json: cannot unmarshal number into Go struct field Device.devices.output of type string", +// func() { ReadFile(filename) }, +// "Incorrect json (no output type in string format) should panic") +//} + +func TestDeviceActionNotPresent(t *testing.T) { + filename := "../../../resources/testing/testDeviceActionNotPresent.json" + assert.PanicsWithValue(t, + "device with id telephone not found in map", + func() { ReadFile(filename) }, + "Device used in action should be present in device list") +} + +func TestComponentActionNotPresent(t *testing.T) { + filename := "../../../resources/testing/testComponentActionNotPresent.json" + assert.PanicsWithValue(t, + "component id: light not found in device input", + func() { ReadFile(filename) }, + "Component used in action should be present in device list") +} + +func TestDeviceConstraintNotPresent(t *testing.T) { + filename := "../../../resources/testing/testDeviceConstraintNotPresent.json" + assert.PanicsWithValue(t, + "device with id telephone not found in map", + func() { ReadFile(filename) }, + "Device used in constraint should be present in device list") +} + +func TestComponentConstraintNotPresent(t *testing.T) { + filename := "../../../resources/testing/testComponentConstraintNotPresent.json" + assert.PanicsWithValue(t, + "component id: color not found in device input", + func() { ReadFile(filename) }, + "Component used in constraint should be present in device list") +} + +func TestIncorrectTypeCondition(t *testing.T) { + filename := "../../../resources/testing/testIncorrectTypeCondition.json" + assert.PanicsWithValue(t, + "invalid type of condition: mytype", + func() { ReadFile(filename) }, + "Condition type should be 'device' or 'timer'") +} + +func TestIncorrectTypeAction(t *testing.T) { + filename := "../../../resources/testing/testIncorrectTypeAction.json" + assert.PanicsWithValue(t, + "invalid type of action: my-type", + func() { ReadFile(filename) }, + "Condition type should be 'device' or 'timer'") +} + +func TestCheckActionComponentType(t *testing.T) { + filename := "../../../resources/testing/testCheckActionComponentType.json" + assert.PanicsWithValue(t, + "Value was not of type string: 0", + func() { ReadFile(filename) }, + "Condition type should be 'device' or 'timer'") +} + +/////////////////////////////////// Test timer actions +func TestTimerNoInstruction(t *testing.T) { + filename := "../../../resources/testing/timer/testTimerNoInstruction.json" + assert.PanicsWithValue(t, + "timer should have an instruction defined", + func() { ReadFile(filename) }, + "Timer should have an instruction defined") +} + +func TestTimerIncorrectInstruction(t *testing.T) { + filename := "../../../resources/testing/timer/testTimerIncorrectInstruction.json" + assert.PanicsWithValue(t, + "timer should have an instruction defined, which is either stop or subtract", + func() { ReadFile(filename) }, + "Timer should have a correct instruction defined: stop or subtract") +} + +func TestTimerNoSubtractValue(t *testing.T) { + filename := "../../../resources/testing/timer/testTimerNoSubtractValue.json" + assert.PanicsWithValue(t, + "timer with subtract instruction should have value", + func() { ReadFile(filename) }, + "Timer should have a value defined for subtract instruction") +} + +func TestTimerIncorrectSubtractValue(t *testing.T) { + filename := "../../../resources/testing/timer/testTimerIncorrectSubtractValue.json" + assert.PanicsWithValue(t, + "timer with subtract instruction should have value in string format", + func() { ReadFile(filename) }, + "Timer should have a string value defined for subtract instruction") +} + +func TestTimerInvalidSubtractValue(t *testing.T) { + filename := "../../../resources/testing/timer/testTimerInvalidSubtractValue.json" + assert.PanicsWithValue(t, + "test did not match pattern 'hh:mm:ss'", + func() { ReadFile(filename) }, + "Timer should have a value defined for subtract instruction in format 'hh:mm:ss'") +} + +/////////////////////////////////// Wrong inputs +func TestTimerIncorrectPattern(t *testing.T) { + filename := "../../../resources/testing/wrong-types/testTimerIncorrectPattern.json" + assert.PanicsWithValue(t, + "30:00 did not match pattern 'hh:mm:ss'", + func() { ReadFile(filename) }, + "Time should be entered in correct pattern") +} + +func TestTimerIncorrectType(t *testing.T) { + filename := "../../../resources/testing/wrong-types/testTimerIncorrectType.json" + assert.PanicsWithValue(t, + "30 cannot be cast to string, for time constraint", + func() { ReadFile(filename) }, + "Timer should take in string value") +} + +func TestStringIncorrectType(t *testing.T) { + filename := "../../../resources/testing/wrong-types/testStringIncorrectType.json" + assert.PanicsWithValue(t, + "Value was not of type string: 30", + func() { ReadFile(filename) }, + "If device input specifies string type, constraint value should be a string") +} + +func TestNumericIncorrectType(t *testing.T) { + filename := "../../../resources/testing/wrong-types/testNumericIncorrectType.json" + assert.PanicsWithValue(t, + "Value was not of type numeric: 30", + func() { ReadFile(filename) }, + "If device input specifies numeric type, constraint value should be a numeric") +} + +func TestBooleanIncorrectType(t *testing.T) { + filename := "../../../resources/testing/wrong-types/testBooleanIncorrectType.json" + assert.PanicsWithValue(t, + "Value was not of type boolean: 30", + func() { ReadFile(filename) }, + "If device input specifies boolean type, constraint value should be a boolean") +} + +func TestNumArrayIncorrectType(t *testing.T) { + filename := "../../../resources/testing/wrong-types/testNumArrayIncorrectType.json" + assert.PanicsWithValue(t, + "Value was not of type num-array: [true]", + func() { ReadFile(filename) }, + "If device input specifies num array type, constraint value should be a num array") +} + +func TestNumArrayIncorrectType2(t *testing.T) { + filename := "../../../resources/testing/wrong-types/testNumArrayIncorrectType2.json" + assert.PanicsWithValue(t, + "Value was not of type array: test", + func() { ReadFile(filename) }, + "If device input specifies num array type, constraint value should be a num array") +} + +func TestStringArrayIncorrectType(t *testing.T) { + filename := "../../../resources/testing/wrong-types/testStringArrayIncorrectType.json" + assert.PanicsWithValue(t, + "Value was not of type string-array: [30]", + func() { ReadFile(filename) }, + "If device input specifies string array type, constraint value should be a string array") +} + +func TestStringArrayIncorrectType2(t *testing.T) { + filename := "../../../resources/testing/wrong-types/testStringArrayIncorrectType2.json" + assert.PanicsWithValue(t, + "Value was not of type array: 30", + func() { ReadFile(filename) }, + "If device input specifies string array type, constraint value should be a string array") +} + +func TestBoolArrayIncorrectType(t *testing.T) { + filename := "../../../resources/testing/wrong-types/testBoolArrayIncorrectType.json" + assert.PanicsWithValue(t, + "Value was not of type bool-array: [30]", + func() { ReadFile(filename) }, + "If device input specifies bool array type, constraint value should be a bool array") +} + +func TestBoolArrayIncorrectType2(t *testing.T) { + filename := "../../../resources/testing/wrong-types/testBoolArrayIncorrectType2.json" + assert.PanicsWithValue(t, + "Value was not of type array: 30", + func() { ReadFile(filename) }, + "If device input specifies bool array type, constraint value should be a bool array") +} + +func TestIncorrectComparisonType(t *testing.T) { + filename := "../../../resources/testing/wrong-types/testIncorrectTypeComparison.json" + assert.PanicsWithValue(t, + "comparison '30' cannot be cast to string", + func() { ReadFile(filename) }, + "Comparison should be entered in string format") +} + +func TestInvalidComparisonType(t *testing.T) { + filename := "../../../resources/testing/wrong-types/testInvalidTypeComparison.json" + assert.PanicsWithValue(t, + "comparison should be 'eq', 'lt', 'lte', 'gt', 'gte' or 'contains'", + func() { ReadFile(filename) }, + "Comparison should be a correct type") +} + +func TestIncorrectComponentIDType(t *testing.T) { + filename := "../../../resources/testing/wrong-types/testIncorrectTypeComponentID.json" + assert.PanicsWithValue(t, + "component_id 30 cannot be cast to string", + func() { ReadFile(filename) }, + "Component ID should be entered in string format") +} + +/////////////////////////////////// Missing condition keys +func TestNoComponentIDForDeviceConstraintBool(t *testing.T) { + filename := "../../../resources/testing/test_config.json" + config := ReadFile(filename) + assert.PanicsWithValue(t, + "Condition ID: ID not in constraint map", + func() { GetConstraintBool(config, "ID") }, + "Panic thrown when ID not found for bool") + assert.PanicsWithValue(t, + "Condition ID: ID not in constraint map", + func() { GetConstraintString(config, "ID") }, + "Panic thrown when ID not found for string") + assert.PanicsWithValue(t, + "Condition ID: ID not in constraint map", + func() { GetConstraintNumeric(config, "ID") }, + "Panic thrown when ID not found for numeric") + assert.PanicsWithValue(t, + "Condition ID: ID not in constraint map", + func() { GetConstraintStringArray(config, "ID") }, + "Panic thrown when ID not found for string array") + assert.PanicsWithValue(t, + "Condition ID: ID not in constraint map", + func() { GetConstraintNumArray(config, "ID") }, + "Panic thrown when ID not found for num array") + assert.PanicsWithValue(t, + "Condition ID: ID not in constraint map", + func() { GetConstraintBoolArray(config, "ID") }, + "Panic thrown when ID not found for bool array") + assert.PanicsWithValue(t, + "Condition ID: ID not in constraint map", + func() { GetConstraintCustomType(config, "ID") }, + "Panic thrown when ID not found for custom type") + assert.PanicsWithValue(t, + "Condition ID: ID not in constraint map", + func() { GetConstraintTimer(config, "ID") }, + "Panic thrown when ID not found for timer") +} diff --git a/back-end/src/sciler/config/helperConfigTypes.go b/back-end/src/sciler/config/helperConfigTypes.go new file mode 100644 index 00000000..4aa59aa6 --- /dev/null +++ b/back-end/src/sciler/config/helperConfigTypes.go @@ -0,0 +1,70 @@ +package config + +// WorkingConfig has additional fields to ReadConfig, with lists of conditions, constraints and actions. +type WorkingConfig struct { + General General + Puzzles []Puzzle + GeneralEvents []GeneralEvent + //TODO maps and concurrency + Devices map[string]Device + Rules map[string]Rule + ActionMap map[string][]Action + // first key is condition pointer, second is type of constraint: + // string, bool, numeric, string-array, bool-array, num-array, custom, timer + // ex: ConstraintMap[conditionID][boolean] = []ConstraintBool + ConstraintMap map[string]map[string][]interface{} +} + +// Constraint is main constraint type with comparison and componentID (latter only for device condition). +type Constraint struct { + Comparison string + ComponentID string +} + +// ConstraintTimer is struct for constraint with timer value: "hh:mm:ss". +type ConstraintTimer struct { + Constraint + Value string +} + +// ConstraintNumeric is struct for constraint with numeric value. +type ConstraintNumeric struct { + Constraint + Value float64 +} + +// ConstraintString is struct for constraint with string value. +type ConstraintString struct { + Constraint + Value string +} + +// ConstraintBool is struct for constraint with boolean value. +type ConstraintBool struct { + Constraint + Value bool +} + +// ConstraintStringArray is struct for constraint with string array value. +type ConstraintStringArray struct { + Constraint + Value []string +} + +// ConstraintNumericArray is struct for constraint with numeric array value. +type ConstraintNumericArray struct { + Constraint + Value []float64 +} + +// ConstraintBoolArray is struct for constraint with bool array value. +type ConstraintBoolArray struct { + Constraint + Value []bool +} + +// ConstraintCustomType is struct for constraint with custom type value. +type ConstraintCustomType struct { + Constraint + Value interface{} +} diff --git a/back-end/src/sciler/config/testConfig_test.go b/back-end/src/sciler/config/testConfig_test.go new file mode 100644 index 00000000..6bb17226 --- /dev/null +++ b/back-end/src/sciler/config/testConfig_test.go @@ -0,0 +1,186 @@ +package config + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +/////////////////////////////////// Test correct test_config +func TestGeneralInformation(t *testing.T) { + filename := "../../../resources/testing/test_config.json" + config := ReadFile(filename) + general := General{"Escape X", "00:30:00", "192.0.0.84", 1883} + assert.Equal(t, general, config.General, + "General information should be correct") +} + +func TestPuzzleSize(t *testing.T) { + filename := "../../../resources/testing/test_config.json" + config := ReadFile(filename) + assert.Equal(t, 3, len(config.Puzzles), "Should have read three puzzles") +} + +func TestDeviceInput(t *testing.T) { + filename := "../../../resources/testing/test_config.json" + config := ReadFile(filename) + input := map[string]string{ + "redSwitch": "boolean", + "orangeSwitch": "boolean", + "greenSwitch": "boolean", + "slider1": "numeric", + "slider2": "numeric", + "slider3": "numeric", + "mainSwitch": "boolean", + } + assert.Equal(t, input, config.Devices["controlBoard"].Input, + "Input of device should be retrieved correctly from the devices map") +} + +func TestActionOutput(t *testing.T) { + filename := "../../../resources/testing/test_config.json" + config := ReadFile(filename) + output := OutputObject{ + "greenLight1": false, + "greenLight2": true, + "greenLight3": false, + "redLight1": false, + "redLight2": false, + "redLight3": false, + } + assert.Equal(t, output, config.Puzzles[1].Rules[0].Actions[1].Message.Output, + "Message from action should be of OutputObject type, retrieved through puzzles in config") +} + +func TestRulesMap(t *testing.T) { + filename := "../../../resources/testing/test_config.json" + config := ReadFile(filename) + assert.Equal(t, "De juiste volgorde van cijfers moet gedraaid worden.", + config.Rules["correctSequence"].Description, + "Description from rule should retrieved correctly through rules map") +} + +func TestGeneralEvents(t *testing.T) { + filename := "../../../resources/testing/test_config.json" + config := ReadFile(filename) + assert.Equal(t, "Start", config.GeneralEvents[0].Name, + "Name of general event should be retrieved correctly") +} + +func TestComponentIDForDeviceConstraintNum(t *testing.T) { + filename := "../../../resources/testing/test_config.json" + config := ReadFile(filename) + list := GetConstraintNumeric(config, config.Puzzles[1].Rules[0].Conditions[0].GetID()) + assert.Equal(t, 1, len(list), "There should be one numeric constraint for this condition") + assert.Equal(t, "slider2", list[0].ComponentID, + "ComponentID should be retrieved correctly through puzzle, rule, condition, constraint") +} + +func TestComponentIDForDeviceConstraintBool(t *testing.T) { + filename := "../../../resources/testing/test_config.json" + config := ReadFile(filename) + list := GetConstraintBool(config, config.Puzzles[1].Rules[0].Conditions[0].GetID()) + assert.Equal(t, 3, len(list), "There should be three boolean constraints for this condition") + assert.Equal(t, "greenSwitch", list[1].ComponentID, + "ComponentID should be retrieved correctly through puzzle, rule, condition, constraint") +} + +func TestComponentIDForDeviceConstraintDouble(t *testing.T) { + filename := "../../../resources/testing/test_config.json" + config := ReadFile(filename) + list := GetConstraintNumeric(config, config.Puzzles[2].Rules[0].Conditions[0].GetID()) + assert.Equal(t, 1, len(list), "There should be one numeric constraint for this condition") + assert.Equal(t, "numeric", list[0].ComponentID, + "ComponentID should be retrieved correctly through puzzle, rule, condition, constraint") + assert.Equal(t, 2.5, list[0].Value, + "Custom type constraint should have correctly retrieved value") +} + +func TestComponentIDForDeviceConstraintString(t *testing.T) { + filename := "../../../resources/testing/test_config.json" + config := ReadFile(filename) + list := GetConstraintString(config, config.Puzzles[2].Rules[0].Conditions[0].GetID()) + assert.Equal(t, 1, len(list), "There should be one string constraint for this condition") + assert.Equal(t, "string", list[0].ComponentID, + "ComponentID should be retrieved correctly through puzzle, rule, condition, constraint") + assert.Equal(t, "mystring", list[0].Value, + "Custom type constraint should have correctly retrieved value") +} + +func TestComponentIDForDeviceConstraintNumArray(t *testing.T) { + filename := "../../../resources/testing/test_config.json" + config := ReadFile(filename) + list := GetConstraintNumArray(config, config.Puzzles[2].Rules[0].Conditions[0].GetID()) + assert.Equal(t, 1, len(list), "There should be one num-array constraint for this condition") + assert.Equal(t, "num-array", list[0].ComponentID, + "ComponentID should be retrieved correctly through puzzle, rule, condition, constraint") + assert.Equal(t, []float64{0, -1, 0.5, 25, 9.12}, list[0].Value, + "Custom type constraint should have correctly retrieved value") +} + +func TestComponentIDForDeviceConstraintStringArray(t *testing.T) { + filename := "../../../resources/testing/test_config.json" + config := ReadFile(filename) + list := GetConstraintStringArray(config, config.Puzzles[2].Rules[0].Conditions[0].GetID()) + assert.Equal(t, 1, len(list), "There should be one num-array constraint for this condition") + assert.Equal(t, "string-array", list[0].ComponentID, + "ComponentID should be retrieved correctly through puzzle, rule, condition, constraint") + assert.Equal(t, []string{"mystring1", "mystring2"}, list[0].Value, + "Custom type constraint should have correctly retrieved value") +} + +func TestComponentIDForDeviceConstraintBoolArray(t *testing.T) { + filename := "../../../resources/testing/test_config.json" + config := ReadFile(filename) + list := GetConstraintBoolArray(config, config.Puzzles[2].Rules[0].Conditions[0].GetID()) + assert.Equal(t, 1, len(list), "There should be one bool-array constraint for this condition") + assert.Equal(t, "bool-array", list[0].ComponentID, + "ComponentID should be retrieved correctly through puzzle, rule, condition, constraint") + assert.Equal(t, []bool{true, false, true}, list[0].Value, + "Custom type constraint should have correctly retrieved value") +} + +func TestComponentIDForDeviceConstraintCustom(t *testing.T) { + filename := "../../../resources/testing/test_config.json" + config := ReadFile(filename) + list := GetConstraintCustomType(config, config.Puzzles[2].Rules[0].Conditions[0].GetID()) + assert.Equal(t, 1, len(list), "There should be one custom type constraint for this condition") + assert.Equal(t, "custom", list[0].ComponentID, + "ComponentID should be retrieved correctly through puzzle, rule, condition, constraint") + output := map[string]interface{}{"instruction": "test"} + assert.Equal(t, output, list[0].Value, + "Custom type constraint should have correctly retrieved value") +} + +func TestNoComponentForTimeConstraint(t *testing.T) { + filename := "../../../resources/testing/test_config.json" + config := ReadFile(filename) + list := GetConstraintTimer(config, config.GeneralEvents[0].Rules[0].Conditions[0].GetID()) + assert.Equal(t, 1, len(list), "There should be one timer constraint for this condition") + assert.Equal(t, "", list[0].ComponentID, + "There should not be a component id for a timer constraint, it should be ''") + assert.Equal(t, "00:01:00", list[0].Value, + "Timer constraint should have correctly retrieved value") +} + +/////////////////////////////////// Edge case behavior +func TestDeviceInputCustomType(t *testing.T) { + filename := "../../../resources/testing/testDeviceCustomType.json" + config := ReadFile(filename) + for key, value := range config.Devices["telephone"].Input { + assert.Equal(t, "turningWheel", key, + "Id of component should be key in input map") + assert.Equal(t, "my-type", value, + "Custom type of component should be value in input map") + } +} + +func TestDeviceOutputCustomType(t *testing.T) { + filename := "../../../resources/testing/testDeviceCustomType.json" + config := ReadFile(filename) + for key, value := range config.Devices["telephone"].Output { + assert.Equal(t, "audio", key, + "Id of component should be key in input map") + assert.Equal(t, "string", value, + "Custom type of component should be value in output map") + } +} diff --git a/back-end/src/sciler/main.go b/back-end/src/sciler/main.go new file mode 100644 index 00000000..df25f032 --- /dev/null +++ b/back-end/src/sciler/main.go @@ -0,0 +1,106 @@ +package main + +import ( + "encoding/json" + mqtt "github.com/eclipse/paho.mqtt.golang" + "github.com/sirupsen/logrus" + "os" + "sciler/communication" + "sciler/config" + "time" +) + +var topics = []string{"back-end", "status", "hint", "connect"} + +// Message is a type that follows the structure all messages have, described in resources/message_manual.md +type Message struct { + DeviceID string `json:"device_id"` + TimeSent string `json:"time_sent"` + Type string `json:"type"` + Contents map[string]interface{} `json:"contents"` +} + +func main() { + dir, err := os.Getwd() + if err != nil { + logrus.Fatal(err) + } + filename := dir + "/back-end/resources/room_config.json" + configurations := config.ReadFile(filename) + logrus.Info("configurations read from: " + filename) + host := configurations.General.Host + port := configurations.General.Port + + communicator := communication.NewCommunicator(host, port, topics) + go communicator.Start(func(client mqtt.Client, message mqtt.Message) { + // TODO: Make advanced message handler which acts according to the events / configuration + var raw Message + if err := json.Unmarshal(message.Payload(), &raw); err != nil { + logrus.Errorf("Invalid JSON received: %v", err) + } + switch raw.Type { + case "instruction": + logrus.Info("instruction message received") + { + if raw.Contents["instruction"] == "test all" && raw.DeviceID == "front-end" { // TODO maybe switch again + message := Message{ + DeviceID: "back-end", + TimeSent: time.Now().Format("02-01-2006 15:04:05"), + Type: "instruction", + Contents: map[string]interface{}{ + "instruction": "test", + }, + } + jsonMessage, err := json.Marshal(&message) + if err != nil { + logrus.Errorf("Error occurred while constructing message to publish: %v", err) + } else { + communicator.Publish("test", string(jsonMessage), 3) + } + } + } + case "status": + { + logrus.Info("status message received") + if raw.DeviceID == "controlBoard1" { + var instruction string + if raw.Contents["switch1"] == "1" { + instruction = "turn off" + } else if raw.Contents["switch1"] == "0" { + instruction = "turn on" + } + if instruction != "" { + message := Message{ + DeviceID: "back-end", + TimeSent: time.Now().Format("02-01-2006 15:04:05"), + Type: "instruction", + Contents: map[string]interface{}{ + "instruction": instruction, + }, + } + jsonMessage, err := json.Marshal(&message) + if err != nil { + logrus.Errorf("Error occurred while constructing message to publish: %v", err) + } else { + communicator.Publish("test", string(jsonMessage), 3) + } + } + } + + } + case "confirmation": + { + logrus.Info("confirmation message received") + } + case "connection": + { + logrus.Info("connection message received") + } + } + }) + + // loop for now preventing app to exit + for { + time.Sleep(time.Microsecond * time.Duration(250)) + } +} diff --git a/broker/README.md b/broker/README.md index 4fc6e1c4..85367665 100644 --- a/broker/README.md +++ b/broker/README.md @@ -1,5 +1,7 @@ # BEP_1920_Q2 - broker + +### Set-up When using Mosquitto as a broker, mosquitto.conf should be used to setup a broker with an extra listener for websockets. The bind-address should be changed to local-ip when communication outside a one machine develepment setup is required. @@ -14,3 +16,9 @@ mosquitto -c ``` When there is a mosquitto broker already running, this will not work. Then, first run `net stop mosquitto`. + +### Running +``` +mosquitto -t +mosquitto -h +``` \ No newline at end of file diff --git a/broker/mosquitto.conf b/broker/mosquitto.conf index 6e1b9eac..a80e88f1 100644 --- a/broker/mosquitto.conf +++ b/broker/mosquitto.conf @@ -9,7 +9,7 @@ # given, the default listener will not be bound to a specific # address and so will be accessible to all network interfaces. # bind_address ip-address/host name -bind_address localhost +bind_address 192.168.178.82 # Port to use for the default listener. port 1883 diff --git a/cc_library/README.md b/cc_library/README.md index 67bf9136..b6273a4f 100644 --- a/cc_library/README.md +++ b/cc_library/README.md @@ -7,3 +7,11 @@ To make sure all dependencies are loaded, run `pip install -r cc_library/require - To check unittests, run `python -m unittest discover`. - To update formatting, run `black cc_library`. - To check the codestyle, run `flake8 cc_library`. + +### Raspberry Pi Set up +- Code specific methods of the device into `