Controle um receptor rs-232 como se entendesse o protocolo IP mais recente

2

Eu gostaria de configurar um serviço em uma caixa linux que atua como uma ponte entre meu antigo receptor Onkyo controlado por RS-232 e minha rede local. Até agora eu posso falar de uma maneira usando socat:

sudo socat tcp-l:60128,reuseaddr,fork file:/dev/ttyUSB0,nonblock,raw,echo=0,crnl,waitlock=/ttyUSB0.lock &

Isso me permite alterar configurações como volume, origem etc., mas a resposta que retorna para reconhecer a alteração está faltando uma string simples que os novos receptores equipados com ethernet incluem em sua resposta. Como resultado, não posso usar isso para controlar o receptor usando aplicativos de telefone atuais que esperam a resposta que as unidades habilitadas para Ethernet fornecem.

Existe uma maneira de fazer com que o socat inclua a string adicional como parte da resposta ou posso obter duas instâncias de cada lado de algum código que decida quando e onde adicionar strings extras à mensagem?

O protocolo Onkyo para o RS-232 mais antigo e os métodos IP mais recentes são descritos nesta folha de excel se isso ajudar: link

A solicitação de detecção automática "! xECNQSTN" que todos os vários aplicativos modernos de controle da onkyo enviam, espera receber uma resposta como: '! 1ECNTX-NR609 / 60128 / DX' E esse pedido acontece depois de cada mudança de estado, como volume para cima, volume baixo, etc, então parece que eu preciso fazer algo como ter duas instâncias de socat rodando com alguma lógica no meio.

Eu sempre poderia apenas pegar um novo receptor moderno, mas isso seria muito mais satisfatório: o)

Qualquer ideia de como fazer isso é muito bem vinda!

    
por DrBubbles 12.09.2016 / 08:39

3 respostas

0

Tomei os dois conselhos e tentei fazer isso de outra maneira. No final, decidi fazê-lo como uma página da Web, pois eu poderia adicionar funcionalidades adicionais além do volume e ligar / desligar. Eu confiei em muitos pedaços de várias fontes, obrigado a todos eles. Eu usei node.js com sockets.io e depois de um pouco de tentativa e erro consegui algo que funciona, com feedback do dispositivo (para que ele possa inicializar seu estado quando a página carrega) e que não duplique incrementalmente respostas aninhadas (que levei alguns a descobrir como eu não sabia nada sobre nó ou soquetes até alguns dias atrás!) Aqui está. Provavelmente não é muito bonito para quem conhece essas coisas corretamente, mas parece fazer o que eu quero! Para usá-lo, instale o nó e execute o arquivo node.js usando o comando: nodejs main.js Coloque os arquivos index.html e style.css em um subdiretório (relativo à pasta com node.js) chamado "public" ( sem as aspas). Em seguida, aponte seu navegador para o host (executando main.js) com o número da porta anexado à URL (: 8080, neste caso).

A propósito, isso é para um Onkyo TX-SR804, mas também deve funcionar para seus outros receptores controlados por RS-232, usando um adaptador RS-232 para USB (alguns dólares na Amazon).

Este é o arquivo node.js:

var express = require('express');
app         = express();
server      = require('http').createServer(app);
io          = require('socket.io').listen(server);

var SerialPort = require("serialport")
var serialPort = new SerialPort("/dev/ttyUSB0", { 
        baudRate: 9600,
        dataBits: 8,
        parity: 'none',
        stopBits: 1
        }
    );

server.listen(8080);
app.use(express.static('public'));             
var paramVal = 0;
var countRep = 0;
var countSend = 0;
var buf = new Buffer(16);
var global_socket;

