MacDoppler Multi Rig Control

With the FT-847 down (see the last SSTV experiment) I needed a way to have a radio simultaneously listening on one band and transmitting on another. There are not many radios on the market that are able to do this and I think the IC-9700 is the only one in production. I also don’t have $1800 to drop on a new IC-9700. So I wanted to look at other options.

Well it seems that MacDoppler has some support for just this situation. You can run an instance of MacDoppler that rather than controls a rig, sends out the uplink/downlink data via UDP. Dog Park Software also has another app called MacDopplerRadios which allows you to connect to one radio for transmitting and another for receiving. You could even have these radios on two different computers.

Here is a Mac with a FT-991A and an IC-7100 via MacDopplerRadios. I’m using the FT-991A for the downlink and the IC-7100 for uplink.

But that wasn’t enough for me. Why not use a software defined radio (SDR) for downlink? You get a nice pan adapter to view signals. The issue is that the UDP output from MacDoppler is not compatible with any SDR applications. So I set out to write a translator that takes the UDP output and converts it into a rigctl command. Rigctl is part of the open source hamlibs and is supported by many programs like GQRX and sdrangel.

I wanted something that was quick to develop and deploy so I looked into a familiar platform, Node-Red. There is support for UDP in and out. Dog Park provides good documentation for the UDP broadcast so it was easy to find that it uses port 9932. So I created a listener for that port and then parse the broadcasts. I display the decoded data to a Node-Red web page and send out the downlink frequency and radio mode to a specified IP address to control the SDR. In this case I’m sending it to another Mac running sdrangel controlling a RSPdx SDR receiver.

Above are a couple pics of the Node-Red dashboard and MacDoppler behind it sending out the UDP. The flow is located at the bottom of this post.

Here is sdrangel being doppler corrected via my Node-Red middleware. So the end result is now I have a couple of options to use while I figure out what to do with the FT-847.

