Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
7 changes: 7 additions & 0 deletions Dockerfile_Publisher
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM python:3.6.9

COPY ./MassRobotics-AMR-MQTT-Client /app
WORKDIR /app
RUN pip install paho-mqtt

ENTRYPOINT python publisher.py mqtt
98 changes: 98 additions & 0 deletions MassRobotics-AMR-MQTT-Client/publisher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import asyncio

import paho.mqtt.client as mqtt
import hashlib
import uuid
from datetime import datetime, timezone, timedelta
import json
import random
import math
import time
import sys

OPERATIONAL_STATES = ["navigating", "idle", "disabled", "offline", "charging",
"waitingHumanEvent", "waitingExternalEvent", "waitingInternalEvent", "manualOverride"]

async def sendMessage(host, port, keepalive):
mqtt_client = mqtt.Client()
mqtt_client.connect(host, port, keepalive)
mqtt_client.loop_start()

# These could come from a configuration file or environment variables or any other source, but they are assumed not to change!
identity = {"manufacturerName": "Mass Robotics AMR", "robotModel": "AMR-01", "robotSerialNumber": "0000001", "baseRobotEnvelope": {"x": 0.5, "y": 1}}

# Generate a uuid that will be consistent for this robot
m = hashlib.md5()
seed = identity["manufacturerName"] + identity["robotSerialNumber"]
m.update(seed.encode('utf-8'))
uid = uuid.UUID(m.hexdigest())
identity["uuid"] = str(uid)

# Attach a timestamp
identity["timestamp"] = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S%z")

mqtt_client.publish("identityReport", json.dumps(identity))

status = {"uuid": str(uid)}
# status = {}
while True:
nowDt = datetime.now(timezone.utc)
now = nowDt.timestamp()
status["timestamp"] = nowDt.strftime("%Y-%m-%dT%H:%M:%S%z")

# The following information would need to be pulled out in a vendor-specific way,
# for this example it is just randomly generated

# Get the current operational state and put it in the status message
status["operationalState"] = random.choice(OPERATIONAL_STATES)

# Generate a battery percentage that goes between 20 and 100
status["batteryPercentage"] = 20 + now % 81

# Pick a random velocity
status["velocity"] = random.choice([
# deviating 10 degrees to the left
{ "linear": 3, "angular": { "x": 0., "y": 0., "z": 0.087, "w": 0.996 } },
# deviating 15 degrees to the right
{ "linear": 1, "angular": { "x": 0., "y": 0., "z": -0.131, "w": 0.991 } }
])

# Generate a random location, simulating that the AMR moves in circles
r = 20
angle = random.choice([
{ "x": 0., "y": 0., "z": 0.087, "w": 0.996 },
{ "x": 0., "y": 0., "z": -0.131, "w": 0.991 }
])
status["location"] = {"x": math.cos(now) * r, "y": math.sin(now) * r, "z": 0, "angle": angle, "planarDatum": "4B8302DA-21AD-401F-AF45-1DFD956B80B5"}

# Generate random paths and destinations
side = random.choice([10, 20, 30])
status["path"] = [
{"timestamp": (nowDt+timedelta(seconds=10)).strftime("%Y-%m-%dT%H:%M:%S%z"), "x": 0, "y": 0, "angle": { "x": 0., "y": 0., "z": 0., "w": 1 }, "planarDatumUUID": "4B8302DA-21AD-401F-AF45-1DFD956B80B5"},
{"timestamp": (nowDt+timedelta(seconds=20)).strftime("%Y-%m-%dT%H:%M:%S%z"), "x": 0, "y": side, "angle": { "x": 0., "y": 0., "z": 0., "w": 1 }, "planarDatumUUID": "4B8302DA-21AD-401F-AF45-1DFD956B80B5"},
{"timestamp": (nowDt+timedelta(seconds=30)).strftime("%Y-%m-%dT%H:%M:%S%z"), "x": side, "y": side, "angle": { "x": 0., "y": 0., "z": 0., "w": 1 }, "planarDatumUUID": "4B8302DA-21AD-401F-AF45-1DFD956B80B5"},
{"timestamp": (nowDt+timedelta(seconds=40)).strftime("%Y-%m-%dT%H:%M:%S%z"), "x": side, "y": 0, "angle": { "x": 0., "y": 0., "z": 0., "w": 1 }, "planarDatumUUID": "4B8302DA-21AD-401F-AF45-1DFD956B80B5"},
{"timestamp": (nowDt+timedelta(seconds=50)).strftime("%Y-%m-%dT%H:%M:%S%z"), "x": 0, "y": 0, "angle": { "x": 0., "y": 0., "z": 0., "w": 1 }, "planarDatumUUID": "4B8302DA-21AD-401F-AF45-1DFD956B80B5"}
]
status["destinations"] = [
{"timestamp": (nowDt+timedelta(seconds=30)).strftime("%Y-%m-%dT%H:%M:%S%z"), "x": side, "y": side, "angle": { "x": 0., "y": 0., "z": 0., "w": 1 }, "planarDatumUUID": "4B8302DA-21AD-401F-AF45-1DFD956B80B5"},
{"timestamp": (nowDt+timedelta(seconds=50)).strftime("%Y-%m-%dT%H:%M:%S%z"), "x": 0, "y": 0, "angle": { "x": 0., "y": 0., "z": 0., "w": 1 }, "planarDatumUUID": "4B8302DA-21AD-401F-AF45-1DFD956B80B5"}
]