io.sockets.on('connection', function (socket) {
    global_socket = socket;
    global_socket.on('toOnkyo', function (data) {
        paramVal = data.value;
        buf.write(paramVal, "utf-8");
        serialPort.write(buf);
        console.log(paramVal.toString().substr(0,7) + " (" + parseInt(paramVal.toString().substr(5,2),16) + ")\r\n");               
        global_socket.emit('toOnkyo', {value: paramVal});   
        console.log('new'+paramVal);
        countSend=countSend+1;
        console.log('count send '+ countSend);
        }
    );
    }
);
serialPort.on('data', function(data) {
    console.log('data received: ' + data.toString().substr(0,7) + " (" + parseInt(data.toString().substr(5,2),16) + ")");
    global_socket.emit('onkyoReply', {value: data.toString().substr(0,7)});
    countRep=countRep+1;
    console.log('count '+ countRep);
    }
);
console.log("running");

Este é o arquivo HTML index.html no qual você aponta seu navegador. Ele deve estar em uma pasta chamada public, uma subpasta da que contém node.js. quando você apontar seu navegador para o servidor que executa o node.js, inclua o número da porta (8080 neste caso)

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
        <title>Onkyo Controller</title>
        <meta name="viewport" content="width=400px" />
        <script src="socket.io/socket.io.js"></script>
        <link rel="stylesheet" href="style.css">
  </head>
  <body>
<!--
Sent:       <span id="sliderVolText"></span><br>
Reply:      <span id="replyTextHex"></span>
(Decimal:   <span id="replyText10"></span>)<br>
Mode:       <span id="modeText"></span><br>
PowerText:  <span id="powerText"></span><br>
Power:      <span id="power"></span><br>
onoffText:  <span id="onoffText"></span><br>
onoff:      <span id="onoff"></span>  
-->

<span id="sliderVolText" style="display:none"></span>
<span id="replyTextHex" style="display:none"></span>
<span id="replyText10" style="display:none"></span>
<span id="modeText" style="display:none"></span>
<span id="sourceText" style="display:none"></span>
<span id="powerText" style="display:none"></span>
<span id="power" style="display:none"></span>
<span id="onoffText" style="display:none"></span>
<span id="onoff" style="display:none"></span>  

    <script>
    function setCheckedValue(radioObj, newValue) {
        if(!radioObj)
            return;
        var radioLength = radioObj.length;
        if(radioLength == undefined) {
            radioObj.checked = (radioObj.value == newValue.toString());
            return;
        }
        for(var i = 0; i < radioLength; i++) {
            radioObj[i].checked = false;
            if(radioObj[i].value == newValue.toString()) {
                radioObj[i].checked = true;
            }
        }
    }
    </script>



    <form class="onoffswitch" >
        <input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox" id="myonoffswitch"  onclick="showOnoff(checked)">
        <label class="onoffswitch-label" for="myonoffswitch">
        <span class="onoffswitch-inner"></span>
        <span class="onoffswitch-switch"></span>
        </label>
    </form>
<!--
    <form name="powerForm" method="get" action="" onsubmit="return false;">
        <p>   <label for="power0"><input type="radio" value="0x00" name="powerForm" id="power0" onclick="showPower(this.value)"> Off</label>
        &nbsp;<label for="power1"><input type="radio" value="0x01" name="powerForm" id="power1" onclick="showPower(this.value)"> On</label>
    </form>
-->
    <form name="modeForm" method="get" action="" onsubmit="return false;">
        <p>   <label for="mode0"><input type="radio" value="0x00" name="modeForm" id="mode0" onclick="showMode(this.value)"> Stereo</label>
        &nbsp;<label for="mode1"><input type="radio" value="0x01" name="modeForm" id="mode1" onclick="showMode(this.value)"> Direct</label>
        &nbsp;<label for="mode2"><input type="radio" value="0x0C" name="modeForm" id="mode2" onclick="showMode(this.value)"> All Ch stereo</label>
        &nbsp;<label for="mode3"><input type="radio" value="0x42" name="modeForm" id="mode3" onclick="showMode(this.value)"> THX Cinema</label>
        &nbsp;<label for="mode4"><input type="radio" value="0x84" name="modeForm" id="mode4" onclick="showMode(this.value)"> PLllx THX Cinema</label>
        &nbsp;<label for="mode5"><input type="radio" value="0x11" name="modeForm" id="mode5" onclick="showMode(this.value)"> Pure</label>
    </form>
<br>

