私は既存のRS-232で制御されたOnkyoレシーバーとローカルネットワーク間のブリッジとして機能するLinuxボックスにサービスを設定したいと思います。これまで議論できる1つの方法はsocatを使用することです。
sudo socat tcp-l:60128,reuseaddr,fork file:/dev/ttyUSB0,nonblock,raw,echo=0,crnl,waitlock=/ttyUSB0.lock &
これにより、ボリューム、ソースなどの設定を変更できましたが、変更を確認する応答には、最新のイーサネット搭載レシーバーの応答に含まれる単純な文字列がありませんでした。したがって、イーサネット対応デバイスの応答を期待する現在の電話アプリケーションを使用して受信機を制御するために使用することはできません。
socatに応答の一部として追加の文字列を含める方法はありますか?それとも、メッセージに追加の文字列を追加するタイミングと場所を決定するために、いくつかのコードの両側にsocatの2つのインスタンスを取得できますか?
以前のRS-232および最新のIP方式のOnkyoプロトコルは、このExcelシートに記載されています(役に立つ場合)。 http://blog.siewert.net/files/ISCP%20AV%20Receiver%20v124-1.xls
すべての最新のOnkyo制御アプリケーションは、「!1ECNTX-NR609/60128/DX」などの応答を期待しながら、自動検出要求「!xECNQSTN」を送信し、状態が変わるたびに要求が変更されます(例:音量を上げる、音量を下げる)小さく))などがあるので、2つのsocatインスタンスを実行し、その間にいくつかのロジックを実行するなどのタスクを実行する必要があるようです。
いつでも新しい最新の受信機を購入することができますが、それはもっと満足できると思います:o)
これを行う方法のアイデアは非常に歓迎されています!
答え1
イーサネットプロトコルにはいくつかのバイナリがあるように見えるため、Pythonのようにもっと複雑なものが必要になるかもしれませんが、まず次のようなbashスクリプトを使用できます~/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
各接続のsocatからスクリプトを起動します。
$ socat tcp-l:60128,reuseaddr system:~/myonkyo
あなたはこの問題の原因にならないように努力する必要があります。必要ttyUSB0
に応じて、一時的に権限を付与またはアクセスできる正しいグループ(ダイヤルアウト?)に自分自身を配置してください。
スクリプトはすべての接続で実行され、デバッグ目的で/dev/tty
stderrとして実行される場所を使用します。シリアルポートをfd 3で開きます。 「EOF」文字0x1aで終わる入力ラインを読み取り、cmd
それをシリアルポートに書き込み、標準出力(イーサネットなど)に提供したサンプル文字列を書き込みます。
受信した入力コマンドを識別し、それを同等のRS232プロトコルに変換して応答する必要があります。
答え2
socat
このようなことの最大の問題は、プロトコルが非同期である場合(時にはどちらか一方で送信できる)、何かを行い、それを中心にイベントループを構築する必要があることです。select(2)
発信者の情報を読む能力。これには実際のプログラミング言語(Python、Perl?)が必要であり、慣れるのに時間がかかることがあります。
ただし、プロトコルが同期されている場合(いつでも1人のパーティーのみが話すことができます)、一度に一端で読み取るプログラムを使用できます。プログラムは、特定の時点でどちらが話すべきかを知るためにプロトコルを解釈する必要があります。それ以外の場合は、追加の書き込みを渡さずに間違った側の入力を待って停止することがあります。
ネットワーク上のプログラムと通信するには、プログラムを実行するか、socat tcp-l:60128,reuseaddr exec:/path/to/my_filter_prog
プログラム自体にネットワークソケットを実装するだけです。もう一方の端では、socat
プログラム内で別のプログラムを呼び出してシリアルポートと通信したり、プログラム内で直接シリアルポートを開くことができます。
シリアルポートと通信するためにcoproc
2つのパイプを別のパイプとして開くシミュレートされたBashスクリプトは次のとおりです。socat
(実際のプロトコルの説明を実際に見ていないことに注意してください。)
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
答え3
私はあなたのアドバイスを聞いて別のことを試しました。結局ボリュームやON/OFFの他に他の機能も追加できるので、Webページにすることにしました。私は、さまざまな情報源から収集した多数の情報に大きく依存していました。みんなに感謝します。私はSockets.ioと一緒にnode.jsを使用していますが、何度も試行錯誤の最後にデバイスからのフィードバックで動作することを得て(ページがロードされたときに状態を初期化することができます)、繰り返しネストされた応答があまり増加しません。 (つまり、数日前までにノードやソケットについて何も知らなかったので、調べるのに少し時間がかかりました!)それがすべてです。この内容をよく知っている人にはきれいではないかもしれませんが、私が望む通りになるようです!これを使用するには、nodeをインストールし、次のコマンドでnode.jsファイルを実行します。 nodejs main.js index.htmlファイルとstyle.cssファイルを「public」というサブディレクトリ(node.jsを含むファイルに相対)に配置します。クリップ) (引用符を除く).次に、ブラウザでホスト(main.jsを実行)をポイントし、URLにポート番号を追加します(この場合は8080)。
ちなみに、これはOnkyo TX-SR804で動作しますが、RS-232-USBアダプタ(Amazonでは数ドル安い)を使用して他のRS-232制御受信機でも動作するはずです。
これは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");
これはブラウザが指すHTML index.htmlファイルです。 node.jsを含むフォルダーのサブフォルダーであるpublicフォルダーに存在する必要があります。ブラウザでNode.jsを実行しているサーバーを指す場合は、ポート番号(この場合は8080)を含めてください。
<!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>
<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>
<label for="mode1"><input type="radio" value="0x01" name="modeForm" id="mode1" onclick="showMode(this.value)"> Direct</label>
<label for="mode2"><input type="radio" value="0x0C" name="modeForm" id="mode2" onclick="showMode(this.value)"> All Ch stereo</label>
<label for="mode3"><input type="radio" value="0x42" name="modeForm" id="mode3" onclick="showMode(this.value)"> THX Cinema</label>
<label for="mode4"><input type="radio" value="0x84" name="modeForm" id="mode4" onclick="showMode(this.value)"> PLllx THX Cinema</label>
<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>
<label for="source2"><input type="radio" value="0x24" name="sourceForm" id="source2" onclick="showSource(this.value)"> FM radio</label>
<!--
<label for="source1"><input type="radio" value="0x01" name="sourceForm" id="source1" onclick="showSource(this.value)"> Video 2</label>
<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>
最後に、style.cssファイルがあります。 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;
}