ROS、ELM、タートル

Robotic Operation Systemを使用すると、特別なプロトコルに従って「トピックへのサブスクライブ」および「サービスコール」のメカニズムに従ってサブシステムと対話できます。 しかし、websocketを使用して外部からROSと通信できるようにするrosbridgeパッケージがあります。 説明されているプロトコルを使用すると、基本的な操作を実行して他のサブシステムと対話できます。



ELMは、JavaScriptでコンパイルされた非常にシンプルでエレガントな言語であり、対話型プログラムの開発に最適です。



私はビジネスと喜びを組み合わせ、ROS(現在教えられている )とELMを一緒に学ぶことにしました。



ROSには、タートルロボットをエミュレートするturtlesimデモモジュールがあります。 彼が提供するノードの1つは彼のウィンドウでカメの動きを描き、もう1つはキーボードの矢印の押下をカメの動きと回転のコマンドに変換します。 単純なELMプログラムからこのプロセスに接続できます。



ELMはmodel-updater-viewパターンを使用します。 プログラムの状態はModelデータタイプによって記述され、update関数はMsgタイプの着信イベントを取得し、古いモデルを新しいモデル(および、場合によっては実行する必要がある操作)に変換します。view関数は、Msgタイプのイベントを生成できる。 イベントは、モデルの特別な関数によって作成されたサブスクリプションから発生することもあります。



一般化されたELM Webプログラムは次のようになります。



init : ( Model, Cmd Msg ) update : Msg -> Model -> ( Model, Cmd Msg ) view : Model -> Html Msg subscriptions : Model -> Sub Msg main = Html.program { init = init , view = view , update = update , subscriptions = subscriptions }
      
      





プログラマはこれら4つの機能のみを実装できます。