<form name="sourceForm" method="get" action="" onsubmit="return false;">
        <p>   <label for="source0"><input type="radio" value="0x00" name="sourceForm" id="source0" onclick="showSource(this.value)"> Computer</label>
        &nbsp;<label for="source2"><input type="radio" value="0x24" name="sourceForm" id="source2" onclick="showSource(this.value)"> FM radio</label>
<!--
        &nbsp;<label for="source1"><input type="radio" value="0x01" name="sourceForm" id="source1" onclick="showSource(this.value)"> Video 2</label>
        &nbsp;<label for="source3"><input type="radio" value="0x26" name="sourceForm" id="source3" onclick="showSource(this.value)"> Tuner</label>
-->
        </form>
<br>


    <form name="slideForm" method="get" action="" onsubmit="return false;">
        <input type="range" id= "inputSlider" min="0" max="100" value="vol" step="1" oninput="showVolume(this.value)" />
    </form>
    <br>
    <div class="results"></div>
    <script type="text/javascript">
//      function toggle(checked) {
//        var elm = document.getElementById('checkbox');
//        if (checked != elm.checked) {
//          elm.click();
//        }
//      }

        var socket = io.connect();
        var ctrlType = "";
            socket.on('toOnkyo', function (data) {
                ctrlType = data.value.toString().substr(2,3);
                if (ctrlType == "MVL" && !(data.value.toString().substr(5,4)=="QSTN")){
                    document.getElementById("inputSlider").value =  parseInt(data.value.toString().substr(5,2),16);
                    document.getElementById("sliderVolText").innerHTML = data.value;
                }
                if (ctrlType == "LMD" && !(data.value.toString().substr(5,4)=="QSTN")){
                    document.getElementById("mode").value =  parseInt(data.value.toString().substr(5,2),16);
                    document.getElementById("modeText").innerHTML = data.value;
                }
                if (ctrlType == "PWR" && !(data.value.toString().substr(5,4)=="QSTN")   ){
                    document.getElementById("power").value =  parseInt(data.value.toString().substr(5,2),16);
                    document.getElementById("powerText").innerHTML = data.value;
                }
                if (ctrlType == "PWR" && !(data.value.toString().substr(5,4)=="QSTN")   ){
                    document.getElementById("onoff").value =  parseInt(data.value.toString().substr(5,2),16);
                    document.getElementById("onoffText").innerHTML = data.value;
                }
                if (ctrlType == "SLI" && !(data.value.toString().substr(5,4)=="QSTN")){
                    document.getElementById("source").value =  parseInt(data.value.toString().substr(5,2),16);
                    document.getElementById("sourceText").innerHTML = data.value;
                }
            });
            socket.on('onkyoReply', function (data) {
                var done = false;
                ctrlType = data.value.toString().substr(2,3);
                document.getElementById("replyTextHex").innerHTML = data.value;
                document.getElementById("replyText10").innerHTML = parseInt(data.value.toString().substr(5,2),16);
                if (ctrlType == "LMD"){
                    setCheckedValue(document.forms['modeForm'].elements['modeForm'],"0x"+data.value.toString().substr(5,2));
                }
                if (ctrlType == "SLI"){
                    setCheckedValue(document.forms['sourceForm'].elements['sourceForm'],"0x"+data.value.toString().substr(5,2));
                }
                if (ctrlType == "PWR"){
                    var val = parseInt(data.value.toString().substr(5,2),16);
//                  setCheckedValue(document.forms['powerForm'].elements['powerForm'],"0x"+data.value.toString().substr(5,2));
                    document.getElementById("myonoffswitch").checked = (data.value.toString().substr(6,1) != 0);

//                  console.log(ctrlType);
//                  If (val == 1) {
//                      document.getElementById("myonoffswitch").checked = true;
//                  }
//                  If (data.value.toString().substr(6,1)=='0') {
//                      document.getElementById("myonoffswitch").checked = false;
//                  } else {
//                      document.getElementById("myonoffswitch").checked = true;
//                  };
//                  document.getElementById('myonoffswitch').click();
                }
                if (ctrlType == "MVL" && done == false){
                    document.getElementById("inputSlider").value = parseInt(data.value.toString().substr(5,2),16);
                    document.querySelector('.results').innerHTML = parseInt(data.value.toString().substr(5,2),16);
                    done = true;                        
                }
            });

            function showVolume(newValue) {                     
                document.getElementById("sliderVolText").innerHTML="\!1MVL"+("0" + Number(newValue).toString(16)).slice(-2)+"\r\n";
                socket.emit('toOnkyo', { value: "\!1MVL"+("0" + Number(newValue).toString(16)).slice(-2)+"\r\n" });
            }

            function showMode(newValue) {
                document.getElementById("modeText").innerHTML="\!1LMD"+("0" + Number(newValue).toString(16)).slice(-2)+"\r\n";
                socket.emit('toOnkyo', { value: "\!1LMD"+("0" + Number(newValue).toString(16)).slice(-2)+"\r\n" });
            }

            function showSource(newValue) {
                document.getElementById("sourceText").innerHTML="\!1SLI"+("0" + Number(newValue).toString(16)).slice(-2)+"\r\n";
                socket.emit('toOnkyo', { value: "\!1SLI"+("0" + Number(newValue).toString(16)).slice(-2)+"\r\n" });
            }

