initial commit

This commit is contained in:
OddlyTimbot 2024-08-06 11:33:18 -04:00
commit 819d8e6ea6
16 changed files with 1595 additions and 0 deletions

17
README.md Normal file
View File

@ -0,0 +1,17 @@
In this workshop, we will learn how to use MQTT for connected devices.
* Install Mosquitto Broker and Client
* Adjust ports/firewalls
On the client side, we will learn to install and use an MQTT client on diverse devices.
* Install MicroPython client on RP2040
* Install GDScript client on Godot Game Engine
## Resources
* Mosquitto man pages https://mosquitto.org/man/
* Godot MQTT client https://github.com/goatchurchprime/godot-mqtt
* Install broker/client on Linux http://www.steves-internet-guide.com/install-mosquitto-linux/
* Core Electronics RP2040 guide https://core-electronics.com.au/guides/getting-started-with-mqtt-on-raspberry-pi-pico-w-connect-to-the-internet-of-things/
* RP2040 client https://github.com/micropython/micropython-lib/tree/master/micropython/umqtt.simple

View File

@ -0,0 +1,68 @@
extends Control
var brokerurl = "wss://test.mosquitto.org:8081"
var possibleusernames = ["Alice", "Beth", "Cath", "Dan", "Earl", "Fred", "George", "Harry", "Ivan",
"John", "Kevin", "Larry", "Martin", "Oliver", "Peter", "Quentin", "Robert",
"Samuel", "Thomas", "Ulrik", "Victor", "Wayne", "Xavier", "Youngs", "Zephir"]
var fetchingscores = true
var highscores = [ ]
var regexscores = RegEx.new()
func _ready():
randomize()
$VBox/HBoxPlayername/LineEdit.text = possibleusernames[randi()%len(possibleusernames)]
$VBox/HBoxHighScore/LineEdit.text = "%d" % (randi()%1000)
regexscores.compile("^\\w+/(\\w+)/score")
#$MQTT.verbose_level = 0
_on_fetch_scores_pressed()
func _on_mqtt_broker_connected():
# this quality of service means we get an acknowledgement message back from the broker
# which we can use to disconnect from the broker when the work is done
var qos = 1
if fetchingscores:
$MQTT.subscribe("%s/+/score" % $VBox/HBoxGamename/LineEdit.text)
# this follow-on message is acknowledged after all subscribed retained messages have arrived
$MQTT.publish("%s" % $VBox/HBoxGamename/LineEdit.text, "acknowledgemessage", false, qos)
else:
var topic = "%s/%s/score" % [$VBox/HBoxGamename/LineEdit.text, $VBox/HBoxPlayername/LineEdit.text]
$MQTT.publish(topic, $VBox/HBoxHighScore/LineEdit.text, true, qos)
func _on_mqtt_broker_connection_failed():
print("Connection failed")
func _on_send_score_pressed():
fetchingscores = false
$MQTT.connect_to_broker(brokerurl)
func _on_fetch_scores_pressed():
fetchingscores = true
highscores = [ ]
$MQTT.connect_to_broker(brokerurl)
func _on_mqtt_broker_disconnected():
print("disconnected")
func _on_mqtt_publish_acknowledge(pid):
print("Publish message acknowledged ", pid)
$MQTT.disconnect_from_server()
if fetchingscores:
highscores.sort()
highscores.reverse()
$VBox/HBoxTopscores/RichTextLabel.clear()
$VBox/HBoxTopscores/RichTextLabel.append_text("[u]Top ten scores[/u]\n")
while len(highscores) < 10:
highscores.append([0, " --- "])
for i in range(10):
$VBox/HBoxTopscores/RichTextLabel.append_text("%d. [b]%s:[/b] %d\n" % [i+1, highscores[i][1], highscores[i][0]])
print(highscores)
func _on_mqtt_received_message(topic, message):
var rem = regexscores.search(topic)
if rem:
var playername = rem.get_string(1)
highscores.append([int(message), playername])

21
godot_client/LICENSE.md Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Julian Todd
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

154
godot_client/README.md Normal file
View File

