Skip to content

Commit

Permalink
Implement MQTTClient.jl Extension (#9)
Browse files Browse the repository at this point in the history
* Implement MQTTClient.jl Extension

+ update code formatting
+ add/update docs
+ add tests
+ get MQTTClient working

* update documentation

* fix tests and MQTTClient version

* fix documentation error

* fix tests for mqtt client on windows

* Format files using JuliaFormatter (#10)
  • Loading branch information
NickMcSweeney authored May 31, 2024
1 parent 5728eb2 commit 9aa452a
Show file tree
Hide file tree
Showing 14 changed files with 384 additions and 108 deletions.
2 changes: 2 additions & 0 deletions .JuliaFormatter.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
style = "blue"

margin = 120
remove_extra_newlines = true
format_docstrings = true
6 changes: 4 additions & 2 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ jobs:
fail-fast: false
matrix:
version:
- '1.9'
- 'nightly'
- '1.9' # minimum version
- '1'
os:
- ubuntu-latest
- macOS-latest
- windows-latest
arch:
- x64
steps:
Expand Down
13 changes: 11 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "MQTT"
uuid = "ebefff21-3b8f-497c-a71a-d17835ab79ba"
authors = ["Nicholas Shindler <[email protected]> and contributors"]
version = "0.0.2-DEV"
version = "0.1.0"

[deps]

Expand All @@ -14,10 +14,19 @@ AWSCRTExt = "AWSCRT"
MQTTClientExt = "MQTTClient"

[compat]
AWSCRT = "^0.3"
Aqua = "^0.8"
MQTTClient = "^0.3.1"
Test = "^0"
Sockets = "^0"
julia = "1.9"

[extras]
AWSCRT = "df31ea59-17a4-4ebd-9d69-4f45266dc2c7"
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
MQTTClient = "985f35cc-2c3d-4943-b8c1-f0931d5f0959"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Sockets = "6462fe0b-24de-5631-8697-dd941f90decc"

[targets]
test = ["Test"]
test = ["Aqua", "AWSCRT", "MQTTClient", "Test", "Sockets"]
49 changes: 42 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,58 @@
[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://JuliaMessaging.github.io/MQTT.jl/dev/)
[![Build Status](https://github.com/JuliaMessaging/MQTT.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/JuliaMessaging/MQTT.jl/actions/workflows/CI.yml?query=branch%3Amain)
[![Coverage](https://codecov.io/gh/JuliaMessaging/MQTT.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/JuliaMessaging/MQTT.jl)
[![Coverage](https://coveralls.io/repos/github/JuliaMessaging/MQTT.jl/badge.svg?branch=main)](https://coveralls.io/github/JuliaMessaging/MQTT.jl?branch=main)
[![Code Style: Blue](https://img.shields.io/badge/code%20style-blue-4495d1.svg)](https://github.com/invenia/BlueStyle)
[![PkgEval](https://JuliaCI.github.io/NanosoldierReports/pkgeval_badges/M/MQTT.svg)](https://JuliaCI.github.io/NanosoldierReports/pkgeval_badges/report.html)
[![ColPrac: Contributor's Guide on Collaborative Practices for Community Packages](https://img.shields.io/badge/ColPrac-Contributor's%20Guide-blueviolet)](https://github.com/SciML/ColPrac)

This is an interface package for MQTT Messaging. See the [documentation](https://JuliaMessaging.github.io/MQTT.jl) for more information.

#### This is an interface package, it contains no functionality by itself.
You will need to use a MQTT backend to connect to a broker. This Package is intended to make it easier for developers to integrate with MQTT without locking themselves into the syntax of a specific MQTT Backend.

## Example Useage

### AWSCRT.jl

```julia
using AWSCRT
using MQTT

mqttconnection = ...
```

### MQTTClient.jl

```julia
using MQTTClient
using MQTT

broker = "192.168.1.100" # address to mqtt broker
port = 1883

mqttconnection = MQTT.MQTTConnection(MQTTClient.MakeConnection(broker, port))
connect!(mqttconnection)

subscribe!(mqttconnection, "foo/test", EXACTLY_ONCE) do (topic, payload)
println("MQTT[$topic]: $payload")
end

publish!(mqttconnection, "foo/test", "bar", EXACTLY_ONCE)

unsubscribe!(mqttconnection, "foo/test")

disconnect!(mqttconnection)
```

## Contributing

If you would like to contribute to the project, please submit a PR. All contributions are welcomed and appreciated.

If there is an MQTT package that you would like to see included please submit an issue. Or even better create the extension for it and submit a PR!

### TODO

- [ ] add tests
- [x] add tests
- [ ] check if other clients can integrate
- [ ] register in Julia General
- [ ] general doctrings for interface functions

## Acknowledgements

We thank [MapXact](https://mapxact.com/) for sponsoring and supporting the work on this package.
- [x] general doctrings for interface functions
4 changes: 1 addition & 3 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,4 @@ makedocs(;
],
)

deploydocs(;
repo="github.com/JuliaMessaging/MQTT.jl",
)
deploydocs(; repo="github.com/JuliaMessaging/MQTT.jl")
12 changes: 6 additions & 6 deletions docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
```

```@docs
AbstractConnection
QOS
connect
MQTTConnection
connect_async!
connect!
subscribe
subscribe_async!
subscribe!
publish
publish_async!
publish!
unsubscribe
unsubscribe_async!
unsubscribe!
disconnect
disconnect_async!
disconnect!
```
6 changes: 3 additions & 3 deletions docs/src/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## How it works

MQTT.jl uses the (new) Weak Deps features to allow multiple MQTT packages to be added as weak dependancies.
MQTT.jl uses the Weak Deps features to allow multiple MQTT packages to be added as weak dependancies.

Each MQTT client implimentation needs to have a extension added in the `ext` directory (and registered in the `Project.toml`).

Expand All @@ -22,6 +22,6 @@ Aditionally the following utility function(s) need to be implemented:

* `_resolve(f)`: wrapper function for fetching a async result.

Most importantly the connection struct needs to be created.
Most importantly the connection struct needs to be created with the standard constructor function.

* `MQTTConnection` this needs to be a subtype of `AbstractConnection` and contain all information specific to the MQTT Client that is being used.
* `MQTTConnection` needs to to construct your struct, that is a subtype of `AbstractConnection`. The struct must contain all information specific to the MQTT Client that is being used.
54 changes: 33 additions & 21 deletions docs/src/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,19 @@ using MQTT, MQTTClient
MQTT provides a `MQTTConnection` object for each backend, this struct is passed to the other included functions.

```julia
client = MQTTClient.Client()
connection = MQTTClient.Connection()
client, connection = MQTTClient.MakeConnection(...)

mqtt_connection = MQTTConnection(client, connection)
```

Advanced Usage
--------------
### Basic example
Refer to the corresponding method documentation to find more options. Refer to the MQTT Client documentation for specifics about the client.

## Getting started
To use this library you need to follow at least these steps:
1. Define any client data structures needed for a given backend.
2. Create an instance of the `MQTTConnection` struct passing the backend specific information.
3. Call the connect method with your `Client` instance.
4. Exchange data with the broker through publish, subscribe and unsubscribe. When subscribing, pass your callback function for that topic.
5. Disconnect from the broker. (Not strictly necessary, if you don't want to resume the session but considered good form and less likely to crash).
#### AWSCRT.jl Example

#### Basic example
Refer to the corresponding method documentation to find more options. Refer to the MQTT Client documentation for specifics about the client.
TODO

#### MQTTClient.jl Example

```julia
using MQTT, MQTTClient
Expand All @@ -51,22 +45,40 @@ function on_msg(topic, payload)
end

# Instantiate a client.
client = MQTTClient.Client()
connection = MQTTClient.Connection()
mqtt_connection = MQTTConnection(client, connection)
mqttconnection = MQTTConnection(MQTTClient.MakeConnection(broker, 1883))

connect!(mqtt_connection)
# connect to the broker
connect!(mqttconnection)

# Subscribe to the topic we will publish to.
subscribe!(mqtt_connection, "jlExample", on_msg, qos=AT_LEAST_ONCE))
subscribe!(on_msg, mqttconnection, "foo/test", EXACTLY_ONCE)

publish!(mqtt_connection, "jlExample", "Hello World!")
# Publish some data to the topic, you should see this prionted by the on_msg function
publish!(mqttconnection, "foo/test", "bar", EXACTLY_ONCE)

# Unsubscribe from the topic
unsubscribe!(mqtt_connection, "jlExample")
unsubscribe!(mqttconnection, "foo/test")

# Disconnect from the broker. Not strictly needed as the broker will also
# disconnect us if the socket is closed. But this is considered good form
# and needed if you want to resume this session later.
disconnect!(mqttconnection)

# Unsubscribe from the topic
unsubscribe!(mqtt_connection, "jlExample")


disconnect!(mqtt_connection)
```
```

Developer Usage
--------------

## Adding a new backend
To use a new MQTT backend with MQTT.jl you need to follow at least these steps:
1. Create a `MyMQTTClientExt.jl` in `ext/`
2. Define the 6 internal functions `_resolve`, `_connect`, `_subscribe`, `_unsubscribe`, `_publish`, `_disconnect` for your package.
3. Define a struct that is a subtype of `AbstractConnection` and extend the `MQTTConnection` to construct your struct. This struct should contain all the information for making connections, publishing etc.
4. Add some documentation for how to use your package.
5. Add some tests (optional).

38 changes: 21 additions & 17 deletions ext/AWSCRTExt.jl
Original file line number Diff line number Diff line change
@@ -1,49 +1,53 @@
module AWSCRTExt

import AWSCRT, MQTT
using AWSCRT: AWSCRT
using MQTT: MQTT

struct MQTT.MQTTConnection <: MQTT.AbstractConnection
struct AWSCRTConfig <: MQTT.AbstractConnection
connection::AWSCRT.MQTTConnection
endpoint::String
port::Int
id::String
connect_kwargs::Dict{Symbol,Any}
end

MQTT.MQTTConnection(connection, endpoint, port, id; connect_kwargs = Dict()) =
MQTT.MQTTConnection(connection, endpoint, port, id, connect_kwargs)
function MQTT.MQTTConnection(connection, endpoint, port, id; connect_kwargs=Dict())
return AWSCRTConfig(connection, endpoint, port, id, connect_kwargs)
end

function MQTT._resolve(async_object)
fetch(async_object)
return fetch(async_object)
end

function MQTT._connect(c::MQTT.MQTTConnection)
function MQTT._connect(c::AWSCRTConfig)
return AWSCRT.connect(c.connection, c.endpoint, c.port, c.id; c.connect_kwargs...)
end

function MQTT._subscribe(callback, c::MQTT.MQTTConnection, topic, qos)
task, id = AWSCRT.subscribe(c.connection, topic, qos = AWSCRT.aws_mqtt_qos(Int(qos)), _adapt_on_message(callback))
function MQTT._subscribe(callback::AWSCRT.OnMessage, c::AWSCRTConfig, topic, qos)
task, id = AWSCRT.subscribe(c.connection, topic; qos=AWSCRT.aws_mqtt_qos(Int(qos)), callback)
return task
end

function MQTT._publish(c::MQTT.MQTTConnection, topic, payload, qos, retain)
task, id = AWSCRT.publish(c.connection, topic, payload, qos = AWSCRT.aws_mqtt_qos(Int(qos)), retain = retain)
function MQTT._publish(c::AWSCRTConfig, topic, payload, qos, retain)
task, id = AWSCRT.publish(c.connection, topic, payload; qos=AWSCRT.aws_mqtt_qos(Int(qos)), retain=retain)
return task
end

function MQTT._unsubscribe(c::MQTT.MQTTConnection, topic)
function MQTT._unsubscribe(c::AWSCRTConfig, topic)
task, id = AWSCRT.unsubscribe(c.connection, topic)
return task
end

function MQTT._disconnect(c::MQTT.MQTTConnection)
function MQTT._disconnect(c::AWSCRTConfig)
return AWSCRT.disconnect(c.connection)
end

function _adapt_on_message(cb::MQTT.OnMessage)
return function _awscrt_on_message(topic, payload, dup, qos, retain)
return cb(topic, payload)
end
end
# function _adapt_on_message(cb::MQTT.OnMessage)
# return function essage(topic, payload, dup, qos, retain)
# return cb(topic, payload)
# end
# end

MQTT.OnMessage = AWSCRT.OnMessage

end # module
33 changes: 19 additions & 14 deletions ext/MQTTClientExt.jl
Original file line number Diff line number Diff line change
@@ -1,34 +1,39 @@
module MQTTClientExt

using MQTTClient, MQTT
using MQTTClient: MQTTClient
using MQTT: MQTT

struct MQTT.MQTTConnection <: MQTT.AbstractConnection
struct MQTTClientConfig <: MQTT.AbstractConnection
client::MQTTClient.Client
connection::MQTTClient.MQTTConnection
connection::MQTTClient.Connection
end
function MQTT.MQTTConnection(configuration::MQTTClient.Configuration)
return MQTTClientConfig(configuration.client, configuration.connection)
end
MQTT.MQTTConnection(client::MQTTClient.Client, connection::MQTTClient.Connection) = MQTTClientConfig(client, connection)

function MQTT._resolve(async_object)
MQTTClient.resolve(async_object)
return MQTTClient.resolve(async_object)
end

function MQTT._connect(c::MQTT.MQTTConnection)
MQTTClient.connect_asyc(c.client, c.connection)
function MQTT._connect(c::MQTTClientConfig)
return MQTTClient.connect_async(c.client, c.connection)
end

function MQTT._subscribe(callback, c::MQTT.MQTTConnection, topic, qos::MQTT.QOS)
MQTTClient.subscribe_async(c.client, topic, on_msg, qos = MQTTClient.QOS(UInt8(qos)))
function MQTT._subscribe(callback::MQTT.OnMessage, c::MQTTClientConfig, topic::AbstractString, qos::MQTT.QOS)
return MQTTClient.subscribe_async(c.client, topic, callback; qos=MQTTClient.QOS(UInt8(qos)))
end

function MQTT._publish(c::MQTT.MQTTConnection, topic, payload, qos::MQTT.QOS, retain)
publish_async(c.client, topic, payload, qos = MQTTClient.QOS(UInt8(qos)), retain = retain)
function MQTT._publish(c::MQTTClientConfig, topic::AbstractString, payload, qos::MQTT.QOS, retain)
return MQTTClient.publish_async(c.client, topic, payload; qos=MQTTClient.QOS(UInt8(qos)), retain=retain)
end

function MQTT._unsubscribe(c::MQTT.MQTTConnection, topic)
unsubscribe_async(c.client, topic)
function MQTT._unsubscribe(c::MQTTClientConfig, topic::AbstractString)
return MQTTClient.unsubscribe_async(c.client, topic)
end

function MQTT._disconnect(c::MQTT.MQTTConnection)
disconnect(c.client)
function MQTT._disconnect(c::MQTTClientConfig)
return MQTTClient.disconnect(c.client)
end

end # module
Loading

4 comments on commit 9aa452a

@NickMcSweeney
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator register

Release notes:

First version.

  • there ARE tests
  • there IS documentation
  • there ARE functional MQTT Backends

what more can you ask for.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error while trying to register: Register Failed
@NickMcSweeney, it looks like you are not a publicly listed member/owner in the parent organization (JuliaMessaging).
If you are a member/owner, you will need to change your membership to public. See GitHub Help

@NickMcSweeney
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator register

Release notes:

First version.

  • there ARE tests
  • there IS documentation
  • there ARE functional MQTT Backends

what more can you ask for.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/108037

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.1.0 -m "<description of version>" 9aa452a3c0fb9a04e04973c28015822f104d6b81
git push origin v0.1.0

Please sign in to comment.