diff --git a/CHANGELOG.md b/CHANGELOG.md index f52b7f8..15a0ec0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +* Add Grasshopper components for publishing and subscribing to topics, for creating messages and connecting to MQTT transports. + ### Changed ### Removed diff --git a/src/compas_eve/ghpython/components/Ce_Message/code.py b/src/compas_eve/ghpython/components/Ce_Message/code.py new file mode 100644 index 0000000..d5c7915 --- /dev/null +++ b/src/compas_eve/ghpython/components/Ce_Message/code.py @@ -0,0 +1,64 @@ +""" +Create a message. + +COMPAS EVE v0.5.0 +""" + +import System + +import Rhino.Geometry as rg +import rhinoscriptsyntax as rs + +from compas.geometry import Brep +from compas_eve import Message +from compas_rhino import conversions + +component = ghenv.Component # noqa: F821 + +local_values = locals() +data = dict() + +for input_param in component.Params.Input: + name = input_param.NickName + value = local_values[name] + if isinstance(value, System.Guid): + try: + value = rs.coercegeometry(value) + except: # noqa: E722 + pass + if isinstance(value, rg.Point3d): + value = conversions.point_to_compas(value) + elif isinstance(value, rg.Box): + value = conversions.box_to_compas(value) + elif isinstance(value, rg.Vector3d): + value = conversions.vector_to_compas(value) + elif isinstance(value, rg.Arc): + value = conversions.arc_to_compas(value) + elif isinstance(value, rg.Circle): + value = conversions.circle_to_compas(value) + elif isinstance(value, rg.Curve): + value = conversions.curve_to_compas(value) + elif isinstance(value, rg.Cone): + value = conversions.cone_to_compas(value) + elif isinstance(value, rg.Cylinder): + value = conversions.cylinder_to_compas(value) + elif isinstance(value, rg.Line): + value = conversions.line_to_compas(value) + elif isinstance(value, rg.Mesh): + value = conversions.mesh_to_compas(value) + elif isinstance(value, rg.Plane): + value = conversions.plane_to_compas_frame(value) + elif isinstance(value, rg.Sphere): + value = conversions.sphere_to_compas(value) + elif isinstance(value, rg.PolylineCurve): + value = conversions.polygon_to_compas(value) + elif isinstance(value, rg.Polyline): + value = conversions.polyline_to_compas(value) + elif isinstance(value, rg.Surface): + value = conversions.surface_to_compas(value) + elif isinstance(value, rg.Brep): + value = Brep.from_native(value) + + data[name] = value + +message = Message(**data) diff --git a/src/compas_eve/ghpython/components/Ce_Message/icon.png b/src/compas_eve/ghpython/components/Ce_Message/icon.png new file mode 100644 index 0000000..af64901 Binary files /dev/null and b/src/compas_eve/ghpython/components/Ce_Message/icon.png differ diff --git a/src/compas_eve/ghpython/components/Ce_Message/metadata.json b/src/compas_eve/ghpython/components/Ce_Message/metadata.json new file mode 100644 index 0000000..82fa9f4 --- /dev/null +++ b/src/compas_eve/ghpython/components/Ce_Message/metadata.json @@ -0,0 +1,25 @@ +{ + "name": "Create message", + "nickname": "Message", + "category": "COMPAS EVE", + "subcategory": "Events", + "description": "Create a new message.", + "exposure": 2, + + "ghpython": { + "isAdvancedMode": false, + "iconDisplay": 2, + "inputParameters": [ + { + "name": "x", + "description": "Example input field. Add or remove fields as needed." + } + ], + "outputParameters": [ + { + "name": "message", + "description": "The newly created message." + } + ] + } +} diff --git a/src/compas_eve/ghpython/components/Ce_MqttTransport/code.py b/src/compas_eve/ghpython/components/Ce_MqttTransport/code.py new file mode 100644 index 0000000..ef0eba9 --- /dev/null +++ b/src/compas_eve/ghpython/components/Ce_MqttTransport/code.py @@ -0,0 +1,40 @@ +""" +Connect or disconnect to an MQTT broker. + +COMPAS EVE v0.5.0 +""" + +from threading import Event + +from compas_ghpython import create_id +from ghpythonlib.componentbase import executingcomponent as component +from scriptcontext import sticky as st + +from compas_eve.mqtt import MqttTransport + + +class MqttTransportConnect(component): + def RunScript(self, host, port, connect): + mqtt_transport = None + + host = host or "127.0.0.1" + port = port or 1883 + + key = create_id(self, "mqtt_transport") + mqtt_transport = st.get(key, None) + + if mqtt_transport: + st[key].close() + + if connect: + event = Event() + transport = MqttTransport(host, port) + transport.on_ready(event.set) + if not event.wait(5): + raise Exception("Failed to connect to MQTT broker.") + + st[key] = transport + + mqtt_transport = st.get(key, None) + is_connected = mqtt_transport._is_connected if mqtt_transport else False + return (mqtt_transport, is_connected) diff --git a/src/compas_eve/ghpython/components/Ce_MqttTransport/icon.png b/src/compas_eve/ghpython/components/Ce_MqttTransport/icon.png new file mode 100644 index 0000000..b5877cd Binary files /dev/null and b/src/compas_eve/ghpython/components/Ce_MqttTransport/icon.png differ diff --git a/src/compas_eve/ghpython/components/Ce_MqttTransport/metadata.json b/src/compas_eve/ghpython/components/Ce_MqttTransport/metadata.json new file mode 100644 index 0000000..5288a2c --- /dev/null +++ b/src/compas_eve/ghpython/components/Ce_MqttTransport/metadata.json @@ -0,0 +1,40 @@ +{ + "name": "MQTT Transport", + "nickname": "MQTT", + "category": "COMPAS EVE", + "subcategory": "Events", + "description": "Connect or disconnect to an MQTT broker.", + "exposure": 2, + + "ghpython": { + "isAdvancedMode": true, + "iconDisplay": 2, + "inputParameters": [ + { + "name": "host", + "description": "The host address of the MQTT broker. Defaults to 127.0.0.1.", + "typeHintID": "str" + }, + { + "name": "port", + "description": "The port of MQTT broker. Defaults to 1883.", + "typeHintID": "int" + }, + { + "name": "connect", + "description": "If True, connects to MQTT broker. If False, disconnects from MQTT. Defaults to False.", + "typeHintID": "bool" + } + ], + "outputParameters": [ + { + "name": "mqtt_transport", + "description": "The MQTT transport instance." + }, + { + "name": "is_connected", + "description": "True if connection established." + } + ] + } +} diff --git a/src/compas_eve/ghpython/components/Ce_Publish/code.py b/src/compas_eve/ghpython/components/Ce_Publish/code.py new file mode 100644 index 0000000..a8c6837 --- /dev/null +++ b/src/compas_eve/ghpython/components/Ce_Publish/code.py @@ -0,0 +1,43 @@ +""" +Publish messages to a topic. + +COMPAS EVE v0.5.0 +""" + +import time + +from ghpythonlib.componentbase import executingcomponent as component +from scriptcontext import sticky as st + +from compas_eve import Topic +from compas_eve import Publisher +from compas_ghpython import create_id + + +class PublishComponent(component): + def RunScript(self, transport, topic_name, message, on): + if not topic_name: + raise ValueError("Please specify the name of the topic") + + if on is None: + on = True + + key = create_id(self, "publisher_{}".format(id(transport))) + key_count = create_id(self, "publisher_count_{}".format(id(transport))) + publisher = st.get(key, None) + + if not publisher: + topic = Topic(topic_name) + publisher = Publisher(topic, transport=transport) + publisher.advertise() + time.sleep(0.2) + + st[key] = publisher + st[key_count] = 0 + + if on and message: + st[key_count] += 1 + publisher.publish(message) + self.Message = "Published {} messages".format(st[key_count]) + + return st[key_count] diff --git a/src/compas_eve/ghpython/components/Ce_Publish/icon.png b/src/compas_eve/ghpython/components/Ce_Publish/icon.png new file mode 100644 index 0000000..c0e24f7 Binary files /dev/null and b/src/compas_eve/ghpython/components/Ce_Publish/icon.png differ diff --git a/src/compas_eve/ghpython/components/Ce_Publish/metadata.json b/src/compas_eve/ghpython/components/Ce_Publish/metadata.json new file mode 100644 index 0000000..eb95ab8 --- /dev/null +++ b/src/compas_eve/ghpython/components/Ce_Publish/metadata.json @@ -0,0 +1,40 @@ +{ + "name": "Publish to topic", + "nickname": "Publish", + "category": "COMPAS EVE", + "subcategory": "Events", + "description": "Publish messages to a topic.", + "exposure": 4, + + "ghpython": { + "isAdvancedMode": true, + "iconDisplay": 2, + "inputParameters": [ + { + "name": "transport", + "description": "An instance of transport. If no transport is specified, it will use in-memory transport." + }, + { + "name": "topic_name", + "description": "The name of the topic to publish to to, e.g. `/compas_eve/hello_world/`.", + "typeHintID": "str" + }, + { + "name": "message", + "description": "The message to publish. It can be an instance of `Message` or a dictionary" + }, + { + "name": "on", + "description": "Turn ON or OFF the publisher. Defaults to True.", + "typeHintID": "bool" + } + + ], + "outputParameters": [ + { + "name": "count", + "description": "The count of published messages on the selected topic." + } + ] + } +} diff --git a/src/compas_eve/ghpython/components/Ce_Subscribe/code.py b/src/compas_eve/ghpython/components/Ce_Subscribe/code.py new file mode 100644 index 0000000..44170b2 --- /dev/null +++ b/src/compas_eve/ghpython/components/Ce_Subscribe/code.py @@ -0,0 +1,64 @@ +""" +Subscribe to a topic to receive messages. + +COMPAS EVE v0.5.0 +""" + +from ghpythonlib.componentbase import executingcomponent as component + +from compas_eve import Topic +from compas_eve import Subscriber +from compas_eve.ghpython import BackgroundWorker + + +class SubscribeComponent(component): + def RunScript(self, transport, topic_name, start, on): + if not topic_name: + raise ValueError("Please specify the name of the topic") + + if on is None: + on = True + + if not on: + BackgroundWorker.stop_instance_by_component(ghenv) # noqa: F821 + return None + + args = ( + transport, + topic_name, + ) + + self.worker = BackgroundWorker.instance_by_component( + ghenv, # noqa: F821 + self.start_subscriber, + dispose_function=self.stop_subscriber, + force_new=start, + auto_set_done=False, + args=args, + ) + + if not self.worker.is_working() and not self.worker.is_done() and start: + self.worker.start_work() + + if hasattr(self.worker, "result"): + return self.worker.result + else: + return None + + def start_subscriber(self, worker, transport, topic_name): + worker.count = 0 + + def received_message(message): + worker.count += 1 + worker.display_message("Received {} messages".format(worker.count)) + worker.update_result(message, 10) + + topic = Topic(topic_name) + worker.subscriber = Subscriber(topic, callback=received_message, transport=transport) + worker.subscriber.subscribe() + worker.display_message("Subscribed") + + def stop_subscriber(self, worker): + if hasattr(worker, "subscriber"): + worker.subscriber.unsubscribe() + worker.display_message("Stopped") diff --git a/src/compas_eve/ghpython/components/Ce_Subscribe/icon.png b/src/compas_eve/ghpython/components/Ce_Subscribe/icon.png new file mode 100644 index 0000000..d1a0efe Binary files /dev/null and b/src/compas_eve/ghpython/components/Ce_Subscribe/icon.png differ diff --git a/src/compas_eve/ghpython/components/Ce_Subscribe/metadata.json b/src/compas_eve/ghpython/components/Ce_Subscribe/metadata.json new file mode 100644 index 0000000..53890d9 --- /dev/null +++ b/src/compas_eve/ghpython/components/Ce_Subscribe/metadata.json @@ -0,0 +1,41 @@ +{ + "name": "Subscribe to topic", + "nickname": "Subscribe", + "category": "COMPAS EVE", + "subcategory": "Events", + "description": "Subscribe to a topic to receive messages.", + "exposure": 4, + + "ghpython": { + "isAdvancedMode": true, + "iconDisplay": 2, + "inputParameters": [ + { + "name": "transport", + "description": "An instance of transport. If no transport is specified, it will use in-memory transport." + }, + { + "name": "topic_name", + "description": "The name of the topic to subscribe to, e.g. `/compas_eve/hello_world/`.", + "typeHintID": "str" + }, + { + "name": "start", + "description": "Starts/Restarts the subscriber.", + "typeHintID": "bool" + }, + { + "name": "on", + "description": "Turn ON or OFF the subscriber. Defaults to True.", + "typeHintID": "bool" + } + + ], + "outputParameters": [ + { + "name": "message", + "description": "The last message received on the selected topic." + } + ] + } +}