モデルについて説明します。



 type alias Model = { x : Float , y : Float --   , dir : Float -- ,     , connected : Bool --    , ws : String -- URL websocket,   rosbridge --  ROS     --    , -- url  ws://localhost:9090/ , topic : String -- ,    , --  /turtle1/cmd_vel , input : String -- JSON ,     --      , messages : List String --    rosbridge  --       --     } init : ( Model, Cmd Msg ) init = ( Model 50 50 0 False "ws://192.168.56.101:9090/" "/turtle1/cmd_vel" "" [] , Cmd.none )
      
      





これまでのところ、複雑なことはありませんが、モデルは名前付きフィールドを持つ構造です。

Msgタイプは、OOプログラマーにはあまり一般的ではありません。



 type Msg = Send String | NewMessage String | EnterUrl String | EnterTopic String | Connect | Input String
      
      





これは、いわゆる代数型であり、いくつかの選択肢の直接(ラベル付き)合計を表します。 OOPのこのタイプの最も近い表現であるMsgは抽象クラスとして宣言され、代替の各行はMsgから継承された新しい具象クラスを記述します。 入力、送信などは、これらのクラスのコンストラクター名であり、クラスフィールドに変わるコンストラクターパラメーターが後に続きます。



それぞれの選択肢は、モデルを変更し、インターフェイス(ビュー)または外部イベント(websocketからデータを受信)を使用してユーザーアクションによって生成された操作を実行する要求です。





更新機能を実装する方法は多かれ少なかれ明確です。



 update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of EnterTopic newInput -> ( { model | topic = newInput }, Cmd.none ) EnterUrl newInput -> ( { model | ws = newInput }, Cmd.none ) Connect -> ( { model | connected = True }, WebSocket.send model.ws (subscr model.topic) ) Input newInput -> ( { model | input = newInput }, Cmd.none ) Send data -> ( { model | input = "" }, WebSocket.send model.ws data ) NewMessage str -> case Decode.decodeString (decodePublish decodeTwist) str of Err _ -> ( { model | messages = str :: model.messages }, Cmd.none ) Ok t -> let ( r, a ) = turtleMove t.msg dir = model.dir + a in ( { model | x = model.x + r * sin dir , y = model.y + r * cos dir , dir = dir , messages = str :: model.messages } , Cmd.none )
      
      





ここではいくつかの関数が使用されますが、これらは後で定義します。





それまでの間、ビュー関数を定義します。



 view : Model -> Html Msg view model = div [] <| if model.connected then let x = toString model.x y = toString model.y dirx = toString (model.x + 5 * sin model.dir) diry = toString (model.y + 5 * cos model.dir) in [ svg [ viewBox "0 0 100 100", Svg.Attributes.width "300px" ] [ circle [ cx x, cy y, r "4" ] [] , line [ x1 x, y1 y, x2 dirx, y2 diry, stroke "red" ] [] ] , br [] [] , button [ onClick <| Send <| pub model.topic 0 1 ] [ Html.text "Left" ] , button [ onClick <| Send <| pub model.topic 1 0 ] [ Html.text "Forward" ] , button [ onClick <| Send <| pub model.topic -1 0 ] [ Html.text "Back" ] , button [ onClick <| Send <| pub model.topic 0 -1 ] [ Html.text "Rigth" ] , br [] [] , input [ Html.Attributes.type_ "textaria", onInput Input ] [] , button [ onClick (Send model.input) ] [ Html.text "Send" ] , div [] (List.map viewMessage model.messages) ] else [ Html.text "WS: " , input [ Html.Attributes.type_ "text" , Html.Attributes.value model.ws , onInput EnterUrl ] [] , Html.text "Turtlr topic: " , input [ Html.Attributes.type_ "text" , Html.Attributes.value model.topic , onInput EnterTopic ] [] , br [] [] , button [ onClick Connect ] [ Html.text "Connect" ] ] viewMessage : String -> Html msg viewMessage msg = div [] [ Html.text msg ]
      
      





viewはDOMを作成します(htmlだけを読むことができます)。 各オブジェクト(タグ)は、2つのパラメーター(Html.Attributeなどの属性のリストとネストされたオブジェクト/タグのリスト)を受け取る「elm-lang / html」ライブラリーからの個別の関数によって生成されます。 (個人的に、私はこの決定が失敗したと考えています-ネストされた要素をbrタグに何らかの方法で入れた後、画面上で長い間見つけることができませんでした。正しいライブラリはそのようなエラーを許可せず、brの属性を持つ引数のみを残します。アプローチは、フロントエンドの専門家にとって深い意味があります。)



別に、属性について説明したいと思います。 Html.Attributeタイプは、完全に異種のエンティティのための寄せ集めです。 たとえば、 Html.Attributes.type_ : String -> Html.Attribute msg



は、入力などのタグのタイプを設定し、 Html.Events.onClick : msg -> Html.Attribute msg



は、この要素がクリックされたときに発生するイベントを設定します。



Html.Attributes.type_は、Svg.Attributes.type_との競合のため、コードで完全に記述する必要がありました。



読みにくいコードを考えてみましょう。



 onClick <| Send <| pub model.topic 0 1
      
      





同等です



 onClick (Send (pub model.topic 0 1))
      
      





<|



-これは引数に関数を適用する演算子です(Haskellでは '$'と呼ばれます)。これにより、より少ない括弧を使用できます。



onClick



すでに考慮されている属性の作成。そのパラメーターは生成されたイベントです。



Send



は、Msg型のコンストラクターの1つです。そのパラメーターは、後でwebsocketに送信する文字列です。



ELMのコンストラクターと型は大文字で表記され、変数(より正確には定数と関数パラメーター)は通常のものであり、小さいものもあります。



pub model.topic 0 1



関数を呼び出して、トピック上のカメの動きに関するメッセージを送信するリクエストを作成します。 トピックはモデルから取得され、0と1-移動と回転。



欠落している機能について説明します。 最も簡単な方法は、websocketに送信するメッセージを作成することです。これらは単なる文字列です。



 subscr : String -> String subscr topic = "{\"op\":\"subscribe\",\"topic\":\"" ++ topic ++ "\"}" pub : String -> Float -> Float -> String pub topic mr = "{\"topic\":\"" ++ topic ++ "\",\"msg\":{\"linear\":{\"y\":0.0,\"x\":" ++ toString m ++ ",\"z\": 0.0},\"angular\":{\"y\":0.0,\"x\":0.0,\"z\":" ++ toString r ++ "}},\"op\":\"publish\"}"
      
      





メッセージ処理はもう少し複雑です。 turtlesimが動作するメッセージのタイプは、ROSを使用して表示できます。



 ros:~$ rosmsg info geometry_msgs/Twist geometry_msgs/Vector3 linear float64 x float64 y float64 z geometry_msgs/Vector3 angular float64 x float64 y float64 z
      
      





Rosbridgeはそれをjsonに変換し、トピックに関するイベントに関するメッセージでラップします。



デコードは次のようになります。



 type alias Vector3 = ( Float, Float, Float ) type alias Twist = { linear : Vector3, angular : Vector3 } decodV3 : Decode.Decoder Vector3 decodV3 = Decode.map3 (,,) (Decode.at [ "x" ] Decode.float) (Decode.at [ "y" ] Decode.float) (Decode.at [ "z" ] Decode.float) decodeTwist : Decode.Decoder Twist decodeTwist = Decode.map2 Twist (Decode.at [ "linear" ] decodV3) (Decode.at [ "angular" ] decodV3) type alias Publish a = { msg : a, topic : String, op : String } decodePublish : Decode.Decoder a -> Decode.Decoder (Publish a) decodePublish decMsg = Decode.map3 (\tmo -> { msg = m, topic = t, op = o }) (Decode.at [ "topic" ] Decode.string) (Decode.at [ "msg" ] decMsg) (Decode.at [ "op" ] Decode.string)
      
      





あるタイプのJSON表現デコーダーは、他のデコーダーと組み合わされます。

Decode.map3 (,,)



は、パラメーターで渡された3つのデコーダーを使用し、操作(,,)



を使用して3つのデコードされた要素のタプルを作成します。



Decode.at



は、特定のデコーダーによってJsonの特定のパスで取得した値をデコードします。



コード



 (\tmo -> { msg = m, topic = t, op = o })
      
      





閉鎖について説明します。 jsコードに似ています:



 function (t,m,o) { return {"msg":m, "t":t, "op":p} }
      
      





完全なコードはgithubから取得できます。



ROSを試してみたい場合は、自分でインストールする必要があります。 ELMをインストールする代わりに、 サービスを使用できます。



All Articles