Skip to content
Merged
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 74 additions & 64 deletions apps/generator/docs/generator-template.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Suppose you can only sleep when the AC in your bedroom is set to 22 °C, and you

In this tutorial:

- You'll use the [Eclipse Mosquito](https://test.mosquitto.org) **MQTT broker**, which you'll connect to subscribe and publish messages using an MQTT client.
- You'll use the [Eclipse Mosquitto](https://test.mosquitto.org) **MQTT broker**, which you'll connect to subscribe and publish messages using an MQTT client.
- You'll use [Python Paho-MQTT](https://pypi.org/project/paho-mqtt/) as the **MQTT client** in this project.
- You'll create a React template that will use the MQTT broker to allow you to monitor your bedroom's temperature and notify you when the temperature drops or rises above 22 °C.
- Lastly, create a reusable component for the output code's `sendTemperatureDrop` and `sendTemperatureRise` functions.
Expand Down Expand Up @@ -44,7 +44,7 @@ Before you create the template, you'll need to have an [AsyncAPI document](https

``` yml

asyncapi: 2.6.0
asyncapi: 3.0.1

info:
title: Temperature Service
Expand All @@ -53,22 +53,26 @@ info:

servers:
dev:
url: test.mosquitto.org #in case you're using local mosquitto instance, change this value to localhost.
host: test.mosquitto.org #in case you're using local mosquitto instance, change this value to localhost.
protocol: mqtt

channels:
temperature/changed:
description: Updates the bedroom temperature in the database when the temperatures drops or goes up.
publish:
operationId: temperatureChange
message:
temperatureChanged:
address: temperature/changed
messages:
temperatureChange:
description: Message that is being sent when the temperature in the bedroom changes.
payload:
type: object
additionalProperties: false
properties:
temperatureId:
type: string
$ref: '#/components/schemas/temperatureId'
description: Updates the bedroom temperature in the database when the temperatures drops or goes up.

operations:
temperatureChange:
action: receive
summary: Message sent to the broker when the temperature is changed.
channel:
$ref: '#/channels/temperatureChanged'

components:
schemas:
temperatureId:
Expand Down Expand Up @@ -112,8 +116,8 @@ The **package.json** file is used to define the dependencies for your template.
"version": "0.0.1",
"description": "A template that generates a Python MQTT client using MQTT.",
"generator": {
"apiVersion": "v1",
"generator": ">=1.10.0 <2.0.0",
"apiVersion": "v3",
"generator": ">=2.0.0 <4.0.0",
"supportedProtocols": ["mqtt"]
},
"dependencies": {
Expand Down Expand Up @@ -326,8 +330,8 @@ In **package.json** you can have the scripts property that you invoke by calling
"test": "npm run test:clean && npm run test:generate && npm run test:start"
},
"generator": {
"apiVersion": "v1",
"generator": ">=1.10.0 <2.0.0",
"apiVersion": "v3",
"generator": ">=2.0.0 <4.0.0",
"supportedProtocols": ["mqtt"]
},
"dependencies": {
Expand Down Expand Up @@ -357,7 +361,7 @@ You often have different runtime environments in programming, e.g., development
```yml
servers:
dev:
url: test.mosquitto.org
host: test.mosquitto.org
protocol: mqtt
```

Expand Down Expand Up @@ -391,7 +395,7 @@ Update your `test:generate` script in **package.json** to include the server par
"test:generate": "asyncapi generate fromTemplate test/fixtures/asyncapi.yml ./ --output test/project --force-write --param server=dev"
```

You can now replace the static broker from `mqttBroker = 'test.mosquitto.org'` to `mqttBroker = "${asyncapi.servers().get(params.server).url()}"` in **index.js**.
You can now replace the static broker from `mqttBroker = 'test.mosquitto.org'` to `mqttBroker = "${asyncapi.servers().get(params.server).host()}"` in **index.js**.

Now the template code looks like this:

