diff --git a/config/robotdescriptions.json b/config/robotdescriptions.json deleted file mode 100644 index 4f99356..0000000 --- a/config/robotdescriptions.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "turtle1": { - "descriptions": [ - "http://wiki.ros.org/ROS/Tutorials/UsingRxconsoleRoslaunch", - "http://wiki.ros.org/ROS/Tutorials/UnderstandingNodes", - "http://wiki.ros.org/ROS/Tutorials/UnderstandingTopics", - "http://wiki.ros.org/ROS/Tutorials/UnderstandingServicesParams", - "http://wiki.ros.org/ROS/Tutorials/UsingRqtconsoleRoslaunch" - ] - } -} \ No newline at end of file diff --git a/config/robots.json b/config/robots.json deleted file mode 100644 index 694264b..0000000 --- a/config/robots.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "robot2":{ - "topics": { - "cmd_vel_mux/input/teleop": { - "msg": "geometry_msgs.msg.Twist", - "type": "publisher" - }, - "cmd_serial": { - "msg": "std_msgs.msg.String", - "type": "publisher" - }, - "move_base/goal": { - "msg": "move_base_msgs.msg.MoveBaseActionGoal", - "type": "publisher" - }, - "move_base/result": { - "msg": "move_base_msgs.msg.MoveBaseActionResult", - "type": "subscriber" - } - } - }, - "end_end_test":{ - "topics": { - "p1": { - "msg": "std_msgs.msg.String", - "type": "publisher" - }, - "s1": { - "msg": "std_msgs.msg.String", - "type": "subscriber" - } - } - } -} \ No newline at end of file diff --git a/config/topics.json b/config/topics.json new file mode 100644 index 0000000..157c267 --- /dev/null +++ b/config/topics.json @@ -0,0 +1,9 @@ +{ + "/robot2/cmd_vel_mux/input/teleop": ["geometry_msgs/Twist", "publisher" ], + "/robot2/cmd_serial": ["std_msgs/String", "publisher" ], + "/robot2/move_base/goal": ["move_base_msgs/MoveBaseActionGoal", "publisher" ], + "/robot2/move_base/result": ["move_base_msgs/MoveBaseActionResult", "subscriber" ], + + "/end_end_test/p1": ["std_msgs/String", "publisher" ], + "/end_end_test/s1": ["std_msgs/String", "subscriber" ] +} \ No newline at end of file diff --git a/config/whitelist.json b/config/whitelist.json index 6d10f15..0b6cf09 100644 --- a/config/whitelist.json +++ b/config/whitelist.json @@ -1,14 +1,7 @@ { - "turtle\\w+": { - "publisher": ["cmd_vel"], - "subscriber": ["pose"] - }, - "robot\\w+": { - "publisher": ["cmd_vel.*teleop", ".*move_base/goal", ".*move_base/cancel"], - "subscriber": [".*move_base/result"] - }, - "\\w+bot\\w*": { - "publisher": [".*cmd_vel.*", ".*move_base/goal.*"], - "subscriber": [] - } + "publisher" : ["/turtle\\w+/cmd_vel", + "/robot\\w+/cmd_vel.*"], + + "subscriber": ["/turtle\\w+/pose", + ".*move_base/result"] } \ No newline at end of file diff --git a/doc/deprecated.md b/doc/deprecated.md index 4d99ce8..ebc52f9 100644 --- a/doc/deprecated.md +++ b/doc/deprecated.md @@ -1,13 +1,3 @@ # Deprecated Features -## REST-API `whitelist` on FIROS - -FIROS currently has a REST-API, where someone can manipulate the whitelist of an FIROS-instance (see: -[API](user/api.md)). We are currently not planning to expand this functionality, because the `whitelist.json` is -definitely known prior and is overwritten by the configuration in `robots.json`. - -Specifically, the following methods are provided as is and are not maintained: - -- `FIROS:/whitelist/write` -- `FIROS:/whitelist/remove` -- `FIROS:/whitelist/restore` +Currently no deprecated Features in Firos. diff --git a/doc/install/configuration-files.md b/doc/install/configuration-files.md index d28fa6b..a4fb424 100644 --- a/doc/install/configuration-files.md +++ b/doc/install/configuration-files.md @@ -1,8 +1,8 @@ # Configuration-Files -FIROS needs 3 different configuration files inside the Configuration-Folder. An example Configuration-Folder can be -found in the `config`-Folder at the base of this repository. It needs to contain the files `config.json`, `robots.json` -and `whitelist.json` (optionally: `robotdescriptions.json`). +FIROS needs 2 different configuration files inside the Configuration-Folder. An example Configuration-Folder can be +found in the `config`-Folder at the base of this repository. It needs to contain the files `config.json` and +`topics.json` (optionally: `whitelist.json`). In the follwing each of the configuration files are explained in detail: @@ -35,6 +35,10 @@ configuration as shown in the following example: "subscription_refresh_delay": 0.9 } }, + "endpoint": { + "address": "10.16.55.3", + "port": 1234 + }, "log_level": "INFO" } } @@ -44,17 +48,16 @@ We also added here the contextbroker configuration, since we want to publish and Here is the list of all currently possibilities for a configuration: -| Attribute | Value | Required | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | :------: | -| "endpoint" | An object, which can have an `address` and a `port`. If the Address differs, where FIROS should get the notifications from, then at this here. | | -| "log_level" | Can be either `"INFO"` (Default), `"DEBUG"`, `"WARNING"`, `"ERROR"` or `"CRITICAL"`. | | -| "node_name" | This sets the ROS-Node-Name for this FIROS instance. The default is `"firos"`. | | -| "ros_subscriber_queue" | The queue-size of the `rospy.Publisher`. See more [here](http://wiki.ros.org/rospy/Overview/Publishers%20and%20Subscribers). Default is `10` | | -| "context_type" | This sets the context type of an entity (the `type`-value of the base-entity). Default is `"ROBOT"` but can be changed if necessary | | -| "rosbridge_port" | Changes the ROS-Port, where to listen. Default is `9090` | | -| "server" | An object `{}` which contains the attribute `"port"` | | -| "contextbroker" | An object `{}` which contains the attributes `"adress"`, `"port"` and `"subscriptions"` | x | -| "pub_frequency" | An Integer of Milliseconds. This limits the number of publishes e.g. to the Context-Broker. This blocks the next publish for `pub_frequency` milliseconds. | | +| Attribute | Value | Required | +| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------------------------------------------------: | +| "endpoint" | An object, which can have an `address` and a `port`. If the Address differs, where FIROS should get the notifications from, then add this here. | | +| "log_level" | Can be either `"INFO"` (Default), `"DEBUG"`, `"WARNING"`, `"ERROR"` or `"CRITICAL"`. | | +| "node_name" | This sets the ROS-Node-Name for this FIROS instance. The default is `"firos"`. | | +| "ros_subscriber_queue" | The queue-size of the `rospy.Publisher`. See more [here](http://wiki.ros.org/rospy/Overview/Publishers%20and%20Subscribers). Default is `10` | | +| "rosbridge_port" | Changes the ROS-Port, where to listen. Default is `9090` | | +| "server" | An object `{}` which contains the attribute `"port"` | | +| "contextbroker" | An object `{}` which contains the attributes `"adress"`, `"port"` and `"subscriptions"` | (`x`, firos should at least know where to publish data) | +| "pub_frequency" | An Integer of Milliseconds. This limits the number of publishes e.g. to the Context-Broker. This blocks the next publish for `pub_frequency` milliseconds. | | ### `"server"`-Configuration @@ -85,22 +88,12 @@ Information about the `cmd_vel` (`Twist`-Information), its data is published int ```json { - "turtle1": { - "topics": { - "cmd_vel": { - "msg": "geometry_msgs.msg.Twist", - "type": "publisher" - }, - "pose": { - "msg": "turtlesim.msg.Pose", - "type": "subscriber" - } - } - } + "/turtle1/cmd_vel": ["geometry_msgs/Twist", "publisher"], + "/turtle1/pose": ["turtlesim/Pose", "subscriber"] } ``` -This json in particular listens to the rostopic `/turtle1/pose` with the message type `"turtlesim.msg.Pose"` (the +This json in particular listens to the rostopic `/turtle1/pose` with the message type `"turtlesim/Pose"` (the corresponding python message from `turtlesim/Pose`) and sends all retreived data to the specified server in the Non-ROS-World. It publishes data into `/turtle1/cmd_vel` after receiving a notifcation of the server from the Non-ROS-World from type `geometry_msgs/Twist`. @@ -115,31 +108,6 @@ from the green arrows, which happens automatically. You do not have to specify `publisher` and `subscriber` of all available topics or at all for a robot. Only specify the needed ones, which need to be displayed from/or need to obtain information on the Non-ROS-World -```json -{ - "ROBOT_ID": { - "topics": { - "TOPIC_1": { - "msg": "PYTHON_REPRESENTATION_MESSAGE_TYPE", - "type": "publisher" - }, - "TOPIC_2": { - "msg": "PYTHON_REPRESENTATION_MESSAGE_TYPE", - "type": "subscriber" - } - } - }, - "ROBOT_ID_2": { - "topics": { - "TOPIC_1": { - "msg": "PYTHON_REPRESENTATION_MESSAGE_TYPE", - "type": "subscriber" - } - } - } -} -``` - The Information given by the `robots.json` is appended/replaced to the `whitelist.json` which is described below. --- @@ -147,26 +115,29 @@ The Information given by the `robots.json` is appended/replaced to the `whitelis ## `whitelist.json` As the name suggests, the `whitelist.json` functions as a whitelist to let FIROS know which messages it should keep -track of. Given an environment where already ROS-Applications are running, FIROS will automatically subscribe to all -available topics if no `whitelist.json` is given. In a small ROS-World with few ROS-Applications, this can be desirable. +track of. Given an environment where already ROS-Applications are running, FIROS will not automatically subscribe to all +available topics if no `whitelist.json` is given. In a small ROS-World with few ROS-Applications, it can be desirable to +subscribe to all topics. This can be achieved via: + +```json +{ + "publisher": [], + "subscriber": [".*"] +} +``` + But this can cause problems in a ROS-World, where many ROS-Applications are running. To let FIROS only subscribe to specific topics, the following configuration can be used: ```json { - "turtle2": { - "publisher": ["cmd_vel"], - "subscriber": ["pose"] - } + "publisher": [".*/pose"], + "subscriber": [".*/cmd_vel"] } ``` -This only allows FIROS to subscribe/publish to `"/turtle2/pose"` and `"turtle2/cmd_vel"` plus the extra-configuration -given in `robots.json` which in the above example would also be `"/turtle1/pose"` and `"turtle1/cmd_vel"`. - -**Note**, that an empty configuration of `whitelist.json` (`-> {}`) will also behave as an -non-existent-configuration-file. Usually for normal usecases, the `whitelist.json` contains the same information as the -`robots.json` and should be sufficient. +This only allows FIROS to subscribe/publish to specific topics plus the extra-configuration given in `robots.json` which +in the above example would also be `"/turtle1/pose"` and `"/turtle1/cmd_vel"`. **Note:** The FIROS only captures running ROS-Applications at the startup. All applications started after FIROS will not be recognized. @@ -175,55 +146,5 @@ be recognized. Message-Type. However, the "Message-Implementation" still needs to be present locally at the FIROS-Instance (via the `msgs`-Folder, or compiled by catkin) -The `whitelist.json` also supports Regular Expressions (Regex), so you can refer to more Robots and Topics in just a few -lines. - -Here we address all `turtle[a-zA-Z0-9]+` and `robot[a-zA-Z0-9]+` Robots with their topics: - -```json -{ - "turtle\\w+": { - "publisher": ["cmd_vel"], - "subscriber": ["pose"] - }, - "robot\\w+": { - "publisher": ["cmd_vel.*teleop", ".*move_base/goal", ".*move_base/cancel"], - "subscriber": [".*move_base/result"] - } -} -``` - ---- - -## `robotdescriptions.json` - -This configuration is optional and just appends additional information into the Non-ROS-World if even implemented. - -E. g. The Context-Broker puts them under the `"descriptions"`-attribute. Those can be Links/Strings or maybe some -'static' values you need to have present for a robot/topic. - -It can look like this: - -```json -{ - "turtle1": { - "descriptions": [ - "http://wiki.ros.org/ROS/Tutorials/UsingRxconsoleRoslaunch", - "http://wiki.ros.org/ROS/Tutorials/UnderstandingNodes" - ] - } -} -``` - -Or like this: - -```json -{ - "turtle1": { - "descriptions": { - "MySanatiyValue": 42, - "SomeReferenceLink": "http://wiki.ros.org/ROS/Tutorials/UnderstandingNodes" - } - } -} -``` +The `whitelist.json` also supports more complex Regular Expressions (Regex), so you can refer to more topics in just a +few lines. diff --git a/doc/install/install.md b/doc/install/install.md index f32b21d..5ff90b0 100644 --- a/doc/install/install.md +++ b/doc/install/install.md @@ -1,4 +1,4 @@ -# Installion From Scratch With ROS, Orion Context-Broker and catkin +# Installation From Scratch With ROS, Orion Context-Broker and catkin To Install Firos you first need to follow this [Installaion Instuctions](http://wiki.ros.org/ROS/Installation). ROS is needed for FIROS, since it imports `ROS-messages` and uses other specific `ROS-Executables` like `rospy` or `rostopic`. @@ -126,7 +126,7 @@ Beginning from the base of this repository, FIROS can be built via docker using: > docker build -f ./docker/Dockerfile --tag firos:localbuild . This will create an image with a pre-configured `config.json` which requires the Orion-ContextBroker. Before running -this image, you need to specify a `robots.json`. Information on how to create the configuration-files can be found in +this image, you need to specify a `topics.json`. Information on how to create the configuration-files can be found in [Configuration-Files](configuration-files.md) or in the [Turtlesim-Example](turtlesim-example.md). An example-pre-configured configuration for docker can be found in `firos/docker/docker-config` @@ -147,7 +147,7 @@ docker run -it --rm --net finet --name orion --link mongodb -p 1026:1026 fiware/ docker run -it --net finet --name firos \ -p 10100:10100 \ --env ROS_MASTER_URI=http://rosmaster:11311 \ - -v CONFIG_FILE_ROBOTS:/catkin_ws/src/firos/config/robots.json \ + -v CONFIG_FILE_TOPICS:/catkin_ws/src/firos/config/topics.json \ -v CONFIG_FILE_WHITELIST:/catkin_ws/src/firos/config/whitelist.json \ firos:localbuild ``` diff --git a/doc/install/standards.md b/doc/install/standards.md index 05ebb4e..705463f 100644 --- a/doc/install/standards.md +++ b/doc/install/standards.md @@ -2,7 +2,7 @@ FIROS now allows to implement custom standards to do multiple publications of data on multiple platforms with only one instance. In order to write a new standard, where the ROS-Messages are transformed in your standard and vice-versa, you -need to think about following: +need to think about the following: - Think of a name for your standard. In this example we choose `testbroker` - Does your standard need any Configuration to work porperly? A server might need an `address` or `port`. Or if you @@ -65,7 +65,7 @@ class SomeExamplePublisher(Publisher): def __init__(self): pass - def publish(self, robotID, topic, rawMsg, msgDefinitions): + def publish(self, topic, rawMsg, msgDefinitions): pass def unpublish(self): @@ -96,7 +96,7 @@ class SomeExampleSubscriber(Subscriber): def __init__(self): pass - def subscribe(self, robotID, topicList, msgDefinitions): + def subscribe(self, topicList, msgDefinitions): pass def unsubscribe(self): @@ -117,7 +117,7 @@ The received Messages need to be converted into a special class which can be dir After the received Message is converted correctly, you can publish it via: ```python -RosTopicHandler.publish(robotID, topic, convertedData, dataStruct): +RosTopicHandler.publish(topic, convertedData, dataStruct): ``` and it should be published in the ROS-World automatically! diff --git a/doc/install/turtlesim-example.md b/doc/install/turtlesim-example.md index ace7384..8fe1b44 100644 --- a/doc/install/turtlesim-example.md +++ b/doc/install/turtlesim-example.md @@ -29,33 +29,18 @@ Before executing any commands you need to create two configuration folders. In t `whitelist.json` : ```json -{ - "turtle\\w+": { - "publisher": ["cmd_vel"], - "subscriber": ["pose"] - } -} +{} ``` **NOTE** `config.json`: The Context-Broker runs locally, so the configuration should be fine for you, as long as your Context-Broker is also locally. -Inside `config_1` we add the file `robots.json` with the following content: +Inside `config_1` we add the file `topics.json` with the following content: ```json { - "turtle1": { - "topics": { - "cmd_vel": { - "msg": "geometry_msgs.msg.Twist", - "type": "publisher" - }, - "pose": { - "msg": "turtlesim.msg.Pose", - "type": "subscriber" - } - } - } + "/turtle1/cmd_vel": ["geometry_msgs/Twist", "publisher"], + "/turtle1/pose": ["turtlesim/Pose", "subscriber"] } ``` @@ -63,18 +48,8 @@ and for `config_2` the following swapped content: ```json { - "turtle1": { - "topics": { - "cmd_vel": { - "msg": "geometry_msgs.msg.Twist", - "type": "subscriber" - }, - "pose": { - "msg": "turtlesim.msg.Pose", - "type": "publisher" - } - } - } + "/turtle1/cmd_vel": ["geometry_msgs/Twist", "subscriber"], + "/turtle1/pose": ["turtlesim/Pose", "publisher"] } ``` @@ -84,11 +59,11 @@ Executing `tree` should give you the follwing structure: . ├── config_1 │   ├── config.json -│   ├── robots.json +│   ├── topics.json │   └── whitelist.json └── config_2 ├── config.json - ├── robots.json # Publisher/Subscriber should be swapped here + ├── topics.json # Publisher/Subscriber should be swapped here └── whitelist.json ``` @@ -180,9 +155,13 @@ Turtlesim is stopped and you can observe that the two FIROS-Instances are still ![Demonstration](../media/turtlesim_example.gif) +**Note:** The small Demonstration above uses the old configuration files (`robots.json`), which was redesigned into +`topics.json`. Also, newer Versions of the Orion Context-Broker do not send updates to Firos, when no data changed. + ## Troubleshooting: ### No Messages are sent between the two FIROS-instances If this is the case. Please check your firewall-configuration (or disable the firewall temporarily, for a quick sanity -check) +check). It can also be a Orion Context-Broker version, which does not send an update to the second firos instance. In +this case, the message is only sent ones. diff --git a/doc/removed.md b/doc/removed.md new file mode 100644 index 0000000..4dbbd2f --- /dev/null +++ b/doc/removed.md @@ -0,0 +1,13 @@ +# Removed Features + +## REST-API `whitelist` on FIROS + +FIROS had a REST-API, where someone could manipulate the whitelist of an FIROS-instance (see: [API](user/api.md)). We +removed this functionallity, because the `whitelist.json` is definitely known prior and is overwritten by the +configuration in `robots.json`. + +Specifically, the following methods are no longer available: + +- `FIROS:/whitelist/write` +- `FIROS:/whitelist/remove` +- `FIROS:/whitelist/restore` diff --git a/doc/user/api.md b/doc/user/api.md index 316a4d5..7c6f138 100644 --- a/doc/user/api.md +++ b/doc/user/api.md @@ -4,233 +4,77 @@ FIROS has several REST entry points that can be used to get or post data from/to You can find the old FIROS API [here](https://firos.docs.apiary.io/) (OLD) -## GET /robots +## GET /topics -Get robots handled by FIROS with their corresponding _topics_. Each _topic_ contains the `name`, `type`, `pubsub`-Role -and `structure` as follows: +Get topics handled by FIROS with their corresponding _topics_. Each _topic_ contains the `topic`, `messageType`, +`pubsub` and `structure` as follows: ```json [ { - "topics": [ - { - "type": "turtlesim.msg.Pose", - "name": "pose", - "structure": { - "y": "float32", - "x": "float32", - "linear_velocity": "float32", - "angular_velocity": "float32", - "theta": "float32" - }, - "pubsub": "subscriber" + "topic": "/turtle1/cmd_vel", + "structure": { + "linear": { + "y": "float64", + "x": "float64", + "z": "float64" }, - { - "type": "geometry_msgs.msg.Twist", - "name": "cmd_vel", - "structure": { - "linear": { - "y": "float64", - "x": "float64", - "z": "float64" - }, - "angular": { - "y": "float64", - "x": "float64", - "z": "float64" - } - }, - "pubsub": "publisher" - } - ], - "name": "turtle1" - } -] -``` - -## GET /robot/NAME - -Gets the data which is published by the robot to e.g the Context-Broker. - -Here as an example for `/robot/turtle1`: the content of `turtle1` with its publishing topic `pose`: - -```json -{ - "id": "turtle1", - "type": "MyROBOT", - "pose": { - "type": "turtlesim.Pose", - "value": { - "y": { - "type": "number", - "value": 5.544444561 - }, - "x": { - "type": "number", - "value": 5.544444561 - }, - "linear_velocity": { - "type": "number", - "value": 0 - }, - "theta": { - "type": "number", - "value": 0 - }, - "angular_velocity": { - "type": "number", - "value": 0 + "angular": { + "y": "float64", + "x": "float64", + "z": "float64" } }, - "metadata": { - "dataType": { - "type": "dataType", - "value": { - "y": "float32", - "x": "float32", - "linear_velocity": "float32", - "theta": "float32", - "angular_velocity": "float32" - } - } - } + "messageType": "geometry_msgs/Twist", + "pubSub": "subscriber" } -} +] ``` -## POST /firos +## GET /topic/TOPIC -This API handles the subscription data of the context broker. +Gets the data which is published by the topic to e.g the Context-Broker. -## POST /robot/connect +Topics, which are retrieved by the Non-ROS-World (`publisher`) are not visible here. -This call restores the configuration of FIROS. Disconnected robots are connected again. - -## POST /robot/disconnect/NAME - -This call forces FIROS to disconnect from the robot specified by the **NAME** parameter. If Publisher, FIROS will no -longer publish its data. If Subscriber, FIROS will not push the Information into the ROS-World - -## POST /whitelist/write - -This API overwrites or creates entries in the robot _whitelist_. This can be done by sending the following data: +Here as an example for `/topic/turtle1/pose`: the content of `/turtle1/pose`: ```json { - "turtle\\w+": { - "publisher": ["cmd_vel"], - "subscriber": ["pose"] + "angular_velocity": { + "type": "number", + "value": 0.0 }, - "robot\\w+": { - "publisher": ["cmd_vel.*teleop", ".*move_base/goal"], - "subscriber": [".*move_base/result"] - } -} -``` - -NOTE: In case you want to keep any element, it must be sent along with the ones to be replaced. - -EXAMPLE: - -Take this _whitelist.json_ as a starting point: - -```json -{ - "turtle\\w+": { - "publisher": ["cmd_vel"], - "subscriber": ["pose"] - } -} -``` - -Now, the following command is sent: - -```json -POST /whitelist/write -{ - "turtle\\w+": { - "publisher": ["cmd_vel2"], - "subscriber": [] - } -} -``` - -The resulting _whitelist_ will be as follows: - -```json -{ - "turtle\\w+": { - "publisher": ["cmd_vel2"], - "subscriber": [] - } -} -``` - -## POST /whitelist/remove - -This API removes elements from the _whitelist_. The format is as follows: - -```json -{ - "turtle\\w+": { - "publisher": [], - "subscriber": ["pose"] + "linear_velocity": { + "type": "number", + "value": 0.0 }, - "robot\\w+": { - "publisher": ["cmd_vel.*teleop", ".*move_base/goal"], - "subscriber": [] - } -} -``` - -EXAMPLE: - -Take this _whitelist.json_ as a starting point: - -```json -{ - "turtle\\w+": { - "publisher": ["cmd_vel"], - "subscriber": ["pose"] + "theta": { + "type": "number", + "value": 0.0 }, - "robot\\w+": { - "publisher": ["cmd_vel.*teleop", ".*move_base/goal"], - "subscriber": [".*move_base/result"] - } + "y": { + "type": "number", + "value": 5.544444561004639 + }, + "x": { + "type": "number", + "value": 5.544444561004639 + }, + "type": "turtlesim/Pose", + "id": "/turtle1/pose" } ``` -Now, the following json is sent: +## POST /firos -```json -POST /whitelist/remove -{ - "turtle\\w+": { - "publisher": [], - "subscriber": ["pose"] - }, - "robot\\w+": { - "publisher": ["cmd_vel.*teleop", ".*move_base/goal"], - "subscriber": [] - } -} -``` +This API handles the subscription data of the context broker. -The resulting _whitelist_ will look as follows: +## POST /connect -```json -{ - "turtle\\w+": { - "publisher": ["cmd_vel"], - "subscriber": [] - }, - "robot\\w+": { - "publisher": [], - "subscriber": [".*move_base/result"] - } -} -``` +This call restores the configuration of FIROS. Disconnected topics are connected again. -## POST /whitelist/restore +## POST /disconnect/NAME -This API restores the _whitelist_ file to its initial state. +This call forces FIROS to disconnect from the topic specified by the **NAME** parameter. If Publisher, FIROS will no +longer publish its data. If Subscriber, FIROS will not push the Information into the ROS-World diff --git a/firos/core.py b/firos/core.py index f37eb48..4b05471 100755 --- a/firos/core.py +++ b/firos/core.py @@ -36,15 +36,10 @@ from include.constants import Constants as C -_NODE_NAME = "firos" -STD_PARAM_NAME_CONF_PATH = "config_path" - # Main function. if __name__ == '__main__': - rospy.init_node(_NODE_NAME) - # If launched via roslaunch, we get addtional parameters rosLaunch_name = None for i in range(len(sys.argv)): @@ -74,15 +69,10 @@ current_path = os.path.dirname(os.path.abspath(__file__)) conf_path = current_path + "/../config" - # Check if a value for the path is specified already in ROS - if rospy.has_param("~" + STD_PARAM_NAME_CONF_PATH): - conf_path = rospy.get_param("~" + STD_PARAM_NAME_CONF_PATH) # Check if the CLI specified a config folder if results.conf_Fold is not None: - current_path = os.getcwd() - conf_path = current_path + "/" + results.conf_Fold - + conf_path = os.path.abspath(results.conf_Fold) # Initialize global variables (Constants.py) C.init(conf_path) diff --git a/firos/include/confManager.py b/firos/include/confManager.py index 1febdaf..f82a2b2 100644 --- a/firos/include/confManager.py +++ b/firos/include/confManager.py @@ -35,31 +35,35 @@ def getRobots(refresh=False): ''' try: # Retrieves the whitelist.json. If it does not exists, it returns all topics. - robots = copy.deepcopy(RosConfigurator.systemTopics(refresh)) + topics_regex = copy.deepcopy(RosConfigurator.systemTopics(refresh)) # TODO DL change to topics # Retrieves the robots.json. - robots_json = getRobotsByJson() - if len(robots_json) == 0: - Log("ERROR", "The file 'robots.json' is either empty or does not exist!\n\nExiting") + topics_json = getTopicsByJson() + if len(topics_json) == 0: + Log("ERROR", "The file 'topics.json' is either empty or does not exist!\n\nExiting") sys.exit(1) - # Merge robots.json into whitelist.json (overwrite if neccessary) - for robot_name in robots_json: - robot_name = str(robot_name) - if robot_name not in robots: - robots[robot_name] = { - "topics": {} - } - for topic_name in robots_json[robot_name]["topics"]: - topic = robots_json[robot_name]["topics"][topic_name] + # check if structure is as needed + for key in topics_json.keys(): + if len(topics_json[key]) != 2: + Log("ERROR", "The topic: '{}', does not have a list of length 2 (topics.json)! \n\nExiting".format(key)) + sys.exit(1) - # Overwrite or add! - robots[robot_name]["topics"][str(topic_name)] = { - "msg": str(topic["msg"]), - "type": str(topic["type"]) - } + if not key.startswith("/"): + Log("ERROR", "The topic: '{}', does not start with '/' (topics.json)! \n\nExiting".format(key)) + sys.exit(1) - return robots + if topics_json[key][1] not in ["publisher", "subscriber"]: + Log("ERROR", "The topic: '{}', does not specify publisher or subscriber (topics.json)! \n\nExiting".format(key)) + sys.exit(1) + + + # Merge both dictionaties: + # Here topics_json overrides entries in topics_regex: + topics_regex.update(topics_json) + topics = topics_regex + + return topics except Exception as e: traceback.print_exc() @@ -67,11 +71,11 @@ def getRobots(refresh=False): return {} -def getRobotsByJson(): - ''' Load the 'robots.json'-File +def getTopicsByJson(): + ''' Load the 'topics.json'-File ''' try: - json_path = C.PATH + "/robots.json" + json_path = C.PATH + "/topics.json" return json.load(open(json_path)) except: return {} diff --git a/firos/include/constants.py b/firos/include/constants.py index 6ebaca9..f55b073 100644 --- a/firos/include/constants.py +++ b/firos/include/constants.py @@ -35,8 +35,7 @@ class Constants: EP_SERVER_ADRESS = None EP_SERVER_PORT = None MAP_SERVER_PORT = 10100 - ROSBRIDGE_PORT = 9090 - CONTEXT_TYPE = "ROBOT" + ROSBRIDGE_PORT = 9090 PUB_FREQUENCY = 0 # In Milliseconds ROS_NODE_NAME = "firos" @@ -71,9 +70,6 @@ def init(cls, path): if "ros_subscriber_queue" in configData: cls.ROS_SUB_QUEUE_SIZE = int(configData["ros_subscriber_queue"]) - if "context_type" in configData: - cls.CONTEXT_TYPE = configData["context_type"] - if "endpoint" in configData and "address" in configData["endpoint"]: cls.EP_SERVER_ADRESS = configData["endpoint"]["address"] else: diff --git a/firos/include/libLoader.py b/firos/include/libLoader.py index f98c18b..289bed4 100644 --- a/firos/include/libLoader.py +++ b/firos/include/libLoader.py @@ -36,7 +36,7 @@ class LibLoader: the class could not be generated), this LibLoader tries to load the message via roslib. If every method fails FIROS will shutdown, since FIROS need the messages due to rospy beforehand. At least for The Subscriptions and Publishes defined in - 'robots.json' + 'topics.json' ''' # Our custom search path for genpy @@ -114,20 +114,14 @@ def _init_searchpath_for_available_msgs_on_system(): @staticmethod - def loadFromSystem(msgType, robotID, topic): + def loadFromSystem(msgType, topic): ''' This actually tries all three methods mentioned above. - - Remark: If the regex does not find a match, we are also not able - to parse the Configuration-File ('robots.json') and exit. ''' - matches = re.search(regex, msgType) # get (PACKAGE).(msg).(MSGTYPE) + splits = msgType.split("/") - if matches is not None: - # We have a Match, we can start to get the Message now! - module_name = str(matches.group(1)) + str(matches.group(2))[:-4] # PACKAGE + '.msg' - modules = str(matches.group(3)).split(".") # MSGTYPE - modules = modules[1: len(modules)] - module_msg = modules[0] + if len(splits) == 2: + module_name = splits[0] # PACKAGE + module_msg = splits[1] # MESSAGE ##### 1: Try to load it via Python-Import @@ -167,7 +161,7 @@ def loadFromSystem(msgType, robotID, topic): LibLoader.isGenerated = True module = imp.load_source(module_msg, msgsFold + module_name + "/_" + module_msg + ".py") clazz = getattr(module, module_msg) - Log("INFO", "Message {}/{}.msg succesfully loaded.".format(module_name, module_msg)) + Log("INFO", "Message {}/{} succesfully loaded.".format(module_name, module_msg)) return clazz @@ -176,10 +170,10 @@ def loadFromSystem(msgType, robotID, topic): try: import roslib.message import rostopic - type_name = rostopic.get_topic_type('/{}/{}'.format(robotID, topic), blocking=False)[0] + type_name = rostopic.get_topic_type(topic, blocking=False)[0] if type_name: clazz = roslib.message.get_message_class(type_name) - Log("INFO", "Message {}/{}.msg loaded via roslib.message!".format(module_name, module_msg)) + Log("INFO", "Message {}/{} loaded via roslib.message!".format(module_name, module_msg)) return clazz except Exception: pass diff --git a/firos/include/pubsub/contextbroker/cbPublisher.py b/firos/include/pubsub/contextbroker/cbPublisher.py index d5f18c9..b97d17d 100644 --- a/firos/include/pubsub/contextbroker/cbPublisher.py +++ b/firos/include/pubsub/contextbroker/cbPublisher.py @@ -45,9 +45,8 @@ class CbPublisher(Publisher): ''' # Keeps track of the posted Content on the ContextBroker - # via posted_history[ROBOT_ID][TOPIC] + # via posted_history[ROBOT_ID + "/" + TOPIC] posted_history = {} - definitionDict = {} CB_HEADER = {'Content-Type': 'application/json'} CB_BASE_URL = None @@ -74,12 +73,11 @@ def __init__(self): self.CB_BASE_URL = "http://{}:{}/v2/entities/".format(data["address"], data["port"]) - def publish(self, robotID, topic, rawMsg, msgDefintionDict): + def publish(self, topic, rawMsg, msgDefintionDict): ''' This is the actual publish-Routine which updates and creates Entities on the ContextBroker. It also keeps track via posted_history on already posted entities and topics - robotID: A string corresponding to the Robot-Id - topic: Also a string, corresponding to the topic of the robot + topic: a string, corresponding to the topic in ros rawMsg: the raw data directly obtained from rospy msgDefintionDict: The Definition as obtained directly from ROS-Messages @@ -94,82 +92,41 @@ def publish(self, robotID, topic, rawMsg, msgDefintionDict): # if struct not initilized, intitilize it even on ContextBroker! - if robotID not in self.posted_history: - self.posted_history[robotID] = {} - self.posted_history[robotID]['type'] = C.CONTEXT_TYPE - self.posted_history[robotID]['id'] = robotID - # Intitialize Entitiy/Robot-Construct on ContextBroker - jsonStr = ObjectFiwareConverter.obj2Fiware(self.posted_history[robotID], ind=0, ignorePythonMetaData=True) + if topic not in self.posted_history: + self.posted_history[topic] = rawMsg + + obj = {s: getattr(rawMsg, s, None) for s in rawMsg.__slots__} + obj["type"] = rawMsg._type.replace("/", ".") # OCB Specific!! + obj["id"] = (topic).replace("/", ".") # OCB Specific!! + jsonStr = ObjectFiwareConverter.obj2Fiware(obj, ind=None, dataTypeDict=msgDefintionDict[topic], ignorePythonMetaData=True) response = requests.post(self.CB_BASE_URL, data=jsonStr, headers=self.CB_HEADER) - self._responseCheck(response, attrAction=0, topEnt=robotID) - - if topic not in self.posted_history[robotID]: - self.posted_history[robotID][topic] = {} - - # Check if descriptions are already added, if not execute again with descriptions! - if 'descriptions' not in self.posted_history[robotID]: - self.posted_history[robotID]['descriptions'] = self._loadDescriptions(robotID) - if self.posted_history[robotID]['descriptions'] is not None: - self.publish(robotID, 'descriptions', self.posted_history[robotID]['descriptions'], None) - - # check if previous posted topic type is the same, iff not, we do not post it to the context broker - if (self.posted_history[robotID][topic] != {} and topic != "descriptions" - and rawMsg._type != self.posted_history[robotID][topic]._type): - Log("ERROR", "Received Msg-Type '{}' but expected '{}' on Topic '{}'".format(rawMsg._type, self.posted_history[robotID][topic]._type, topic)) + self._responseCheck(response, attrAction=0, topEnt=topic) return - # Replace previous rawMsg with current one - self.posted_history[robotID][topic] = rawMsg - - # Set Definition-Dict if not set - if msgDefintionDict is None: - msgDefintionDict = {} - # Convert rawMsg - completeJsonStr = ObjectFiwareConverter.obj2Fiware(self.posted_history[robotID], ind=0, dataTypeDict=msgDefintionDict, ignorePythonMetaData=True) - - # format json, so that the contextbroker accepts it. - partJsonStr = json.dumps({ - topic: json.loads(completeJsonStr)[topic] - }) + self.posted_history[topic] = rawMsg + # Create Update-JSON + obj = {s: getattr(rawMsg, s, None) for s in rawMsg.__slots__} + obj["type"] = rawMsg._type.replace("/", ".") # OCB Specific!! + obj["id"] = (topic).replace("/", ".") # OCB Specific!! + jsonStr = ObjectFiwareConverter.obj2Fiware(obj, ind=0, dataTypeDict=msgDefintionDict[topic], ignorePythonMetaData=True, showIdValue=False) # Update attribute on ContextBroker - response = requests.post(self.CB_BASE_URL + robotID + "/attrs", data=partJsonStr, headers=self.CB_HEADER) + response = requests.post(self.CB_BASE_URL + obj["id"] + "/attrs", data=jsonStr, headers=self.CB_HEADER) self._responseCheck(response, attrAction=1, topEnt=topic) def unpublish(self): ''' - Removes all previously tracked Entities/Robots on ContextBroker + Removes all previously tracked topics on ContextBroker This method also gets automaticall called, someone sent Firos the Shutdown Signal ''' - for robotID in self.posted_history: - response = requests.delete(self.CB_BASE_URL + robotID) - self._responseCheck(response, attrAction=2, topEnt=robotID) + for idd in self.posted_history.keys(): + response = requests.delete(self.CB_BASE_URL + idd.replace("/", ".")) # OCB Specific!! + self._responseCheck(response, attrAction=2, topEnt=idd) - def _loadDescriptions(self, robotID): - ''' This simply load the descriptions from the 'robotdescriptions.json'-file and - return its value. We publish the data contained also onto the ContextBroker - - (It is not necessary!) - - robotID: The Robot-Id-String - ''' - - json_path = C.PATH + "/robotdescriptions.json" - - if not os.path.isfile(json_path): - return None - - description_data = json.load(open(json_path)) - # Check if a robotID has descriptions - if robotID in description_data: - if 'descriptions' in description_data[robotID]: - return description_data[robotID]['descriptions'] - - return None def _responseCheck(self, response, attrAction=0, topEnt=None): @@ -181,11 +138,11 @@ def _responseCheck(self, response, attrAction=0, topEnt=None): ''' if not response.ok: if attrAction == 0: - Log("WARNING", "Could not create Entitiy/Robot {} in Contextbroker :".format(topEnt)) + Log("WARNING", "Could not create Entitiy {} in Contextbroker :".format(topEnt)) Log("WARNING", response.content) elif attrAction == 1: Log("ERROR", "Cannot update attributes in Contextbroker for topic: {} :".format(topEnt)) Log("ERROR", response.content) else: - Log("WARNING", "Could not delete Entitiy/Robot {} in Contextbroker :".format(topEnt)) + Log("WARNING", "Could not delete Entitiy {} in Contextbroker :".format(topEnt)) Log("WARNING", response.content) \ No newline at end of file diff --git a/firos/include/pubsub/contextbroker/cbSubscriber.py b/firos/include/pubsub/contextbroker/cbSubscriber.py index 7e3c24b..05b4fc7 100644 --- a/firos/include/pubsub/contextbroker/cbSubscriber.py +++ b/firos/include/pubsub/contextbroker/cbSubscriber.py @@ -107,9 +107,8 @@ def __init__(self): self.CB_BASE_URL = "http://{}:{}".format(data["address"], data["port"]) - def subscribe(self, robotID, topicList, msgDefintions): - ''' robotID: The string of the robotID - topicList: A list of topics, corresponding to the robotID + def subscribe(self, topicList, topicTypes, msgDefintions): + ''' topicList: A list of topics msgDefintions: The Messages-Definitions from ROS This method only gets called once (or multiple times, if we get a reset!)! So we need to make sure, that in this file @@ -148,11 +147,10 @@ def subscribe(self, robotID, topicList, msgDefintions): # If not already subscribed, start a new thread which handles the subscription for each topic for an robot. # And only If the topic list is not empty! - if robotID not in self.subscriptionIds and topicList: - Log("INFO", "Subscribing on Context-Broker to " + robotID + " and topics: " + str(list(topicList))) - self.subscriptionIds[robotID] = {} - for topic in topicList: - thread.start_new_thread(self.subscribeThread, (robotID, topic)) #Start Thread via subscription + for topic in topicList: + if topic not in self.subscriptionIds: + Log("INFO", "Subscribing on Context-Broker to topics: " + str(list(topicList))) + thread.start_new_thread(self.subscribeThread, (topic, topicTypes, msgDefintions)) #Start Thread via subscription def unsubscribe(self): @@ -168,10 +166,9 @@ def unsubscribe(self): self.server.close() # Unsubscribe to all Topics - for robotID in self.subscriptionIds: - for topic in self.subscriptionIds[robotID]: - response = requests.delete(self.CB_BASE_URL + self.subscriptionIds[robotID][topic]) - self._checkResponse(response, subID=self.subscriptionIds[robotID][topic]) + for topic in self.subscriptionIds: + response = requests.delete(self.CB_BASE_URL + self.subscriptionIds[topic]) + self._checkResponse(response, subID=self.subscriptionIds[topic]) @@ -179,44 +176,42 @@ def unsubscribe(self): ########## Helpful Classes and Methods ############# #################################################### - def subscribeThread(self, robotID, topic): + def subscribeThread(self, topic, topicTypes, msgDefintions): ''' A Subscription-Thread. Its Life-Cycle is as follows: -> Subscribe -> Delete old Subs-ID -> Save new Subs-ID -> Wait -> - robotID: A string corresponding to the robotID topic: The Topic (string) to subscribe to. ''' while True: # Subscribe - jsonData = self.subscribeJSONGenerator(robotID, topic) + jsonData = self.subscribeJSONGenerator(topic, topicTypes, msgDefintions) response = requests.post(self.CB_BASE_URL + "/v2/subscriptions", data=jsonData, headers={'Content-Type': 'application/json'}) - self._checkResponse(response, created=True, robTop=(robotID, topic)) + self._checkResponse(response, created=True, robTop=topic) if 'Location' in response.headers: newSubID = response.headers['Location'] # <- get subscription-ID else: - Log("WARNING", "Firos was not able to subscribe to topic: {} for robot {}".format(topic, robotID)) + Log("WARNING", "Firos was not able to subscribe to topic: {}".format(topic)) # Unsubscribe - if robotID in self.subscriptionIds and topic in self.subscriptionIds[robotID]: - response = requests.delete(self.CB_BASE_URL + self.subscriptionIds[robotID][topic]) - self._checkResponse(response, subID=self.subscriptionIds[robotID][topic]) + if topic in self.subscriptionIds: + response = requests.delete(self.CB_BASE_URL + self.subscriptionIds[topic]) + self._checkResponse(response, subID=self.subscriptionIds[topic]) # Save new ID - self.subscriptionIds[robotID][topic] = newSubID + self.subscriptionIds[topic] = newSubID # Wait time.sleep(int(self.data["subscription"]["subscription_length"] * self.data["subscription"]["subscription_refresh_delay"])) # sleep Length * Refresh-Rate (where 0 < Refresh-Rate < 1) - Log("INFO", "Refreshing Subscription for " + robotID + " and topic: " + str(topic)) + Log("INFO", "Refreshing Subscription for topic: " + str(topic)) - def subscribeJSONGenerator(self, robotID, topic): + def subscribeJSONGenerator(self, topic, topicTypes, msgDefintions): ''' This method returns the correct JSON-format to subscribe to the ContextBroker. - The Expiration-Date/Throttle and Type of robots is retreived here via the configuration we got + The Expiration-Date/Throttle and Type of topics is retreived here via the configuration we got - robotID: The String of the Robot-Id. topic: The actual topic to subscribe to. ''' # This struct correspondes to following JSON-format: @@ -225,19 +220,16 @@ def subscribeJSONGenerator(self, robotID, topic): "subject": { "entities": [ { - "id": str(robotID), - "type": C.CONTEXT_TYPE + "id": str(topic).replace("/", "."), # OCB Specific!! + "type": topicTypes[topic].replace("/", ".") # OCB Specific!! } - ], - "condition": { - "attrs": [str(topic)] - } + ] }, "notification": { "http": { "url": "http://{}:{}".format(C.EP_SERVER_ADRESS, self.server.port) }, - "attrs": [str(topic)] + "attrs": list(msgDefintions[topic].keys()) }, "expires": time.strftime("%Y-%m-%dT%H:%M:%S.00Z", time.gmtime(time.time() + self.data["subscription"]["subscription_length"])), # ISO 8601 "throttling": self.data["subscription"]["throttling"] @@ -250,13 +242,13 @@ def _checkResponse(self, response, robTop=None, subID=None, created=False): If a not good response from ContextBroker is received, the error will be printed. response: The response from ContextBroker - robTop: A string Tuple (robotId, topic), for the curretn robot/topic + robTop: A string (topic), for the curretn robot/topic subID: The Subscription ID string, which should get deleted created: Creation or Deletion of a subscription (bool) ''' if not response.ok: if created: - Log("ERROR", "Could not create subscription for Robot {} and topic {} in Context-Broker :".format(robTop[0], robTop[1])) + Log("ERROR", "Could not create subscription for topic {} in Context-Broker :".format(robTop)) Log("ERROR", response.content) else: Log("WARNING", "Could not delete subscription {} from Context-Broker :".format(subID)) @@ -317,7 +309,9 @@ class CBHandler(BaseHTTPRequestHandler): Requests and converts the received Data into a "ROS-conform Message". in """do_POST""" we invoke """RosTopicHandler.publish""" ''' - + def log_message(self, format, *args): + ''' Suppress prints! ''' + return def do_GET(self): ''' @@ -340,21 +334,25 @@ def do_POST(self): receivedData = json.loads(recData) data = receivedData['data'][0] # Specific to NGSIv2 jsonData = json.dumps(data) - topics = data.keys() # Convention Topic-Names are the attributes by an JSON Object, except: type, id - # iterate through every 'topic', since we only receive updates from one topic - # Only id, type and 'topicname' are present - for topic in topics: - if topic != 'id' and topic != 'type': - dataStruct = self._buildTypeStruct(data[topic]) - # Convert Back into a Python-Object - obj = self.TypeValue() - ObjectFiwareConverter.fiware2Obj(jsonData, obj, setAttr=True, useMetaData=False) - # Publish in ROS - RosTopicHandler.publish(data['id'], topic, getattr(obj, topic), dataStruct) - # Send OK! + obj = self.TypeValue() + ObjectFiwareConverter.fiware2Obj(jsonData, obj, setAttr=True, useMetaData=False) + obj.id = obj.id.replace(".", "/") + obj.type = obj.type.replace(".", "/") + + objType = obj.type + topic = obj.id + + del data["id"] + del data["type"] + tempDict = dict(type=objType, value=data) + + dataStruct = self._buildTypeStruct(tempDict) + + RosTopicHandler.publish(topic, obj.__dict__, dataStruct) + # # Send OK! self.send_response(204) self.end_headers() # Python 3 needs an extra end_headers after send_response @@ -371,8 +369,8 @@ def _buildTypeStruct(self, obj): ''' s = {} - # Searching for a point to get ROS-Message-Types from the obj, see Fiware-Object-Converter - if 'value' in obj and 'type' in obj and "." in obj['type'] : + # Searching for a point to get ROS-Message-Types from the obj + if 'value' in obj and 'type' in obj and "/" in obj['type'] : s['type'] = obj['type'] objval = obj['value'] s['value'] = {} diff --git a/firos/include/pubsub/examplePubSub/publisher.py b/firos/include/pubsub/examplePubSub/publisher.py index a981157..181faee 100644 --- a/firos/include/pubsub/examplePubSub/publisher.py +++ b/firos/include/pubsub/examplePubSub/publisher.py @@ -22,7 +22,7 @@ def __init__(self): ''' - def publish(self, robotID, topic, rawMsg, msgDefinitions): + def publish(self, topic, rawMsg, msgDefinitions): ''' Here goes the Routine to publish something. It is called automatically! diff --git a/firos/include/pubsub/examplePubSub/subscriber.py b/firos/include/pubsub/examplePubSub/subscriber.py index fd9d283..99db98b 100644 --- a/firos/include/pubsub/examplePubSub/subscriber.py +++ b/firos/include/pubsub/examplePubSub/subscriber.py @@ -24,7 +24,7 @@ def __init__(self): - def subscribe(self, robotID, topicList, msgDefinitions): + def subscribe(self, topicList, topicTypes, msgDefinitions): ''' Here goes the Subscription Routine This Routine needs to make sure it that it be called somehow from an extern Signal diff --git a/firos/include/pubsub/genericPubSub.py b/firos/include/pubsub/genericPubSub.py index 85cef92..b5530c5 100644 --- a/firos/include/pubsub/genericPubSub.py +++ b/firos/include/pubsub/genericPubSub.py @@ -47,7 +47,7 @@ class Publisher(ABC): configData = dict() @abc.abstractmethod - def publish(self, robotID, topic, rawMsg, msgDefinitions): + def publish(self, topic, rawMsg, msgDefinitions): pass @abc.abstractmethod @@ -66,7 +66,7 @@ class Subscriber(ABC): configData = dict() @abc.abstractmethod - def subscribe(self, robotID, topicList, msgDefinitions): + def subscribe(self, topicList, topicTypes, msgDefinitions): pass @abc.abstractmethod @@ -151,12 +151,12 @@ def _getPubSubConstants(self, fold): return None - def publish(self, robotID, topic, rawMsg, msgDefinitions): + def publish(self, topic, rawMsg, msgDefinitions): ''' Call publish on each Publisher ''' for pub in self.publishers: - pub.publish(robotID, topic, rawMsg, msgDefinitions) + pub.publish(topic, rawMsg, msgDefinitions) def unpublish(self): @@ -166,12 +166,12 @@ def unpublish(self): for pub in self.publishers: pub.unpublish() - def subscribe(self, robotID, topicList, msgDefinitions): + def subscribe(self, topicList, topicTypes, msgDefinitions): ''' Call subscribe on each Subscriber ''' for sub in self.subscribers: - sub.subscribe(robotID, topicList, msgDefinitions) + sub.subscribe(topicList, topicTypes, msgDefinitions) def unsubscribe(self): ''' diff --git a/firos/include/ros/rosConfigurator.py b/firos/include/ros/rosConfigurator.py index d2f4798..142602c 100644 --- a/firos/include/ros/rosConfigurator.py +++ b/firos/include/ros/rosConfigurator.py @@ -14,6 +14,7 @@ # FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +import os import re import json import rostopic @@ -25,7 +26,7 @@ entries = [] # Entries we found in the ROS-World whitelist = {} # The Current Whitelist FIROS is currently using -robots = {} # The dictionary containing: robots[ROBOT_ID]["topics"][TOPIC_NAME]["msg"/"type"] +robots = {} # The dictionary containing: robots["topics"] = [MessageType, pubSub] class RosConfigurator: ''' @@ -43,8 +44,8 @@ def getAllTopics(refresh=True): global entries if refresh or len(entries) == 0: listOfData = rospy.get_published_topics() - entries = [item for sublist in listOfData for item in sublist] - + entries = [item for sublist in listOfData for item in sublist if item.startswith("/")] + return entries @@ -56,10 +57,10 @@ def getWhiteList(restore=False): ''' global whitelist if whitelist == {} or restore: + if not os.path.isfile(C.PATH + "/whitelist.json"): + return {} json_path = C.PATH + "/whitelist.json" whitelist = json.load(open(json_path)) - if len(whitelist) == 0: - Log("WARNING", "The 'whitelist.json' was not set. You might want to use a whitelist to avoid subscribing to every existing topic!") return whitelist @@ -83,25 +84,16 @@ def systemTopics(refresh=False, restore=False): entries = RosConfigurator.getAllTopics(refresh=refresh) whitelist = RosConfigurator.getWhiteList(restore=restore) - # Create the robots Structure as follows: - # robots[robotID]['topic'][topic]['publisher'/'subscriber']['type'/'msg'] + # Create the robots Structure _robots = {} - for robotIDRegex in whitelist: - # For each RobotID Regex - if "publisher" in whitelist[robotIDRegex]: - for topicRegex in whitelist[robotIDRegex]["publisher"]: - # For each topic Regex in publisher - # Build the Regex: - fullRegex = ("^\/("+ robotIDRegex + ")\/("+ topicRegex + ")$") - RosConfigurator.addRobots(_robots, fullRegex, entries, "publisher") - - if "subscriber" in whitelist[robotIDRegex]: - for topicRegex in whitelist[robotIDRegex]["subscriber"]: - # For each topic Regex in subscriber - # Build the Regex: - fullRegex = ("^\/("+ robotIDRegex + ")\/("+ topicRegex + ")$") - RosConfigurator.addRobots(_robots, fullRegex, entries, "subscriber") + if "publisher" in whitelist: + for regex in whitelist["publisher"]: + RosConfigurator.addRobots(_robots, regex, entries, "publisher") + + if "subscriber" in whitelist: + for regex in whitelist["subscriber"]: + RosConfigurator.addRobots(_robots, regex, entries, "subscriber") robots = _robots return robots @@ -114,7 +106,7 @@ def addRobots(robots, regex, entries, pubsub): We iterate over each entry and initialize the robots-dict appropiately. Then It is simply added. - robots: The dictionary robots[ROBOT_ID]["topics"][TOPIC_NAME] = {msg, type} + robots: The dictionary robots["topics"] = [MessageType , pubSub] regex: The Regex we try to match in each entry entries:The String Entries. Each element is in the following structure "/ROBOT_ID/TOPIC_NAME" pubsub: A String. Either "publisher" or "subscriber" @@ -123,35 +115,21 @@ def addRobots(robots, regex, entries, pubsub): matches = re.search(regex, entry) if matches is not None: # We found a Match. Now add it to robots - robotID = matches.group(1) - topic = matches.group(2) - if robotID not in robots: - # Init robot Dict - robots[robotID] = {} - robots[robotID]["topics"] = {} - - if topic not in robots[robotID]["topics"]: - # Init topic Dict - robots[robotID]["topics"][topic] = {} - - # Get Message Type and convert it to Python-Specific Import - topic_type, _, _ = rostopic.get_topic_type(entry) - msg_type = topic_type.replace("/", ".msg.") - - # Set it in robots - robots[robotID]["topics"][topic]["type"] = pubsub - robots[robotID]["topics"][topic]["msg"] = msg_type + if entry not in robots: + # if not already added, add it + topic_type, _, _ = rostopic.get_topic_type(entry) + robots[entry] = [topic_type ,pubsub] @staticmethod - def removeRobot(robot_name): + def removeTopic(topic): ''' - This removes the robot from robots + This removes the topic ''' global robots - if robot_name in robots: - del robots[robot_name] + if topic in robots: + del robots[topic] @staticmethod diff --git a/firos/include/ros/topicHandler.py b/firos/include/ros/topicHandler.py index acaecf4..eabe31a 100644 --- a/firos/include/ros/topicHandler.py +++ b/firos/include/ros/topicHandler.py @@ -36,14 +36,14 @@ # this Message is needed, for the Listeners on connect on disconnect import std_msgs.msg -# Structs whith robotID and each robotID with a topic: -# ROS_PUBSUB[robotID][topic] --> returns rospy Publisher/Subsriber +# Structs with topic: +# ROS_PUBSUB[topic] --> returns rospy Publisher/Subsriber ROS_PUBLISHER = {} ROS_SUBSCRIBER_LAST_MESSAGE = {} ROS_SUBSCRIBER = {} # A Struct which is used to minimize the Number of publishes. Here we only -# save time stamps of ids. LAST_PUBLISH_TIME[robotID, topic] would return a time +# save time stamps of ids. LAST_PUBLISH_TIME[topic] would return a time LAST_PUBLISH_TIME = dict() # Topics in ROS do only have one data-type! @@ -68,16 +68,16 @@ def initPubAndSub(): global CloudPubSub CloudPubSub = PubSub() -def loadMsgHandlers(robot_data): +def loadMsgHandlers(topics_data): ''' This method initializes The Publisher and Subscriber for ROS and the Subscribers for the Context-broker (based on ROS-Publishers). It also initializes the structs for ROS messages (types, dicts and classes) - For each robotID with its topic, the ROS message-structs are initialized. + For each topic, the ROS message-structs are initialized. Then (depending on Publisher or Subscriber) the corrsponding rospy Publisher/Subscriber is generated and added in its struct. - robot_data: The data, as in robots.json specified + topics_data: The data, as in topics.json (and whitelist) specified. TODO DL maybe change the message loading to something simpler? ''' @@ -87,49 +87,39 @@ def loadMsgHandlers(robot_data): Log("INFO", "Generating topic handlers:") # Generate + for topic in topics_data.keys(): + # for each topic and topic in topics_data: - for robotID in robot_data.keys(): - for topic in robot_data[robotID]['topics'].keys(): - # for each robotID and topic in robot_data: + # Load specific message from robot_data + # TODO DL maybe change this in config to ._type values? Refactor! + msg = str(topics_data[topic][0]) + theclass = LibLoader.loadFromSystem(msg, topic) + + # Add specific message in struct to not load it again later. + if theclass._type not in ROS_MESSAGE_CLASSES: + ROS_MESSAGE_CLASSES[theclass._type] = theclass # setting Class - # Load specific message from robot_data - # TODO DL maybe change this in config to ._type values? Refactor! - msg = str(robot_data[robotID]['topics'][topic]['msg']) - theclass = LibLoader.loadFromSystem(msg, robotID, topic) - - # Add specific message in struct to not load it again later. - if theclass._type not in ROS_MESSAGE_CLASSES: - ROS_MESSAGE_CLASSES[theclass._type.replace("/", ".")] = theclass # Replacing '/' with '.' see Obejct-Converter + # Create, if not already, a dictionary from the corresponding message-type + if topic not in ROS_TOPIC_AS_DICT: + ROS_TOPIC_AS_DICT[topic] = rosMsg2Dict(theclass()) + + # Set the topic class-type, which is for each topic always the same + ROS_TOPIC_TYPE[topic] = theclass._type + + # Create Publisher or Subscriber + if topics_data[topic][1].lower() == "subscriber": + # Case it is a subscriber, add it in subscribers + additionalArgsCallback = {"topic": topic} # Add addtional Infos about topic + ROS_SUBSCRIBER[topic] = rospy.Subscriber(topic, theclass, _publishToCBRoutine, additionalArgsCallback) + ROS_SUBSCRIBER_LAST_MESSAGE[topic] = None # No message currently published + else: + # Case it is a publisher, add it in publishers + ROS_PUBLISHER[topic] = rospy.Publisher(topic, theclass, queue_size=C.ROS_SUB_QUEUE_SIZE, latch=True) - # Create, if not already, a dictionary from the corresponding message-type - if topic not in ROS_TOPIC_AS_DICT: - ROS_TOPIC_AS_DICT[topic] = rosMsg2Dict(theclass()) - - # Set the topic class-type, which is for each topic always the same - ROS_TOPIC_TYPE[topic] = theclass._type - - # Create Publisher or Subscriber - if robot_data[robotID]['topics'][topic]['type'].lower() == "subscriber": - # Case it is a subscriber, add it in subscribers - if robotID not in ROS_SUBSCRIBER: - ROS_SUBSCRIBER[robotID] = {} - ROS_SUBSCRIBER_LAST_MESSAGE[robotID] = {} - - additionalArgsCallback = {"robot": robotID, "topic": topic} # Add addtional Infos about topic and robotID - ROS_SUBSCRIBER[robotID][topic] = rospy.Subscriber(robotID + "/" + topic, theclass, _publishToCBRoutine, additionalArgsCallback) - ROS_SUBSCRIBER_LAST_MESSAGE[robotID][topic] = None # No message currently published - else: - # Case it is a publisher, add it in publishers - if robotID not in ROS_PUBLISHER: - ROS_PUBLISHER[robotID] = {} - - ROS_PUBLISHER[robotID][topic] = rospy.Publisher(robotID + "/" + topic, theclass, queue_size=C.ROS_SUB_QUEUE_SIZE, latch=True) - - # After initializing ROS-PUB/SUBs, intitialize ContextBroker-Subscriber based on ROS-Publishers for each robot - if robotID in ROS_PUBLISHER: - CloudPubSub.subscribe(str(robotID), ROS_PUBLISHER[robotID].keys(), ROS_TOPIC_AS_DICT) - Log("INFO", "\n") - Log("INFO", "Subscribed to " + robotID + "'s topics\n") + # After initializing ROS-PUB/SUBs, intitialize ContextBroker-Subscriber based on ROS-Publishers for each robot + CloudPubSub.subscribe(ROS_PUBLISHER.keys(), ROS_TOPIC_TYPE, ROS_TOPIC_AS_DICT) + Log("INFO", "\n") + Log("INFO", "Subscribed to " + str(list(ROS_PUBLISHER.keys())) + "\n") def _publishToCBRoutine(data, args): @@ -143,17 +133,16 @@ def _publishToCBRoutine(data, args): args: additional arguments we set prior ''' if not SHUTDOWN_SIGNAL: - robot = args['robot'] topic = args['topic'] # Retreiving additional Infos, which were set on initialization - + t = time.time() * 1000 # Get Millis - if (robot+topic) in LAST_PUBLISH_TIME and LAST_PUBLISH_TIME[robot+topic] >= t: + if topic in LAST_PUBLISH_TIME and LAST_PUBLISH_TIME[topic] >= t: # Case: We want it to publish again, but we did not wait PUB_FREQUENCY milliseconds return - CloudPubSub.publish(robot, topic, data, ROS_TOPIC_AS_DICT) - ROS_SUBSCRIBER_LAST_MESSAGE[robot][topic] = data - LAST_PUBLISH_TIME[robot+topic] = t + C.PUB_FREQUENCY + CloudPubSub.publish(topic, data, ROS_TOPIC_AS_DICT) + ROS_SUBSCRIBER_LAST_MESSAGE[topic] = data + LAST_PUBLISH_TIME[topic] = t + C.PUB_FREQUENCY @@ -166,22 +155,21 @@ class RosTopicHandler: @staticmethod - def publish(robotID, topic, convertedData, dataStruct): + def publish(topic, convertedData, dataStruct): ''' This method publishes the receive data from the ContextBroker to ROS - robotID: The Robot-Id topic: The topic to be published convertedData: the converted data from the Subscriber dataStruct: The struct of convertedData, specified by their types ''' - if robotID in ROS_PUBLISHER and topic in ROS_PUBLISHER[robotID]: - if topic in ROS_TOPIC_TYPE and ROS_TOPIC_TYPE[topic].replace("/", ".") == dataStruct['type']: - # check if a publisher to this robotID and topic is set + if topic in ROS_PUBLISHER: + if topic in ROS_TOPIC_TYPE and ROS_TOPIC_TYPE[topic] == dataStruct['type']: + # check if a publisher to this topic is set # then check the received and expected type to be equal # Iff, then publish received message to ROS newMsg = instantiateROSMessage(convertedData, dataStruct) - ROS_PUBLISHER[robotID][topic].publish(newMsg) + ROS_PUBLISHER[topic].publish(newMsg) @staticmethod @@ -200,9 +188,8 @@ def unregisterAll(): Log("INFO", "Unsubscribing topics...") for subscriber in subscribers: subscriber.unregister() - for robotID in ROS_SUBSCRIBER: - for topic in ROS_SUBSCRIBER[robotID]: - ROS_SUBSCRIBER[robotID][topic].unregister() + for topic in ROS_SUBSCRIBER: + ROS_SUBSCRIBER[topic].unregister() Log("INFO", "Unsubscribed topics\n") @@ -220,9 +207,7 @@ def instantiateROSMessage(obj, dataStruct): # Load Message-Class only if not already loaded! if dataStruct['type'] not in ROS_MESSAGE_CLASSES: - msgType = dataStruct['type'].split(".") # see Fiware-Object-Converter, explicit Types of ROS-Messages are retreived from there - moduleLoader = importlib.import_module(msgType[0] + ".msg") - msgClass = getattr(moduleLoader, msgType[1]) + msgClass = LibLoader.loadFromSystem(dataStruct['type'], None) ROS_MESSAGE_CLASSES[dataStruct['type']] = msgClass #instantiate Message instance = ROS_MESSAGE_CLASSES[dataStruct['type']]() @@ -291,22 +276,24 @@ def createConnectionListeners(): def _robotDisconnection(data): - ''' Unregisters from a given robotID by a ROBOT + ''' Unregisters from a given topic data: The String which was sent to firos ''' - robotID = str(data.data) - - Log("INFO", "Disconnected robot: " + robotID) - if robotID in ROS_PUBLISHER: - for topic in ROS_PUBLISHER[robotID]: - ROS_PUBLISHER[robotID][topic].unregister() - del ROS_PUBLISHER[robotID] - - if robotID in ROS_SUBSCRIBER: - for topic in ROS_SUBSCRIBER[robotID]: - ROS_SUBSCRIBER[robotID][topic].unregister() - del ROS_SUBSCRIBER[robotID] + topic = str(data.data) + + + if topic in ROS_PUBLISHER: + for topic in ROS_PUBLISHER[topic]: + ROS_PUBLISHER[topic][topic].unregister() + Log("INFO", "Disconnected publisher for: " + topic) + del ROS_PUBLISHER[topic] + + if topic in ROS_SUBSCRIBER: + for topic in ROS_SUBSCRIBER[topic]: + ROS_SUBSCRIBER[topic][topic].unregister() + Log("INFO", "Disconnected subscriber for: " + topic) + del ROS_SUBSCRIBER[topic] def _robotConnection(data): diff --git a/firos/include/server/requestHandler.py b/firos/include/server/requestHandler.py index 52525c1..9d87961 100644 --- a/firos/include/server/requestHandler.py +++ b/firos/include/server/requestHandler.py @@ -103,33 +103,19 @@ def getAction(path, method): ############################################################################### ############################# Request Mapping ############################# -############################################################################### +############################################################################### -def listRobots(request, action): - ''' Generates a list of all robots (depending on RosConfigurator, confManager) +def listTopics(request, action): + ''' Generates a list of all topics (depending on RosConfigurator, confManager) and returns them back as json - - TODO DL, currently a List of containing 'topics' with a list of topics is returned - Better would be a list of robotIds with their corresponding topics and types ''' robots = getRobots(False) data = [] - for robot_name in robots.keys(): - robot_data = {"name": robot_name, "topics": []} - robot = robots[robot_name] - for topic_name in robot["topics"]: - topic = robot["topics"][topic_name] - topic_data = { - "name": topic_name, - "pubsub": topic["type"] - } - if type(topic["msg"]) is dict: - topic_data["type"] = "Custom" - topic_data["structure"] = topic["msg"] - else: - topic_data["type"] = topic["msg"] - topic_data["structure"] = ROS_TOPIC_AS_DICT[topic_name] - robot_data["topics"].append(topic_data) + for topic in robots.keys(): + robot_data = {"topic": topic, + "pubSub": robots[topic][1], + "messageType": robots[topic][0] } + robot_data["structure"] = ROS_TOPIC_AS_DICT[topic] data.append(robot_data) # Return data and success @@ -143,12 +129,20 @@ def onRobotData(request, action): Depending what is written after 'robot', specific content is published ''' - name = request.path[7:] - lastPubData = ROS_SUBSCRIBER_LAST_MESSAGE[name] - lastPubData["type"] = C.CONTEXT_TYPE - lastPubData["id"] = name + name = request.path[6:] + if name in ROS_SUBSCRIBER_LAST_MESSAGE: + lastPubData = ROS_SUBSCRIBER_LAST_MESSAGE[name] + if lastPubData is not None: + obj = {s: getattr(lastPubData, s, None) for s in lastPubData.__slots__} + obj["id"] = name + obj["type"] = lastPubData._type + json = ObjectFiwareConverter.obj2Fiware(obj, dataTypeDict=ROS_TOPIC_AS_DICT[name], ignorePythonMetaData=True, ind=None) + else: + json = "" + else: + json = "" - json = ObjectFiwareConverter.obj2Fiware(lastPubData, dataTypeDict=ROS_TOPIC_AS_DICT,ignorePythonMetaData=True, ind=0) + # Return the Information provided by the Context-Broker end_request(request, ('Content-Type', 'application/json'), 200, json) @@ -160,7 +154,7 @@ def onConnect(request, action): TODO DL reset, instead of connect? TODO DL Add real connect for only one Robot? ''' - Log("INFO", "Connecting robots") + Log("INFO", "Connecting topics") loadMsgHandlers(RosConfigurator.systemTopics(True)) # Return Success @@ -183,59 +177,37 @@ def onDisConnect(request, action): partURL = partURL[:-1] # Get ROBOT_ID, which is the last element - robotID = partURL.split("/")[-1] + topic = partURL[11:] # Get everything after "/disconnect" - Log("INFO", "Disconnecting robot '{}'".format(robotID)) + # Iterate through every topic and unregister, then delete it - if robotID in ROS_PUBLISHER: - for topic in ROS_PUBLISHER[robotID]: - ROS_PUBLISHER[robotID][topic].unregister() - del ROS_PUBLISHER[robotID] - RosConfigurator.removeRobot(robotID) + if topic in ROS_PUBLISHER: + ROS_PUBLISHER[topic].unregister() + del ROS_PUBLISHER[topic] + Log("INFO", "Disconnecting publisher on '{}'".format(topic)) + RosConfigurator.removeTopic(topic) - if robotID in ROS_SUBSCRIBER: - for topic in ROS_SUBSCRIBER[robotID]: - ROS_SUBSCRIBER[robotID][topic].unregister() - del ROS_SUBSCRIBER[robotID] - RosConfigurator.removeRobot(robotID) + if topic in ROS_SUBSCRIBER: + ROS_SUBSCRIBER[topic].unregister() + del ROS_SUBSCRIBER[topic] + Log("INFO", "Disconnecting subscriber on '{}'".format(topic)) + RosConfigurator.removeTopic(topic) # Return success end_request(request, None, 200, "") -### The below Operations are no longer maintained. -def onWhitelistWrite(request, action): - data = getPostParams(request) - RosConfigurator.setWhiteList(data, None) - end_request(request, None, 200, "") - - -def onWhitelistRemove(request, action): - data = getPostParams(request) - RosConfigurator.setWhiteList(None, data) - end_request(request, None, 200, "") - - -def onWhitelistRestore(request, action): - RosConfigurator.setWhiteList(None, None, True) - end_request(request, None, 200, "") -### The above Operations are no longer maintained - - - # Mapper to the methods MAPPER = { "GET": [ - {"regexp": "^/robots/*$", "action": listRobots}, - {"regexp": "^/robot/.*$", "action": onRobotData}], + {"regexp": "^/topics/*$", "action": listTopics}, + {"regexp": "^/topic/.*$", "action": onRobotData}], "POST": [ - {"regexp": "^/robot/connect/*$", "action": onConnect}, - {"regexp": "^/robot/disconnect/(\w+)/*$", "action": onDisConnect}, - {"regexp": "^/whitelist/write/*$", "action": onWhitelistWrite}, - {"regexp": "^/whitelist/remove/*$", "action": onWhitelistRemove}, - {"regexp": "^/whitelist/restore/*$", "action": onWhitelistRestore}] + {"regexp": "^/connect/*$", "action": onConnect}, + {"regexp": "^/disconnect/.*$", "action": onDisConnect} + ] } diff --git a/firos/include/test_Constants.py b/firos/include/test_Constants.py index 69da504..1bcd920 100644 --- a/firos/include/test_Constants.py +++ b/firos/include/test_Constants.py @@ -42,7 +42,6 @@ def tearDown(self): C.MAP_SERVER_PORT = 10100 C.ROSBRIDGE_PORT = 9090 C.DATA = None - C.CONTEXT_TYPE = "ROBOT" C.PUB_FREQUENCY = 0 C.ROS_NODE_NAME = "firos" C.ROS_SUB_QUEUE_SIZE = 10 @@ -58,7 +57,6 @@ def test_Standard_Parameters(self): self.assertEqual(C.MAP_SERVER_PORT, 10100) self.assertEqual(C.ROSBRIDGE_PORT, 9090) self.assertEqual(C.DATA, None) - self.assertEqual(C.CONTEXT_TYPE, "ROBOT") self.assertEqual(C.PUB_FREQUENCY, 0) self.assertEqual(C.ROS_NODE_NAME, "firos") @@ -91,7 +89,6 @@ def test_Constants_With_Non_Existent_Config_JSON(self): self.assertEqual(C.MAP_SERVER_PORT, 10100) self.assertEqual(C.ROSBRIDGE_PORT, 9090) self.assertEqual(C.DATA, {}) - self.assertEqual(C.CONTEXT_TYPE, "ROBOT") self.assertEqual(C.PUB_FREQUENCY, 0) self.assertEqual(C.ROS_NODE_NAME, "firos") @@ -113,7 +110,6 @@ def test_Constants_With_Missing_Endpoint(self): self.assertEqual(C.MAP_SERVER_PORT, 12345) self.assertEqual(C.ROSBRIDGE_PORT, 4321) self.assertEqual(C.DATA, C.setConfiguration("../test_data/testConfigFiles/missEndpoint")) - self.assertEqual(C.CONTEXT_TYPE, "TestContext") self.assertEqual(C.PUB_FREQUENCY, 1) self.assertEqual(C.ROS_NODE_NAME, "TestFIROS") @@ -137,7 +133,6 @@ def test_Constants_With_Missing_log_level(self): self.assertEqual(C.MAP_SERVER_PORT, 12345) self.assertEqual(C.ROSBRIDGE_PORT, 4321) self.assertEqual(C.DATA, C.setConfiguration("../test_data/testConfigFiles/missLogLevel")) - self.assertEqual(C.CONTEXT_TYPE, "TestContext") self.assertEqual(C.PUB_FREQUENCY, 1) self.assertEqual(C.ROS_NODE_NAME, "TestFIROS") @@ -159,7 +154,6 @@ def test_Constants_With_Missing_server_port(self): self.assertEqual(C.MAP_SERVER_PORT, 10100) self.assertEqual(C.ROSBRIDGE_PORT, 4321) self.assertEqual(C.DATA, C.setConfiguration("../test_data/testConfigFiles/missServerPort")) - self.assertEqual(C.CONTEXT_TYPE, "TestContext") self.assertEqual(C.PUB_FREQUENCY, 1) self.assertEqual(C.ROS_NODE_NAME, "TestFIROS") @@ -181,7 +175,6 @@ def test_Constants_With_Missing_ContextBroker(self): self.assertEqual(C.MAP_SERVER_PORT, 12345) self.assertEqual(C.ROSBRIDGE_PORT, 4321) self.assertEqual(C.DATA, C.setConfiguration("../test_data/testConfigFiles/missContextBroker")) - self.assertEqual(C.CONTEXT_TYPE, "TestContext") self.assertEqual(C.PUB_FREQUENCY, 1) self.assertEqual(C.ROS_NODE_NAME, "TestFIROS") @@ -203,7 +196,6 @@ def test_Constants_With_Missing_node_name(self): self.assertEqual(C.MAP_SERVER_PORT, 12345) self.assertEqual(C.ROSBRIDGE_PORT, 4321) self.assertEqual(C.DATA, C.setConfiguration("../test_data/testConfigFiles/missNodeName")) - self.assertEqual(C.CONTEXT_TYPE, "TestContext") self.assertEqual(C.PUB_FREQUENCY, 1) self.assertEqual(C.ROS_NODE_NAME, "firos") @@ -224,7 +216,6 @@ def test_Constants_With_Missing_ros_subscriber_queue(self): self.assertEqual(C.MAP_SERVER_PORT, 12345) self.assertEqual(C.ROSBRIDGE_PORT, 4321) self.assertEqual(C.DATA, C.setConfiguration("../test_data/testConfigFiles/missRosSubscriberQueue")) - self.assertEqual(C.CONTEXT_TYPE, "TestContext") self.assertEqual(C.PUB_FREQUENCY, 1) self.assertEqual(C.ROS_NODE_NAME, "TestFIROS") @@ -233,27 +224,6 @@ def test_Constants_With_Missing_ros_subscriber_queue(self): self.assertEqual(C.PATH, "../test_data/testConfigFiles/missRosSubscriberQueue") self.assertEqual(C.configured, True) - def test_Constants_With_Missing_context_type(self): - C = Constants - Constants.configured = False - C.init("../test_data/testConfigFiles/missContextType") - - self.assertEqual(C.LOGLEVEL, "WARNING") - - self.assertEqual(C.EP_SERVER_ADRESS, "123.456.789.254") - self.assertEqual(C.EP_SERVER_PORT, 1235) - self.assertEqual(C.MAP_SERVER_PORT, 12345) - self.assertEqual(C.ROSBRIDGE_PORT, 4321) - self.assertEqual(C.DATA, C.setConfiguration("../test_data/testConfigFiles/missContextType")) - self.assertEqual(C.CONTEXT_TYPE, "ROBOT") - self.assertEqual(C.PUB_FREQUENCY, 1) - - self.assertEqual(C.ROS_NODE_NAME, "TestFIROS") - self.assertEqual(C.ROS_SUB_QUEUE_SIZE, 9) - - self.assertEqual(C.PATH, "../test_data/testConfigFiles/missContextType") - self.assertEqual(C.configured, True) - def test_Constants_With_Missing_Pub_Frequency(self): @@ -268,7 +238,6 @@ def test_Constants_With_Missing_Pub_Frequency(self): self.assertEqual(C.MAP_SERVER_PORT, 12345) self.assertEqual(C.ROSBRIDGE_PORT, 4321) self.assertEqual(C.DATA, C.setConfiguration("../test_data/testConfigFiles/missPubFrequency")) - self.assertEqual(C.CONTEXT_TYPE, "TestContext") self.assertEqual(C.PUB_FREQUENCY, 0) self.assertEqual(C.ROS_NODE_NAME, "TestFIROS") @@ -290,7 +259,6 @@ def test_Constants_With_Missing_Ros_Bridge(self): self.assertEqual(C.MAP_SERVER_PORT, 12345) self.assertEqual(C.ROSBRIDGE_PORT, 9090) self.assertEqual(C.DATA, C.setConfiguration("../test_data/testConfigFiles/missRosBridge")) - self.assertEqual(C.CONTEXT_TYPE, "TestContext") self.assertEqual(C.PUB_FREQUENCY, 1) self.assertEqual(C.ROS_NODE_NAME, "TestFIROS") @@ -312,7 +280,6 @@ def test_Constants_With_minimal_Configuration(self): self.assertEqual(C.MAP_SERVER_PORT, 10100) self.assertEqual(C.ROSBRIDGE_PORT, 9090) self.assertEqual(C.DATA, C.setConfiguration("../test_data/testConfigFiles/minimal")) - self.assertEqual(C.CONTEXT_TYPE, "ROBOT") self.assertEqual(C.PUB_FREQUENCY, 0) self.assertEqual(C.ROS_NODE_NAME, "firos") diff --git a/launch/firos.launch b/launch/firos.launch index 2b2799c..2760a2e 100644 --- a/launch/firos.launch +++ b/launch/firos.launch @@ -1,9 +1,9 @@ - + - - + + \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 7be99f3..6b09fb8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -31,4 +31,5 @@ nav: - Upgrading from previous versions: install/upgrade.md - Usage: usage.md - Deprecated Functionality: deprecated.md + - Removed Functionality: removed.md - Roadmap: roadmap.md diff --git a/test_data/testConfigFiles/missContextType/config.json b/test_data/testConfigFiles/missContextType/config.json deleted file mode 100644 index 5ac6b37..0000000 --- a/test_data/testConfigFiles/missContextType/config.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "environment": "test", - - "test": { - "server": { - "port": 12345 - }, - "contextbroker": { - "address" : "TestAdress", - "port" : 1234, - "subscription": { - "throttling": 0, - "subscription_length": 99, - "subscription_refresh_delay": 0.1 - } - }, - - "node_name": "TestFIROS", - "ros_subscriber_queue": 9, - "rosbridge_port": 4321, - "pub_frequency": 1, - - "log_level": "WARNING", - "endpoint": { - "address": "123.456.789.254", - "port": 1235 - } - } -} \ No newline at end of file