[
    {
        "id": "3057caa6d21dfee9",
        "type": "tab",
        "label": "Flow 1",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "a9ee49be4cc956c6",
        "type": "udp in",
        "z": "3057caa6d21dfee9",
        "name": "",
        "iface": "",
        "port": "9932",
        "ipv": "udp4",
        "multicast": "false",
        "group": "",
        "datatype": "utf8",
        "x": 60,
        "y": 100,
        "wires": [
            [
                "6f85ad569a2c5ea0",
                "8fa90f318ab10429",
                "435cfd5ea3a849f9",
                "965022efef60b04c",
                "0cc355e842e87f5b",
                "c33923cbab069e53",
                "ce65baa0401c0980",
                "42b410e99589af06"
            ]
        ]
    },
    {
        "id": "435cfd5ea3a849f9",
        "type": "debug",
        "z": "3057caa6d21dfee9",
        "name": "debug 1",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 140,
        "y": 400,
        "wires": []
    },
    {
        "id": "681989db1373f647",
        "type": "ui_text",
        "z": "3057caa6d21dfee9",
        "group": "fa1a3e4363b5b21d",
        "order": 8,
        "width": 3,
        "height": 1,
        "name": "",
        "label": "Down",
        "format": "{{msg.payload}}",
        "layout": "row-left",
        "className": "",
        "style": false,
        "font": "",
        "fontSize": 16,
        "color": "#000000",
        "x": 410,
        "y": 80,
        "wires": []
    },
    {
        "id": "6f85ad569a2c5ea0",
        "type": "string",
        "z": "3057caa6d21dfee9",
        "name": "",
        "methods": [
            {
                "name": "between",
                "params": [
                    {
                        "type": "str",
                        "value": "Down Mhz:"
                    },
                    {
                        "type": "str",
                        "value": ","
                    }
                ]
            }
        ],
        "prop": "payload",
        "propout": "payload",
        "object": "msg",
        "objectout": "msg",
        "x": 230,
        "y": 80,
        "wires": [
            [
                "681989db1373f647",
                "ab22d0157d65f14e"
            ]
        ]
    },
    {
        "id": "8fa90f318ab10429",
        "type": "string",
        "z": "3057caa6d21dfee9",
        "name": "",
        "methods": [
            {
                "name": "between",
                "params": [
                    {
                        "type": "str",
                        "value": "Up MHz:"
                    },
                    {
                        "type": "str",
                        "value": ","
                    }
                ]
            }
        ],
        "prop": "payload",
        "propout": "payload",
        "object": "msg",
        "objectout": "msg",
        "x": 230,
        "y": 160,
        "wires": [
            [
                "34b083707bdd16c4"
            ]
        ]
    },
    {
        "id": "965022efef60b04c",
        "type": "string",
        "z": "3057caa6d21dfee9",
        "name": "",
        "methods": [
            {
                "name": "between",
                "params": [
                    {
                        "type": "str",
                        "value": "Down Mode:"
                    },
                    {
                        "type": "str",
                        "value": ","
                    }
                ]
            }
        ],
        "prop": "payload",
        "propout": "payload",
        "object": "msg",
        "objectout": "msg",
        "x": 230,
        "y": 120,
        "wires": [
            [
                "623b57a6aecb2606",
                "fec86768288fb1c0"
            ]
        ]
    },
    {
        "id": "623b57a6aecb2606",
        "type": "ui_text",
        "z": "3057caa6d21dfee9",
        "group": "fa1a3e4363b5b21d",
        "order": 9,
        "width": 1,
        "height": 1,
        "name": "Down Mode",
        "label": "",
        "format": "{{msg.payload}}",
        "layout": "row-left",
        "className": "",
        "style": false,
        "font": "",
        "fontSize": 16,
        "color": "#000000",
        "x": 430,
        "y": 120,
        "wires": []
    },
    {
        "id": "34b083707bdd16c4",
        "type": "ui_text",
        "z": "3057caa6d21dfee9",
        "group": "fa1a3e4363b5b21d",
        "order": 5,
        "width": 3,
        "height": 1,
        "name": "",
        "label": "Up",
        "format": "{{msg.payload}}",
        "layout": "row-left",
        "className": "",
        "style": false,
        "font": "",
        "fontSize": 16,
        "color": "#000000",
        "x": 410,
        "y": 160,
        "wires": []
    },
    {
        "id": "c9e254fb49defe0f",
        "type": "ui_text",
        "z": "3057caa6d21dfee9",
        "group": "fa1a3e4363b5b21d",
        "order": 6,
        "width": 1,
        "height": 1,
        "name": "Up Mode",
        "label": "",
        "format": "{{msg.payload}}",
        "layout": "row-left",
        "className": "",
        "style": false,
        "font": "",
        "fontSize": 16,
        "color": "#000000",
        "x": 420,
        "y": 200,
        "wires": []
    },
    {
        "id": "0cc355e842e87f5b",
        "type": "string",
        "z": "3057caa6d21dfee9",
        "name": "",
        "methods": [
            {
                "name": "between",
                "params": [
                    {
                        "type": "str",
                        "value": "Up Mode:"
                    },
                    {
                        "type": "str",
                        "value": ","
                    }
                ]
            }
        ],
        "prop": "payload",
        "propout": "payload",
        "object": "msg",
        "objectout": "msg",
        "x": 230,
        "y": 200,
        "wires": [
            [
                "c9e254fb49defe0f"
            ]
        ]
    },
    {
        "id": "c33923cbab069e53",
        "type": "string",
        "z": "3057caa6d21dfee9",
        "name": "",
        "methods": [
            {
                "name": "between",
                "params": [
                    {
                        "type": "str",
                        "value": "tone:"
                    },
                    {
                        "type": "str",
                        "value": ","
                    }
                ]
            }
        ],
        "prop": "payload",
        "propout": "payload",
        "object": "msg",
        "objectout": "msg",
        "x": 230,
        "y": 240,
        "wires": [
            [
                "8cdf9749f8b1499f"
            ]
        ]
    },
    {
        "id": "42b410e99589af06",
        "type": "string",
        "z": "3057caa6d21dfee9",
        "name": "",
        "methods": [
            {
                "name": "between",
                "params": [
                    {
                        "type": "str",
                        "value": "SatName:"
                    },
                    {
                        "type": "str",
                        "value": "]"
                    }
                ]
            }
        ],
        "prop": "payload",
        "propout": "payload",
        "object": "msg",
        "objectout": "msg",
        "x": 230,
        "y": 320,
        "wires": [
            [
                "0a16535c05404024"
            ]
        ]
    },
    {
        "id": "ce65baa0401c0980",
        "type": "string",
        "z": "3057caa6d21dfee9",
        "name": "",
        "methods": [
            {
                "name": "between",
                "params": [
                    {
                        "type": "str",
                        "value": "ctone:"
                    },
                    {
                        "type": "str",
                        "value": ","
                    }
                ]
            }
        ],
        "prop": "payload",
        "propout": "payload",
        "object": "msg",
        "objectout": "msg",
        "x": 230,
        "y": 280,
        "wires": [
            [
                "646a6d1b767c4071"
            ]
        ]
    },
    {
        "id": "0a16535c05404024",
        "type": "ui_text",
        "z": "3057caa6d21dfee9",
        "group": "fa1a3e4363b5b21d",
        "order": 2,
        "width": 5,
        "height": 1,
        "name": "",
        "label": "Current Sat",
        "format": "{{msg.payload}}",
        "layout": "row-left",
        "className": "",
        "style": true,
        "font": "Arial,Arial,Helvetica,sans-serif",
        "fontSize": "20",
        "color": "#000000",
        "x": 430,
        "y": 320,
        "wires": []
    },
    {
        "id": "8cdf9749f8b1499f",
        "type": "ui_text",
        "z": "3057caa6d21dfee9",
        "group": "fa1a3e4363b5b21d",
        "order": 11,
        "width": 2,
        "height": 1,
        "name": "",
        "label": "Tone",
        "format": "{{msg.payload}}",
        "layout": "row-spread",
        "className": "",
        "style": false,
        "font": "",
        "fontSize": 16,
        "color": "#000000",
        "x": 410,
        "y": 240,
        "wires": []
    },
    {
        "id": "646a6d1b767c4071",
        "type": "ui_text",
        "z": "3057caa6d21dfee9",
        "group": "fa1a3e4363b5b21d",
        "order": 12,
        "width": 2,
        "height": 1,
        "name": "",
        "label": "CTone",
        "format": "{{msg.payload}}",
        "layout": "row-spread",
        "className": "",
        "style": false,
        "font": "",
        "fontSize": 16,
        "color": "#000000",
        "x": 410,
        "y": 280,
        "wires": []
    },
    {
        "id": "ccde0ea77c3fb24d",
        "type": "inject",
        "z": "3057caa6d21dfee9",
        "d": true,
        "name": "",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "1",
        "crontab": "",
        "once": true,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "str",
        "x": 90,
        "y": 520,
        "wires": [
            [
                "fd23c2d1e8f2bc12"
            ]
        ]
    },
    {
        "id": "0e30bc3aef4cd1eb",
        "type": "tcp request",
        "z": "3057caa6d21dfee9",
        "name": "",
        "server": "${RIGADDRESS}",
        "port": "${RIGPORT}",
        "out": "sit",
        "ret": "string",
        "splitc": " ",
        "newline": "",
        "trim": false,
        "tls": "",
        "x": 480,
        "y": 520,
        "wires": [
            [
                "54fdcb8ff542df3b"
            ]
        ]
    },
    {
        "id": "fd23c2d1e8f2bc12",
        "type": "function",
        "z": "3057caa6d21dfee9",
        "name": "get frequency",
        "func": "msg.payload = \"f\\n\";\nreturn msg;\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 240,
        "y": 520,
        "wires": [
            [
                "0e30bc3aef4cd1eb"
            ]
        ]
    },
    {
        "id": "0aab057cfc2f488e",
        "type": "function",
        "z": "3057caa6d21dfee9",
        "name": "get mode",
        "func": "msg.payload = \"m\\n\";\nreturn msg;\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 220,
        "y": 560,
        "wires": [
            [
                "62828e95d66c61ec"
            ]
        ]
    },
    {
        "id": "54fdcb8ff542df3b",
        "type": "function",
        "z": "3057caa6d21dfee9",
        "name": "frequency manipulation",
        "func": "msg.payload = (msg.payload/1000000).toFixed(6);\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 770,
        "y": 520,
        "wires": [
            [
                "29255d3df04085ce"
            ]
        ]
    },
    {
        "id": "62828e95d66c61ec",
        "type": "tcp request",
        "z": "3057caa6d21dfee9",
        "name": "",
        "server": "${RIGADDRESS}",
        "port": "${RIGPORT}",
        "out": "sit",
        "ret": "string",
        "splitc": " ",
        "newline": "",
        "tls": "",
        "x": 480,
        "y": 560,
        "wires": [
            [
                "c93713360f45c229",
                "8424b3fdec2a1e1b"
            ]
        ]
    },
    {
        "id": "c93713360f45c229",
        "type": "function",
        "z": "3057caa6d21dfee9",
        "name": "mode manipulation - get mode",
        "func": "//do any needed mode manipulations here\n\nmsg.payload = msg.payload.substring(0, msg.payload.indexOf(\"\\n\"));\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 790,
        "y": 560,
        "wires": [
            [
                "eead0dc019bcbf2a"
            ]
        ]
    },
    {
        "id": "8424b3fdec2a1e1b",
        "type": "function",
        "z": "3057caa6d21dfee9",
        "name": "mode manipulation - get passband",
        "func": "//do any needed mode manipulations here\n\nmsg.payload = msg.payload.substring(msg.payload.indexOf(\"\\n\") + 1);\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 800,
        "y": 600,
        "wires": [
            [
                "4b49553792b76f79"
            ]
        ]
    },
    {
        "id": "29255d3df04085ce",
        "type": "ui_text",
        "z": "3057caa6d21dfee9",
        "d": true,
        "group": "",
        "order": 1,
        "width": 0,
        "height": 0,
        "name": "",
        "label": "SDR Frequency",
        "format": "{{msg.payload}}",
        "layout": "row-center",
        "className": "",
        "style": false,
        "font": "",
        "fontSize": "",
        "color": "#000000",
        "x": 1000,
        "y": 520,
        "wires": []
    },
    {
        "id": "eead0dc019bcbf2a",
        "type": "ui_text",
        "z": "3057caa6d21dfee9",
        "d": true,
        "group": "",
        "order": 2,
        "width": 2,
        "height": 2,
        "name": "",
        "label": "Mode",
        "format": "{{msg.payload}}",
        "layout": "col-center",
        "className": "",
        "style": false,
        "font": "",
        "fontSize": "",
        "color": "#000000",
        "x": 1030,
        "y": 560,
        "wires": []
    },
    {
        "id": "4b49553792b76f79",
        "type": "ui_gauge",
        "z": "3057caa6d21dfee9",
        "d": true,
        "name": "",
        "group": "",
        "order": 4,
        "width": 2,
        "height": 2,
        "gtype": "gage",
        "title": "",
        "label": "width",
        "format": "{{value}}",
        "min": 0,
        "max": "4000",
        "colors": [
            "#00b500",
            "#00b500",
            "#00b500"
        ],
        "seg1": "",
        "seg2": "",
        "diff": false,
        "className": "",
        "x": 1030,
        "y": 600,
        "wires": []
    },
    {
        "id": "c83be611b0abd423",
        "type": "inject",
        "z": "3057caa6d21dfee9",
        "d": true,
        "name": "",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "2",
        "crontab": "",
        "once": true,
        "onceDelay": "0.5",
        "topic": "",
        "payload": "",
        "payloadType": "str",
        "x": 90,
        "y": 560,
        "wires": [
            [
                "0aab057cfc2f488e"
            ]
        ]
    },
    {
        "id": "ab22d0157d65f14e",
        "type": "function",
        "z": "3057caa6d21dfee9",
        "name": "set frequency",
        "func": "msg.payload = msg.payload * 1000000\nmsg.payload = \"set_freq \"+msg.payload+\"\\n\";\nreturn msg;\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 560,
        "y": 60,
        "wires": [
            [
                "d37fbbb1059f494a"
            ]
        ]
    },
    {
        "id": "d37fbbb1059f494a",
        "type": "tcp out",
        "z": "3057caa6d21dfee9",
        "name": "",
        "host": "${RIGADDRESS}",
        "port": "${RIGPORT}",
        "beserver": "client",
        "base64": false,
        "end": false,
        "tls": "",
        "x": 820,
        "y": 60,
        "wires": []
    },
    {
        "id": "fec86768288fb1c0",
        "type": "function",
        "z": "3057caa6d21dfee9",
        "name": "set mode",
        "func": "msg.payload = \"set_mode \" + msg.payload + \"\\n\";\nreturn msg;\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 580,
        "y": 100,
        "wires": [
            [
                "68aa6d8ed52b3386"
            ]
        ]
    },
    {
        "id": "68aa6d8ed52b3386",
        "type": "tcp out",
        "z": "3057caa6d21dfee9",
        "name": "",
        "host": "${RIGADDRESS}",
        "port": "${RIGPORT}",
        "beserver": "client",
        "base64": false,
        "end": false,
        "tls": "",
        "x": 820,
        "y": 100,
        "wires": []
    },
    {
        "id": "d74974c3f714ffb5",
        "type": "ui_spacer",
        "z": "3057caa6d21dfee9",
        "name": "spacer",
        "group": "359ae5ed2e1fb9a7",
        "order": 3,
        "width": 1,
        "height": 1
    },
    {
        "id": "ccf4e8bf63aadd7f",
        "type": "ui_spacer",
        "z": "3057caa6d21dfee9",
        "name": "spacer",
        "group": "359ae5ed2e1fb9a7",
        "order": 5,
        "width": 1,
        "height": 1
    },
    {
        "id": "ba56a02563c05a24",
        "type": "ui_spacer",
        "z": "3057caa6d21dfee9",
        "name": "spacer",
        "group": "359ae5ed2e1fb9a7",
        "order": 6,
        "width": 1,
        "height": 1
    },
    {
        "id": "edc1bbe28a90547c",
        "type": "ui_spacer",
        "z": "3057caa6d21dfee9",
        "name": "spacer",
        "group": "359ae5ed2e1fb9a7",
        "order": 7,
        "width": 1,
        "height": 1
    },
    {
        "id": "20574cef60bc0014",
        "type": "ui_spacer",
        "z": "3057caa6d21dfee9",
        "name": "spacer",
        "group": "fa1a3e4363b5b21d",
        "order": 1,
        "width": 2,
        "height": 1
    },
    {
        "id": "8e06d95815994aaa",
        "type": "ui_spacer",
        "z": "3057caa6d21dfee9",
        "name": "spacer",
        "group": "fa1a3e4363b5b21d",
        "order": 3,
        "width": 3,
        "height": 1
    },
    {
        "id": "d9d83aa8582a2c95",
        "type": "ui_spacer",
        "z": "3057caa6d21dfee9",
        "name": "spacer",
        "group": "fa1a3e4363b5b21d",
        "order": 7,
        "width": 1,
        "height": 1
    },
    {
        "id": "36a36834ceb109da",
        "type": "ui_spacer",
        "z": "3057caa6d21dfee9",
        "name": "spacer",
        "group": "fa1a3e4363b5b21d",
        "order": 10,
        "width": 1,
        "height": 1
    },
    {
        "id": "5f0ce9cf1297e84a",
        "type": "ui_spacer",
        "z": "3057caa6d21dfee9",
        "name": "spacer",
        "group": "fa1a3e4363b5b21d",
        "order": 13,
        "width": 6,
        "height": 1
    },
    {
        "id": "910ec9e1e5eb725f",
        "type": "ui_spacer",
        "z": "3057caa6d21dfee9",
        "name": "spacer",
        "group": "fa1a3e4363b5b21d",
        "order": 16,
        "width": 3,
        "height": 1
    },
    {
        "id": "3eb93d90b53ae11c",
        "type": "ui_spacer",
        "z": "3057caa6d21dfee9",
        "name": "spacer",
        "group": "fa1a3e4363b5b21d",
        "order": 18,
        "width": 3,
        "height": 1
    },
    {
        "id": "fa1a3e4363b5b21d",
        "type": "ui_group",
        "name": "Sat Info",
        "tab": "de7e26f01a6c1820",
        "order": 1,
        "disp": true,
        "width": 10,
        "collapse": false,
        "className": ""
    },
    {
        "id": "de7e26f01a6c1820",
        "type": "ui_tab",
        "name": "UDP Info",
        "icon": "dashboard",
        "order": 0,
        "disabled": false,
        "hidden": false
    }
]