//          function showPower(newValue) {
//              document.getElementById("powerText").innerHTML="\!1PWR"+("0" + Number(newValue).toString(16)).slice(-2)+"\r\n";
//              socket.emit('toOnkyo', { value: "\!1PWR"+("0" + Number(newValue).toString(16)).slice(-2)+"\r\n" });
//          }

            function showOnoff(newValue) {
                document.getElementById("onoffText").innerHTML="\!1PWR"+("0" + Number(newValue).toString(16)).slice(-2)+"\r\n";
                socket.emit('toOnkyo', { value: "\!1PWR"+("0" + Number(newValue).toString(16)).slice(-2)+"\r\n" });
            }

            socket.emit('toOnkyo', { value: "\!1LMDQSTN"+"\r\n" });
            socket.emit('toOnkyo', { value: "\!1MVLQSTN"+"\r\n" });
            socket.emit('toOnkyo', { value: "\!1PWRQSTN"+"\r\n" });
            socket.emit('toOnkyo', { value: "\!1SLIQSTN"+"\r\n" });

    </script>
  </body>
</html>

Finalmente o arquivo style.css. Deve ser colocado na mesma pasta que o arquivo index.html.

body {
    text-align: center;
    margin-top: 50px;
    background: #50D0A0;
}

input[type=range]{
    -webkit-appearance: none;
    width: 80%;
}

input[type=range]::-webkit-slider-runnable-track {
    height: 10px;
    background: #ddd;
    border: none;
    border-radius: 3px;
}

input[type=range]::-webkit-slider-thumb {
    -webkit-appearance: none;
    border: none;
    height: 32px;
    width: 32px;
    border-radius: 50%;
    background: /* goldenrod */ #34A7C1;
    margin-top: -12px;
}

input[type=range]:focus {
    outline: none;
}

input[type=range]:focus::-webkit-slider-runnable-track {
    background: #ccc;
}
.radioLeft
{
    text-align:left;
}

.onoffswitch {
    position: relative; width: 90px;
    -webkit-user-select:none; -moz-user-select:none; -ms-user-select: none;

    left: 50%;
    margin-right: -50%;
    transform: translate(-50%, -50%) 

    }
.onoffswitch-checkbox {
    display: none;
}
.onoffswitch-label {
    display: block; overflow: hidden; cursor: pointer;
    border: 2px solid #999999; border-radius: 20px;
}
.onoffswitch-inner {
    display: block; width: 200%; margin-left: -100%;
    transition: margin 0.3s ease-in 0s;
}
.onoffswitch-inner:before, .onoffswitch-inner:after {
    display: block; float: left; width: 50%; height: 30px; padding: 0; line-height: 30px;
    font-size: 14px; color: white; font-family: Trebuchet, Arial, sans-serif; font-weight: bold;
    box-sizing: border-box;
}
.onoffswitch-inner:before {
    content: "ON";
    padding-left: 10px;
    background-color: #34A7C1; color: #FFFFFF;
}
.onoffswitch-inner:after {
    content: "OFF";
    padding-right: 10px;
    background-color: #EEEEEE; color: #999999;
    text-align: right;
}
.onoffswitch-switch {
    display: block; width: 18px; margin: 6px;
    background: #FFFFFF;
    position: absolute; top: 0; bottom: 0;
    right: 56px;
    border: 2px solid #999999; border-radius: 20px;
    transition: all 0.3s ease-in 0s; 
}
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner {
    margin-left: 0;
}
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch {
    right: 0px; 
}

    
por 18.10.2016 / 07:52
0