@ -0,0 +1,154 @@
# MQTT
Godot 4 addon - to implement MQTT
## Installation
Clone the repo and copy the `addons/mqtt` folder into your project's `addons` folder
(or use the AssetLib to install when
## Applications
**MQTT** is probably the simplest known networking protocol you can use
to communicate between systems, which is why it is often used by
low power IoT (Internet of Things) devices.
In spite of its simplicity, MQTT is extremely flexible and has the
advantage of being standardized enough that you don't need to write
your own server-side software. You can often get by with "borrowing " the use of a public server on the internet (see [test.mosquitto.org](https://test.mosquitto.org/)).
Uses for MQTT in Godot are chat rooms, receiving and sending messages to physical devices, high score tables, remote monitoring of runtime metrics (eg framerate) of an alpha release for quality control, and as a
[signalling server](https://docs.godotengine.org/en/stable/tutorials/networking/webrtc.html#id1)
for the powerful WebRTC peer-to-peer networking protocol.
Here is a [talk](https://www.youtube.com/watch?v=en9fMP4g9y8) from
GodotCon 2021 that included demo of controlling a wheeled robot from
the Dodge the Creeps game.
[![image](https://github.com/goatchurchprime/godot-mqtt/assets/677254/593b8a53-801e-480f-9812-cfc4e9ae360b)](https://www.youtube.com/watch?v=en9fMP4g9y8)
The **mqttexample** project in this repo is good for
experimenting and exploring the features.
![image](https://github.com/goatchurchprime/godot-mqtt/assets/677254/264473c6-6ad1-4a87-8bb5-49fd28789bed)
There's also an even simpler **HighScoreExample** scene that demonstrates how to implement a list of
global high scores in a game.
## The protocol
The easiest way to understand this protocol (without using Godot or this plugin) is to [install mosquitto](https://mosquitto.org/download/),
and open two command line consoles on your computer.
Run the following command in the first console:
* `mosquitto_sub -v -h test.mosquitto.org -t "godot/#"`
and then run this other command in the second console:
* `mosquitto_pub -h test.mosquitto.org -t "godot/abcd" -m "Bingo!"`
The first console should have now printed:
* `godot/abcd Bingo!`
*What's going on here?*
The first command connected to the broker `test.mosquitto.org`
and subscribed to the topic `"godot/#"` where the `'#'` is a wild-card
that matches the remained of the topic string.
The second command publishes the message `"Bingo!"` to the topic `"godot/abcd"`
on the broker at the address `test.mosquitto.org`, which is picked up by the first
command since it matches the pattern.
Now you understand the basics, you can do the same thing on a webpage
version of the mqtt client at [hivemq.com](https://www.hivemq.com/demos/websocket-client/).
(The ClientID is a unique identifier for the client, often randomly generated.)
### Advanced features
To make this protocol insanely useful, there are two further features:
When the **`retain`** flag is set to true, the broker not only sends the message to
all the subscribers, it also keeps a copy and sends it to any new subscriber that matches the topic.
This allows for persistent data (eg high scores) to be recorded and updated on the
broker. To delete a retained message, publish an empty message to its topic.
When a new connection is made to the broker, a **`lastwill`** topic and message can optionally be included.
This message will be published automatically on disconnect. The lastwill message
can also have its `retain` flag set.
These two features can be combined to provide a connection status feature for a player instance
by first executing:
* `set_last_will( topic="playerstates/myplayerid", msg="disconnected", retain=true )`
before connecting to the broker.
Then the first message you send after successfully connecting to the broker is:
* `publish( topic="playerstates/myplayerid", msg="readytoplay", retain=true )`
This persistent message will be automatically over-written at disconnect.
At any time when a player elsewhere connects to this broker and subscribes
to the topic `"playerstates/+"` the the messages returned will
correctly give their online statuses.
There is final setting in the publish fuction, **`qos`** for "Quality of Service".
This tells the broker whether you want an acknowledgement that the message
has gotten through to it (qos=1) as well as enforce confirmation
and de-duplication feature (qos=2). This has not been fully implemented in this
library.
## Usage
* `$MQTT.set_last_will(topic, msg, retain=false, qos=0)`
Must be set before connection is made.
* `$MQTT.connect_to_broker(brokerurl)`
This URL should contain protocol and port, eg
`"tcp://test.mosquitto.org:1883/"` for the basic default setting
or for secure websocket `"wss://test.mosquitto.org:8081/"`.
Some MQTT brokers do not have the WebSocket interface enabled
since the primary interface is TCP. WebSockets are, however,
necessary to get round restrictions in HTML5 not having direct access
to TCP sockets.
* `$MQTT.subscribe(topic, qos=0)`
This subscribes to a topic. All subscribed messages go to the
same `received_message`.
* `$MQTT.unsubscribe(topic)`
Little used since all topics are unsubscribed on disconnect.
The topic has to match exactly to one that was subscribed.
(You cannot use this for include/exclude rules.)
* `$MQTT.publish(topic, msg, retain=false, qos=0)`
Publish a message to the broker.
### @export parameters
* @export var client_id = ""
* @export var verbose_level = 2 # 0 quiet, 1 connections and subscriptions, 2 all messages
* @export var binarymessages = false
* @export var pinginterval = 30
### Signals
* received_message(topic, message)
* signal broker_connected()
* signal broker_disconnected()
* signal broker_connection_failed()
* signal broker_connection_failed()
* signal publish_acknowledge(id)

View File

@ -0,0 +1,478 @@
extends Node
# MQTT client implementation in GDScript
# Loosely based on https://github.com/pycom/pycom-libraries/blob/master/lib/mqtt/mqtt.py
# and initial work by Alex J Lennon <ajlennon@dynamicdevices.co.uk>
# but then heavily rewritten to follow https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/mqtt-v3.1.1.html
# mosquitto_sub -h test.mosquitto.org -v -t "metest/#"
# mosquitto_pub -h test.mosquitto.org -t "metest/retain" -m "retained message" -r
@export var client_id = ""
@export var verbose_level = 2 # 0 quiet, 1 connections and subscriptions, 2 all messages
@export var binarymessages = false
@export var pinginterval = 30
var socket = null
var sslsocket = null
var websocket = null
const BCM_NOCONNECTION = 0
const BCM_WAITING_WEBSOCKET_CONNECTION = 1
const BCM_WAITING_SOCKET_CONNECTION = 2
const BCM_WAITING_SSL_SOCKET_CONNECTION = 3
const BCM_FAILED_CONNECTION = 5
const BCM_WAITING_CONNMESSAGE = 10
const BCM_WAITING_CONNACK = 19
const BCM_CONNECTED = 20
var brokerconnectmode = BCM_NOCONNECTION
var regexbrokerurl = RegEx.new()
const DEFAULTBROKERPORT_TCP = 1883
const DEFAULTBROKERPORT_SSL = 8886
const DEFAULTBROKERPORT_WS = 8080
const DEFAULTBROKERPORT_WSS = 8081
const CP_PINGREQ = 0xc0
const CP_PINGRESP = 0xd0
const CP_CONNACK = 0x20
const CP_CONNECT = 0x10
const CP_PUBLISH = 0x30
const CP_SUBSCRIBE = 0x82
const CP_UNSUBSCRIBE = 0xa2
const CP_PUBREC = 0x40
const CP_SUBACK = 0x90
const CP_UNSUBACK = 0xb0
var pid = 0
var user = null
var pswd = null
var keepalive = 120
var lw_topic = null
var lw_msg = null
var lw_qos = 0
var lw_retain = false
signal received_message(topic, message)
signal broker_connected()
signal broker_disconnected()
signal broker_connection_failed()
signal publish_acknowledge(pid)
var receivedbuffer : PackedByteArray = PackedByteArray()
var common_name = null
func senddata(data):
var E = 0
if sslsocket != null:
E = sslsocket.put_data(data)
elif socket != null:
E = socket.put_data(data)
elif websocket != null:
E = websocket.put_packet(data)
if E != 0:
print("bad senddata packet E=", E)
func receiveintobuffer():
if sslsocket != null:
var sslsocketstatus = sslsocket.get_status()
if sslsocketstatus == StreamPeerTLS.STATUS_CONNECTED or sslsocketstatus == StreamPeerTLS.STATUS_HANDSHAKING:
sslsocket.poll()
var n = sslsocket.get_available_bytes()
if n != 0:
var sv = sslsocket.get_data(n)
assert (sv[0] == 0) # error code
receivedbuffer.append_array(sv[1])
elif socket != null and socket.get_status() == StreamPeerTCP.STATUS_CONNECTED:
socket.poll()
var n = socket.get_available_bytes()
if n != 0:
var sv = socket.get_data(n)
assert (sv[0] == 0) # error code
receivedbuffer.append_array(sv[1])
elif websocket != null:
websocket.poll()
while websocket.get_available_packet_count() != 0:
receivedbuffer.append_array(websocket.get_packet())
var pingticksnext0 = 0
func _process(delta):
if brokerconnectmode == BCM_NOCONNECTION:
pass
elif brokerconnectmode == BCM_WAITING_WEBSOCKET_CONNECTION:
websocket.poll()
var websocketstate = websocket.get_ready_state()
if websocketstate == WebSocketPeer.STATE_CLOSED:
if verbose_level:
print("WebSocket closed with code: %d, reason %s." % [websocket.get_close_code(), websocket.get_close_reason()])
brokerconnectmode = BCM_FAILED_CONNECTION
emit_signal("broker_connection_failed")
elif websocketstate == WebSocketPeer.STATE_OPEN:
brokerconnectmode = BCM_WAITING_CONNMESSAGE
if verbose_level:
print("Websocket connection now open")
elif brokerconnectmode == BCM_WAITING_SOCKET_CONNECTION:
socket.poll()
var socketstatus = socket.get_status()
if socketstatus == StreamPeerTCP.STATUS_ERROR:
if verbose_level:
print("TCP socket error")
brokerconnectmode = BCM_FAILED_CONNECTION
emit_signal("broker_connection_failed")
if socketstatus == StreamPeerTCP.STATUS_CONNECTED:
brokerconnectmode = BCM_WAITING_CONNMESSAGE
elif brokerconnectmode == BCM_WAITING_SSL_SOCKET_CONNECTION:
socket.poll()
var socketstatus = socket.get_status()
if socketstatus == StreamPeerTCP.STATUS_ERROR:
if verbose_level:
print("TCP socket error before SSL")
brokerconnectmode = BCM_FAILED_CONNECTION
emit_signal("broker_connection_failed")
if socketstatus == StreamPeerTCP.STATUS_CONNECTED:
if sslsocket == null:
sslsocket = StreamPeerTLS.new()
if verbose_level:
print("Connecting socket to SSL with common_name=", common_name)
var E3 = sslsocket.connect_to_stream(socket, common_name)
if E3 != 0:
print("bad sslsocket.connect_to_stream E=", E3)
brokerconnectmode = BCM_FAILED_CONNECTION
emit_signal("broker_connection_failed")
sslsocket = null
if sslsocket != null:
sslsocket.poll()
var sslsocketstatus = sslsocket.get_status()
if sslsocketstatus == StreamPeerTLS.STATUS_CONNECTED:
brokerconnectmode = BCM_WAITING_CONNMESSAGE
elif sslsocketstatus >= StreamPeerTLS.STATUS_ERROR:
print("bad sslsocket.connect_to_stream")
emit_signal("broker_connection_failed")
elif brokerconnectmode == BCM_WAITING_CONNMESSAGE:
senddata(firstmessagetoserver())
brokerconnectmode = BCM_WAITING_CONNACK
elif brokerconnectmode == BCM_WAITING_CONNACK or brokerconnectmode == BCM_CONNECTED:
receiveintobuffer()
while wait_msg():
pass
if brokerconnectmode == BCM_CONNECTED and pingticksnext0 < Time.get_ticks_msec():
pingreq()
pingticksnext0 = Time.get_ticks_msec() + pinginterval*1000
elif brokerconnectmode == BCM_FAILED_CONNECTION:
cleanupsockets()
func _ready():
regexbrokerurl.compile('^(tcp://|wss://|ws://|ssl://)?([^:\\s]+)(:\\d+)?(/\\S*)?$')
if client_id == "":
randomize()
client_id = "rr%d" % randi()
func set_last_will(stopic, smsg, retain=false, qos=0):
assert((0 <= qos) and (qos <= 2))
assert(stopic)
self.lw_topic = stopic.to_ascii_buffer()
self.lw_msg = smsg if binarymessages else smsg.to_ascii_buffer()
self.lw_qos = qos
self.lw_retain = retain
if verbose_level:
print("LASTWILL%s topic=%s msg=%s" % [ " <retain>" if retain else "", stopic, smsg])
func set_user_pass(suser, spswd):
if suser != null:
self.user = suser.to_ascii_buffer()
self.pswd = spswd.to_ascii_buffer()
else:
self.user = null
self.pswd = null
func firstmessagetoserver():
var clean_session = true
var msg = PackedByteArray()
msg.append(CP_CONNECT);
msg.append(0x00);
msg.append(0x00);
msg.append(0x04);
msg.append_array("MQTT".to_ascii_buffer());
msg.append(0x04);
msg.append(0x02);
msg.append(0x00);
msg.append(0x3C);
msg[1] = 10 + 2 + len(self.client_id)
msg[9] = (1<<1) if clean_session else 0
if self.user != null:
msg[1] += 2 + len(self.user) + 2 + len(self.pswd)
msg[9] |= 0xC0
if self.keepalive:
assert(self.keepalive < 65536)
msg[10] |= self.keepalive >> 8
msg[11] |= self.keepalive & 0x00FF
if self.lw_topic:
msg[1] += 2 + len(self.lw_topic) + 2 + len(self.lw_msg)
msg[9] |= 0x4 | (self.lw_qos & 0x1) << 3 | (self.lw_qos & 0x2) << 3
msg[9] |= 1<<5 if self.lw_retain else 0
msg.append(len(self.client_id) >> 8)
msg.append(self.client_id.length() & 0xFF)
msg.append_array(self.client_id.to_ascii_buffer())
if self.lw_topic:
msg.append(len(self.lw_topic) >> 8)
msg.append(len(self.lw_topic) & 0xFF)
msg.append_array(self.lw_topic)
msg.append(len(self.lw_msg) >> 8)
msg.append(len(self.lw_msg) & 0xFF)
msg.append_array(self.lw_msg)
if self.user != null:
msg.append(len(self.user) >> 8)
msg.append(len(self.user) & 0xFF)
msg.append_array(self.user)
msg.append(len(self.pswd) >> 8)
msg.append(len(self.pswd) & 0xFF)
msg.append_array(self.pswd)
return msg
func cleanupsockets(retval=false):
if verbose_level:
print("cleanupsockets")
if socket:
if sslsocket:
sslsocket = null
socket.disconnect_from_host()
socket = null
else:
assert (sslsocket == null)
if websocket:
websocket.close()
websocket = null
brokerconnectmode = BCM_NOCONNECTION
return retval
func connect_to_broker(brokerurl):
assert (brokerconnectmode == BCM_NOCONNECTION)
var brokermatch = regexbrokerurl.search(brokerurl)
if brokermatch == null:
print("ERROR: unrecognized brokerurl pattern:", brokerurl)
return cleanupsockets(false)
var brokercomponents = brokermatch.strings
var brokerprotocol = brokercomponents[1]
var brokerserver = brokercomponents[2]
var iswebsocket = (brokerprotocol == "ws://" or brokerprotocol == "wss://")
var isssl = (brokerprotocol == "ssl://" or brokerprotocol == "wss://")
var brokerport = ((DEFAULTBROKERPORT_WSS if isssl else DEFAULTBROKERPORT_WS) if iswebsocket else (DEFAULTBROKERPORT_SSL if isssl else DEFAULTBROKERPORT_TCP))
if brokercomponents[3]:
brokerport = int(brokercomponents[3].substr(1))
var brokerpath = brokercomponents[4] if brokercomponents[4] else ""
common_name = null
if iswebsocket:
websocket = WebSocketPeer.new()
websocket.supported_protocols = PackedStringArray(["mqttv3.1"])
var websocketurl = ("wss://" if isssl else "ws://") + brokerserver + ":" + str(brokerport) + brokerpath
if verbose_level:
print("Connecting to websocketurl: ", websocketurl)
var E = websocket.connect_to_url(websocketurl)
if E != 0:
print("ERROR: websocketclient.connect_to_url Err: ", E)
return cleanupsockets(false)
print("Websocket get_requested_url ", websocket.get_requested_url())
brokerconnectmode = BCM_WAITING_WEBSOCKET_CONNECTION
else:
socket = StreamPeerTCP.new()
if verbose_level:
print("Connecting to %s:%s" % [brokerserver, brokerport])
var E = socket.connect_to_host(brokerserver, brokerport)
if E != 0:
print("ERROR: socketclient.connect_to_url Err: ", E)
return cleanupsockets(false)
if isssl:
brokerconnectmode = BCM_WAITING_SSL_SOCKET_CONNECTION
common_name = brokerserver
else:
brokerconnectmode = BCM_WAITING_SOCKET_CONNECTION
return true
func disconnect_from_server():
if brokerconnectmode == BCM_CONNECTED:
senddata(PackedByteArray([0xE0, 0x00]))
emit_signal("broker_disconnected")
cleanupsockets()
func publish(stopic, smsg, retain=false, qos=0):
var msg = smsg.to_ascii_buffer() if not binarymessages else smsg
var topic = stopic.to_ascii_buffer()
var pkt = PackedByteArray()
pkt.append(CP_PUBLISH | (2 if qos else 0) | (1 if retain else 0));
pkt.append(0x00);
var sz = 2 + len(topic) + len(msg)
if qos > 0:
sz += 2
assert(sz < 2097152)
var i = 1
while sz > 0x7f:
pkt[i] = (sz & 0x7f) | 0x80
sz >>= 7
i += 1
if i + 1 > len(pkt):
pkt.append(0x00);
pkt[i] = sz
pkt.append(len(topic) >> 8)
pkt.append(len(topic) & 0xFF)
pkt.append_array(topic)
if qos > 0:
pid += 1
pkt.append(pid >> 8)
pkt.append(pid & 0xFF)
pkt.append_array(msg)
senddata(pkt)
if verbose_level >= 2:
print("CP_PUBLISH%s%s topic=%s msg=%s" % [ "[%d]"%pid if qos else "", " <retain>" if retain else "", stopic, smsg])
return pid
func subscribe(stopic, qos=0):
pid += 1
var topic = stopic.to_ascii_buffer()
var length = 2 + 2 + len(topic) + 1
var msg = PackedByteArray()
msg.append(CP_SUBSCRIBE);
msg.append(length)
msg.append(pid >> 8)
msg.append(pid & 0xFF)
msg.append(len(topic) >> 8)
msg.append(len(topic) & 0xFF)
msg.append_array(topic)
msg.append(qos);
if verbose_level:
print("SUBSCRIBE[%d] topic=%s" % [pid, stopic])
senddata(msg)
func pingreq():
if verbose_level >= 2:
print("PINGREQ")
senddata(PackedByteArray([CP_PINGREQ, 0x00]))
func unsubscribe(stopic):
pid += 1
var topic = stopic.to_ascii_buffer()
var length = 2 + 2 + len(topic)
var msg = PackedByteArray()
msg.append(CP_UNSUBSCRIBE);
msg.append(length)
msg.append(pid >> 8)
msg.append(pid & 0xFF)
msg.append(len(topic) >> 8)
msg.append(len(topic) & 0xFF)
msg.append_array(topic)
if verbose_level:
print("UNSUBSCRIBE[%d] topic=%s" % [pid, stopic])
senddata(msg)
func wait_msg():
var n = receivedbuffer.size()
if n < 2:
return false
var op = receivedbuffer[0]
var i = 1
var sz = receivedbuffer[i] & 0x7f
while (receivedbuffer[i] & 0x80):
i += 1
if i == n:
return false
sz += (receivedbuffer[i] & 0x7f) << ((i-1)*7)
i += 1
if n < i + sz:
return false
if op == CP_PINGRESP:
assert (sz == 0)
if verbose_level >= 2:
print("PINGRESP")
elif op & 0xf0 == 0x30:
var topic_len = (receivedbuffer[i]<<8) + receivedbuffer[i+1]
var im = i + 2
var topic = receivedbuffer.slice(im, im + topic_len).get_string_from_ascii()
im += topic_len
var pid1 = 0
if op & 6:
pid1 = (receivedbuffer[im]<<8) + receivedbuffer[im+1]
im += 2
var data = receivedbuffer.slice(im, i + sz)
var msg = data if binarymessages else data.get_string_from_ascii()
if verbose_level >= 2:
print("received topic=", topic, " msg=", msg)
emit_signal("received_message", topic, msg)
if op & 6 == 2:
senddata(PackedByteArray([0x40, 0x02, (pid1 >> 8), (pid1 & 0xFF)]))
elif op & 6 == 4:
assert(0)
elif op == CP_CONNACK:
assert (sz == 2)
var retcode = receivedbuffer[i+1]
if verbose_level:
print("CONNACK ret=%02x" % retcode)
if retcode == 0x00:
brokerconnectmode = BCM_CONNECTED
emit_signal("broker_connected")
else:
if verbose_level:
print("Bad connection retcode=", retcode) # see https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/mqtt-v3.1.1.html
emit_signal("broker_connection_failed")
elif op == CP_PUBREC:
assert (sz == 2)
var apid = (receivedbuffer[i]<<8) + receivedbuffer[i+1]
if verbose_level >= 2:
print("PUBACK[%d]" % apid)
emit_signal("publish_acknowledgewait_msg", apid)
elif op == CP_SUBACK:
assert (sz == 3)
var apid = (receivedbuffer[i]<<8) + receivedbuffer[i+1]
if verbose_level:
print("SUBACK[%d] ret=%02x" % [apid, receivedbuffer[i+2]])
#if receivedbuffer[i+2] == 0x80:
# E = FAILED
elif op == CP_UNSUBACK:
assert (sz == 2)
var apid = (receivedbuffer[i]<<8) + receivedbuffer[i+1]
if verbose_level:
print("UNSUBACK[%d]" % apid)
else:
if verbose_level:
print("Unknown MQTT opcode op=%x" % op)
trimreceivedbuffer(i + sz)
return true
func trimreceivedbuffer(n):
if n == receivedbuffer.size():
receivedbuffer = PackedByteArray()
else:
assert (n <= receivedbuffer.size())
receivedbuffer = receivedbuffer.slice(n)

View File

@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://ktm7k0co2o7l"]
[ext_resource type="Script" path="res://addons/mqtt/mqtt.gd" id="1_unkqx"]
[node name="MQTT" type="Node"]
script = ExtResource("1_unkqx")

View File

@ -0,0 +1,7 @@
[plugin]
name="MQTT"
description="MQTT client in GDScript"
author="Julian Todd"
version="1.0"
script="mqtt.gd"

View File

@ -0,0 +1,37 @@
[preset.0]
name="Web"
platform="Web"
runnable=true
dedicated_server=false
custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter=""
export_path="../jjjj/Godot-mqtt.html"
encryption_include_filters=""
encryption_exclude_filters=""
encrypt_pck=false
encrypt_directory=false
[preset.0.options]
custom_template/debug=""
custom_template/release=""
variant/extensions_support=false
vram_texture_compression/for_desktop=true
vram_texture_compression/for_mobile=false
html/export_icon=true
html/custom_html_shell=""
html/head_include=""
html/canvas_resize_policy=2
html/focus_canvas_on_start=true
html/experimental_virtual_keyboard=false
progressive_web_app/enabled=false
progressive_web_app/offline_page=""
progressive_web_app/display=1
progressive_web_app/orientation=0
progressive_web_app/icon_144x144=""
progressive_web_app/icon_180x180=""
progressive_web_app/icon_512x512=""
progressive_web_app/background_color=Color(0, 0, 0, 1)

View File

@ -0,0 +1,96 @@
[gd_scene load_steps=3 format=3 uid="uid://8726w0yv488e"]
[ext_resource type="Script" path="res://HighScoreExample.gd" id="1_a1y26"]
[ext_resource type="PackedScene" uid="uid://ktm7k0co2o7l" path="res://addons/mqtt/mqtt.tscn" id="2_8e2pi"]
[node name="HighScoreExample" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_a1y26")
[node name="MQTT" parent="." instance=ExtResource("2_8e2pi")]
[node name="VBox" type="VBoxContainer" parent="."]
layout_mode = 0
offset_right = 40.0
offset_bottom = 40.0
[node name="Label" type="Label" parent="VBox"]
custom_minimum_size = Vector2(400, 0)
layout_mode = 2
text = "MQTT Based High Score System"
horizontal_alignment = 1
[node name="HBoxGamename" type="HBoxContainer" parent="VBox"]
layout_mode = 2
[node name="Label" type="Label" parent="VBox/HBoxGamename"]
custom_minimum_size = Vector2(200, 0)
layout_mode = 2
text = "Game name"
[node name="LineEdit" type="LineEdit" parent="VBox/HBoxGamename"]
custom_minimum_size = Vector2(200, 0)
layout_mode = 2
text = "A_Great_Godot_Game"
[node name="HBoxPlayername" type="HBoxContainer" parent="VBox"]
layout_mode = 2
[node name="Label" type="Label" parent="VBox/HBoxPlayername"]
custom_minimum_size = Vector2(200, 0)
layout_mode = 2
text = "Player name"
[node name="LineEdit" type="LineEdit" parent="VBox/HBoxPlayername"]
custom_minimum_size = Vector2(200, 0)
layout_mode = 2
text = "Jonathan"
[node name="HBoxHighScore" type="HBoxContainer" parent="VBox"]
layout_mode = 2
[node name="Label" type="Label" parent="VBox/HBoxHighScore"]
custom_minimum_size = Vector2(200, 0)
layout_mode = 2
text = "High score"
[node name="LineEdit" type="LineEdit" parent="VBox/HBoxHighScore"]
custom_minimum_size = Vector2(200, 0)
layout_mode = 2
text = "1000"
[node name="HBoxButtons" type="HBoxContainer" parent="VBox"]
layout_mode = 2
[node name="SendScore" type="Button" parent="VBox/HBoxButtons"]
custom_minimum_size = Vector2(200, 0)
layout_mode = 2
text = "Send score"
[node name="FetchScores" type="Button" parent="VBox/HBoxButtons"]
custom_minimum_size = Vector2(200, 0)
layout_mode = 2
text = "Fetch Scores"
[node name="HBoxTopscores" type="HBoxContainer" parent="VBox"]
layout_mode = 2
[node name="RichTextLabel" type="RichTextLabel" parent="VBox/HBoxTopscores"]
custom_minimum_size = Vector2(400, 300)
layout_mode = 2
bbcode_enabled = true
text = "[u]Top ten[/u]
"
[connection signal="broker_connected" from="MQTT" to="." method="_on_mqtt_broker_connected"]
[connection signal="broker_connection_failed" from="MQTT" to="." method="_on_mqtt_broker_connection_failed"]
[connection signal="broker_disconnected" from="MQTT" to="." method="_on_mqtt_broker_disconnected"]
[connection signal="publish_acknowledge" from="MQTT" to="." method="_on_mqtt_publish_acknowledge"]
[connection signal="received_message" from="MQTT" to="." method="_on_mqtt_received_message"]
[connection signal="pressed" from="VBox/HBoxButtons/SendScore" to="." method="_on_send_score_pressed"]
[connection signal="pressed" from="VBox/HBoxButtons/FetchScores" to="." method="_on_fetch_scores_pressed"]

BIN
godot_client/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://3qd766itdoge"
path="res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.png"
dest_files=["res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

114
godot_client/mqttexample.gd Normal file
View File

@ -0,0 +1,114 @@
extends Control
var receivedmessagecount = 0
func _ready():
connectedactionsactive(false)
func _on_brokerprotocol_item_selected(index):
# port defaults for [tcp, ssl, ws, wss] used at https://test.mosquitto.org/
$VBox/HBoxBroker/brokerport.text = "%d" % ([ 1883, 8886, 8080, 8081 ][index])
func _on_button_connect_toggled(button_pressed):
if button_pressed:
randomize()
$MQTT.client_id = "s%d" % randi()
if $VBox/HBoxLastwill/lastwilltopic.text:
$MQTT.set_last_will($VBox/HBoxLastwill/lastwilltopic.text,
$VBox/HBoxLastwill/lastwillmessage.text,
$VBox/HBoxLastwill/lastwillretain.button_pressed)
else:
$MQTT.set_last_will("", "", false)
if $VBox/HBoxBroker/brokeruser.text:
$MQTT.set_user_pass($VBox/HBoxBroker/brokeruser.text,
$VBox/HBoxBroker/brokerpswd.text)
else:
$MQTT.set_user_pass(null, null)
$VBox/HBoxBrokerControl/status.text = "connecting..."
var brokerurl = $VBox/HBoxBroker/brokeraddress.text
var protocol = $VBox/HBoxBroker/brokerprotocol.get_item_text($VBox/HBoxBroker/brokerprotocol.selected)
$MQTT.connect_to_broker("%s%s:%s" % [protocol, brokerurl, $VBox/HBoxBroker/brokerport.text])
else:
#$VBox/HBoxBrokerControl/status.text = "disconnecting..."
$MQTT.disconnect_from_server()
func brokersettingsactive(active):
$VBox/HBoxBroker/brokeraddress.editable = active
$VBox/HBoxBroker/brokerport.editable = active
$VBox/HBoxBroker/brokerprotocol.disabled = not active
$VBox/HBoxLastwill/lastwilltopic.editable = active
$VBox/HBoxLastwill/lastwillmessage.editable = active
$VBox/HBoxLastwill/lastwillretain.disabled = not active
$VBox/HBoxBrokerControl/ButtonConnect.button_pressed = not active
func connectedactionsactive(active):
$VBox/HBoxSubscriptions/subscribetopic.editable = active
$VBox/HBoxSubscriptions/subscribe.disabled = not active
$VBox/HBoxPublish/publishtopic.editable = active
$VBox/HBoxPublish/publishmessage.editable = active
$VBox/HBoxPublish/publishretain.disabled = not active
$VBox/HBoxPublish/publish.disabled = not active
if not active:
$VBox/HBoxSubscriptions/subscriptions.clear()
$VBox/HBoxSubscriptions/subscriptions.disabled = true
$VBox/HBoxSubscriptions/unsubscribe.disabled = true
func _on_mqtt_broker_connected():
$VBox/HBoxBrokerControl/status.text = "connected."
brokersettingsactive(false)
connectedactionsactive(true)
receivedmessagecount = 0
func _on_mqtt_broker_disconnected():
$VBox/HBoxBrokerControl/status.text = "disconnected."
brokersettingsactive(true)
connectedactionsactive(false)
func _on_mqtt_broker_connection_failed():
$VBox/HBoxBrokerControl/status.text = "failed."
brokersettingsactive(true)
connectedactionsactive(false)
func _on_mqtt_received_message(topic, message):
if receivedmessagecount == 0:
$VBox/subscribedmessages.clear()
receivedmessagecount += 1
$VBox/subscribedmessages.append_text("[b]%s[/b] %s\n" % [topic, message])
func _on_publish_pressed():
var qos = 0
$MQTT.publish($VBox/HBoxPublish/publishtopic.text,
$VBox/HBoxPublish/publishmessage.text,
$VBox/HBoxPublish/publishretain.button_pressed,
qos)
func _on_subscribe_pressed():
var qos = 0
var topic = $VBox/HBoxSubscriptions/subscribetopic.text.strip_edges()
$MQTT.subscribe(topic, qos)
for i in range($VBox/HBoxSubscriptions/subscriptions.item_count):
if topic == $VBox/HBoxSubscriptions/subscriptions.get_item_text(i):
return
$VBox/HBoxSubscriptions/subscriptions.add_item(topic)
$VBox/HBoxSubscriptions/subscriptions.select($VBox/HBoxSubscriptions/subscriptions.item_count - 1)
$VBox/HBoxSubscriptions/subscriptions.disabled = false
$VBox/HBoxSubscriptions/unsubscribe.disabled = false
func _on_unsubscribe_pressed():
var seloptbutt = $VBox/HBoxSubscriptions/subscriptions
var sel = seloptbutt.selected
var topic = seloptbutt.get_item_text(sel)
$MQTT.unsubscribe(topic)
seloptbutt.remove_item(seloptbutt.selected)
$VBox/HBoxSubscriptions/subscriptions.disabled = (seloptbutt.item_count == 0)
$VBox/HBoxSubscriptions/unsubscribe.disabled = (seloptbutt.item_count == 0)
if seloptbutt.item_count != 0:
seloptbutt.select(min(sel, seloptbutt.item_count - 1))

View File

@ -0,0 +1,275 @@
[gd_scene load_steps=8 format=3 uid="uid://sca5nhn6lprk"]
[ext_resource type="Script" path="res://mqttexample.gd" id="1_6i2w6"]
[ext_resource type="PackedScene" uid="uid://ktm7k0co2o7l" path="res://addons/mqtt/mqtt.tscn" id="2_t6rxa"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_iasn2"]
content_margin_left = 5.0
content_margin_right = 5.0
bg_color = Color(0.215686, 0.4, 0.4, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ppk4t"]
content_margin_left = 5.0
content_margin_right = 5.0
bg_color = Color(0.341176, 0.101961, 0.0392157, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_8b54y"]
content_margin_left = 5.0
content_margin_right = 5.0
bg_color = Color(0.341176, 0.101961, 0.0392157, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xasl1"]
content_margin_left = 5.0
content_margin_right = 5.0
bg_color = Color(0.341176, 0.101961, 0.0392157, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_njfq6"]
bg_color = Color(0.0156863, 0.133333, 0.0431373, 1)
[node name="mqttexample" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -123.0
offset_top = -64.0
offset_right = -1023.0
offset_bottom = -584.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_6i2w6")
[node name="MQTT" parent="." instance=ExtResource("2_t6rxa")]
[node name="VBox" type="VBoxContainer" parent="."]
self_modulate = Color(0.0588235, 1, 1, 1)
layout_mode = 0
offset_left = 137.0
offset_top = 82.0
offset_right = 1017.0
offset_bottom = 576.0
[node name="HBoxBrokerControl" type="HBoxContainer" parent="VBox"]
layout_mode = 2
[node name="Labelbroker" type="Label" parent="VBox/HBoxBrokerControl"]
layout_mode = 2
theme_type_variation = &"HeaderLarge"
text = "MQTT broker:"
[node name="ButtonConnect" type="CheckButton" parent="VBox/HBoxBrokerControl"]
layout_mode = 2
size_flags_horizontal = 6
text = "Connect to broker"
[node name="status" type="Label" parent="VBox/HBoxBrokerControl"]
layout_mode = 2
size_flags_horizontal = 2
theme_override_styles/normal = SubResource("StyleBoxFlat_iasn2")
text = "...Status"
horizontal_alignment = 1
[node name="HBoxBroker" type="HBoxContainer" parent="VBox"]
layout_mode = 2
[node name="Label" type="Label" parent="VBox/HBoxBroker"]
layout_mode = 2
text = "URL: "
[node name="brokeraddress" type="LineEdit" parent="VBox/HBoxBroker"]
layout_mode = 2
size_flags_horizontal = 3
text = "broker.hivemq.com"
[node name="Label4" type="Label" parent="VBox/HBoxBroker"]
layout_mode = 2
text = "User:"
[node name="brokeruser" type="LineEdit" parent="VBox/HBoxBroker"]
layout_mode = 2
[node name="Label5" type="Label" parent="VBox/HBoxBroker"]
layout_mode = 2
text = "Pswd:"
[node name="brokerpswd" type="LineEdit" parent="VBox/HBoxBroker"]
layout_mode = 2
[node name="Label2" type="Label" parent="VBox/HBoxBroker"]
layout_mode = 2
text = "Port: "
[node name="brokerport" type="LineEdit" parent="VBox/HBoxBroker"]
layout_mode = 2
tooltip_text = "Port
"
text = "1883"
[node name="Label3" type="Label" parent="VBox/HBoxBroker"]
layout_mode = 2
text = "Protocol: "
[node name="brokerprotocol" type="OptionButton" parent="VBox/HBoxBroker"]
layout_mode = 2
item_count = 4
selected = 0
popup/item_0/text = "tcp://"
popup/item_0/id = 0
popup/item_1/text = "ssl://"
popup/item_1/id = 1
popup/item_2/text = "ws://"
popup/item_2/id = 2
popup/item_3/text = "wss://"
popup/item_3/id = 3
[node name="HSeparator" type="HSeparator" parent="VBox"]
custom_minimum_size = Vector2(0, 10)
layout_mode = 2
[node name="HBoxLastwill" type="HBoxContainer" parent="VBox"]
layout_mode = 2
[node name="Labellastwill" type="Label" parent="VBox/HBoxLastwill"]
layout_mode = 2
theme_type_variation = &"HeaderMedium"
text = "Last will:"
[node name="Label" type="Label" parent="VBox/HBoxLastwill"]
layout_mode = 2
size_flags_horizontal = 10
text = "topic:"
[node name="lastwilltopic" type="LineEdit" parent="VBox/HBoxLastwill"]
layout_mode = 2
size_flags_horizontal = 3
text = "godot/myname/mywill"
[node name="Label2" type="Label" parent="VBox/HBoxLastwill"]
layout_mode = 2
text = "message:"
[node name="lastwillmessage" type="LineEdit" parent="VBox/HBoxLastwill"]
layout_mode = 2
size_flags_horizontal = 3
text = "goodbye world"
[node name="lastwillretain" type="CheckButton" parent="VBox/HBoxLastwill"]
layout_mode = 2
text = "Retain
"
[node name="HSeparator2" type="HSeparator" parent="VBox"]
custom_minimum_size = Vector2(0, 10)
layout_mode = 2
[node name="HBoxSubscriptions" type="HBoxContainer" parent="VBox"]
layout_mode = 2
[node name="Labellastwill2" type="Label" parent="VBox/HBoxSubscriptions"]
layout_mode = 2
theme_type_variation = &"HeaderMedium"
text = "Subscriptions:"
[node name="Label3" type="Label" parent="VBox/HBoxSubscriptions"]
layout_mode = 2
size_flags_horizontal = 10
text = "topic:"
[node name="subscribetopic" type="LineEdit" parent="VBox/HBoxSubscriptions"]
layout_mode = 2
size_flags_horizontal = 3
text = "godot/+"
[node name="subscribe" type="Button" parent="VBox/HBoxSubscriptions"]
layout_mode = 2
theme_override_styles/normal = SubResource("StyleBoxFlat_ppk4t")
text = "subscribe
"
[node name="subscriptions" type="OptionButton" parent="VBox/HBoxSubscriptions"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
size_flags_horizontal = 10
tooltip_text = "Subscribed topics
"
item_count = 1
selected = 0
popup/item_0/text = "<none>"
popup/item_0/id = 0
[node name="unsubscribe" type="Button" parent="VBox/HBoxSubscriptions"]
layout_mode = 2
theme_override_constants/outline_size = 0
theme_override_styles/normal = SubResource("StyleBoxFlat_8b54y")
text = "unsubscribe
"
[node name="HSeparator3" type="HSeparator" parent="VBox"]
custom_minimum_size = Vector2(0, 10)
layout_mode = 2
[node name="HBoxPublish" type="HBoxContainer" parent="VBox"]
layout_mode = 2
[node name="Labellastwill" type="Label" parent="VBox/HBoxPublish"]
layout_mode = 2
theme_type_variation = &"HeaderMedium"
text = "Publish:"
[node name="Label" type="Label" parent="VBox/HBoxPublish"]
layout_mode = 2
size_flags_horizontal = 10
text = "topic:"
[node name="publishtopic" type="LineEdit" parent="VBox/HBoxPublish"]
layout_mode = 2
size_flags_horizontal = 3
text = "godot/myname"
[node name="Label2" type="Label" parent="VBox/HBoxPublish"]
layout_mode = 2
text = "message:"
[node name="publishmessage" type="LineEdit" parent="VBox/HBoxPublish"]
layout_mode = 2
size_flags_horizontal = 3
text = "hello there"
[node name="publishretain" type="CheckButton" parent="VBox/HBoxPublish"]
layout_mode = 2
text = "Retain
"
[node name="publish" type="Button" parent="VBox/HBoxPublish"]
layout_mode = 2
theme_override_styles/normal = SubResource("StyleBoxFlat_xasl1")
text = "Publish"
[node name="HSeparator4" type="HSeparator" parent="VBox"]
custom_minimum_size = Vector2(0, 10)
layout_mode = 2
[node name="subscribedmessages" type="RichTextLabel" parent="VBox"]
layout_mode = 2
size_flags_vertical = 3
theme_override_styles/normal = SubResource("StyleBoxFlat_njfq6")
bbcode_enabled = true
text = "[b]Instructions[/b]
MQTT is a lightweight easy-to-use publish and subscribe (\"pubsub\") networking protocol.
By default this app connects to the public server hosted at [u]https://test.mosquitto.org/[/u] , but you can connect to another broker or your own instance.
[i]Commands to run on another computer terminal:[/i]
> mosquitto_pub -h test.mosquitto.org -t \"godot/abcd\" -m \"Bingo!\"
> mosquitto_sub -v -h test.mosquitto.org -t \"godot/#\""
[connection signal="broker_connected" from="MQTT" to="." method="_on_mqtt_broker_connected"]
[connection signal="broker_connection_failed" from="MQTT" to="." method="_on_mqtt_broker_connection_failed"]
[connection signal="broker_disconnected" from="MQTT" to="." method="_on_mqtt_broker_disconnected"]
[connection signal="received_message" from="MQTT" to="." method="_on_mqtt_received_message"]
[connection signal="toggled" from="VBox/HBoxBrokerControl/ButtonConnect" to="." method="_on_button_connect_toggled"]
[connection signal="item_selected" from="VBox/HBoxBroker/brokerprotocol" to="." method="_on_brokerprotocol_item_selected"]
[connection signal="pressed" from="VBox/HBoxSubscriptions/subscribe" to="." method="_on_subscribe_pressed"]
[connection signal="pressed" from="VBox/HBoxSubscriptions/unsubscribe" to="." method="_on_unsubscribe_pressed"]
[connection signal="pressed" from="VBox/HBoxPublish/publish" to="." method="_on_publish_pressed"]

View File

@ -0,0 +1,29 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[application]
config/name="Godot-mqtt"
run/main_scene="res://mqttexample.tscn"
config/features=PackedStringArray("4.2", "Forward Plus")
config/icon="res://icon.png"
[audio]
buses/default_bus_layout=""
[display]
window/size/viewport_width=900
window/size/viewport_height=520
[editor_plugins]
enabled=PackedStringArray("res://addons/mqtt/plugin.cfg")

View File

@ -0,0 +1,212 @@
import socket
import struct
from binascii import hexlify
class MQTTException(Exception):
pass
class MQTTClient:
def __init__(
self,
client_id,
server,
port=0,
user=None,
password=None,
keepalive=0,
ssl=None,
):
if port == 0:
port = 8883 if ssl else 1883
self.client_id = client_id
self.sock = None
self.server = server
self.port = port
self.ssl = ssl
self.pid = 0
self.cb = None
self.user = user
self.pswd = password
self.keepalive = keepalive
self.lw_topic = None
self.lw_msg = None
self.lw_qos = 0
self.lw_retain = False
def _send_str(self, s):
self.sock.write(struct.pack("!H", len(s)))
self.sock.write(s)
def _recv_len(self):
n = 0
sh = 0
while 1:
b = self.sock.read(1)[0]
n |= (b & 0x7F) << sh
if not b & 0x80:
return n
sh += 7
def set_callback(self, f):
self.cb = f
def set_last_will(self, topic, msg, retain=False, qos=0):
assert 0 <= qos <= 2
assert topic
self.lw_topic = topic
self.lw_msg = msg
self.lw_qos = qos
self.lw_retain = retain
def connect(self, clean_session=True):
self.sock = socket.socket()
addr = socket.getaddrinfo(self.server, self.port)[0][-1]
self.sock.connect(addr)
if self.ssl:
self.sock = self.ssl.wrap_socket(self.sock, server_hostname=self.server)
premsg = bytearray(b"\x10\0\0\0\0\0")
msg = bytearray(b"\x04MQTT\x04\x02\0\0")
sz = 10 + 2 + len(self.client_id)
msg[6] = clean_session << 1
if self.user:
sz += 2 + len(self.user) + 2 + len(self.pswd)
msg[6] |= 0xC0
if self.keepalive:
assert self.keepalive < 65536
msg[7] |= self.keepalive >> 8
msg[8] |= self.keepalive & 0x00FF
if self.lw_topic:
sz += 2 + len(self.lw_topic) + 2 + len(self.lw_msg)
msg[6] |= 0x4 | (self.lw_qos & 0x1) << 3 | (self.lw_qos & 0x2) << 3
msg[6] |= self.lw_retain << 5
i = 1
while sz > 0x7F:
premsg[i] = (sz & 0x7F) | 0x80
sz >>= 7
i += 1
premsg[i] = sz
self.sock.write(premsg, i + 2)
self.sock.write(msg)
# print(hex(len(msg)), hexlify(msg, ":"))
self._send_str(self.client_id)
if self.lw_topic:
self._send_str(self.lw_topic)
self._send_str(self.lw_msg)
if self.user:
self._send_str(self.user)
self._send_str(self.pswd)
resp = self.sock.read(4)
assert resp[0] == 0x20 and resp[1] == 0x02
if resp[3] != 0:
raise MQTTException(resp[3])
return resp[2] & 1
def disconnect(self):
self.sock.write(b"\xe0\0")
self.sock.close()
def ping(self):
self.sock.write(b"\xc0\0")
def publish(self, topic, msg, retain=False, qos=0):
pkt = bytearray(b"\x30\0\0\0")
pkt[0] |= qos << 1 | retain
sz = 2 + len(topic) + len(msg)
if qos > 0:
sz += 2
assert sz < 2097152
i = 1
while sz > 0x7F:
pkt[i] = (sz & 0x7F) | 0x80
sz >>= 7
i += 1
pkt[i] = sz
# print(hex(len(pkt)), hexlify(pkt, ":"))
self.sock.write(pkt, i + 1)
self._send_str(topic)
if qos > 0:
self.pid += 1
pid = self.pid
struct.pack_into("!H", pkt, 0, pid)
self.sock.write(pkt, 2)
self.sock.write(msg)
if qos == 1:
while 1:
op = self.wait_msg()
if op == 0x40:
sz = self.sock.read(1)
assert sz == b"\x02"
rcv_pid = self.sock.read(2)
rcv_pid = rcv_pid[0] << 8 | rcv_pid[1]
if pid == rcv_pid:
return
elif qos == 2:
assert 0
def subscribe(self, topic, qos=0):
assert self.cb is not None, "Subscribe callback is not set"
pkt = bytearray(b"\x82\0\0\0")
self.pid += 1
struct.pack_into("!BH", pkt, 1, 2 + 2 + len(topic) + 1, self.pid)
# print(hex(len(pkt)), hexlify(pkt, ":"))
self.sock.write(pkt)
self._send_str(topic)
self.sock.write(qos.to_bytes(1, "little"))
while 1:
op = self.wait_msg()
if op == 0x90:
resp = self.sock.read(4)
# print(resp)
assert resp[1] == pkt[2] and resp[2] == pkt[3]
if resp[3] == 0x80:
raise MQTTException(resp[3])
return
# Wait for a single incoming MQTT message and process it.
# Subscribed messages are delivered to a callback previously
# set by .set_callback() method. Other (internal) MQTT
# messages processed internally.
def wait_msg(self):
res = self.sock.read(1)
self.sock.setblocking(True)
if res is None:
return None
if res == b"":
raise OSError(-1)
if res == b"\xd0": # PINGRESP
sz = self.sock.read(1)[0]
assert sz == 0
return None
op = res[0]
if op & 0xF0 != 0x30:
return op
sz = self._recv_len()
topic_len = self.sock.read(2)
topic_len = (topic_len[0] << 8) | topic_len[1]
topic = self.sock.read(topic_len)
sz -= topic_len + 2
if op & 6:
pid = self.sock.read(2)
pid = pid[0] << 8 | pid[1]
sz -= 2
msg = self.sock.read(sz)
self.cb(topic, msg)
if op & 6 == 2:
pkt = bytearray(b"\x40\x02\0\0")
struct.pack_into("!H", pkt, 2, pid)
self.sock.write(pkt)
elif op & 6 == 4:
assert 0
return op
# Checks whether a pending message from server is available.
# If not, returns immediately with None. Otherwise, does
# the same processing as wait_msg.
def check_msg(self):
self.sock.setblocking(False)
return self.wait_msg()

47
rp2040_client/main.py Normal file
View File

@ -0,0 +1,47 @@
import time
from umqtt.simple import MQTTClient
import network
import time
from math import sin
# Received messages from subscriptions will be delivered to this callback
def sub_cb(topic, msg):
print((topic, msg))
def main(server="192.168.1.59"):
# Fill in your WiFi network name (ssid) and password here:
wifi_ssid = "OddlyAsus"
wifi_password = "8699869986"
# Connect to WiFi
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(wifi_ssid, wifi_password)
while wlan.isconnected() == False:
print('Waiting for connection...')
time.sleep(1)
print("Connected to WiFi")
c = MQTTClient("pico_board", server, 1883)
c.set_callback(sub_cb)
c.connect()
c.subscribe(b"godot/#")
while True:
if True:
# Blocking wait for message
c.wait_msg()
else:
# Non-blocking wait for message
c.check_msg()
# Then need to sleep to avoid 100% CPU usage (in a real
# app other useful actions would be performed instead)
time.sleep(1)
c.disconnect()
if __name__ == "__main__":
main()