🕌
HaskellでMQTTを使用する。
概要
HaskellでMQTT通信を行うためのライブラリとしてはnet-mqttがあります。このページにはサンプルコードがありますが整備されていないようでそのまま書いてもエラーが出て動かないかエラー解決しても通信がうまくできません。パケットキャプチャなどを多用しながら苦労してサンプルを動かしておきましたので残しておきます。MQTTの一般的な知識は適宜検索されてください。
ソフト環境
名前 | バージョン |
---|---|
OS | Ubuntu 20.04 |
package.xml:dependencies
- base >= 4.7 && < 5
- bytestring
- text
- network
- network-uri
- net-mqtt
- aeson
ハード環境
名前 | バージョン |
---|---|
CPU | AMD Ryzen 9 3900XT 12-Core |
GPU | RTX3080 |
RAM | 76GB |
準備
localhost内でMQTTブローカーのmosquittoのサービスを立てました。予め以下コマンドなどで動作を確認すると良いです。
mosquitto_sub -d -V 5 -t tmp/topic1
mosquitto_pub -d -V 5 -t tmp/topic1 -m "Hello world!"
Publisher
準備のmosquitto_subコマンドを確認用に起動させておきます。これに対してPublishを行うコードが以下です。送るパケットはJSONで、コマンドライン引数を取ります(サンプルとして無駄ですが消すのが面倒だった)。PropertyやConfigはmosquitto_pubコマンドのパケットと合わせるためのものです。
実行すればmosquitto_subコマンドの出力にJSONデータが表示されます。
pub.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric #-}
import System.Environment
import GHC.Generics
import qualified Data.Text as T
import qualified Data.ByteString.Lazy.Char8 as BL
import Data.ByteString.Lazy (ByteString)
import Network.MQTT.Client
import Network.URI (parseURI)
import Network.MQTT.Topic (mkTopic)
import qualified Data.Aeson as A
data PointerPosition = PointerPosition
{
s :: Bool
, x :: Int
, y :: Int
, a :: String
} deriving (Generic, Show)
instance A.ToJSON PointerPosition
main :: IO ()
main = do
args <- getArgs
putStrLn "started"
let (Just uri) = parseURI "mqtt://localhost"
let props = [ PropReceiveMaximum 20 ]
let myMqttConfig = mqttConfig{ _protocol=Protocol50
, _cleanSession=True
, _connProps=props
}
mc <- connectURI myMqttConfig uri
connected <- isConnected mc
print connected
let (Just topic) = mkTopic $ T.pack "tmp/topic1"
let position = PointerPosition False 100 201 (head args)
let message = A.encode position
result <- publish mc topic message True
print result
putStrLn "finished"
disResult <- normalDisconnect mc
print disResult
Subscriber
mosquitto_subを閉じます。同一な機能を持つSubscriberをhaskellで作成したのが以下です。
実行させたあとに別ターミナルでPublisherを実行させると同様に内容表示されるはずです。
sub.hs
import qualified Data.Text as T
import Network.MQTT.Client
import Network.MQTT.Topic
import Network.URI (parseURI)
main :: IO ()
main = do
let (Just uri) = parseURI "mqtt://localhost"
mc <- connectURI mqttConfig{_msgCB=SimpleCallback msgReceived} uri
let t1 = T.pack "tmp/topic1"
let t2 = T.pack "tmp/topic2"
let (Just fil1) = mkFilter t1
let (Just fil2) = mkFilter t2
print =<< subscribe mc [(fil1, subOptions), (fil2, subOptions)] []
waitForClient mc -- wait for the the client to disconnect
where
msgReceived _ t m p = print (t,m,p)
感想
びっくりするぐらい動作出来なくて苦労しました。熟練者からすると当たり前に上記コードが導き出せるものなのでしょうか。Wiresharkは素晴らしいソフトウェアです。
Discussion