Expand All @@ -405,7 +409,7 @@ export default function ({ asyncapi, params }) {
<File name="client.py">
{`import paho.mqtt.client as mqtt

mqttBroker = "${asyncapi.servers().get(params.server).url()}"
mqttBroker = "${asyncapi.servers().get(params.server).host()}"

class TemperatureServiceClient:
def __init__(self):
Expand Down Expand Up @@ -436,7 +440,7 @@ export default function ({ asyncapi, params }) {
// 2
<Text newLines={2}>import paho.mqtt.client as mqtt</Text>
// 3
<Text newLines={2}>mqttBroker = "{asyncapi.servers().get(params.server).url()}"</Text>
<Text newLines={2}>mqttBroker = "{asyncapi.servers().get(params.server).host()}"</Text>
// 4
<Text newLines={2}>class {asyncapi.info().title().replaceAll(' ', '')}Client:</Text>
// 5
Expand Down Expand Up @@ -483,23 +487,23 @@ class TemperatureServiceClient:

```

You'll then need to template to dynamically generate `sendTemperatureDrop` and `sendTemperatureRise` functions in the generated code based off the AsyncAPI document content. The goal is to write template code that returns functions for channels that the Temperature Service application is subscribed to. The template code to generate these functions will look like this:
You'll then need a template to dynamically generate `sendTemperatureDrop` and `sendTemperatureRise` functions in the generated code based off the AsyncAPI document content. The goal is to write template code that returns functions for operations marked with `action: receive`, using their associated channels. The template code to generate these functions will look like this:

```js
<Text indent={2} newLines={2}>
<TopicFunction channels={asyncapi.channels().filterByReceive()} />
<TopicFunction operations={asyncapi.operations().filterByReceive()} />
</Text>
```

It's recommended to put reusable components outside the template directory in a new directory called components. You'll create a component that will dynamically generate functions in the output for as many channels as there are in your AsyncAPI document that contains a `publish` operation. Add the following code in the **python-mqtt-client-template/components/TopicFunction.js** file, after creating the **python-mqtt-client-template/components/** directory:
It's recommended to put reusable components outside the template directory in a new directory called components. You'll create a component that will dynamically generate functions in the output for as many operations as there are in your AsyncAPI document that are marked with `action: receive`. Add the following code in the **python-mqtt-client-template/components/TopicFunction.js** file, after creating the **python-mqtt-client-template/components/** directory:

```js
/*
* This component returns a block of functions that user can use to send messages to specific topic.
* As input it requires a list of Channel models from the parsed AsyncAPI document
* As input it requires a list of Operation models from the parsed AsyncAPI document marked with `action: receive`.
*/
export function TopicFunction({ channels }) {
const topicsDetails = getTopics(channels);
export function TopicFunction({ operations }) {
const topicsDetails = getTopics(operations);
let functions = '';

topicsDetails.forEach((t) => {
Expand All @@ -518,25 +522,28 @@ export function TopicFunction({ channels }) {
*
* As input it requires a list of Channel models from the parsed AsyncAPI document
*/
function getTopics(channels) {
const channelsCanSendTo = channels;
function getTopics(operations) {
let topicsDetails = [];

channelsCanSendTo.forEach((ch) => {
const topic = {};
const operationId = ch.operations().filterByReceive()[0].id();
topic.name = operationId.charAt(0).toUpperCase() + operationId.slice(1);
topic.topic = ch.address();

topicsDetails.push(topic);
})

operations.forEach((op) => {
const channels = op.channels().all();
if (!channels.length) return;

const channel = channels[0];
const operationId = op.operationId() || op.id();

topicsDetails.push({
name: operationId.charAt(0).toUpperCase() + operationId.slice(1),
topic: channel.address()
});
});

return topicsDetails;
}
```

`{ channels }`: the `TopicFunction` component accepts a custom prop called channels and in your template code
`getTopics(channels)`: Returns a list of objects, one for each channel with two properties; name and topic. The **name** holds information about the `operationId` provided in the AsyncAPI document while the **topic** holds information about the address of the topic.
`{ operations }`: the `TopicFunction` component accepts a custom prop called operations and in your template code
`getTopics(operations)`: Returns a list of objects, one for each operation with two properties; name and topic. The **name** holds information about the `operationId` provided in the AsyncAPI document (or a generated ID if operationId is not set) while the **topic** holds information about the address of the topic from the operation's associated channel.

Import the `TopicFunction` component in your template code in **index.js** and add the template code to generate the functions to topics that the `Temperature Service` application is subscribed to. In your case, the final version of your template code should look like this:

Expand All @@ -549,7 +556,7 @@ export default function ({ asyncapi, params }) {
<File name="client.py">
<Text newLines={2}>import paho.mqtt.client as mqtt</Text>

<Text newLines={2}>mqttBroker = "{asyncapi.servers().get(params.server).url()}"</Text>
<Text newLines={2}>mqttBroker = "{asyncapi.servers().get(params.server).host()}"</Text>

<Text newLines={2}>class {asyncapi.info().title().replaceAll(' ', '')}Client:</Text>

Expand All @@ -560,7 +567,7 @@ export default function ({ asyncapi, params }) {
</Text>

<Text indent={2} newLines={2}>
<TopicFunction channels={asyncapi.channels().filterByReceive()} />
<TopicFunction operations={asyncapi.operations().filterByReceive()} />
</Text>
</File>
)
Expand All @@ -587,39 +594,42 @@ python-mqtt-client-template

Run `npm test` on your terminal to ensure everything works as expected.

In the next section, you'll add another channel to **asyncapi.yml** file called `temperature/dropped` and `temperature/risen` then run the template again to make sure it still works as expected.
In the next section, you'll add another channel to **asyncapi.yml** file called `temperatureDropped` and `temperatureRisen` then run the template again to make sure it still works as expected.

#### 5d. Update AsyncAPI document

Update the AsyncAPI document to use two channels:

```yml
channels:
temperature/dropped:
description: Notifies the user when the temperature drops past a certain point.
publish:
operationId: temperatureDrop
message:
temperatureDropped:
address: temperature/dropped
messages:
temperatureDrop:
description: Message that is being sent when the temperature drops past a certain point.
payload:
type: object
additionalProperties: false
properties:
temperatureId:
type: string

temperature/risen:
description: Notifies the user when the temperature rises past a certain point.
publish:
operationId: temperatureRise
message:
$ref: '#/components/schemas/temperatureId'
description: Notifies the user when the temperature drops past a certain point.
temperatureRisen:
address: temperature/risen
messages:
temperatureRise:
description: Message that is being sent when the temperature rises past a certain point.
payload:
type: object
additionalProperties: false
properties:
temperatureId:
type: string
$ref: '#/components/schemas/temperatureId'
description: Notifies the user when the temperature rises past a certain point.

operations:
temperatureDrop:
action: receive
summary: Message sent to the broker when the temperature is dropped.
channel:
$ref: '#/channels/temperatureDropped'
temperatureRise:
action: receive
summary: Message sent to the broker when the temperature is risen.
channel:
$ref: '#/channels/temperatureRisen'
```

And update your test script in test.py to test the two functions as below:
Expand Down