🕌

HaskellでMQTTを使用する。

2023/08/07に公開

概要

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