# Publish the report message
mqtt_client.publish("statusReport", json.dumps(status))
# mqtt_client.publish("identityReport", json.dumps(status))

# Wait for 1 second
time.sleep(1)

if __name__ == '__main__':
host = "localhost"
if len(sys.argv) > 1:
host = sys.argv[1]
port = 1883
keepalive = 60
asyncio.get_event_loop().run_until_complete(sendMessage(host, port, keepalive))
31 changes: 31 additions & 0 deletions MassRobotics-AMR-MQTT-Client/subscriber.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import paho.mqtt.client as mqtt
import json
from jsonschema import validate, ValidationError

host = "localhost"
port = 1883
keepalive = 60

with open('../AMR_Interop_Standard.json') as file_obj:
json_schema = json.load(file_obj)

def on_message(mqttc, obj, msg):
print(msg.topic)
json_dict = json.loads(msg.payload)
try:
validate(json_dict, json_schema)
print(json_dict)
except ValidationError as e:
print(e.message)

mqtt_client = mqtt.Client()
mqtt_client.on_message = on_message
mqtt_client.connect(host, port, keepalive)

mqtt_client.subscribe("identityReport")
mqtt_client.subscribe("statusReport")

mqtt_client.loop_forever()
5 changes: 3 additions & 2 deletions MassRobotics-AMR-Receiver/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "MassRobotics-AMR-Receiver",
"version": "0.1.0",
"description": "Application to validate the schema of websocket messages",
"version": "0.2.0",
"description": "Application to validate the schema of websocket messages and mqtt messages",
"scripts": {
"install-all": "npm install; gulp build;",
"install-server": "npm install --only=prod",
Expand Down Expand Up @@ -48,6 +48,7 @@
"body-parser": "1.18.0",
"express": "4.15.4",
"express-ws": "4.0.0",
"mqtt": "4.2.8",
"underscore": "1.12.0"
}
}
25 changes: 24 additions & 1 deletion MassRobotics-AMR-Receiver/server/services/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ const Ajv = require("ajv");
const ajv = new Ajv({strictSchema: false});
let validate = ajv.compile(schema);
let UIsockets = {};
const mqtt = require('mqtt');
var mqtt_client;

module.exports = function(app) {
module.exports = function(app, mqtt_broker = "mosquitto") {
app.use(bodyParser.json());

app.ws('/ui', function(ws, req) {
Expand Down Expand Up @@ -43,6 +45,25 @@ module.exports = function(app) {
app.get('/test-message', function(req, res) {
res.send(testMessage);
});

mqtt_client = mqtt.connect('mqtt://' + mqtt_broker);
mqtt_client.on('connect', function() {
mqtt_client.subscribe('identityReport', function (err) {
if (err) {
console.log("Error: identityReport subscribe");
}
});
mqtt_client.subscribe('statusReport', function (err) {
if (err) {
console.log("Error: statusReport subscribe");
}
});
});

mqtt_client.on('message', function (topic, message) {
console.log("subscribe : " + topic);
processMessage(message);
})
};

function processMessage(msg) {
Expand Down Expand Up @@ -75,6 +96,8 @@ function processMessage(msg) {
errors: errors
});
});
// Issuing mqtt protocol is not necessary,
// because it is publish/subscribe type.
}

function sendMessage(ws, data = {}) {
Expand Down
17 changes: 17 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ services:
context: .
dockerfile: Dockerfile_Receiver
container_name: receiver
depends_on:
- "mqtt"
ports:
- "3000:3000"
volumes:
Expand All @@ -21,5 +23,20 @@ services:
depends_on:
- "receiver"
tty: true
mqtt:
build: ./mosquitto
hostname: mosquitto
container_name: mqtt
ports:
- "1883:1883"
publisher:
image: publisher
build:
context: .
dockerfile: Dockerfile_Publisher
container_name: publisher
depends_on:
- "receiver"
tty: true
volumes:
node_modules:
3 changes: 3 additions & 0 deletions mosquitto/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM eclipse-mosquitto

COPY ./mosquitto.conf /mosquitto/config
6 changes: 6 additions & 0 deletions mosquitto/mosquitto.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
allow_anonymous true

persistence true
persistence_location /mosquitto/data/

listener 1883