Você provavelmente precisará de algo mais sofisticado, digamos em Python, já que o protocolo ethernet parece ter algum binário nele, mas, para começar, você pode usar um script bash, digamos, ~/myonkyo :

#!/bin/bash
exec 2>/dev/tty
set -x
tty=/dev/ttyUSB0
stty -F $tty raw clocal -echo
exec 3<>$tty
echo "connection" >&2
while IFS= read -r -d $'\x1a' cmd
do  echo "$cmd" >&3
    echo "!1ECNTX-NR609/60128/DX"
done

Inicie o script a partir da socat em cada conexão:

$ socat tcp-l: 60128, SISTEMA reuseaddr: ~ / myonkyo

Você deve tentar não ser root para isso. Se você precisa ser para ttyUSB0 , use-o temporariamente ou coloque-se no grupo certo (dialout?) Para acesso.

O script é executado em cada conexão e, para depuração, usa o /dev/tty no qual você o executa como stderr. Ele abre a porta serial como fd 3. Ele lê a linha de entrada que termina com o caractere "EOF" 0x1a em cmd , grava isso na porta serial e grava em stdout, ou seja, a ethernet, a string de exemplo que você forneceu.

Você terá que reconhecer qual comando de entrada está recebendo, convertê-lo para o equivalente do protocolo rs232 e responder.

    
por 12.09.2016 / 15:08
0

O maior problema com algo parecido é que, se o protocolo é assíncrono (há momentos em que qualquer um dos terminais pode enviar), você terá que fazer o que o socat faz e criar um loop de eventos em torno de select(2) para poder ler quem quer que esteja enviando. Isso exigiria alguma linguagem de programação real (Python, Perl?) E pode levar algum tempo para se acostumar.

No entanto, se o protocolo for síncrono (a qualquer momento apenas uma parte pode falar), você poderá fazer com um programa que leia de uma extremidade por vez. O programa precisará interpretar o protocolo para saber qual parte deve estar falando em um determinado ponto, caso contrário, ele pode ficar preso esperando a entrada do lado errado, sem passar por quaisquer outras gravações.

Para falar com seu programa pela rede, você pode executá-lo com socat tcp-l:60128,reuseaddr exec:/path/to/my_filter_prog (ou implementar o soquete de rede no próprio programa). E, para o outro lado, chamar outro socat do programa para falar com a porta serial, ou abra a porta serial diretamente do programa.

Um script Bash simulado pode ser parecido com isso, usando coproc para abrir dois canais para outro socat para falar com a porta serial. (Note que eu realmente não olhei para a descrição real do protocolo).

coproc socat - file:/dev/ttyUSB0,nonblock,raw,echo=0,crnl,waitlock=/ttyUSB0.lock
serin=${COPROC[0]}
serout=${COPROC[1]}

# assume we have stdin/stdout connected to the other end,
# as with socat tcp-listen:... exec:./this

while true ; do
    # read a command from stdin, pass it through to serial
    read -r cmd 
    echo "$cmd" >&$serout
    # do we need to read and pass another line at this point?
    # might depend on the command, but we need to know that.

    # read the reply and pass it through
    read -r reply <&$serin
    # add/modify something based on the command or the reply?
    echo "$reply" 
    if [ "$cmd" = "!xECNQSTN" ] ; then
        echo "!1ECNTX-NR609/60128/DX"
    fi
done        
    
por 13.09.2016 / 19:36