├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── awsgreengrasspubsubsdk ├── __init__.py ├── message_formatter.py ├── pubsub_client.py ├── pubsub_ipc.py └── pubsub_mqtt.py ├── docs ├── api-docs │ ├── .pages │ ├── README.md │ ├── message_formatter.md │ ├── pubsub_client.md │ ├── pubsub_ipc.md │ └── pubsub_mqtt.md └── developer-guide │ └── README.md ├── images ├── gg-component-published.png ├── message-processing-health-check.png ├── pubsub-sdk-overview.png └── select-component-deployment.png ├── lazydocs.sh ├── samples ├── README.md └── gg-pubsub-sdk-component-template │ └── src │ ├── gdk-config.json │ ├── main.py │ ├── pubsub_message_handlers │ ├── my_sensor_message_handler.py │ └── my_system_message_handler.py │ └── recipe.json └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Greengrass GDK / Build tools files 2 | zip-build 3 | greengrass-build 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | pip-wheel-metadata/ 28 | share/python-wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .nox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | *.py,cover 55 | .hypothesis/ 56 | .pytest_cache/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | .python-version 90 | 91 | # pipenv 92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 95 | # install all needed dependencies. 96 | #Pipfile.lock 97 | 98 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 99 | __pypackages__/ 100 | 101 | # Celery stuff 102 | celerybeat-schedule 103 | celerybeat.pid 104 | 105 | # SageMath parsed files 106 | *.sage.py 107 | 108 | # Environments 109 | .env 110 | .venv 111 | env/ 112 | venv/ 113 | ENV/ 114 | env.bak/ 115 | venv.bak/ 116 | 117 | # Spyder project settings 118 | .spyderproject 119 | .spyproject 120 | 121 | # Rope project settings 122 | .ropeproject 123 | 124 | # mkdocs documentation 125 | /site 126 | 127 | # mypy 128 | .mypy_cache/ 129 | .dmypy.json 130 | dmypy.json 131 | 132 | # Pyre type checker 133 | .pyre/ 134 | 135 | # Stupid Mac files. 136 | .DS_Store 137 | 138 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AWS IoT Greengrass PubSub SDK for Python 2 | 3 | This document provides information about the AWS IoT Greengrass PubSub SDK for Python. 4 | 5 | If you have any issues or feature requests, please file an issue or pull request. 6 | 7 | **Note:** This is a library based implementation and replacement for the AWS IoT Greengrass PubSub Framework previoulsy hosted in this repository. 8 | 9 | ## Overview 10 | 11 | The AWS IoT Greengrass PubSub SDK for Python provides an IoT PubSub application architecture delivered as a Python library to simplify and accelerate development of distributed IoT applications built on AWS IoT Greengrass Components. The SDK abstracts the AWS IoT Greengrass IPC and MQTT PubSub functionality and uses a data driven message format to route PubSub messages to user defined call-backs. This provides a Low/No-Code PubSub messaging service without the common design dependencies of distributed IoT applications. 12 | 13 | ![pubsub-sdk-overview](https://github.com/awslabs/aws-greengrass-labs-iot-pubsub-sdk-for-python/blob/main/images/pubsub-sdk-overview.png) 14 | 15 | For more details see the AWS IoT Greengrass PubSub SDK: 16 | * [Developer Guide](https://github.com/awslabs/aws-greengrass-labs-iot-pubsub-sdk-for-python/tree/main/docs/developer-guide) 17 | * [API Docs](https://github.com/awslabs/aws-greengrass-labs-iot-pubsub-sdk-for-python/tree/main/docs/api-docs) 18 | * [Samples](https://github.com/awslabs/aws-greengrass-labs-iot-pubsub-sdk-for-python/tree/main/samples) 19 | * [Detailed Deployment Workshop](https://catalog.us-east-1.prod.workshops.aws/workshops/32dc1f13-985f-4f94-9b86-a859806d28f0) 20 | 21 | ## Getting Started 22 | 23 | The easiest way to get started is to build and deploy the AWS IoT Greengrass PubSub SDK component template described in the [Samples](https://github.com/awslabs/aws-greengrass-labs-iot-pubsub-sdk-for-python/tree/main/samples) directory. 24 | 25 | For new users just starting out with AWS IoT Greengrass or those that want to see a more detailed example of using the SDK to deploy multiple components that we recommend running through the workshop [Build a Distributed IoT Application with the AWS IoT Greengrass PubSub SDK](https://catalog.us-east-1.prod.workshops.aws/workshops/32dc1f13-985f-4f94-9b86-a859806d28f0/en-US) where you build a Smart Factory IoT application using the AWS IoT Greengrass PubSub SDK. 26 | 27 | ## Installation 28 | 29 | ### Minimum Requirements 30 | * Python 3.6+ 31 | 32 | ### Install via an AWS IoT Greengrass Custom Component Recipe 33 | 34 | To configure the AWS IoT Greengrass PubSub SDK to deploy with an AWS IoT Greengrass Component, update the recipe Lifecycle / Install event as per below: 35 | ``` 36 | "Lifecycle": { 37 | "Install" : { 38 | "Timeout" : 300, 39 | "Script" : "python3 -m pip install awsgreengrasspubsubsdk" 40 | }, 41 | ..... 42 | ``` 43 | 44 | A full example of this is given in the [Greengrass component recipe example](https://github.com/awslabs/aws-greengrass-labs-iot-pubsub-sdk-for-python/blob/main/samples/gg-pubsub-sdk-component-template/src/recipe.json) 45 | 46 | ### Installing Manually 47 | The AWS IoT Greengrass PubSub SDK provides functionality within an AWS IoT Greengrass component. It only has context within that framework and so for production solutions, its preferred to deploy the SDK with the component as described in the previous example. Manual installation examples given below are intended for development machines. 48 | 49 | **Note**: If installing manually, do so as the user that will own the Greengrass component process (i.e: gcc_user by default). 50 | 51 | #### Install from PyPI 52 | ``` 53 | python3 -m pip install awsgreengrasspubsubsdk 54 | ``` 55 | 56 | #### Install from source 57 | ``` 58 | git clone https://github.com/awslabs/aws-greengrass-labs-iot-pubsub-sdk-for-python.git 59 | python3 -m pip install ./aws-greengrass-pubsub-sdk-for-python 60 | ``` 61 | 62 | ## Usage and Code Examples 63 | 64 | Once the AWS IoT Greengrass PubSub SDK is deployed with an AWS IoT Greengrass Component, you can route messages to your application logic via user defined Message Handlers that you register with the SDK 65 | 66 | A production ready AWS IoT Greengrass component template and more detailed examples are provided in the [Samples](https://github.com/awslabs/aws-greengrass-labs-iot-pubsub-sdk-for-python/tree/main/samples) directory. 67 | 68 | The SDK defines the following required message format (with more details provided in the [Developer Guide](https://github.com/awslabs/aws-greengrass-labs-iot-pubsub-sdk-for-python/tree/main/docs/developer-guide) 69 | ``` 70 | { 71 | "sdk_version": "0.1.4", 72 | "message_id": "20220403170948930231", 73 | "status": 200, 74 | "route": "MyPubSubMessageHandler.pubsub_message_route_target", 75 | "message": { 76 | "my-message-param01": "param01", 77 | "my-message-param02": "param02" 78 | } 79 | } 80 | ``` 81 | The SDK will route received PubSub messages to a user defined message handler class and method as provided in the message **route** value. The message route value is a **Class.Method** namespace representation of the desired message route target. 82 | 83 | Using this approach, the AWS IoT Greengrass PubSub SDK routes PubSub messages to your application logic in easily defined message callbacks. 84 | 85 | ### Initialising the SDK and Message Handler Classes 86 | 1. Add a method that will be the route of last resort for received PubSub messages and the expected message parameters. 87 | ``` 88 | def default_message_handler(self, protocol, topic, message_id, status, route, message): 89 | 90 | # Process messages without a registered handler router target 91 | log.error('Received message to unknown route / message handler: {} - message: {}'.format(route, message))) 92 | ``` 93 | 94 | 2. Initialise the AWS IoT Greengrass PubSub SDK Client 95 | ``` 96 | # Import the AWS IoT Greengrass PubSub SDK 97 | from awsgreengrasspubsubsdk.pubsub_client import AwsGreengrassPubSubSdkClient 98 | 99 | # Declare the PubSub Base topic, this is used to build the default Ingress and Egress PubSub topics. 100 | pubsub_base_topic = 'com.my_greengrass.application' 101 | 102 | # Initilise the AwsGreengrassPubSubSdkClient with the pubsub_base_topic and default_message_handler 103 | pubsub_client = AwsGreengrassPubSubSdkClient(pubsub_base_topic, default_message_handler ) 104 | 105 | ``` 106 | 107 | 3. Create one or more user defined message handler class/es with named functions to route PubSub messages with the expected parameters. This method allows easy separation of message processing functionality within your application. 108 | ``` 109 | class MyPubSubMessageHandler(): 110 | 111 | def pubsub_message_route_target(self, protocol, topic, message_id, status, route, message): 112 | 113 | # Process messages with route = 'MyPubSubMessageHandler.pubsub_message_route_target' 114 | log.info('MyPubSubMessageHandler.pubsub_message_route_target received message: {}'.format(message)) 115 | ``` 116 | 117 | 4. Register the message handler class/es with the PubSub SDK Client with the [register_message_handler](https://github.com/awslabs/aws-greengrass-labs-iot-pubsub-sdk-for-python/tree/main/docs/api-docs/pubsub_client.md#method-register_message_handler) call. 118 | ``` 119 | my_pubsub_message_handler = MyPubSubMessageHandler() 120 | pubsub_client.register_message_handler(my_pubsub_message_handler) 121 | ``` 122 | 123 | 5. Activate the IPC and / or MQTT Protocols in the SDK: 124 | ``` 125 | # Activate IPC Protocol 126 | pubsub_client.activate_ipc_pubsub() 127 | 128 | # Acticate MQTT Protocol 129 | pubsub_client.activate_mqtt_pubsub() 130 | ``` 131 | 132 | 6. Complete! 133 | On completion of the above and once successfully deployed, your AWS IoT Greengrass component will listen on the default Ingress topic **(base-pubsub-topic/THING_NAME/ingress)** and any other user defined topics. The SDK will begin routing PubSub messages to your registered Message Handler classes as per the message **route** value. As describe above, any message received on the SDK subscribed topic with value **route = MyPubSubMessageHandler.pubsub_message_route_target** will be automatically forwarded to the method provided in the example. 134 | 135 | **Note:** If the route value does not match any route target methods, the message will be forwarded to the default_message_handler class. 136 | 137 | ### Subscribing to Custom Topics 138 | The SDK provides a programmatic means of subscribing to user defined topics. You can subscribe to any topic that is permitted by the access policy provided in the component recipe. 139 | ``` 140 | my_topic = 'my/interesting/topic' 141 | 142 | # Subscribe to topic on IPC 143 | pubsub_client.subscribe_to_topic('ipc', topic) 144 | 145 | # Subscribe to topic on MQTT 146 | pubsub_client.subscribe_to_topic('mqtt', topic) 147 | 148 | # Subscribe to topic on IPC and MQTT 149 | pubsub_client.subscribe_to_topic('ipc_mqtt', topic) 150 | 151 | ``` 152 | 153 | If the protocol (IPC or MQTT) is activated, the SDK will subscribe to the topic and begin routig messages immediatly. If not, the subscription request will be stored and actioned when the selected protocol is activated. 154 | 155 | ### Publishing Message to PubSub 156 | The SDK provides a message formatter class to ensure consistent messages. See the [message_formatter](https://github.com/awslabs/aws-greengrass-labs-iot-pubsub-sdk-for-python/tree/main/docs/api-docs/message_formatter.md) API Docs for more detail. 157 | 158 | ``` 159 | # Initialise the PubSubMessageFormatter 160 | from awsgreengrasspubsubsdk.message_formatter import PubSubMessageFormatter 161 | message_formatter = PubSubMessageFormatter() 162 | 163 | # Use the message formatter to create a well-formatted PubSub SDK response message 164 | receive_route = 'MyPubSubMessageHandler.pubsub_message_response' 165 | my_message = { 166 | "my-message-param01": "param01", 167 | "my-message-param02": "param02" 168 | } 169 | # Defaults will be applied for parameteres not provided here. See API Docs. 170 | sdk_format_msg = message_formatter.get_message(route=receive_route, message=my_message) 171 | 172 | ## Publish the message to IPC and MQTT. 173 | pubsub_client.publish_message('ipc_mqtt', sdk_format_msg) 174 | ``` 175 | 176 | ### Installation Issues 177 | 178 | 1. The AWS IoT Greengrass PubSub SDK (`awsgreengrasspubsubsdk`) installs [awsiotsdk](https://github.com/aws/aws-iot-device-sdk-python-v2) as a dependancy with the following listed [Installation issues](https://github.com/aws/aws-iot-device-sdk-python-v2#installation). 179 | 180 | 2. If running the AWS IoT Greengrass component with root privileges, you will need to install in a python virtual environment by replacing the component recipe install policy with the below: 181 | ``` 182 | "Install" : { 183 | "Timeout" : 300, 184 | "Script" : "python3 -m venv pubsubsdk; . pubsubsdk/bin/activate; pip3 install --upgrade pip; python3 -m pip install awsgreengrasspubsubsdk" 185 | } 186 | ``` 187 | 188 | ## Samples 189 | 190 | A complete production ready AWS IoT Greengrass component template is provided in the [Samples](https://github.com/awslabs/aws-greengrass-labs-iot-pubsub-sdk-for-python/tree/main/samples) directory. 191 | 192 | ## Getting Help 193 | 194 | The best way to interact with our team is through GitHub. You can [open an issue](https://github.com/awslabs/aws-greengrass-labs-iot-pubsub-sdk-for-python/issues) for guidance, bug reports, or feature requests. 195 | 196 | Please make sure to check out our resources before opening an issue: 197 | * [Developer Guide](https://github.com/awslabs/aws-greengrass-labs-iot-pubsub-sdk-for-python/tree/main/docs/developer-guide) 198 | * [API Docs](https://github.com/awslabs/aws-greengrass-labs-iot-pubsub-sdk-for-python/tree/main/docs/api-docs) 199 | * [AWS IoT Greengrass PubSub SDK Deployment Workshop](https://catalog.us-east-1.prod.workshops.aws/workshops/32dc1f13-985f-4f94-9b86-a859806d28f0) 200 | * [AWS IoT Greengrass V2 Developer Guide](https://docs.aws.amazon.com/greengrass/v2/developerguide/what-is-iot-greengrass.html) 201 | * [AWS IoT Core Documentation](https://docs.aws.amazon.com/iot/) 202 | * [AWS Dev Blog](https://aws.amazon.com/blogs/?awsf.blog-master-iot=category-internet-of-things%23amazon-freertos%7Ccategory-internet-of-things%23aws-greengrass%7Ccategory-internet-of-things%23aws-iot-analytics%7Ccategory-internet-of-things%23aws-iot-button%7Ccategory-internet-of-things%23aws-iot-device-defender%7Ccategory-internet-of-things%23aws-iot-device-management%7Ccategory-internet-of-things%23aws-iot-platform) 203 | 204 | ## Giving Feedback and Contributions 205 | 206 | We need your help in making this SDK great. Please participate in the community and contribute to this effort by submitting issues, participating in discussion forums and submitting pull requests through the following channels. 207 | 208 | * [Contributions Guidelines](https://github.com/awslabs/aws-greengrass-labs-iot-pubsub-sdk-for-python/blob/main/CONTRIBUTING.md) 209 | * Submit [Issues, Feature Requests or Bugs](https://github.com/awslabs/aws-greengrass-labs-iot-pubsub-sdk-for-python/issues) 210 | 211 | ## AWS IoT Core Resources 212 | 213 | * [AWS IoT Core Documentation](https://docs.aws.amazon.com/iot/) 214 | * [AWS IoT Developer Guide](https://docs.aws.amazon.com/iot/latest/developerguide/what-is-aws-iot.html) ([source](https://github.com/awsdocs/aws-iot-docs)) 215 | * [AWS Dev Blog](https://aws.amazon.com/blogs/?awsf.blog-master-iot=category-internet-of-things%23amazon-freertos%7Ccategory-internet-of-things%23aws-greengrass%7Ccategory-internet-of-things%23aws-iot-analytics%7Ccategory-internet-of-things%23aws-iot-button%7Ccategory-internet-of-things%23aws-iot-device-defender%7Ccategory-internet-of-things%23aws-iot-device-management%7Ccategory-internet-of-things%23aws-iot-platform) 216 | 217 | 218 | ## Security 219 | 220 | See [CONTRIBUTING](https://github.com/awslabs/aws-greengrass-labs-iot-pubsub-sdk-for-python/blob/main/CONTRIBUTING.md#security-issue-notifications) for more information. 221 | 222 | ## License 223 | 224 | This library is licensed under the MIT-0 License. See the LICENSE file. 225 | -------------------------------------------------------------------------------- /awsgreengrasspubsubsdk/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/aws-greengrass-labs-iot-pubsub-sdk-for-python/2e5190d22d15951bd873ec577dd0989627c44b55/awsgreengrasspubsubsdk/__init__.py -------------------------------------------------------------------------------- /awsgreengrasspubsubsdk/message_formatter.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0. 3 | 4 | ''' 5 | Defines standard message format for PubSub communications. 6 | ''' 7 | 8 | __version__ = "0.1.4" 9 | __status__ = "Development" 10 | __copyright__ = "Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved." 11 | __author__ = "Dean Colcott " 12 | 13 | from datetime import datetime 14 | 15 | class PubSubMessageFormatter(): 16 | ''' 17 | Defines standard message format for PubSub communications between Greengrass 18 | components and / or MQTT IoT Core in the AWS Greengrass V2 PubSub SDK. 19 | 20 | This enables versioned, efficient and consistent PubSub Message routing and 21 | processing between components. 22 | ''' 23 | 24 | sdk_version = __version__ 25 | 26 | def get_message(self, **kwargs): 27 | ''' 28 | Returns a well formatted PubSub Message as per this PubSub SDK versioned formatting. 29 | 30 | ### Parameters 31 | 32 | **message_id** : str, (Optional) Default=Current Timestamp 33 | 34 | Unique message ID to aid tracking across request / response message patterns. 35 | If None or missing, a current timestamp is generated for this value in the format: %Y%m%d%H%M%S%f 36 | 37 | **status** : int (Optional) Default=200 38 | 39 | Status code of this message. Any int supported, expected / typical values: 40 | 41 | 200: OK COMPLETE, 202: ASYNC REQUEST ACCEPTED, 404: COMMAND NOT RECOGNISED, 500: INTERNAL COMPONENT ERROR. 42 | 43 | **route** : str (Optional) Default='default_message_handler' 44 | 45 | Name of the message handler fuction in PubSubMessageHandler() to process this message on the receiving component. 46 | If None or missing will be set to default_message_handler 47 | 48 | **message** : Object (preferred dict), (Optional) default={} 49 | 50 | Dict, Array or any JSON serializable object containing the payload of this message. 51 | If None or missing, an empty dict is generated for this value. 52 | 53 | ### Usage: 54 | 55 | All input parameters are optional and provided as named keyword arguments. 56 | Defaults are applied for any values not provided or provided as None. 57 | 58 | e.g: 59 | 60 | ``` 61 | get_message(message={"param01" : "message param01"}) 62 | get_message(message_id=123456, route="health_check_response", message={"status" : "System OK"}) 63 | ``` 64 | 65 | ### Returns 66 | 67 | Example (Dict) returned message object: 68 | 69 | ``` 70 | { 71 | "sdk_version" : "0.1.4", 72 | "message_id" : 123456, 73 | "status" : 200, 74 | "route" : "health_check_response", 75 | "message": { 76 | "status" : "System OK" 77 | } 78 | } 79 | ``` 80 | 81 | ''' 82 | 83 | # Set message_id or default value 84 | message_id = datetime.now().strftime("%Y%m%d%H%M%S%f") 85 | if('message_id' in kwargs and kwargs['message_id']): 86 | message_id = kwargs['message_id'] 87 | 88 | # Set status or default value 89 | status = 200 90 | if('status' in kwargs and kwargs['status']): 91 | status = kwargs['status'] 92 | 93 | # Set route or default value 94 | route = 'default_message_handler' 95 | if('route' in kwargs and kwargs['route']): 96 | route = kwargs['route'] 97 | 98 | # Set message object or default value 99 | message = {} 100 | if('message' in kwargs and kwargs['message']): 101 | message = kwargs['message'] 102 | 103 | # Return a well formatted PubSub REQUEST 104 | retval = { 105 | 'sdk_version' : self.sdk_version, 106 | 'message_id' : message_id, # Message Timestamp / ID to track the request flow. 107 | 'status' : status, # Message status code. 108 | 'route' : route, # Message handler function name that will process message on receiving system. 109 | 'message': message # Optional message payload / data object. 110 | } 111 | 112 | return retval 113 | 114 | def get_error_message(self, **kwargs): 115 | ''' 116 | Convenience method that returns a well formatted PubSub Error Message 117 | with status default to 500 and route to 'default_error_handler' 118 | 119 | A status value that isn't a 2xx value will route this message to the 120 | default error PubSub topic and default error handler function on the receiving component 121 | (Assuming it also implements the AWS Greengrass PubSub Component SDK). 122 | 123 | ### Parameters 124 | 125 | **message_id** : str, (Optional) Default=Current Timestamp 126 | 127 | Unique message ID to aid tracking across request / response message patterns. 128 | If None or missing, a current timestamp is generated for this value in the format: %Y%m%d%H%M%S%f 129 | 130 | **message** : Object (preferred Dict), (Optional) default={} 131 | 132 | Dict, Array or any JSON serializable object containing the payload of this message. 133 | If None or missing, an empty Dict is generated for this value. 134 | 135 | ### Usage 136 | 137 | All input parameters are optional and provided as named keyword arguments. 138 | Defaults are applied for any values not provided or provided as None. 139 | 140 | e.g: 141 | 142 | ``` 143 | get_error_message(message={"error" : "PubSub Timeout for process 123XYZ"}) 144 | get_error_message(message_id=123456, message={"error" : "PubSub Timeout for process 123XYZ"}) 145 | ``` 146 | 147 | ### Returns 148 | 149 | Example (Dict) returned message object: 150 | 151 | ``` 152 | { 153 | "sdk_version" : "0.1.4", 154 | "message_id" : 123456, 155 | "status" : 500, 156 | "route" : "default_error_handler", 157 | "message": { 158 | "error" : "PubSub Timeout for process 123XYZ" 159 | } 160 | } 161 | ``` 162 | 163 | ''' 164 | 165 | # Set message_id or default value 166 | message_id = datetime.now().strftime("%Y%m%d%H%M%S%f") 167 | if('message_id' in kwargs and kwargs['message_id']): 168 | message_id = kwargs['message_id'] 169 | 170 | # Set message object or default value 171 | message = {} 172 | if('message' in kwargs and kwargs['message']): 173 | message = kwargs['message'] 174 | 175 | return self.get_message(message_id=message_id, status=500, route='default_error_handler', message=message) 176 | -------------------------------------------------------------------------------- /awsgreengrasspubsubsdk/pubsub_client.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0. 3 | 4 | ''' 5 | Provided as the entry point for the AWS Greengrass V2 PubSub Component SDK. 6 | ''' 7 | 8 | __version__ = "0.1.4" 9 | __status__ = "Development" 10 | __copyright__ = "Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved." 11 | __author__ = "Dean Colcott " 12 | 13 | import os, sys, json, inspect, logging 14 | 15 | from awsgreengrasspubsubsdk.pubsub_ipc import IpcPubSub 16 | from awsgreengrasspubsubsdk.pubsub_mqtt import MqttPubSub 17 | from awsgreengrasspubsubsdk.message_formatter import PubSubMessageFormatter 18 | 19 | # Init / Config the logger. 20 | log = logging.getLogger(__name__) 21 | logging.basicConfig(format="[%(name)s.%(funcName)s():%(lineno)d] - [%(levelname)s] - %(message)s", 22 | stream=sys.stdout, 23 | level=logging.INFO) 24 | 25 | class AwsGreengrassPubSubSdkClient(): 26 | ''' 27 | This client abstracts the AWS Greengrass IPC and MQTT PubSub functionality, 28 | defines a simple PubSub topic schema and routes PubSub messages directly to 29 | the given instance of PubSubMessageHandler() in the user’s application logic. 30 | 31 | PubSub subscriptions are only initialised and messages processed once the 32 | activate_ipc_pubsub() and / or activate_mqtt_pubsub() functions are called 33 | to activate the IPC and MQTT PubSub message channels respectively. 34 | 35 | This defines the base PubSub topic schema with topics for ingress and egress messages. 36 | 37 | The PubSub topic schema is defined as: 38 | 39 | * Ingress Topic - (base_topic/THING_NAME/ingress): Subscribes to this topic for incoming messages. 40 | 41 | * Egress Topic - (base_topic/THING_NAME/egress): Default message publish topic. 42 | 43 | ### Parameters 44 | 45 | **base_topic**: str 46 | 47 | Based string for the ingress / egress and error topics for this component. 48 | 49 | **message_handler**: Class (Instance of: PubSubMessageHandler() ) 50 | 51 | An instance of the message_handler.PubSubMessageHandler() class in the AWS Greengrass component code. 52 | All messages received on any active PubSub protocols will be forwarded to this class. 53 | 54 | 55 | ### Usage / Constructor 56 | 57 | base_topic = 'my_project_name" 58 | 59 | my_message_handler = MyPubSubMessageHandler() 60 | 61 | pubsub_client = AwsGreengrassPubSubSdkClient(base_topic, my_message_handler) 62 | 63 | ''' 64 | 65 | def __init__(self, base_topic, default_message_handler): 66 | ''' 67 | Initialises the AWS Greengrass V2 PubSub SDK. 68 | 69 | **Note:** Initialising this client doesn't immediately trigger IPC or MQTT message processing. 70 | User must call activate_ipc_pubsub() and / or activate_mqtt_pubsub() 71 | to activate the respective PubSub protocols to begin processing messages. 72 | 73 | ''' 74 | 75 | super().__init__() 76 | 77 | ####################################################### 78 | # Set Thing Name and Log the start of the process 79 | self.base_topic = base_topic 80 | self.formatter = PubSubMessageFormatter() 81 | self.thing_name = os.getenv('AWS_IOT_THING_NAME') 82 | log.info('Initialising AWS Greengrass PubSub SDK on Thing: {} with Base Topic: {}.....'.format(self.thing_name, self.base_topic)) 83 | 84 | # Set the default message handler to catch messages with no matching route. 85 | log.info('Setting default_message_handler function callback') 86 | self.default_message_handler = default_message_handler 87 | 88 | # Create objet to hold user defined message handlers. 89 | # Expected these are Classes with functions that can route messages to by function name. 90 | self.message_handlers = {} 91 | # There are the required paramaters for a method in a registered message_handler class to 92 | # be considered as a valid message route by this SDK. 93 | self.handler_required_params = ['protocol', 'topic', 'message_id', 'status', 'route', 'message'] 94 | 95 | # Set the initial IPC / MQTT activated modes 96 | self.is_ipc_active = False 97 | self.is_mqtt_active = False 98 | 99 | ####################################################### 100 | # Parse SDK config and / or set local topics / parameters to default. 101 | log.info('Setting SDK Default PubSub Topics...') 102 | 103 | # Set Ingress PubSub topic 104 | self.ingress_topic = '{}/{}/ingress'.format(self.base_topic, self.thing_name) 105 | log.info('Ingress Topic: {}'.format(self.ingress_topic )) 106 | 107 | # Set Egress PubSub topic 108 | self.egress_topic = '{}/{}/egress'.format(self.base_topic, self.thing_name) 109 | log.info('Egress topic: {}'.format(self.egress_topic )) 110 | 111 | # Set the subscribe topics for the SDK 112 | self.ipc_subscribe_topics = [self.ingress_topic ] 113 | self.mqtt_subscribe_topics = [self.ingress_topic ] 114 | 115 | log.info('Setting SDK Default PubSub Topics Complete.') 116 | 117 | log.info('Initialising PubSub message formats....') 118 | 119 | # Build and log test message types for user examples and to validate messages has initialised. 120 | test_request = self.formatter.get_message(message_id=123456, status_code=200, route='default_message_handler', message={"param01" : "message param01"}) 121 | log.info('Built test Request Message: {}'.format(test_request)) 122 | 123 | log.info('Initialising PubSub Message Formats Complete.') 124 | 125 | # Completed initialising Greengrass PubSub SDK. 126 | log.info('Initialising AWS Greengrass V2 PubSub SDK Complete.') 127 | 128 | ################################################## 129 | ### Register message_handler classes to route messages 130 | ################################################## 131 | 132 | def register_message_handler(self, message_handler_class): 133 | ''' 134 | Registers a message handler class to route messages too. 135 | A message_handler is any user defined class that contains named functions 136 | that this SDK will route messages to based on the route value in the message. 137 | ''' 138 | 139 | # Scan the message_handler class for non private functions that are assumed to 140 | # be valid message handling functions for this SDK based on the route field. 141 | 142 | # Get all callable methods in the provided class 143 | class_name = type(message_handler_class).__name__ 144 | all_handler_methods = [func for func in dir(message_handler_class) if callable(getattr(message_handler_class, func))] 145 | 146 | # For each method, ,check if is non private and has the SDK specified required input paramaters 147 | # of protocol, topic, message_id, status, route, and message then add all valid to stored message_handlers 148 | log.info('Registering Message Handler Class: {}'.format(class_name)) 149 | for method_name in all_handler_methods: 150 | # Exclude private functions if any. 151 | if not method_name.startswith('_'): 152 | # Check that this method has the required paramaters to be considered as a value message route. 153 | method = getattr(message_handler_class, method_name) 154 | method_params = inspect.signature(method).parameters 155 | is_valid_method=True 156 | 157 | # Check method has required parameters to be a valid SDK message handler. 158 | for param in self.handler_required_params: 159 | if not param in method_params: 160 | is_valid_method=False 161 | break 162 | 163 | if is_valid_method: 164 | handler_route = '{}.{}'.format(class_name, method_name) 165 | self.message_handlers[handler_route] = method 166 | log.info('Adding Message Handler Function: {}'.format(method_name)) 167 | 168 | log.info('Registering Message Handler Class: {} - Complete'.format(class_name)) 169 | 170 | ################################################## 171 | ### Activate calls for PubSub (IPC / MQTT) Clients 172 | ################################################## 173 | 174 | def activate_ipc_pubsub(self): 175 | ''' 176 | Activate and initialise the IPC PubSub subscribers and publisher. 177 | Will start receiving and processing messages in IPC subscribed topics 178 | immediately on calling this function. 179 | ''' 180 | 181 | log.info('Initialising IPC Topic PubSub inter-service messaging.') 182 | self.ipc_pubsub = IpcPubSub(self._received_message_callback, self.ipc_subscribe_topics) 183 | 184 | # Publish a 200 OK message to indicate IPC is activated 185 | succ_msg = self.formatter.get_message(message={"event" : "IPC Client Activated"}) 186 | self.publish_message('ipc', succ_msg) 187 | 188 | # Is IpcPubSub initilises successfully then set the is_ipc_active=True 189 | self.is_ipc_active = True 190 | 191 | def activate_mqtt_pubsub(self): 192 | ''' 193 | Activate and initialise the MQTT PubSub subscribers and publisher. 194 | Will start receiving and processing messages in MQTT subscribed topics 195 | immediately on calling this function. 196 | ''' 197 | 198 | log.info('Initialising IPC MQTT IoT Core PubSub messaging.') 199 | self.mqtt_pubsub = MqttPubSub(self._received_message_callback, self.mqtt_subscribe_topics) 200 | 201 | # Publish a 200 OK message to indicate IPC is activated 202 | succ_msg = self.formatter.get_message(message={"event" : "MQTT Client Activated"}) 203 | self.publish_message('mqtt', succ_msg) 204 | 205 | # Is IpcPubSub initilises successfully then set the is_mqtt_active=True 206 | self.is_mqtt_active = True 207 | 208 | ################################################## 209 | ### PubSub Received Message Callback 210 | ################################################## 211 | 212 | def _received_message_callback(self, protocol, topic, payload): 213 | ''' 214 | Callback for all (IPC and MQTT) PubSub Client received messages. 215 | Provides initial message validation and passing to PubSub topic routers. 216 | Expects message payload provided is JSON formatted. 217 | ''' 218 | 219 | try: 220 | 221 | # Debug Log incoming message 222 | log.debug('Received PubSub Message. Protocol: {} - Topic: {} - Message: {}'.format(protocol, topic, payload)) 223 | 224 | ######################################################## 225 | #### Message Parsing and SDK Message format parameter validation 226 | 227 | # Parse the message to JSON. If not JSON or not valid message format 228 | # for this SDK then route to the custom message processor. 229 | message = self._parse_json_message(payload) 230 | 231 | if self._is_sdk_formatted_message(message): 232 | self._sdk_formatted_message_router(protocol, topic, message) 233 | else: 234 | raise Exception('Message received not meeting AWS Greengrass PubSub SDK required format.') 235 | 236 | except Exception as err: 237 | err_msg = 'Exception raised from _received_message_callback. ERROR MESSAGE: {} - TOPIC: {} - PAYLOAD: {}'.format(err, topic, payload) 238 | self.publish_error('ipc_mqtt', err_msg) 239 | 240 | ################################################## 241 | ### Message Parse / Validate / Version helpers 242 | ################################################## 243 | 244 | def _parse_json_message(self, payload): 245 | ''' 246 | Try to Parse a message as JSON. If parses; return the object, if not return False. 247 | ''' 248 | try: 249 | return json.loads(payload) 250 | 251 | except ValueError as e: 252 | return False 253 | 254 | def _is_sdk_formatted_message(self, message): 255 | ''' 256 | Tests if a given message is well-formatted as per this SDK message formats 257 | ''' 258 | 259 | sdk_message_values = [ 'sdk_version', 'message_id', 'status', 'route', 'message'] 260 | for sdk_message_value in sdk_message_values: 261 | if not sdk_message_value in message.keys(): 262 | return False 263 | 264 | # If all required message parameters present then return True. 265 | return True 266 | 267 | def _is_same_major_version(self, source_version, target_version): 268 | ''' 269 | Simple (but easily fooled) method to check two semantic versions 270 | numbers are of the same major version to detect breaking changes. 271 | 272 | TODO: Doesn't validate the version format so check first. 273 | ''' 274 | source = source_version.split('.') 275 | target = target_version.split('.') 276 | return source[0] == target[0] 277 | 278 | def _get_sdk_message_values(self, message): 279 | ''' 280 | Return the given (expected SDK well-formatted) message as a tuple. 281 | ''' 282 | return (message['sdk_version'], message['message_id'], message['status'], message['route'], message['message']) 283 | 284 | ################################################## 285 | ### Message Routers. 286 | ################################################## 287 | 288 | # Routes messaged based on SDK prescribed well formatted fields of 289 | # route and status. 290 | def _sdk_formatted_message_router(self, protocol, topic, message): 291 | ''' 292 | Process PubSub messages that are formatted using this SDK. 293 | ''' 294 | 295 | try: 296 | log.debug('_sdk_formatted_message_router: Received SDK Formatted PubSub message on topic: {} - Message: {}'.format(topic, message)) 297 | 298 | # Decompose the (expected) message parameter values 299 | message_sdk_version, message_id, status, route, message_payload = self._get_sdk_message_values(message) 300 | 301 | # Validate the receiving message was from a supported SDK version. 302 | sdk_version = self.formatter.sdk_version 303 | if not self._is_same_major_version(message_sdk_version, sdk_version): 304 | raise Exception('Received PubSub SDK Message Version: {} but needing major version installed: {}'.format(message_sdk_version, sdk_version)) 305 | 306 | # Get a hook to the preferred message_handler or default_message_handler if no route match 307 | selected_handler = self.default_message_handler 308 | if route in self.message_handlers.keys(): 309 | selected_handler = self.message_handlers[route] 310 | 311 | # Route the message to best matching message handler found. 312 | selected_handler(protocol, topic, message_id, status, route, message_payload) 313 | 314 | except Exception as err: 315 | err_msg = 'Exception raised from _sdk_formatted_message_router. ERROR MESSAGE: {} - PROTOCOL: {} - TOPIC: {} - PAYLOAD: {}'.format(err, protocol, topic, message) 316 | self.publish_error('ipc_mqtt', err_msg) 317 | 318 | 319 | ################################################## 320 | ### Publish Message / Publish Errors Functions. 321 | ################################################## 322 | 323 | def publish_message(self, protocol, message, topic=None): 324 | ''' 325 | Publishes a JSON message to the respective AWS Greengrass Protocol (IPC or MQTT) Clients. 326 | 327 | ### Parameters 328 | 329 | **protocol**: str 330 | 331 | The protocol client (IPC or MQTT) to publish the message too. 332 | 333 | Supported values: 334 | 335 | * ipc: Publish to IPC message bus. 336 | 337 | * mqtt: Publish to MQTT message bus. 338 | 339 | * ipc_mqtt: Publish to both IPC and MQTT message buses respectively. 340 | 341 | **message**: Object (preferred dict) 342 | 343 | Dict, Array or any object able to be JSON serialised containing data to be published with the response message. 344 | Typically expected to be a JSON object created by the AWS Greengrass SDK messageformatter. 345 | 346 | **topic**: str (Optional) Default: Component Egress Topic (i.e: base-pubsub-topic/THING_NAME/egress ) 347 | 348 | The topic to publish this message to on the selected protocol client 349 | 350 | ''' 351 | 352 | # If topic not set, default it to the components egress topic. 353 | if topic == None: 354 | topic = self.egress_topic 355 | 356 | # Debug the PubSub publish 357 | log.debug('Publishing Message. Topic: {} - Message: {}'.format(topic, message)) 358 | 359 | # Publish the message to the AWS Greengrass IPC or MQTT SDKs 360 | if protocol == 'ipc': 361 | self.ipc_pubsub.publish_to_topic(topic, message) 362 | 363 | elif protocol == 'mqtt': 364 | self.mqtt_pubsub.publish_to_mqtt(topic, message) 365 | 366 | elif protocol == 'ipc_mqtt': 367 | self.ipc_pubsub.publish_to_topic(topic, message) 368 | self.mqtt_pubsub.publish_to_mqtt(topic, message) 369 | 370 | else: 371 | raise Exception('Publish requested for unknown protocol {}. Supported Values: [ipc || mqtt || ipc_mqtt]',format(protocol)) 372 | 373 | def publish_error(self, protocol, err_message): 374 | ''' 375 | A convenience method that logs and publishes an Error message to IPC and / or MQTT 376 | This will build a well formatted message with status = 500 and route = 'default_error_handler' 377 | 378 | ### Parameters 379 | 380 | **protocol**: str 381 | 382 | The protocol client (IPC or MQTT) to publish the message too. 383 | 384 | Supported values: 385 | 386 | * ipc: Publish to IPC message bus. 387 | 388 | * mqtt: Publish to MQTT message bus. 389 | 390 | * ipc_mqtt: Publish to both IPC and MQTT message buses respectively. 391 | 392 | **err_message**: Object (preferred dict) 393 | 394 | Dict, Array or any object able to be JSON serialised containing data to be published with the response message. 395 | Is added as message payload in a well-formatted SDK message with status=500 and route='default_error_handler' 396 | ''' 397 | try: 398 | log.error(err_message) 399 | 400 | # Create a well formed error message and publish to PubSub. 401 | message = self.formatter.get_error_message(message=err_message) 402 | self.publish_message(protocol, message) 403 | 404 | except Exception as err: 405 | # Don't get too clever handling this error as may end up in a recursive loop of error publishing. 406 | # Catch all exceptions and just log locally. 407 | log.error('Exception raised publishing error message. ERROR: {} - MESSAGE PAYLOAD: {}'.format(err, err_message)) 408 | 409 | ################################################## 410 | ### Custom topic subscriber 411 | ################################################## 412 | 413 | def subscribe_to_topic(self, protocol, topic): 414 | ''' 415 | Subscribes to custom PubSub topics on IPC and / or MQTT clients. 416 | If the given protocol client has been activated, then the subscription will take immediate effect 417 | and the registered message_handler will begin receiving messages from this topic. 418 | 419 | If the protocol has not been activated, the subscription request will 420 | be stored and will take effect once the protocol is activated. 421 | 422 | ### Parameters 423 | 424 | **protocol**: str 425 | 426 | The protocol client (IPC or MQTT) to Subscribe too. 427 | 428 | Supported values: 429 | 430 | * ipc: Subscribe to IPC message bus. 431 | 432 | * mqtt: Subscribe to MQTT message bus. 433 | 434 | * ipc_mqtt: Subscribe to both IPC and MQTT message buses respectively. 435 | 436 | **topic**: str 437 | The topic to subscribe too. 438 | 439 | ''' 440 | 441 | # Debug the PubSub publish 442 | log.debug('Received Subscription request for Topic: {}'.format(topic)) 443 | 444 | # Subscribe to requested topic on IPC / MQTT protocols. 445 | if protocol =='ipc': 446 | self._subscribe_to_ipc_topic(topic) 447 | 448 | elif protocol =='mqtt': 449 | self._subscribe_to_mqtt_topic(topic) 450 | 451 | elif protocol =='ipc_mqtt': 452 | self._subscribe_to_ipc_topic(topic) 453 | self._subscribe_to_mqtt_topic(topic) 454 | 455 | else: 456 | raise Exception('Requested subscribe to topic: {} for unknown protocol {}. Supported Values: [ipc || mqtt || ipc_mqtt]',format(topic, protocol)) 457 | 458 | def _subscribe_to_ipc_topic(self, topic): 459 | ''' 460 | Private helper to subscribe to an IPC client topic. 461 | ''' 462 | 463 | if not topic in self.ipc_subscribe_topics: 464 | self.ipc_subscribe_topics.append(topic) 465 | 466 | if self.is_ipc_active: 467 | self.ipc_pubsub.subscribe_to_topic(topic) 468 | 469 | def _subscribe_to_mqtt_topic(self, topic): 470 | ''' 471 | Private helper to subscribe to an MQTT client topic. 472 | ''' 473 | 474 | if not topic in self.mqtt_subscribe_topics: 475 | self.mqtt_subscribe_topics.append(topic) 476 | 477 | if self.is_mqtt_active: 478 | self.mqtt_pubsub.subscribe_to_topic(topic) 479 | -------------------------------------------------------------------------------- /awsgreengrasspubsubsdk/pubsub_ipc.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0. 3 | 4 | ''' 5 | Abstracts the AWS Greengrass V2 IPC PubSub SDK that manages subscriptions 6 | and a method to publish to AWS Greengrass IPC topics. This is intended 7 | for use in an AWS Greengrass V2 Component SDD to provide PubSub services. 8 | 9 | IPC Topic PubSub is for internal communications between AWS Greengrass Components 10 | within the AWS Greengrass Core (the edge device). For communications between 11 | Greengrass component and the AWS IoT Core platform see the MQTT PubSub class. 12 | 13 | For more detail on AWS Greengrass V2 see the Developer Guide at: 14 | https://docs.aws.amazon.com/greengrass/v2/developerguide/what-is-iot-greengrass.html 15 | 16 | ''' 17 | 18 | __version__ = "0.1.4" 19 | __status__ = "Development" 20 | __copyright__ = "Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved." 21 | __author__ = "Dean Colcott " 22 | 23 | import json 24 | import logging 25 | import concurrent.futures 26 | from concurrent.futures import ThreadPoolExecutor 27 | import awsiot.greengrasscoreipc 28 | import awsiot.greengrasscoreipc.client as client 29 | from awsiot.greengrasscoreipc.model import ( 30 | PublishToTopicRequest, 31 | SubscribeToTopicRequest, 32 | SubscriptionResponseMessage, 33 | PublishMessage, 34 | BinaryMessage, 35 | UnauthorizedError 36 | ) 37 | 38 | # Init the logger. 39 | log = logging.getLogger(__name__) 40 | 41 | class IpcPubSub(): 42 | 43 | def __init__(self, message_callback, ipc_subscribe_topics): 44 | 45 | 46 | super().__init__() 47 | 48 | log.info('Initialising / Activating AWS Greengrass V2 IPC PubSub Client....') 49 | 50 | # PubSub timeout default secs. 51 | self.ipc_default_timeout = 10 52 | 53 | # PubSub message callback. 54 | self.message_callback = message_callback 55 | 56 | # IPC Subscribe Topics. 57 | self.ipc_subscribe_topics = ipc_subscribe_topics 58 | 59 | # List of active topics subscribed too. 60 | self.ipc_subscribed_topics = [] 61 | 62 | # Create the ipc_clients. 63 | self.ipc_subscribe_client = awsiot.greengrasscoreipc.connect() 64 | self.ipc_publish_client = awsiot.greengrasscoreipc.connect() 65 | 66 | # Create ThreadPoolExecutor to process PubSub messages. 67 | # Changed in version 3.8: Default max_workers changed to min(32, os.cpu_count() + 4). 68 | self.executor = ThreadPoolExecutor(max_workers=None) 69 | 70 | # Init IPC PubSub's. 71 | self._init_topic_subscriber() 72 | self._init_topic_publisher() 73 | 74 | log.info('Initialising / Activating AWS Greengrass V2 IPC PubSub Client Complete') 75 | 76 | ############################################### 77 | # Setters 78 | def set_ipc_default_timeout(self, ipc_default_timeout): 79 | self.ipc_default_timeout = ipc_default_timeout 80 | 81 | ############################################### 82 | # IPC Topic PubSub Functions 83 | def _init_topic_subscriber(self): 84 | ''' 85 | Initialise subscription to requested IPC local topics. 86 | ''' 87 | 88 | for subscribe_topic in self.ipc_subscribe_topics: 89 | self.subscribe_to_topic(subscribe_topic) 90 | 91 | def subscribe_to_topic(self, topic): 92 | 93 | log.info('IPC SDK Subscribing to Topic: {}:'.format(topic)) 94 | 95 | if (topic in self.ipc_subscribed_topics): 96 | log.info('Returning with no action. Already subscribed to IPC topic: {}'.format(topic)) 97 | return 98 | 99 | request = SubscribeToTopicRequest() 100 | request.topic = topic 101 | handler = IpcPubSub._IpcSubscribeHandler(self.message_callback, topic, self.executor) 102 | operation = self.ipc_subscribe_client.new_subscribe_to_topic(handler) 103 | future = operation.activate(request) 104 | # call the result to ensure the future has completed. 105 | future.result(self.ipc_default_timeout) 106 | 107 | self.ipc_subscribed_topics.append(topic) 108 | log.info('IPC SDK Subscribing to Topic: {} Complete'.format(topic)) 109 | 110 | def _init_topic_publisher(self): 111 | ''' 112 | Initialise publisher to requested IPC local topics. 113 | ''' 114 | 115 | log.info('Initialising IPC Topic Publisher.') 116 | self.pub_request = PublishToTopicRequest() 117 | self.publish_message = PublishMessage() 118 | self.publish_message.binary_message = BinaryMessage() 119 | 120 | def publish_to_topic(self, topic, message_object, timeout=None): 121 | ''' 122 | Publish a Python object sterilised as a JSON message to the requested local IPC topic. 123 | ''' 124 | 125 | try: 126 | log.debug('IPC Publish - Topic: {} - Message: {}'.format(topic, message_object)) 127 | 128 | self.pub_request.topic = topic 129 | json_message = json.dumps(message_object) 130 | self.publish_message.binary_message.message = bytes(json_message, "utf-8") 131 | self.pub_request.publish_message = self.publish_message 132 | operation = self.ipc_publish_client.new_publish_to_topic() 133 | operation.activate(self.pub_request) 134 | future = operation.get_response() 135 | future.result(timeout if timeout else self.ipc_default_timeout) 136 | 137 | except KeyError as key_error: 138 | raise Exception('KeyError occurred publishing to IPC topic. ERROR: {} - TOPIC {} - MESSAGE: {}'.format(key_error, topic, message_object)) 139 | 140 | except concurrent.futures.TimeoutError as timeout_error: 141 | raise Exception('Timeout occurred publishing to IPC topic. ERROR: {} - TOPIC {} - MESSAGE: {}'.format(timeout_error, topic, message_object)) 142 | 143 | except UnauthorizedError as unauth_error: 144 | raise Exception('Unauthorized error publishing to IPC topic. ERROR {} - TOPIC {} - MESSAGE: {}'.format(unauth_error, topic, message_object)) 145 | 146 | except Exception as err: 147 | raise Exception('Exception publishing to IPC topic. ERROR: {} - TOPIC {} - MESSAGE: {}'.format(err, topic, message_object)) 148 | 149 | class _IpcSubscribeHandler(client.SubscribeToTopicStreamHandler): 150 | 151 | def __init__(self, message_callback, ipc_subscribe_topic, executor): 152 | 153 | log.info('Initialising AWS Greengrass V2 IPC Topic Subscriber: {}'.format(ipc_subscribe_topic)) 154 | 155 | super().__init__() 156 | 157 | self.message_callback = message_callback 158 | 159 | #IPC Topic 160 | self.ipc_subscribe_topic = ipc_subscribe_topic 161 | 162 | # PubSub message process ThreadExecutor 163 | self.executor = executor 164 | 165 | # Topic subscription event handlers 166 | def on_stream_event(self, event: SubscriptionResponseMessage) -> None: 167 | try: 168 | 169 | log.debug('IPC EVENT RECEIVED: {}'.format(event)) 170 | 171 | message = str(event.binary_message.message, "utf-8") 172 | 173 | self.executor.submit(self.message_callback, "ipc", self.ipc_subscribe_topic, message) 174 | 175 | except Exception as err: 176 | log.error('EXCEPTION: Exception Raised from IPC Topic Subscriber. ERROR: {} - STREAM EVENT: {}'.format(err, event)) 177 | 178 | def on_stream_error(self, error: Exception) -> bool: 179 | log.error('ON_STREAM_ERROR: IPC PubSub Subscriber Stream Error. ERROR: {}'.format(error)) 180 | return False # Return True to close stream, False to keep stream open. 181 | 182 | def on_stream_closed(self) -> None: 183 | log.error('ON_STREAM_CLOSED: IPC PubSub Subscriber topic: {} Stream Closed.'.format(self.ipc_subscribe_topic)) 184 | -------------------------------------------------------------------------------- /awsgreengrasspubsubsdk/pubsub_mqtt.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0. 3 | 4 | ''' 5 | Abstracts AWS Greengrass V2 MQTT PubSub SDK that manages subscriptions 6 | and a method to publish to AWS Greengrass MQTT topics. This is intended 7 | for use in an AWS Greengrass V2 Component SDK to provide MQTT PubSub services. 8 | 9 | IPC MQTT core is for communications between AWS Greengrass Components and 10 | the AWS IoT Core. This can be used to send MQTT messaging to the AWS IoT core 11 | to save data, trigger alarms or alerts or to trigger other AWS services and applications. 12 | 13 | For more detail on AWS Greengrass V2 see the Developer Guide at: 14 | https://docs.aws.amazon.com/greengrass/v2/developerguide/what-is-iot-greengrass.html 15 | 16 | ''' 17 | 18 | __version__ = "0.1.4" 19 | __status__ = "Development" 20 | __copyright__ = "Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved." 21 | __author__ = "Dean Colcott " 22 | 23 | import json 24 | import logging 25 | import concurrent.futures 26 | from concurrent.futures import ThreadPoolExecutor 27 | import awsiot.greengrasscoreipc 28 | import awsiot.greengrasscoreipc.client as client 29 | from awsiot.greengrasscoreipc.model import ( 30 | PublishToIoTCoreRequest, 31 | SubscribeToIoTCoreRequest, 32 | IoTCoreMessage, 33 | UnauthorizedError, 34 | QOS 35 | ) 36 | 37 | # Init the logger. 38 | log = logging.getLogger(__name__) 39 | 40 | class MqttPubSub(): 41 | 42 | def __init__(self, message_callback, mqtt_subscribe_topics): 43 | 44 | 45 | super().__init__() 46 | 47 | log.info('Initialising / Activating AWS Greengrass V2 MQTT PubSub Client....') 48 | 49 | # PubSub default MQTT Timeout and QoS 50 | self.mqtt_default_timeout = 10 51 | self.mqtt_default_qos = QOS.AT_LEAST_ONCE 52 | 53 | # PubSub message callback. 54 | self.message_callback = message_callback 55 | 56 | # MQTT Subscribe Topics 57 | self.mqtt_subscribe_topics = mqtt_subscribe_topics 58 | 59 | # List of active topics subscribed too. 60 | self.mqtt_subscribed_topics = [] 61 | 62 | # Create the mqtt_clients 63 | self.mqtt_subscribe_client = awsiot.greengrasscoreipc.connect() 64 | self.mqtt_publish_client = awsiot.greengrasscoreipc.connect() 65 | 66 | # Init MQTT PubSub's 67 | self._init_mqtt_subscriber() 68 | self._init_mqtt_publisher() 69 | 70 | log.info('Initialising / Activating AWS Greengrass V2 MQTT PubSub Client Complete') 71 | 72 | ############################################### 73 | # Setters 74 | def set_mqtt_default_qos(self, mqtt_default_qos): 75 | self.mqtt_default_qos = mqtt_default_qos 76 | 77 | def set_mqtt_default_timeout(self, mqtt_default_timeout): 78 | self.mqtt_default_timeout = mqtt_default_timeout 79 | 80 | ############################################### 81 | # IPC MQTT Iot Core PubSub Functions 82 | def _init_mqtt_subscriber(self): 83 | ''' 84 | Initialise subscription to requested MQTT IoT Core topics. 85 | ''' 86 | 87 | self.handler = MqttPubSub.__MqttSubscribeHandler(self.message_callback) 88 | 89 | for subscribe_topic in self.mqtt_subscribe_topics: 90 | self.subscribe_to_topic(subscribe_topic) 91 | 92 | def subscribe_to_topic(self, topic): 93 | 94 | log.info('MQTT Subscribing to Topic: {}:'.format(topic)) 95 | 96 | if (topic in self.mqtt_subscribed_topics): 97 | log.info('Returning with no action. Already subscribed to MQTT topic: {}'.format(topic)) 98 | return 99 | 100 | request = SubscribeToIoTCoreRequest() 101 | request.topic_name = topic 102 | request.qos = self.mqtt_default_qos 103 | operation = self.mqtt_subscribe_client.new_subscribe_to_iot_core(self.handler) 104 | future = operation.activate(request) 105 | # call the result to block until the future has completed. 106 | future.result(self.mqtt_default_timeout) 107 | 108 | self.mqtt_subscribed_topics.append(topic) 109 | log.info('Complete MQTT subscribing to topic: {}'.format(topic)) 110 | 111 | def _init_mqtt_publisher(self): 112 | ''' 113 | Initialise publisher to requested IoT Core MQTT topics. 114 | ''' 115 | 116 | log.info('Initialising MQTT Publisher.') 117 | self.mqtt_request = PublishToIoTCoreRequest() 118 | self.mqtt_request.qos = self.mqtt_default_qos 119 | 120 | def publish_to_mqtt(self, topic, message_object, timeout=None): 121 | ''' 122 | Publish a Python object serlized as a JSON message to the IoT Core MQTT topic. 123 | ''' 124 | 125 | try: 126 | 127 | log.debug('MQTT PUBLISH: topic: {} - Message: {}'.format(topic, message_object)) 128 | self.mqtt_request.topic_name = topic 129 | json_message = json.dumps(message_object) 130 | self.mqtt_request.payload = bytes(json_message, "utf-8") 131 | operation = self.mqtt_publish_client.new_publish_to_iot_core() 132 | operation.activate(self.mqtt_request) 133 | future = operation.get_response() 134 | future.result(timeout if timeout!=None else self.mqtt_default_timeout) 135 | 136 | except KeyError as key_error: 137 | raise Exception('KeyError occurred publishing to IoT Core on MQTT Topic. ERROR: {} - TOPIC: {} - MESSAGE: {}'.format(key_error, topic, message_object)) 138 | 139 | except concurrent.futures.TimeoutError as timeout_error: 140 | raise Exception('Timeout occurred publishing to IoT Core on MQTT Topic. ERROR: {} - TOPIC: {} - MESSAGE: {}'.format(timeout_error, topic, message_object)) 141 | 142 | except UnauthorizedError as unauth_error: 143 | raise Exception('Unauthorized error publishing to IoT Core on MQTT Topic. ERROR: {} - TOPIC: {} - MESSAGE: {}'.format(unauth_error, topic, message_object)) 144 | 145 | except Exception as err: 146 | raise Exception('Exception publishing to IoT Core on MQTT Topic. ERROR: {} - TOPIC: {} - MESSAGE: {}'.format(err, topic, message_object)) 147 | 148 | class __MqttSubscribeHandler(client.SubscribeToIoTCoreStreamHandler): 149 | 150 | def __init__(self, message_callback): 151 | 152 | log.info('Initialising AWS Greengrass V2 IPC MQTT Subscribe Client') 153 | 154 | super().__init__() 155 | 156 | # Create ThreadPoolExecutor to process PubSub reveived messages. 157 | self.executor = ThreadPoolExecutor(max_workers=None) 158 | 159 | self.message_callback = message_callback 160 | 161 | # Topic subscription event handlers 162 | def on_stream_event(self, event: IoTCoreMessage) -> None: 163 | try: 164 | 165 | log.debug('MQTT EVENT RECEIVED: {}'.format(event)) 166 | 167 | topic = event.message.topic_name 168 | message = str(event.message.payload, "utf-8") 169 | self.executor.submit(self.message_callback, "mqtt", topic, message) 170 | 171 | except Exception as err: 172 | log.error('EXCEPTION: Exception Raised from IoT Core on MQTT Subscriber. ERROR MESSAGE: {} - STREAM EVENT: {}'.format(err, event)) 173 | 174 | def on_stream_error(self, error: Exception) -> bool: 175 | log.error('ON_STREAM_ERROR: IoT Core MQTT PubSub Subscriber Stream Error. ERROR MESSAGE: {}'.format(error)) 176 | return True # Return True to close stream, False to keep stream open. 177 | 178 | def on_stream_closed(self) -> None: 179 | log.error('ON_STREAM_CLOSED: IoT Core MQTT PubSub Subscriber Closed.') 180 | pass 181 | -------------------------------------------------------------------------------- /docs/api-docs/.pages: -------------------------------------------------------------------------------- 1 | title: API Reference 2 | nav: 3 | - Overview: README.md 4 | - ... 5 | -------------------------------------------------------------------------------- /docs/api-docs/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # API Overview 4 | 5 | ## Modules 6 | 7 | - [`message_formatter`](./message_formatter.md#module-message_formatter): Defines standard message format for PubSub communications. 8 | - [`pubsub_client`](./pubsub_client.md#module-pubsub_client): Provided as the entry point for the AWS Greengrass V2 PubSub Component SDK. 9 | - [`pubsub_ipc`](./pubsub_ipc.md#module-pubsub_ipc): Abstracts the AWS Greengrass V2 IPC PubSub SDK that manages subscriptions 10 | - [`pubsub_mqtt`](./pubsub_mqtt.md#module-pubsub_mqtt): Abstracts AWS Greengrass V2 MQTT PubSub SDK that manages subscriptions 11 | 12 | ## Classes 13 | 14 | - [`message_formatter.PubSubMessageFormatter`](./message_formatter.md#class-pubsubmessageformatter): Defines standard message format for PubSub communications between Greengrass 15 | - [`pubsub_client.AwsGreengrassPubSubSdkClient`](./pubsub_client.md#class-awsgreengrasspubsubsdkclient): This client abstracts the AWS Greengrass IPC and MQTT PubSub functionality, 16 | - [`pubsub_ipc.IpcPubSub`](./pubsub_ipc.md#class-ipcpubsub) 17 | - [`pubsub_mqtt.MqttPubSub`](./pubsub_mqtt.md#class-mqttpubsub) 18 | 19 | ## Functions 20 | 21 | - No functions 22 | 23 | 24 | --- 25 | 26 | _This file was automatically generated via [lazydocs](https://github.com/ml-tooling/lazydocs)._ 27 | -------------------------------------------------------------------------------- /docs/api-docs/message_formatter.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | # module `message_formatter` 6 | Defines standard message format for PubSub communications. 7 | 8 | 9 | 10 | --- 11 | 12 | 13 | 14 | ## class `PubSubMessageFormatter` 15 | Defines standard message format for PubSub communications between Greengrass components and / or MQTT IoT Core in the AWS Greengrass V2 PubSub SDK. 16 | 17 | This enables versioned, efficient and consistent PubSub Message routing and processing between components. 18 | 19 | 20 | 21 | 22 | --- 23 | 24 | 25 | 26 | ### method `get_error_message` 27 | 28 | ```python 29 | get_error_message(**kwargs) 30 | ``` 31 | 32 | Convenience method that returns a well formatted PubSub Error Message with status default to 500 and route to 'default_error_handler' 33 | 34 | A status value that isn't a 2xx value will route this message to the default error PubSub topic and default error handler function on the receiving component (Assuming it also implements the AWS Greengrass PubSub Component SDK). 35 | 36 | ### Parameters 37 | 38 | **message_id** : str, (Optional) Default=Current Timestamp 39 | 40 | Unique message ID to aid tracking across request / response message patterns. If None or missing, a current timestamp is generated for this value in the format: %Y%m%d%H%M%S%f 41 | 42 | **message** : Object (preferred Dict), (Optional) default={} 43 | 44 | Dict, Array or any JSON serializable object containing the payload of this message. If None or missing, an empty Dict is generated for this value. 45 | 46 | ### Usage 47 | 48 | All input parameters are optional and provided as named keyword arguments. Defaults are applied for any values not provided or provided as None. 49 | 50 | e.g: 51 | 52 | ``` 53 | get_error_message(message={"error" : "PubSub Timeout for process 123XYZ"}) 54 | get_error_message(message_id=123456, message={"error" : "PubSub Timeout for process 123XYZ"}) 55 | ``` 56 | 57 | ### Returns 58 | 59 | Example (Dict) returned message object: 60 | 61 | ``` 62 | { 63 | "sdk_version" : "0.1.4", 64 | "message_id" : 123456, 65 | "status" : 500, 66 | "route" : "default_error_handler", 67 | "message": { 68 | "error" : "PubSub Timeout for process 123XYZ" 69 | } 70 | } 71 | ``` 72 | 73 | 74 | 75 | 76 | 77 | --- 78 | 79 | 80 | 81 | ### method `get_message` 82 | 83 | ```python 84 | get_message(**kwargs) 85 | ``` 86 | 87 | Returns a well formatted PubSub Message as per this PubSub SDK versioned formatting. 88 | 89 | ### Parameters 90 | 91 | **message_id** : str, (Optional) Default=Current Timestamp 92 | 93 | Unique message ID to aid tracking across request / response message patterns. If None or missing, a current timestamp is generated for this value in the format: %Y%m%d%H%M%S%f 94 | 95 | **status** : int (Optional) Default=200 96 | 97 | Status code of this message. Any int supported, expected / typical values: 98 | 99 | 200: OK COMPLETE, 202: ASYNC REQUEST ACCEPTED, 404: COMMAND NOT RECOGNISED, 500: INTERNAL COMPONENT ERROR. 100 | 101 | **route** : str (Optional) Default='default_message_handler' 102 | 103 | Name of the message handler fuction in PubSubMessageHandler() to process this message on the receiving component. If None or missing will be set to default_message_handler 104 | 105 | **message** : Object (preferred dict), (Optional) default={} 106 | 107 | Dict, Array or any JSON serializable object containing the payload of this message. If None or missing, an empty dict is generated for this value. 108 | 109 | ### Usage: 110 | 111 | All input parameters are optional and provided as named keyword arguments. Defaults are applied for any values not provided or provided as None. 112 | 113 | e.g: 114 | 115 | ``` 116 | get_message(message={"param01" : "message param01"}) 117 | get_message(message_id=123456, route="health_check_response", message={"status" : "System OK"}) 118 | ``` 119 | 120 | ### Returns 121 | 122 | Example (Dict) returned message object: 123 | 124 | ``` 125 | { 126 | "sdk_version" : "0.1.4", 127 | "message_id" : 123456, 128 | "status" : 200, 129 | "route" : "health_check_response", 130 | "message": { 131 | "status" : "System OK" 132 | } 133 | } 134 | ``` 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | --- 144 | 145 | _This file was automatically generated via [lazydocs](https://github.com/ml-tooling/lazydocs)._ 146 | -------------------------------------------------------------------------------- /docs/api-docs/pubsub_client.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | # module `pubsub_client` 6 | Provided as the entry point for the AWS Greengrass V2 PubSub Component SDK. 7 | 8 | 9 | 10 | --- 11 | 12 | 13 | 14 | ## class `AwsGreengrassPubSubSdkClient` 15 | This client abstracts the AWS Greengrass IPC and MQTT PubSub functionality, defines a simple PubSub topic schema and routes PubSub messages directly to the given instance of PubSubMessageHandler() in the user’s application logic. 16 | 17 | PubSub subscriptions are only initialised and messages processed once the activate_ipc_pubsub() and / or activate_mqtt_pubsub() functions are called to activate the IPC and MQTT PubSub message channels respectively. 18 | 19 | This defines the base PubSub topic schema with topics for ingress and egress messages. 20 | 21 | The PubSub topic schema is defined as: 22 | 23 | * Ingress Topic - (base_topic/THING_NAME/ingress): Subscribes to this topic for incoming messages. 24 | 25 | * Egress Topic - (base_topic/THING_NAME/egress): Default message publish topic. 26 | 27 | ### Parameters 28 | 29 | **base_topic**: str 30 | 31 | Based string for the ingress / egress and error topics for this component. 32 | 33 | **message_handler**: Class (Instance of: PubSubMessageHandler() ) 34 | 35 | An instance of the message_handler.PubSubMessageHandler() class in the AWS Greengrass component code. All messages received on any active PubSub protocols will be forwarded to this class. 36 | 37 | 38 | 39 | ### Usage / Constructor 40 | 41 | base_topic = 'my_project_name" 42 | 43 | my_message_handler = MyPubSubMessageHandler() 44 | 45 | pubsub_client = AwsGreengrassPubSubSdkClient(base_topic, my_message_handler) 46 | 47 | 48 | 49 | 50 | 51 | ### method `__init__` 52 | 53 | ```python 54 | __init__(base_topic, default_message_handler) 55 | ``` 56 | 57 | Initialises the AWS Greengrass V2 PubSub SDK. 58 | 59 | **Note:** Initialising this client doesn't immediately trigger IPC or MQTT message processing. User must call activate_ipc_pubsub() and / or activate_mqtt_pubsub() to activate the respective PubSub protocols to begin processing messages. 60 | 61 | 62 | 63 | 64 | --- 65 | 66 | 67 | 68 | ### method `activate_ipc_pubsub` 69 | 70 | ```python 71 | activate_ipc_pubsub() 72 | ``` 73 | 74 | Activate and initialise the IPC PubSub subscribers and publisher. Will start receiving and processing messages in IPC subscribed topics immediately on calling this function. 75 | 76 | --- 77 | 78 | 79 | 80 | ### method `activate_mqtt_pubsub` 81 | 82 | ```python 83 | activate_mqtt_pubsub() 84 | ``` 85 | 86 | Activate and initialise the MQTT PubSub subscribers and publisher. Will start receiving and processing messages in MQTT subscribed topics immediately on calling this function. 87 | 88 | --- 89 | 90 | 91 | 92 | ### method `publish_error` 93 | 94 | ```python 95 | publish_error(protocol, err_message) 96 | ``` 97 | 98 | A convenience method that logs and publishes an Error message to IPC and / or MQTT This will build a well formatted message with status = 500 and route = 'default_error_handler' 99 | 100 | ### Parameters 101 | 102 | **protocol**: str 103 | 104 | The protocol client (IPC or MQTT) to publish the message too. 105 | 106 | Supported values: 107 | 108 | * ipc: Publish to IPC message bus. 109 | 110 | * mqtt: Publish to MQTT message bus. 111 | 112 | * ipc_mqtt: Publish to both IPC and MQTT message buses respectively. 113 | 114 | **err_message**: Object (preferred dict) 115 | 116 | Dict, Array or any object able to be JSON serialised containing data to be published with the response message. Is added as message payload in a well-formatted SDK message with status=500 and route='default_error_handler' 117 | 118 | --- 119 | 120 | 121 | 122 | ### method `publish_message` 123 | 124 | ```python 125 | publish_message(protocol, message, topic=None) 126 | ``` 127 | 128 | Publishes a JSON message to the respective AWS Greengrass Protocol (IPC or MQTT) Clients. 129 | 130 | ### Parameters 131 | 132 | **protocol**: str 133 | 134 | The protocol client (IPC or MQTT) to publish the message too. 135 | 136 | Supported values: 137 | 138 | * ipc: Publish to IPC message bus. 139 | 140 | * mqtt: Publish to MQTT message bus. 141 | 142 | * ipc_mqtt: Publish to both IPC and MQTT message buses respectively. 143 | 144 | **message**: Object (preferred dict) 145 | 146 | Dict, Array or any object able to be JSON serialised containing data to be published with the response message. Typically expected to be a JSON object created by the AWS Greengrass SDK messageformatter. 147 | 148 | **topic**: str (Optional) Default: Component Egress Topic (i.e: base-pubsub-topic/THING_NAME/egress ) 149 | 150 | The topic to publish this message to on the selected protocol client 151 | 152 | 153 | 154 | --- 155 | 156 | 157 | 158 | ### method `register_message_handler` 159 | 160 | ```python 161 | register_message_handler(message_handler_class) 162 | ``` 163 | 164 | Registers a message handler class to route messages too. A message_handler is any user defined class that contains named functions that this SDK will route messages to based on the route value in the message. 165 | 166 | --- 167 | 168 | 169 | 170 | ### method `subscribe_to_topic` 171 | 172 | ```python 173 | subscribe_to_topic(protocol, topic) 174 | ``` 175 | 176 | Subscribes to custom PubSub topics on IPC and / or MQTT clients. If the given protocol client has been activated, then the subscription will take immediate effect and the registered message_handler will begin receiving messages from this topic. 177 | 178 | If the protocol has not been activated, the subscription request will be stored and will take effect once the protocol is activated. 179 | 180 | ### Parameters 181 | 182 | **protocol**: str 183 | 184 | The protocol client (IPC or MQTT) to Subscribe too. 185 | 186 | Supported values: 187 | 188 | * ipc: Subscribe to IPC message bus. 189 | 190 | * mqtt: Subscribe to MQTT message bus. 191 | 192 | * ipc_mqtt: Subscribe to both IPC and MQTT message buses respectively. 193 | 194 | **topic**: str The topic to subscribe too. 195 | 196 | 197 | 198 | 199 | 200 | 201 | --- 202 | 203 | _This file was automatically generated via [lazydocs](https://github.com/ml-tooling/lazydocs)._ 204 | -------------------------------------------------------------------------------- /docs/api-docs/pubsub_ipc.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | # module `pubsub_ipc` 6 | Abstracts the AWS Greengrass V2 IPC PubSub SDK that manages subscriptions and a method to publish to AWS Greengrass IPC topics. This is intended for use in an AWS Greengrass V2 Component SDD to provide PubSub services. 7 | 8 | IPC Topic PubSub is for internal communications between AWS Greengrass Components within the AWS Greengrass Core (the edge device). For communications between Greengrass component and the AWS IoT Core platform see the MQTT PubSub class. 9 | 10 | For more detail on AWS Greengrass V2 see the Developer Guide at: https://docs.aws.amazon.com/greengrass/v2/developerguide/what-is-iot-greengrass.html 11 | 12 | 13 | 14 | --- 15 | 16 | 17 | 18 | ## class `IpcPubSub` 19 | 20 | 21 | 22 | 23 | 24 | 25 | ### method `__init__` 26 | 27 | ```python 28 | __init__(message_callback, ipc_subscribe_topics) 29 | ``` 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | --- 39 | 40 | 41 | 42 | ### method `publish_to_topic` 43 | 44 | ```python 45 | publish_to_topic(topic, message_object, timeout=None) 46 | ``` 47 | 48 | Publish a Python object sterilised as a JSON message to the requested local IPC topic. 49 | 50 | --- 51 | 52 | 53 | 54 | ### method `set_ipc_default_timeout` 55 | 56 | ```python 57 | set_ipc_default_timeout(ipc_default_timeout) 58 | ``` 59 | 60 | 61 | 62 | 63 | 64 | --- 65 | 66 | 67 | 68 | ### method `subscribe_to_topic` 69 | 70 | ```python 71 | subscribe_to_topic(topic) 72 | ``` 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | --- 82 | 83 | _This file was automatically generated via [lazydocs](https://github.com/ml-tooling/lazydocs)._ 84 | -------------------------------------------------------------------------------- /docs/api-docs/pubsub_mqtt.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | # module `pubsub_mqtt` 6 | Abstracts AWS Greengrass V2 MQTT PubSub SDK that manages subscriptions and a method to publish to AWS Greengrass MQTT topics. This is intended for use in an AWS Greengrass V2 Component SDK to provide MQTT PubSub services. 7 | 8 | IPC MQTT core is for communications between AWS Greengrass Components and the AWS IoT Core. This can be used to send MQTT messaging to the AWS IoT core to save data, trigger alarms or alerts or to trigger other AWS services and applications. 9 | 10 | For more detail on AWS Greengrass V2 see the Developer Guide at: https://docs.aws.amazon.com/greengrass/v2/developerguide/what-is-iot-greengrass.html 11 | 12 | 13 | 14 | --- 15 | 16 | 17 | 18 | ## class `MqttPubSub` 19 | 20 | 21 | 22 | 23 | 24 | 25 | ### method `__init__` 26 | 27 | ```python 28 | __init__(message_callback, mqtt_subscribe_topics) 29 | ``` 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | --- 39 | 40 | 41 | 42 | ### method `publish_to_mqtt` 43 | 44 | ```python 45 | publish_to_mqtt(topic, message_object, timeout=None) 46 | ``` 47 | 48 | Publish a Python object serlized as a JSON message to the IoT Core MQTT topic. 49 | 50 | --- 51 | 52 | 53 | 54 | ### method `set_mqtt_default_qos` 55 | 56 | ```python 57 | set_mqtt_default_qos(mqtt_default_qos) 58 | ``` 59 | 60 | 61 | 62 | 63 | 64 | --- 65 | 66 | 67 | 68 | ### method `set_mqtt_default_timeout` 69 | 70 | ```python 71 | set_mqtt_default_timeout(mqtt_default_timeout) 72 | ``` 73 | 74 | 75 | 76 | 77 | 78 | --- 79 | 80 | 81 | 82 | ### method `subscribe_to_topic` 83 | 84 | ```python 85 | subscribe_to_topic(topic) 86 | ``` 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | --- 96 | 97 | _This file was automatically generated via [lazydocs](https://github.com/ml-tooling/lazydocs)._ 98 | -------------------------------------------------------------------------------- /docs/developer-guide/README.md: -------------------------------------------------------------------------------- 1 | # Developer Guide 2 | 3 | The AWS Greengrass V2 PubSub Component SDK for Python provides an IoT PubSub application architecture delivered as a Python library to simplify and accelerate development of distributed IoT applications built on AWS Greengrass V2 Components. 4 | 5 | The SDK abstracts the AWS Greengrass IPC and MQTT PubSub functionality and uses a prescribed message format to enable data driven PubSub message routing to user defined application call-backs. This provides a Low/No-Code PubSub messaging service without the common design dependencies of distributed IoT applications. 6 | 7 | When the AWS Greengrass SDK is deployed system wide, it creates a unified message service across distributed IoT components that could span a single Greengrass device or applications, users and consumers from across the globe. 8 | 9 | ![pubsub-sdk-overview](/images/pubsub-sdk-overview.png) 10 | 11 | ## AWS Greengrass IoT PubSub Application Architecture 12 | This SDK provides a codified implementation of the principals described in the [AWS Greengrass IoT PubSub Framework](https://github.com/awslabs/aws-greengrass-labs-iot-pubsub-framework). 13 | 14 | ## Getting Started 15 | 16 | The easiest way to get started is to deploy the AWS Greengrass PubSub SDK component template described in the [Samples](/samples) directory, then develop your custom application logic and update as required. 17 | 18 | ## Functional Overview 19 | 20 | The SDK defines a prescribed message format that includes a **route** field to enable data driven message routing to users’ application logic. This allows for a simplified PubSub topic schema that supports functional interface updates without code changes. Accordingly, the SDK operates all default messaging over a single **Ingress** and **Egress** PubSub topic pair per Greengrass core device which further reduces dependencies between distributed IoT applications and teams. 21 | 22 | Message routing is based on the **route** field in the SDKs prescribed message format as shown below: 23 | ``` 24 | { 25 | "sdk_version": "0.1.4", 26 | "message_id": "20220403170948930231", 27 | "status": 200, 28 | "route": "MyPubSubMessageHandler.pubsub_message_route_target", 29 | "message": { 30 | "my-message-param01": "param01", 31 | "my-message-param02": "param02" 32 | } 33 | } 34 | ``` 35 | 36 | You simply register user defined message handler classes (which are Plain Old Python Objects) with the SDK. The message **route** field is in Class.Method namespace convention. If the SDK receives a PubSub message with a route value matching the Class.Method value of a registered message handler class, it will automatically route the message to that method for processing. 37 | 38 | In this way, you can easily import a complete PubSub message service into your AWS Greengrass custom component and have PubSub messages delivered to your application logic directly. 39 | 40 | As the Class.Method namespace is preserved in message routing; you can register multiple message handler classes. This provides an easy way to separate PubSub message processing into functional code blocks (finally!). 41 | 42 | ### Example 43 | 44 | To include a new **health_check** call in your distributed AWS Greengrass IoT Application. 45 | 46 | * Initialise the AWS Greengrass PubSub Component SDK as shown in the proceeding sections. 47 | * In your application, define a message handler class called **SystemMessageHandler** with a method **health_check** to act as a message route target for the SDK. 48 | 49 | ``` 50 | class SystemMessageHandler(): 51 | 52 | def health_check(self, protocol, topic, message_id, status, route, message): 53 | 54 | # Process messages with route = 'SystemMessageHandler.health_check' 55 | log.info('SystemMessageHandler.health_check received message: {}'.format(message)) 56 | ``` 57 | 58 | Notice that the **health_check** method contains the parameters protocol, topic, message_id, status, route and message. The SDK will only register methods as message route targets that have these exact parameters. The SDK will also ignore any private methods (starting with '_'). And so, you are quite free to add helper and other functional methods as needed in the message handler classes you register with the SDK and not have them interfere with expected message routing functionality. 59 | 60 | * Register the message handler class with the SDK client: 61 | 62 | ``` 63 | system_message_handler = SystemMessageHandler() 64 | pubsub_client.register_message_handler(system_message_handler) 65 | ``` 66 | 67 | * Activate IPC and / or MQTT: 68 | ``` 69 | # Activate IPC Protocol 70 | pubsub_client.activate_ipc_pubsub() 71 | 72 | # Acticate MQTT Protocol 73 | pubsub_client.activate_mqtt_pubsub() 74 | ``` 75 | 76 | * Once the protocol is activated, the SDK will immediately start routing received messages from all subscribed PubSub topics to the registered message handler classes. 77 | 78 | * Advertise to any upstream systems and teams that your application now supports the health_check route! 79 | 80 | Once the message handler is created, it becomes simple to add additional calls to your interface. Just add new methods to the registered message handler classes. 81 | 82 | ![message-processing-health-check-example](/images/message-processing-health-check.png) 83 | 84 | 1. Consuming application publishes a well formatted message to the devices **Ingress Topic** with the **route** field set to **SystemMessageHandler.health_check**, 85 | 1. Message is validated and IPC and MQTT protocols are combined into single message routing pipeline, 86 | 1. SDK routes the message to the method **def health_check(...)** in the SDK registered **SystemMessageHandler** class, 87 | 1. We assume the **health_check** method logic publishes a response message to the SDK programmatically, 88 | 1. SDK publishes the message to the selected protocols (IPC and / or MQTT) and topic (Egress topic by default). 89 | 90 | This health_check example is shown in the SDK component template module [my_system_message_handler.py](/samples/gg-pubsub-sdk-component-template/src/pubsub_message_handlers/my_system_message_handler.py) 91 | 92 | ## PubSub Topic Schema 93 | 94 | One of the key dependencies when developing a distributed IoT application is the PubSub topic schema and topic to function mapping. This is an ongoing dependency that can cause API breaking changes for even minor functional updates. 95 | 96 | The SDK removes this dependency by defining a simple but extensible PubSub topic schema. The data driven message routing described breaks the dependency on topics being used to address interface functionality and so, the SDK base topic schema consists of just two topics, the **ingress** and **egress** topics. 97 | 98 | The topics paths are constructed from the **pubsub-base-topic** provided when initialising the SDK and are as such: 99 | * **Ingress Topic:** [pubsub-base-topic]/[AWS_IOT_THING_NAME]/ingress 100 | * **Egress Topic:**[pubsub-base-topic]/[AWS_IOT_THING_NAME]/egress 101 | 102 | The SDK component will automatically subscribe to and process messages received on the Ingress topic and will by default publish messages to the components Egress topic. 103 | 104 | ### Custom Topics 105 | While greatly simplified, this one in / one out topic schema doesn't support some of the key benefits of PubSub architectures over synchronous message services such as one to many and many to one message patterns. To overcome this, the SDK also supports programmatically subscribing and publishing to user defined topics. 106 | 107 | The SDK provides a [subscribe_to_topic()](/docs/api-docs/pubsub_client.md#method-subscribe_to_topic) function that allows you to programmatically subscribe to any topic that the components recipe access policy will allow. 108 | 109 | Similarly, the [publish_message](/docs/api-docs/pubsub_client.md#method-publish_message) function will default to the components Egress topic if none provided but any custom topic can be given here as long as its allowed by the components recipe access policy. 110 | 111 | You can also define custom topics the component should subscribe to via config in the component’s recipe file. 112 | 113 | ## SDK PubSub Message Format 114 | 115 | An example of an AWS Greengrass PubSub SDK well-formatted message is as below: 116 | ``` 117 | { 118 | "sdk_version": "1.0.0", 119 | "message_id": "123456789", 120 | "status": "200", 121 | "route": "MyPubSubMessageHandler.message_route_target_method", 122 | "message": { 123 | "my-message-param01": "param01", 124 | "my-message-param02": "param02" 125 | } 126 | } 127 | ``` 128 | 129 | 1. **sdk_version**: Semantic version of the SDK message format for compatibility. 130 | 2. **message_id**: Timestamp or unique ID to track messages across request / response patterns. 131 | 3. **status**: Status code of this message. 132 | 4. **route**: Message routing to matching named callback functions in user defined message handler classes. 133 | 5. **message**: User defined data payload in an SDK well formatted message. 134 | 135 | ### Initialising the SDK Client 136 | 1. Define a method that will be the route of last resort for received PubSub messages and the expected message parameters. 137 | ``` 138 | def default_message_handler(self, protocol, topic, message_id, status, route, message): 139 | 140 | # Process messages without a registered handler router target 141 | log.error('Received message to unknown route / message handler: {} - message: {}'.format(route, message))) 142 | ``` 143 | 144 | Any PubSub messages received that don't have a matching route target in any of the registered message handler classes will be router here. 145 | 146 | 147 | 2. Initialise the AWS Greengrass PubSub SDK Client 148 | ``` 149 | # Import the AWS Greengrass PubSub SDK 150 | from awsgreengrasspubsubsdk.pubsub_client import AwsGreengrassPubSubSdkClient 151 | 152 | # Declare the PubSub Base topic, this is used to build the default Ingress and Egress PubSub topics. 153 | pubsub_base_topic = 'com.my_greengrass.application' 154 | 155 | # Initilise the AwsGreengrassPubSubSdkClient with the pubsub_base_topic and default_message_handler 156 | pubsub_client = AwsGreengrassPubSubSdkClient(pubsub_base_topic, default_message_handler ) 157 | 158 | ``` 159 | 160 | ## User Defined Message Handler Class 161 | 162 | Message Handler classes are user defined Plain Old Python Objects (POPOs) that contain methods the SDK will consider as valid route targets for received PubSub messages. To define a Message Handler class, just create a regular Python class with methods that meet the criteria to be considered as valid message route targets. 163 | 164 | ``` 165 | Class MyMessageHandler(): 166 | 167 | def my_custom_messsage_handler(self, protocol, topic, message_id, status, route, message): 168 | # Process message request 169 | ``` 170 | 171 | The SDK deconstructs the message fields and provides these to the message handlers in a parameterised format. To be considered as valid message route targets, methods in the registered message handler classes must accepts the following parameters: 172 | 173 | Where: 174 | * **protocol:** **ipc** or **mqtt** to describe the protocol the message was received on. 175 | * **topic:** The PubSub topic that the message was received on. 176 | * **message_id:** The message_id that was sent in the PubSub message. 177 | * **status:** The status code that was received in the PubSub message. 178 | * **route:** The route value that was received in the PubSub message. 179 | * **message:** The user defined payload field that was received in the PubSub message. 180 | 181 | Methods that are private or don't have these named parameters will be ignored by the SDK and not considered as potential route targets. This allows you to add helpers and private methods in the message handler classes you register without impacting expected message routing functionality. 182 | 183 | ### Registering a Message Handler 184 | To register a message handler class with the SDK, call the SDK client register_message_handler() method. 185 | ``` 186 | my_pubsub_message_handler = MyPubSubMessageHandler() 187 | pubsub_client.register_message_handler(my_pubsub_message_handler) 188 | ``` 189 | 190 | ### Activate IPC and / or MQTT Protocols in the SDK 191 | ``` 192 | # Activate IPC Protocol 193 | pubsub_client.activate_ipc_pubsub() 194 | 195 | # Acticate MQTT Protocol 196 | pubsub_client.activate_mqtt_pubsub() 197 | ``` 198 | 199 | ## Creating Well Formatted Messages 200 | The SDK provides a message_formatter that returns well-formatted messages from a list of parameterised values. These are described in the API Docs [message_formatter](/docs/api-docs/message_formatter.md). 201 | 202 | In particular the [get_message](/docs/api-docs/message_formatter.md#method-get_message) and [get_error_message](/docs/api-docs/message_formatter.md#method-get_error_message) calls as shown below: 203 | ``` 204 | from awsgreengrasspubsubsdk.message_formatter import PubSubMessageFormatter 205 | formatter = PubSubMessageFormatter() 206 | 207 | # Create a well formatted message with default values and local time in message payload. 208 | my_message = { "local-time" : datetime.now().strftime("%d-%b-%Y %H:%M:%S") } 209 | sdk_format_msg = self.formatter.get_message(message=my_message) 210 | ``` 211 | -------------------------------------------------------------------------------- /images/gg-component-published.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/aws-greengrass-labs-iot-pubsub-sdk-for-python/2e5190d22d15951bd873ec577dd0989627c44b55/images/gg-component-published.png -------------------------------------------------------------------------------- /images/message-processing-health-check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/aws-greengrass-labs-iot-pubsub-sdk-for-python/2e5190d22d15951bd873ec577dd0989627c44b55/images/message-processing-health-check.png -------------------------------------------------------------------------------- /images/pubsub-sdk-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/aws-greengrass-labs-iot-pubsub-sdk-for-python/2e5190d22d15951bd873ec577dd0989627c44b55/images/pubsub-sdk-overview.png -------------------------------------------------------------------------------- /images/select-component-deployment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/aws-greengrass-labs-iot-pubsub-sdk-for-python/2e5190d22d15951bd873ec577dd0989627c44b55/images/select-component-deployment.png -------------------------------------------------------------------------------- /lazydocs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | lazydocs \ 4 | --output-path="./docs/api-docs" \ 5 | --overview-file="README.md" \ 6 | awsgreengrasspubsubsdk 7 | -------------------------------------------------------------------------------- /samples/README.md: -------------------------------------------------------------------------------- 1 | # Samples 2 | 3 | This directory contains a sample AWS Greengrass PubSub SDK component template that you can use as a skeleton framework to start developing your own AWS Greengrass custom components. 4 | 5 | ## AWS Greengrass Development Kit 6 | The sample component is built and deployed using the [AWS Greengrass Development Kit](https://docs.aws.amazon.com/greengrass/v2/developerguide/greengrass-development-kit-cli.html). A development tool to create, test, build, publish, and deploy custom Greengrass components. 7 | 8 | ## Deploying the AWS Greengrass PubSub SDK Component Template 9 | 10 | In the following steps we will copy the AWS Greengrass PubSub SDK component template. Then using the AWS Greengrass Development Kit will build a versioned deployment artifact, publish it to AWS IoT Core and finally deploy it to a remote AWS Greengrass core device. 11 | 12 | ### Prerequisites 13 | * An AWS Account, see [How to Create a new AWS account](https://aws.amazon.com/premiumsupport/knowledge-center/create-and-activate-aws-account/) if needed. 14 | * A registered [AWS Greengrass V2 core device](https://docs.aws.amazon.com/greengrass/v2/developerguide/setting-up.html) 15 | * Knowledge of [AWS Greengrass Components](https://docs.aws.amazon.com/greengrass/v2/developerguide/create-components.html) and the [AWS Greengrass Developer Guide](https://docs.aws.amazon.com/greengrass/v2/developerguide). 16 | * The [AWS Greengrass Development Kit](https://docs.aws.amazon.com/greengrass/v2/developerguide/greengrass-development-kit-cli.html) installed on the development machine. 17 | * Ensure your development machine has the AWS GDK installed by following this guide to [Install or Update the AWS IoT Greengrass Development Kit Command-Line Interface](https://docs.aws.amazon.com/greengrass/v2/developerguide/install-greengrass-development-kit-cli.html). 18 | 19 | ### Clone and Copy the Component Template 20 | 21 | ``` 22 | # Clone this GIT Repository 23 | git clone https://github.com/awslabs/aws-greengrass-labs-iot-pubsub-sdk-for-python.git 24 | 25 | # Take a copy of the selected component template and rename to your desired component name. 26 | MY_COMPONENT_NAME=com.example.greengrass-pubsub-component 27 | cp -Rf aws-greengrass-pubsub-sdk-for-python/samples/gg-pubsub-sdk-component-template $MY_COMPONENT_NAME 28 | 29 | # CD into the template component source directory 30 | cd $MY_COMPONENT_NAME/src 31 | ``` 32 | 33 | **Note:** Its recommended to open the new component directory in your preferred IDE or text editor now to help with making the proceeding changes and updates. 34 | 35 | 36 | ### Update the GDK Configuration 37 | The SDK component template is built and published using the AWS Greengrass Development Kit, with behaviors set by the **src/gdk-config.json** config file. 38 | 39 | In the AWS Greengrass component directory: 40 | * Replace the contents of **src/gdk-config.json** with that given below. 41 | * Update the **author** and **region** fields accordingly. (Ensure region supports AWS Greengrass) 42 | * If you changed the component name (i.e: MY_COMPONENT_NAME) above, then update **com.example.greengrass-pubsub-component** here as well. 43 | * Save and close the file. 44 | ``` 45 | { 46 | "component" :{ 47 | "com.example.greengrass-pubsub-component": { 48 | "author": "YOUR NAME", 49 | "version": "NEXT_PATCH", 50 | "build": { 51 | "build_system" :"zip" 52 | }, 53 | "publish": { 54 | "bucket": "aws-greengrass-component-artefacts", 55 | "region": "YOUR AWS REGION" 56 | } 57 | } 58 | }, 59 | "gdk_version": "1.0.0" 60 | } 61 | ``` 62 | **Notes:** 63 | * The GDK will create a unique Amazon S3 bucket to host the component artifacts with the name based on the bucket value, your account ID and the selected region. 64 | * Using the settings above, the GDK will create a bucket named: **aws-greengrass-component-artefacts-[region]-[account-id]**. 65 | * Each component will be given a unique folder and so you can use this S3 bucket globally for all AWS Greengrass components. 66 | * Your AWS Identity and Access Management (IAM) permissions must allow Amazon S3 bucket creation and publishing AWS IoT Greengrass components. 67 | 68 | ### Update the Component Recipe 69 | The AWS Greengrass component recipe is a config file that provides metadata, configuration parameters and PubSub access policies for an AWS Greengrass V2 Component. In the component recipe file, the **ComponentName** field and the PubSub access policy names must be globally unique across all components deployed on any individual Greengrass Core device. In the template recipe file, the **ComponentName**, PubSub access policy names and the **base-pubsub-topic** fields are set to **COMPONENT_NAME** and must be updated on new components being deployed. 70 | 71 | * Using your preferred IDE or text editor, open the components **src/recipe.json** file then Find and Replace **COMPONENT_NAME** with the component name given above (i.e: MY_COMPONENT_NAME) when you copied the template (default: **com.example.greengrass-pubsub-component**) 72 | 73 | This will set the component title, PubSub access policy names and the **pubsub-base-topic** to your selected component name. The SDK will apply the **base-pubsub-topic** value to set this components **Ingress** and **Egress** topics as follows: 74 | 75 | Ingress Topic: MY_COMPONENT_NAME/GREENGRASS CORE THING NAME/ingress 76 | Egress Topic: MY_COMPONENT_NAME/GREENGRASS_CORE_THING_NAME/egress 77 | 78 | **Note:** You can manually set the base-pubsub-topic to something other than the component name to adjust the given topic structure. 79 | 80 | Then, staying in the **src/recipe.json** file: 81 | 82 | * Update the **ComponentDescription** and **ComponentPublisher** as desired. 83 | * (Optional) Update the remaining **GGV2PubSubSdkConfig** fields that enables data injection of application parameters, in particular: 84 | * **ipc-subscribe-topics**: An array of user defined PubSub topics the component should subscribe to on the IPC bus. 85 | * **mqtt-subscribe-topics**: An array of user defined PubSub topics the component should subscribe to on the MQTT bus. 86 | 87 | These are passed to the AWS Greengrass component on start-up and will be access programmatically. 88 | i.e: 89 | ``` 90 | "GGV2PubSubSdkConfig": { 91 | "base-pubsub-topic" : "com.example.greengrass-pubsub-component", 92 | "ipc-subscribe-topics" : ["ipc/my-app/broadcast", "ipc/my-app/error"], 93 | "mqtt-subscribe-topics" : ["mqtt/my-app/broadcast", "mqtt/my-app/error"] 94 | } 95 | ``` 96 | In this example, we have defined application level broadcast and error PubSub topics that may be used to provide differentiated processing based on priority. Another common usage example is to add the egress topic of other components you want to receive messages from in your application. 97 | 98 | You can also add you own parameters in the **GGV2PubSubSdkConfig** field and have them passed into your application logic using the patterns shown in the component template. 99 | 100 | ### Build and Publish the Component to AWS IoT Core 101 | 102 | If not already completed, ensure your development machine has the AWS GDK installed by following this guide to [Install or Update the AWS IoT Greengrass Development Kit Command-Line Interface](https://docs.aws.amazon.com/greengrass/v2/developerguide/install-greengrass-development-kit-cli.html). 103 | 104 | ``` 105 | # (If not already done, CD into the component src directory 106 | cd $MY_COMPONENT_NAME/src 107 | 108 | # Build the component: 109 | gdk component build 110 | 111 | # Publish the component to AWS IoT Core: 112 | gdk component publish 113 | 114 | ``` 115 | 116 | The AWS Greengrass component will now be published to the AWS IoT Core. You can verify in the [AWS IoT Console](https://console.aws.amazon.com/iot/) by selecting your **region** then, selecting **Greengrass** and the **Components** menu as shown below: 117 | 118 | ![published-component](../images/gg-component-published.png) 119 | 120 | ### Deploy the Component to an AWS Greengrass Core Device 121 | 122 | **Note:** The AWS Greengrass Core device will need permissions to access the S3 bucket with the deployment artifact listed in src/gdk-config.json as described above. See [Authorize core devices to interact with AWS services](https://docs.aws.amazon.com/greengrass/v2/developerguide/device-service-role.html) in the AWS Greengrass Developer guide for details. 123 | 124 | The final step is to deploy the component to a registered AWS Greengrass Core device: 125 | * In the [AWS IoT Console](https://console.aws.amazon.com/iot/), select **Greengrass** and then the **Core devices** menu item and click on the Greengrass core you will deploy the component on. 126 | 127 | * Select the **Deployments** tab and click on the managed deployment to add this component too 128 | * If your Greengrass Core device doesn’t have an existing deployment, you will need to create one by going to the **Deployments** menu item, clicking **Create**, select the Core device and then following the below instructions from there. 129 | * Click **Revise**, **Revise Deployment** then **Next** and select the name of the component you published in the **My components** section. 130 | * Click **Next** leaving all fields default until the final page then click **Deploy** 131 | 132 | ## Validate the Component 133 | 134 | Once the template component and SDK are installed and operational on an AWS Greengrass core device, it will periodically publish a local time message on the devices egress topic. It will also process and respond to well-formatted request messages as described below. 135 | 136 | ### Monitoring Greengrass and Component Logs: 137 | 138 | Greengrass logs (on the Greengrass Core device) are located in the /greengrass/v2/logs directory. 139 | 140 | To fault find or just to view component application logs, on the Greengrass Core device: 141 | 142 | ``` 143 | # To display the status of the deployment and any recent errors on the Greengrass core 144 | tail -f /greengrass/v2/logs/greengrass.log 145 | 146 | # The application log for the Greengrass component and will show errors and messages being published and received. 147 | tail -f /greengrass/v2/logs/MY_COMPONENT_NAME.log 148 | ``` 149 | 150 | ### Verify the Published Time Message 151 | 152 | In the [AWS IoT Console](https://console.aws.amazon.com/iot/), select your desired **region** and then **Test** from the menu and subscribe to the components **Egress** topic. 153 | 154 | The Egress topic will be: **[base-pubsub-topic]/[THING_NAME]/egress** 155 | 156 | Where: 157 | * **base-pubsub-topic** is the value provided for this field in the component recipe (Typically set to the components name) and 158 | * **[THING_NAME]** is the AWS IoT Thing name associated with the AWS Greengrass core device. 159 | 160 | You should see a periodic message being received from the device as below: 161 | ``` 162 | { 163 | "sdk_version": "0.1.4", 164 | "message_id": "20220329121327002764", 165 | "status": 200, 166 | "route": "default_message_handler", 167 | "message": { 168 | "local-time": "29-Mar-2022 10:13:27" 169 | } 170 | } 171 | ``` 172 | 173 | For reference, this is generated in the main class [service_loop](/samples/gg-pubsub-sdk-component-template/src/main.py) as per the below code sample: 174 | ``` 175 | def service_loop(self): 176 | ''' 177 | Holds the process up while handling event-driven PubSub triggers. 178 | Put synchronous application logic here or have the component completely event driven. 179 | 180 | This example periodically publishes the local time to IPC and MQTT in a well formatted message. 181 | ''' 182 | 183 | while True: 184 | try: 185 | # Build and publish a well formatted message displaying local time to IPC and MQTT 186 | receive_route = "local_time_update" 187 | my_message = { "local-time" : datetime.now().strftime("%d-%b-%Y %H:%M:%S") } 188 | sdk_format_msg = self.message_formatter.get_message(route=receive_route, message=my_message) 189 | log.info('Publishing message: {}'.format(sdk_format_msg)) 190 | self.publish_message('ipc_mqtt', sdk_format_msg) 191 | 192 | except Exception as err: 193 | # Publish error to IPC and MQTT on default error topic. 194 | protocol = 'ipc_mqtt' 195 | err_msg = 'Exception in main process loop: {}'.format(err) 196 | self.publish_error(protocol, err_msg) 197 | 198 | finally: 199 | time.sleep(10) 200 | ``` 201 | 202 | ### Verify Custom Message Handlers 203 | 204 | The template component registers two user defined message handlers. This is to demonstrate how the SDK can be used to route PubSub messages but also how to separate PubSub message processing code by function. 205 | 206 | The example message handlers are: 207 | 1. [MySystemMessageHandler](/samples/gg-pubsub-sdk-component-template/src/pubsub_message_handlers/my_system_message_handler.py): An example message handler to process system type requests to the custom component including get_health_check_request and get_system_details_request calls 208 | 209 | 2. [MySensorMessageHandler](/samples/gg-pubsub-sdk-component-template/src/pubsub_message_handlers/my_system_message_handler.py): An example message handler to process data requests to connected sensors or IoT Things including a get_temp_sensor_request call. 210 | 211 | These are registered with the SDK in the main __init__ method: 212 | ``` 213 | self.my_system_message_handler = MySystemMessageHandler(self.publish_message, self.publish_error, self.self.message_formatter) 214 | self.pubsub_client.register_message_handler(self.my_system_message_handler) 215 | 216 | self.my_sensor_message_handler = MySensorMessageHandler(self.publish_message, self.publish_error, self.message_formatter) 217 | self.pubsub_client.register_message_handler(self.my_sensor_message_handler) 218 | ``` 219 | 220 | While not compulsory, it’s a common pattern for message handlers to operate in a request / response pattern. To make creating well formatted and publishing response messages easier, a reference to the SDK client publish_message, pubish_error and message formatter call-backs is provided to the sample message handler classes. 221 | 222 | ### Verify the health-check-request function 223 | 224 | In the same AWS IoT Console test facility, publish the below health_check_request message to the component’s ingress topic. 225 | 226 | The Ingress topic will be: **[base-pubsub-topic]/[THING_NAME]/ingress** 227 | 228 | Where: 229 | * **base-pubsub-topic** is the value provided for this field in the component recipe (Typically set to the components name) and 230 | * **[THING_NAME]** is the AWS IoT Thing name associated with the AWS Greengrass core device. 231 | 232 | ``` 233 | { 234 | "sdk_version": "0.1.4", 235 | "message_id": "20220329121327002764", 236 | "status": 200, 237 | "route": "MySystemMessageHandler.get_health_check_request", 238 | "message": { } 239 | } 240 | ``` 241 | 242 | You should receive the below response on the components egress topic. 243 | ``` 244 | { 245 | "sdk_version": "0.1.4", 246 | "message_id": "20220329121327002764", 247 | "status": 200, 248 | "route": "MySystemMessageHandler.get_health_check_response", 249 | "message": { 250 | "status": "System OK" 251 | } 252 | } 253 | ``` 254 | 255 | This is virtue of the SDK routing the request message to the get_health_check_request method in the registered MySystemMessageHandler message handler class. This method is shown below: 256 | 257 | ``` 258 | def get_health_check_request(self, protocol, topic, message_id, status, route, message): 259 | ''' 260 | [Removed for brevity] 261 | ''' 262 | 263 | try: 264 | # Log just for Dev / Debug. 265 | log.info('get_health_check_request received on protocol: {} - topic: {}'.format(protocol, topic)) 266 | 267 | # Create a response message using the Greengrass PubSub SDK prefered message_formatter. 268 | # Reflect the request message_id for tracking, status and other fields as defeult. 269 | response_route = "MySystemMessageHandler.get_health_check_response" 270 | msg = {"status" : "System OK"} 271 | response = self.message_formatter.get_message(message_id=message_id, route=response_route, message=msg) 272 | 273 | # Publish the message on the protocol (IPC or MQTT) that it was received to default egress topic. 274 | self.publish_message(protocol=protocol, message=response) 275 | 276 | except Exception as err: 277 | # Publish error to default error route, will log locally as an error as well. 278 | err_msg = 'Exception in get_health_check_request: {}'.format(err) 279 | self.publish_error(protocol, err_msg) 280 | ``` 281 | 282 | ### Validate Supported Message Processors 283 | The template component supports the following PubSub message route values: 284 | 1. MySystemMessageHandler.get_health_check_request 285 | 2. MySystemMessageHandler.get_system_details_request 286 | 3. MySensorMessageHandler.get_temp_sensor_request 287 | 288 | Repeat the previous step, replacing the request message **route** value with the above supported values and validate the response. 289 | 290 | ## Develop the AWS Greengrass Message Component 291 | You can now develop your custom application logic in the template AWS Greengrass component and redeploy by repeating the GDK build and publish steps then revising the deployment in the AWS IoT console and selecting the latest revision number from the component configuration step. 292 | 293 | ### Add a new Function Call 294 | 295 | Try adding a new PubSub message call to your component. 296 | 297 | In [MySensorMessageHandler](/samples/gg-pubsub-sdk-component-template/src/pubsub_message_handlers/my_system_message_handler.py) add a new vibration sensor call: 298 | 1. Copy the entire **get_temp_sensor_request(...)** method and rename it to **get_vibration_sensor_request(.....)** 299 | 2. In the new get_vibration_sensor_request(.....) method, replace 300 | a. "temp_c" : random.randint(30, 50) with 301 | b. "g-force" : random.randint(1, 6) 302 | 303 | 3. Save the file. 304 | 305 | Now repeat the **gdk build** and **gdk publish** command to build a new version of your component. Finally, repeat the deploy steps and update the component version when you get to the component configuration section. 306 | 307 | Using the same **Test** page as above, send the message with the **route** field now set to: **MySensorMessageHandler.get_vibration_sensor_request** 308 | i.e: 309 | ``` 310 | { 311 | "sdk_version": "0.1.4", 312 | "message_id": "20220329121327002764", 313 | "status": 200, 314 | "route": "MySensorMessageHandler.get_vibration_sensor_request", 315 | "message": { } 316 | } 317 | ``` 318 | 319 | You should see your response return with the new message values you set. 320 | Use this technique to continue developing your AWS Greengrass Custom component. 321 | 322 | ## Monitoring and Debugging the Component 323 | 324 | If there are any issues, you can monitor the deployment and the component itself on the Greengrass core in the following logs: 325 | * **Greengrass Core Log:** /greengrass/v2/greengrass.log and 326 | * **Greengrass Component Log:** /greengrass/v2/[YOUR_GREENGRASS_COMPONENT_NAME].log 327 | 328 | ## Taxonomy of the SDK Template Component 329 | 330 | ### Component Recipe 331 | The component recipe in the template component defines a configuration object **GGV2PubSubSdkConfig** that enables data injection of application parameters to the component’s application logic on initialisation. 332 | 333 | #### PubSub SDK Config Parameters 334 | The default GGV2PubSubSdkConfig recipe field is show below: 335 | ``` 336 | "GGV2PubSubSdkConfig": { 337 | "base-pubsub-topic" : "com.example.greengrass-pubsub-component", 338 | "ipc-subscribe-topics" : ["ipc/my-app/broadcast", "ipc/my-app/error"], 339 | "mqtt-subscribe-topics" : ["mqtt/my-app/broadcast", "mqtt/my-app/error"] 340 | } 341 | ``` 342 | 343 | Apart from enforcing consistent design patterns, this config driven approach allows updates of common PubSub application properties and topic schema without the need for application or code updates. You can also add you own parameters in the GGV2PubSubSdkConfig field and have them passed into your application logic using the same patterns described. 344 | 345 | #### PubSub Access Policy 346 | The component recipe in the template component defines the PubSub Access Control policy that provides permission for the component to subscribe and publish to both the IPC and MQTT message protocols. By default, this is provided as an Allow All policy and can be restricted to the components least required permissions as needed. 347 | 348 | #### Component Lifecycle Events 349 | The template component Lifecycle **Install** event installs the SDK Python package when the component is installed on an AWS Greengrass core device. 350 | 351 | The Lifecycle **Run** event sets the components start command and passed the GGV2PubSubSdkConfig JSON field to the component’s application logic. 352 | 353 | ``` 354 | "Lifecycle": { 355 | "Install" : { 356 | "Timeout" : 300, 357 | "Script" : "python3 -m pip install awsgreengrasspubsubsdk" 358 | }, 359 | "Run": { 360 | "Script": "python3 -u {artifacts:decompressedPath}/src/main.py '{configuration:/GGV2PubSubSdkConfig}'", 361 | "RequiresPrivilege": "false" 362 | } 363 | ``` 364 | 365 | ### Main.py 366 | 367 | Provided as the entry point for the template component, the Main module provides the following: 368 | 369 | #### Component Config Injection 370 | The main module accepts the GGV2PubSubSdkConfig as a sys.arvg into the application logic and parses from JSON to an object called ggv2_component_config. This is provided to initialise the MyAwsGreengrassV2Component class and finally the components service loop function is started. 371 | 372 | ``` 373 | if __name__ == "__main__": 374 | 375 | try: 376 | # Parse config from component recipe into sys.argv[1] 377 | ggv2_component_config = sys.argv[1] 378 | ggv2_component_config = json.loads(ggv2_component_config) 379 | log.info('GG PubSub SDK Config: {}'.format(ggv2_component_config)) 380 | 381 | # Create the component class with the parsed Greengrass recipe config. 382 | ggv2_component = MyAwsGreengrassV2Component(ggv2_component_config) 383 | 384 | # Start the main process loop to hold up the process. 385 | ggv2_component.service_loop() 386 | 387 | except Exception as err: 388 | log.error('Exception occurred initialising component. ERROR MESSAGE: {}'.format(err)) 389 | 390 | ``` 391 | 392 | #### Component Service Loop 393 | Some Greengrass components will be completely event driven with events being triggered in reaction to PubSub messages in which case the service loops only function is to hold the main process up. Its also common for synchronous operations to live within the service loop. 394 | 395 | In the sample template, the service loop periodically publishes a well formatted message containing the local time. 396 | 397 | #### MyAwsGreengrassV2Component Class Initialisation 398 | The template component main class is called MyAwsGreengrassV2Component. On initialisation, this class extracts the base-pubsub-topic, ipc-subscribe-topics and mqtt-subscribe-topics parameters from the ggv2_component_config and the **ingress** and **egress** topics are built as: 399 | * **Ingress Topic**: [base-pubsub-topic]/[AWS_IOT_THING_NAME]/ingress and 400 | * **Egress Topic**: [base-pubsub-topic]/[AWS_IOT_THING_NAME]/egress 401 | 402 | The AWS Greengrass PubSub SDK client message_formatter is initialised and the IPC and MQTT protocols are activated. The SDK doesn't subscribe or allow publishing to any topics until the IPC and / or MQTT protocols are activated as per the example. 403 | 404 | ### User Defined Message Handlers 405 | This is the user defined message handlers in the [pubsub_message_handlers](/samples/gg-pubsub-sdk-component-template/src/pubsub_message_handlers) directory as described above. In the template component, two separate message handlers are provided how to route messages and separate PubSub message processors based on functionality. 406 | 407 | 408 | -------------------------------------------------------------------------------- /samples/gg-pubsub-sdk-component-template/src/gdk-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "component" :{ 3 | "[ENTER COMPONENTS NAME]": { 4 | "author": "[ENTER AUTHORS NAME]", 5 | "version": "NEXT_PATCH", 6 | "build": { 7 | "build_system" :"zip" 8 | }, 9 | "publish": { 10 | "bucket": "[ENTER S3 BUCKET FOR DEPLOYMEMT ARTIFACTS]", 11 | "region": "[ENTER AWS REGON TO PUBLISH COMPONENT]" 12 | } 13 | } 14 | }, 15 | "gdk_version": "1.0.0" 16 | } -------------------------------------------------------------------------------- /samples/gg-pubsub-sdk-component-template/src/main.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0. 3 | 4 | ''' 5 | main.py: 6 | 7 | AWS Greengrass component demo for the AWS Greengrass PubSub SDK. 8 | 9 | Simple Time publish and IPC / MQTT message logger 10 | ''' 11 | 12 | __version__ = "0.0.1" 13 | __status__ = "Development" 14 | __copyright__ = "Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved." 15 | __author__ = "Dean Colcott " 16 | 17 | import sys 18 | import json 19 | import time 20 | import logging 21 | from datetime import datetime 22 | 23 | # AWS Greengrass PubSub Componnht SDK Imports. 24 | from awsgreengrasspubsubsdk.message_formatter import PubSubMessageFormatter 25 | from awsgreengrasspubsubsdk.pubsub_client import AwsGreengrassPubSubSdkClient 26 | 27 | # Example user defined message handling classes 28 | from pubsub_message_handlers.my_system_message_handler import MySystemMessageHandler 29 | from pubsub_message_handlers.my_sensor_message_handler import MySensorMessageHandler 30 | 31 | # Config the logger. 32 | log = logging.getLogger(__name__) 33 | logging.basicConfig(format="[%(name)s.%(funcName)s():%(lineno)d] - [%(levelname)s] - %(message)s", 34 | stream=sys.stdout, 35 | level=logging.DEBUG) 36 | 37 | class MyAwsGreengrassV2Component(): 38 | 39 | def __init__(self, ggv2_component_config): 40 | ''' 41 | Initialises the AWS Greengrass V2 custom component including IPC and MQTT PubSub SDK Client. 42 | ''' 43 | 44 | log.info('Initialising AwsGreengrassV2 PubSub SDK Component Example.....') 45 | 46 | pubsub_base_topic = ggv2_component_config['base-pubsub-topic'] 47 | log.info('PubSub Base Topic: {}'.format(pubsub_base_topic)) 48 | 49 | ipc_subscribe_topics = ggv2_component_config['ipc-subscribe-topics'] 50 | log.info('IPC Custom Subscribe Topics: {}'.format(ipc_subscribe_topics)) 51 | 52 | mqtt_subscribe_topics = ggv2_component_config['mqtt-subscribe-topics'] 53 | log.info('MQTT Custom Subscribe Topics: {}'.format(mqtt_subscribe_topics)) 54 | 55 | # Initilise the PubSub Message Formatter and PubSub Client SDK. 56 | self.message_formatter = PubSubMessageFormatter() 57 | self.pubsub_client = AwsGreengrassPubSubSdkClient(pubsub_base_topic, self.default_message_handler ) 58 | 59 | # Take handles to SDK publish message and error just for easier access. 60 | self.publish_message = self.pubsub_client.publish_message 61 | self.publish_error = self.pubsub_client.publish_error 62 | 63 | ################################################################################## 64 | # Initilise and register example user defined message handler classes for message routing. 65 | ################################################################################## 66 | # Message handler for System type requests 67 | log.info('Initialising and registering MySystemMessageHandler Class') 68 | self.my_system_message_handler = MySystemMessageHandler(self.publish_message, self.publish_error, self.message_formatter) 69 | self.pubsub_client.register_message_handler(self.my_system_message_handler) 70 | log.info('Initialising and registering MySystemMessageHandlerClass - Complete') 71 | 72 | # Message handler for connected sensor type requests 73 | log.info('Initialising and registering MySensorMessageHandler Class') 74 | self.my_sensor_message_handler = MySensorMessageHandler(self.publish_message, self.publish_error, self.message_formatter) 75 | self.pubsub_client.register_message_handler(self.my_sensor_message_handler) 76 | log.info('Initialising and registering MySensorMessageHandler Class - Complete') 77 | 78 | # Activate the MQTT and IPC PubSub Channels, can active one, either or both. 79 | log.info('Activating AWS Greengrass PubSub SDK IPC and MQTT Protocols') 80 | self.pubsub_client.activate_ipc_pubsub() 81 | self.pubsub_client.activate_mqtt_pubsub() 82 | log.info('Activating AWS Greengrass PubSub SDK IPC and MQTT Protocols - Complete') 83 | 84 | # Subscribe to any user defined IPC topics 85 | log.info('Subscribing to user defined IPC Protocols') 86 | for topic in ipc_subscribe_topics: 87 | self.pubsub_client.subscribe_to_topic('ipc', topic) 88 | log.info('Subscribing to user defined IPC Protocols - Complete') 89 | 90 | # Subscribe to any user defined MQTT topics 91 | log.info('Subscribing to user defined MQTT Protocols') 92 | for topic in mqtt_subscribe_topics: 93 | self.pubsub_client.subscribe_to_topic('mqtt', topic) 94 | log.info('Subscribing to user defined MQTT Protocols - Complete') 95 | 96 | log.info('Initilising AwsGreengrassV2 PubSub SDK Component Example Complete.') 97 | 98 | ################################################## 99 | # Main service / process application logic 100 | ################################################## 101 | def service_loop(self): 102 | ''' 103 | Holds the process up while handling event-driven PubSub triggers. 104 | Put synchronous application logic here or have the component completely event driven. 105 | 106 | This example periodically publishes the local time to IPC and MQTT in a well formatted message. 107 | ''' 108 | 109 | while True: 110 | try: 111 | # Build and publish a well formatted message displaying local time to IPC and MQTT 112 | receive_route = "local_time_update" 113 | my_message = { "local-time" : datetime.now().strftime("%d-%b-%Y %H:%M:%S") } 114 | sdk_format_msg = self.message_formatter.get_message(route=receive_route, message=my_message) 115 | log.info('Publishing message: {}'.format(sdk_format_msg)) 116 | self.publish_message('ipc_mqtt', sdk_format_msg) 117 | 118 | except Exception as err: 119 | # Publish error to IPC and MQTT on default error topic. 120 | protocol = 'ipc_mqtt' 121 | err_msg = 'Exception in main process loop: {}'.format(err) 122 | self.publish_error(protocol, err_msg) 123 | 124 | finally: 125 | time.sleep(10) 126 | 127 | def default_message_handler(self, protocol, topic, message_id, status, route, message): 128 | ''' 129 | This default message handler function is a route of last resort to handle 130 | PubSub messages received by the SDK with a route value that does not 131 | match any functions in the registered message_handler classes. 132 | 133 | In this example, we generate and publish an error message to both IPC and MQTT. 134 | You could instead handle this locally or just sliently discard messages here depending 135 | on the application. 136 | ''' 137 | 138 | # Publish error to IPC and MQTT on default error topic, will log locally as an error as well. 139 | err_msg = 'Received message to unknown route / message handler: {} - message: {}'.format(route, message) 140 | self.publish_error(protocol, err_msg) 141 | 142 | if __name__ == "__main__": 143 | 144 | try: 145 | 146 | # Parse config from component recipe into sys.argv[1] 147 | ggv2_component_config = sys.argv[1] 148 | ggv2_component_config = json.loads(ggv2_component_config) 149 | 150 | log.info('GG PubSub SDK Config: {}'.format(ggv2_component_config)) 151 | 152 | # Create the component class with the parsed Greengrass recipe config. 153 | ggv2_component = MyAwsGreengrassV2Component(ggv2_component_config) 154 | 155 | # Start the main process loop to hold up the process. 156 | ggv2_component.service_loop() 157 | 158 | except Exception as err: 159 | log.error('Exception occurred initialising component. ERROR MESSAGE: {}'.format(err)) 160 | -------------------------------------------------------------------------------- /samples/gg-pubsub-sdk-component-template/src/pubsub_message_handlers/my_sensor_message_handler.py: -------------------------------------------------------------------------------- 1 | 2 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # SPDX-License-Identifier: MIT-0. 4 | 5 | ''' 6 | my_system_message_handler.py 7 | ''' 8 | 9 | __version__ = "0.0.1" 10 | __status__ = "Development" 11 | __copyright__ = "Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved." 12 | __author__ = "Dean Colcott " 13 | 14 | import logging, random 15 | 16 | # Config the logger. 17 | log = logging.getLogger(__name__) 18 | 19 | class MySensorMessageHandler(): 20 | ''' 21 | This is an example class that is developed to handle PubSub messages from the 22 | AWS Greengrass PubSub SDK. When this class is registered with the SDK, PubSub 23 | messages will be routed here when the message 'route' value matches any of the 24 | function names in this class. 25 | 26 | In this example, we provide a response to requests to polls a simulated set of 27 | temperature sensors as a means of seperating specific PubSub code functionality. 28 | 29 | i.e: 30 | 1) Register this class with the AWS Greengrass PubSub SDK 31 | 2) Any received messages with 'route' = 'my_message_handler' will route to a function here called def my_message_handler() 32 | 33 | ``` 34 | Message handling functions much be in the below format: 35 | def my_message_handler(self, protocol, topic, message_id, status, route, message): 36 | # Process message 37 | ``` 38 | 39 | The SDK will decompose the received message fields and route to valid functions here accordingly. 40 | 41 | ''' 42 | 43 | def __init__(self, publish_message, publish_error, message_formatter): 44 | ''' 45 | A common patten when receiving a PubSub message is to create and publish a response 46 | so we in this example we pass a reference to the PubSub SDK publish_message, publis_error callbacks 47 | and message_formatter object. 48 | 49 | ''' 50 | self.publish_message = publish_message 51 | self.publish_error = publish_error 52 | self.message_formatter = message_formatter 53 | 54 | ######################################################## 55 | ## Example User Defined Message handlers ############## 56 | ######################################################## 57 | 58 | def get_temp_sensor_request(self, protocol, topic, message_id, status, route, message): 59 | ''' 60 | This is an example of a user defined message handler that would expect to respond 61 | with a temperature measurement. Once this Class is registered with the SDK as a message_handler, 62 | and PubSub messages received (on any subscribed topic) with route = 'MySensorMessageHandler.get_temp_sensor_request' 63 | will be routed here. 64 | 65 | In this example, we read in parameters from the requests user defined message attribute, 66 | create a well formed 'get_temp_sensor_request' message and publish it 67 | to the protocol the request was made on (IPC or MQTT) on the default egress PubSub topic. 68 | 69 | Expected message format: 70 | message = { 71 | "sensor_id" : "Sensor1234" 72 | } 73 | 74 | Here we also publish an error message to notify remote systems of any exception. 75 | ''' 76 | 77 | try: 78 | # Log just for Dev / Debug. 79 | log.info('get_temp_sensor_request received on protocol: {} - topic: {}'.format(protocol, topic)) 80 | 81 | # Just as a demoenstration, take the sensor ID from the user defined message. 82 | # Add default value "default-sensor-1234", otherwise set to vaklue in message parameter. 83 | sensor_id = "default-sensor-1234" 84 | if hasattr(message, 'sensor_id'): 85 | sensor_id = message['sensor_id'] 86 | 87 | # Create a response message using the Greengrass PubSub SDK prefered message_formatter. 88 | # Reflect the request message_id for tracking, status and other fields as defeult. 89 | response_route = "MySensorMessageHandler.get_temp_sensor_response" 90 | msg = { 91 | "sensor_id" : sensor_id, 92 | "temp_c" : random.randint(30, 50) # Generate random temp value for example. 93 | } 94 | response = self.message_formatter.get_message(message_id=message_id, route=response_route, message=msg) 95 | 96 | # Publish the message on the protocol (IPC or MQTT) that it was received on default egress topic. 97 | self.publish_message(protocol=protocol, message=response) 98 | 99 | except Exception as err: 100 | # Publish error to default error route, will log locally as an error as well. 101 | err_msg = 'Exception in get_temp_sensor_request: {}'.format(err) 102 | self.publish_error(protocol, err_msg) 103 | -------------------------------------------------------------------------------- /samples/gg-pubsub-sdk-component-template/src/pubsub_message_handlers/my_system_message_handler.py: -------------------------------------------------------------------------------- 1 | 2 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # SPDX-License-Identifier: MIT-0. 4 | 5 | ''' 6 | my_system_message_handler.py 7 | ''' 8 | 9 | __version__ = "0.0.1" 10 | __status__ = "Development" 11 | __copyright__ = "Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved." 12 | __author__ = "Dean Colcott " 13 | 14 | import logging, platform 15 | 16 | # Config the logger. 17 | log = logging.getLogger(__name__) 18 | 19 | class MySystemMessageHandler(): 20 | ''' 21 | This is an example class that is developed to handle PubSub messages from the 22 | AWS Greengrass PubSub SDK. When this class is registered with the SDK, PubSub 23 | messages will be routed here when the message 'route' value matches any of the 24 | function names in this class. 25 | 26 | In this example, we provide a response to example System related functions 27 | that would provide information form the AWS Greengrass core device. 28 | 29 | i.e: 30 | 1) Register this class with the AWS Greengrass PubSub SDK 31 | 2) Any received messages with 'route' = 'my_message_handler' will route to a function here called def my_message_handler() 32 | 33 | ``` 34 | Message handling functions much be in the below format: 35 | def my_message_handler(self, protocol, topic, message_id, status, route, message): 36 | # Process message 37 | ``` 38 | 39 | The SDK will decompose the received message fields and route to valid functions here accordingly. 40 | 41 | ''' 42 | 43 | def __init__(self, publish_message, publish_error, message_formatter): 44 | ''' 45 | A common patten when receiving a PubSub message is to create and publish a response 46 | so we in this example we pass a reference to the PubSub SDK publish_message, publis_error callbacks 47 | and message_formatter object. 48 | 49 | ''' 50 | self.publish_message = publish_message 51 | self.publish_error = publish_error 52 | self.message_formatter = message_formatter 53 | 54 | ######################################################## 55 | ## Example User Defined Message handlers ############## 56 | ######################################################## 57 | 58 | def get_health_check_request(self, protocol, topic, message_id, status, route, message): 59 | ''' 60 | This is an example of a user defined message handler that would expect to respond 61 | with a system health status. Once this Class is registered with the SDK as a message_handler, 62 | and PubSub messages received (on any subscribed topic) with route = 'MySystemMessageHandler.health_check_request' 63 | will be routed here. 64 | 65 | In this example, we create a well formed 'health_check_response' message and publish it 66 | to the protocol the request was made on (IPC or MQTT) on the default egress PubSub topic. 67 | 68 | Here we also publish an error message to notify remote systems of any exception. 69 | ''' 70 | 71 | try: 72 | # Log just for Dev / Debug. 73 | log.info('get_health_check_request received on protocol: {} - topic: {}'.format(protocol, topic)) 74 | 75 | # Create a response message using the Greengrass PubSub SDK prefered message_formatter. 76 | # Reflect the request message_id for tracking, status and other fields as defeult. 77 | response_route = "MySystemMessageHandler.get_health_check_response" 78 | msg = {"status" : "System OK"} 79 | response = self.message_formatter.get_message(message_id=message_id, route=response_route, message=msg) 80 | 81 | # Publish the message on the protocol (IPC or MQTT) that it was received on default egress topic. 82 | self.publish_message(protocol=protocol, message=response) 83 | 84 | except Exception as err: 85 | # Publish error to default error route, will log locally as an error as well. 86 | err_msg = 'Exception in get_health_check_request: {}'.format(err) 87 | self.publish_error(protocol, err_msg) 88 | 89 | def get_system_details_request(self, protocol, topic, message_id, status, route, message): 90 | ''' 91 | Here we use the Python Platform Library to return platform (arch and OS / Distro) 92 | details. 93 | 94 | In this example, we create a well formed 'get_system_response' message and publish it 95 | to the protocol the request was made on (IPC or MQTT) on the default egress PubSub topic. 96 | ''' 97 | 98 | try: 99 | # Log just for Dev / Debug. 100 | log.info('get_system_request received on protocol: {} - topic: {}'.format(protocol, topic)) 101 | 102 | # Create a response message using the Greengrass PubSub SDK prefered message_formatter. 103 | # Reflect the request message_id for tracking, status and other fields as defeult. 104 | response_route = "get_system_response" 105 | msg = { 106 | "system" : platform.system(), 107 | "release" : platform.release(), 108 | "version" : platform.version(), 109 | "platform" : platform.platform() 110 | } 111 | response = self.message_formatter.get_message(message_id=message_id, route=response_route, message=msg) 112 | 113 | # Publish the message on the protocol (IPC or MQTT) that it was received on default egress topic. 114 | self.publish_message(protocol=protocol, message=response) 115 | 116 | except Exception as err: 117 | # Publish error to default error route, will log locally as an error as well. 118 | err_msg = 'Exception in get_system_request: {}'.format(err) 119 | self.publish_error(protocol, err_msg) 120 | -------------------------------------------------------------------------------- /samples/gg-pubsub-sdk-component-template/src/recipe.json: -------------------------------------------------------------------------------- 1 | { 2 | "RecipeFormatVersion": "2020-01-25", 3 | "ComponentName": "COMPONENT_NAME", 4 | "ComponentVersion": "0.0.1", 5 | "ComponentDescription": "AWS Greengrass PubSub SDK template component.", 6 | "ComponentPublisher": "Dean Colcott: ", 7 | "ComponentConfiguration": { 8 | "DefaultConfiguration": { 9 | "GGV2PubSubSdkConfig": { 10 | "base-pubsub-topic" : "COMPONENT_NAME", 11 | "ipc-subscribe-topics" : ["ipc/my-app/broadcast", "ipc/my-app/error"], 12 | "mqtt-subscribe-topics" : ["mqtt/my-app/broadcast", "mqtt/my-app/error"] 13 | }, 14 | "accessControl": { 15 | "aws.greengrass.ipc.pubsub": { 16 | "COMPONENT_NAME:publish:1": { 17 | "policyDescription": "Allows access to publish to the component IPC topics.", 18 | "operations": [ 19 | "aws.greengrass#PublishToTopic" 20 | ], 21 | "resources": [ 22 | "*" 23 | ] 24 | }, 25 | "COMPONENT_NAME:subscribe:1": { 26 | "policyDescription": "Allows access to subscribe to the component IPC topics.", 27 | "operations": [ 28 | "aws.greengrass#SubscribeToTopic" 29 | ], 30 | "resources": [ 31 | "*" 32 | ] 33 | } 34 | }, 35 | "aws.greengrass.ipc.mqttproxy": { 36 | "COMPONENT_NAME:publish:1": { 37 | "policyDescription": "Allows access to publish to the component MQTT topics.", 38 | "operations": [ 39 | "aws.greengrass#PublishToIoTCore" 40 | ], 41 | "resources": [ 42 | "*" 43 | ] 44 | }, 45 | "COMPONENT_NAME:subscribe:1": { 46 | "policyDescription": "Allows access to subscribe to the component MQTT topics.", 47 | "operations": [ 48 | "aws.greengrass#SubscribeToIoTCore" 49 | ], 50 | "resources": [ 51 | "*" 52 | ] 53 | } 54 | } 55 | } 56 | } 57 | }, 58 | "Manifests": [ 59 | { 60 | "Name": "Linux", 61 | "Platform": { 62 | "os": "linux" 63 | }, 64 | "Artifacts": [ 65 | { 66 | "URI": "s3://aws-greengrass-components/src.zip", 67 | "Unarchive": "ZIP" 68 | } 69 | ], 70 | "Lifecycle": { 71 | "Install" : { 72 | "Timeout" : 300, 73 | "Script" : "python3 -m pip install awsgreengrasspubsubsdk" 74 | }, 75 | "Run": { 76 | "Script": "python3 -u {artifacts:decompressedPath}/src/main.py '{configuration:/GGV2PubSubSdkConfig}'", 77 | "RequiresPrivilege": "false" 78 | } 79 | } 80 | } 81 | ] 82 | } 83 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0. 3 | 4 | import os 5 | import codecs 6 | from setuptools import setup, find_packages 7 | 8 | PROJECT_DIR = os.path.dirname(os.path.realpath(__file__)) 9 | 10 | def _load_readme(): 11 | readme_path = os.path.join(PROJECT_DIR, 'README.md') 12 | with codecs.open(readme_path, 'r', 'utf-8') as f: 13 | return f.read() 14 | 15 | setup( 16 | name='awsgreengrasspubsubsdk', 17 | version='0.1.5', 18 | description='AWS Greengrass IoT Pubsub SDK for Python', 19 | long_description=_load_readme(), 20 | long_description_content_type='text/markdown', 21 | url='https://github.com/awslabs/aws-greengrass-labs-iot-pubsub-sdk-for-python', 22 | author='Dean Colcott', 23 | author_email='dean.colcott@gmail.com', 24 | license='License :: OSI Approved :: MIT License', 25 | packages=find_packages(include=['awsgreengrasspubsubsdk*']), 26 | install_requires=['awsiotsdk'], 27 | python_requires='>=3.6', 28 | classifiers=[ 29 | "Programming Language :: Python :: 3", 30 | 'Intended Audience :: Developers', 31 | 'License :: OSI Approved :: MIT License', 32 | "Operating System :: OS Independent" 33 | ] 34 | ) 35 | --------------------------------------------------------------------------------