DIY KVM IP 3.0

这是IP KVM主题的第三个变体,这次是对概念进行了完全修订,让我们开始构建新的东西。 会有很多有趣的事情,不要离开屏幕。 另一个不寻常的设备将出现,我们将丢弃几乎所有的旧组件,回到我们的本地arduino并扮演一个小黑客。

对于刚刚加入的人,以前的情节摘要:

  • 第一篇
    在Arduino和Raspberry Pi上收集了IP KVM,结果证明价格昂贵且视频质量差。
  • 第二篇
    OrangePI和Atmega16u2便宜,但图像质量仍然令人恶心。

最后,在本文中,将消除以前的所有缺点。 将特别强调最大程度地减少组件成本。

按照传统,我们考虑组装设备的组件:

1.我们的老朋友Atmega16u2:

这是从以前的文章中删除的唯一组件。

2.臭名昭著的ESP8266,在本例中为ESP8266-12e:

您可以使用其他版本的ESP8266,只需要考虑端口的数量和位置即可。

3.而且,事实上,是场合英雄LKV373A



多亏了此设备,才可以通过本地网络传输视频。
分辨率高达全高清。

行动计划如下:


  1. 捉迷藏:我们正在寻找他隐藏在IP地址下的网络上的LKV373A
  2. 黑客游戏:填写固件,重置密码并配置LKV373A
  3. 结交新朋友:ESP8266,Arduino IDE和有趣的图片
  4. 庆祝活动的结局。 我们连接所有组件并通过telnet传输击键

LKV373A背景


因此,让我们开始吧! LKV373A或HDMI Extender设备用于直接从hdmi端口捕获图像并广播到本地网络,这些设备也称为hdmi扩展器。 按照制造商的计划,一组此类设备应分别由一个发射机(发射机)和一个接收机(接收机)组成,分别命名为TX和RX。 为了给制造商带来更多的利润,应该成对使用它们。 但是,有一个男人(昵称Danman)是他博客的链接 ,他对博客的工作方式很感兴趣。 他打开Wireshark,嗅探TX设备传输的流量,结果是什么?

视频流的传输无需任何加密,并且使用VLC Player,您可以毫不费力地观看视频。 但是他并没有停下来,“感到”:他拉开了Web界面,TTL,telnet,甚至与程序员一起拉固件。 他在博客上详细介绍了这一点。 首先,我们感兴趣的固件也上传到了那里:IPTV_TX_PKG_v4_0_0_0_20160427.PKG。 在此固件中,具有高级设置的Web界面(与标准界面不同)仅具有更新按钮。 此外,该固件的telnet具有许多要配置的命令。 通过此固件,我们可以为任务重新配置HDMI Extender。 我在github上发布了固件和我需要的所有内容,这是链接 ,稍后我们将需要它,但是现在我们已经完成了理论。 让我们继续练习。

发射器


我们正在网络中寻找LKV373A


我落入了与Danman(y)相同的扩展器的手中。 下面将描述的所有内容特别适用于HDMI扩展器LKV373A版本V3.0!

我们将LKV373A连接到本地网络,打开电源。 现在,让我们尝试确保该设备在ping 192.168.1.238网络上可见。

192.168.1.238是默认IP地址。 如果扩展器不更改旧固件地址,则不管网络上是否有DHCP服务器。 仅当设备无法从DHCP获取地址时,默认情况下,较新的固件版本才使用IP。 如果您收到对ping的回复,请继续。 如果不是,请不要失望,请尝试将扩展器直接连接到计算机的LAN端口并使用嗅探器。

闪烁


找到HDMI Extender,继续使用固件。 让我们转到github并下载在那里放置的所有内容。 现在,通过浏览器打开扩展程序的网络界面,请参见下图:



单击“浏览...”,选择固件,一个名为IPTV_TX_PKG_v4_0_0_0_20160427.PKG的文件,然后单击“升级”。 塔达姆! 固件已完成,现在通过telnet连接到LKV373A以重置密码。

连接命令类似于Telnet 192.168.1.238 9999,其中9999是要连接的端口。 CEP警告:可以使用网络扫描仪找到从DHCP获得的地址。

通过telnet连接


连接时,将出现以下消息:

 ============================== ========IPTV TX Server======== ============================== input> 

我们写list 。 作为响应,我们获得命令列表:

 ============================== ========IPTV TX Server======== ============================== input>list set_group_id get_group_id set_dhcp get_dhcp set_uart_baudrate get_uart_baudrate set_static_ip get_static_ip set_mac_address get_mac_address get_lan_status get_hdcp get_video_lock get_ip_config set_session_key set_device_name get_device_name set_video_bitrate get_video_bitrate set_downscale_mode get_downscale_mode set_video_out_mode get_video_out_mode set_streaming_mode get_streaming_mode get_fw_version get_company_id factory_reset reboot list exit 

要重置所有设置和密码,请使用factory_reset命令。 我们编写一个命令,按Enter键并得到以下图片:

 input>factory_reset Processing factory reset! System will reboot after few seconds! Connection closed by foreign host. 

网页界面


现在,我们可以根据需要配置设备。 让我们进入Web界面。 我们使用标准登录名:admin密码:123456,这里是带有其他设置的“令人垂涎”的Web界面:



尽管Web界面中的机会有所增加,但是对于我们的目的而言,这些机会仍然不够。 特别缺乏在流方面的更多免费设置,它是硬编码的,远非最方便的,您可以流向的IP地址列表。 当然有多播,但最好将其留给电视。 可以绕开限制,以后再说。

因此,他们找到了网络上的设备,将其汇总起来,重置密码。 发送器几乎准备好继续前进到接收器。

接收者


让我们在将要传输流的操作系统的控制下确定设备。 我认为您不会遇到选择的折磨,只有两种选择:

窗户


对于Windows,我尝试了几种播放器,但结果都差不多。 捕获然后播放视频流时,会出现延迟,这主要取决于播放流的播放器。 在各种播放器上,延迟范围从一秒到五秒或更长时间。 最重要的是,在Windows上,事实证明是这样。

让我们开始做生意。 启动VLC Player,从“媒体”下拉菜单的顶部,在网络地址字段中选择“打开URL ...”,输入udp://@:5004 ,将daw放在“显示高级设置”复选框中,然后将其值放在“缓存”字段中,此参数是单独确定的。 该字段中的值越小,延迟越低,但是值太小会导致“伪像”和丢帧,一切都取决于本地网络基础结构。 我可以获得的最佳结果是延迟约一秒钟。 在Linux上,结果在200-300毫秒左右更好。

的Linux


如实践所示,结合使用socat和mplayer程序可获得最佳结果。 Ubuntu已安装在我的计算机上,因此安装socat的命令如下所示:

 sudo apt-get install socat 

Mplayer的安装方式类似:

 sudo apt-get install mplayer 

好吧,按照丹曼的建议:

 sudo iptables -t raw -A PREROUTING -p udp -m length --length 28 -j DROP 

为了从流中删除所谓的“零长度UDP数据包”(零长度的数据包“阻塞”该流),需要此命令。

上线!


接收器准备好了,您可以开始广播了! 我们输入接收计算机的控制台ifconfigipconfig ,具体取决于操作系统,我们记住接收方的IP地址并返回到发送方的Web界面。 我们将打开网络界面,如果已经关闭,请输入用户名,密码并直接在浏览器的地址栏中输入:

 http:///dev/info.cgi?action=streaminfo&udp=n&rtp=y&multicast=n&unicast=y&mcastaddr=&port=5004 

替换您的IP地址,然后按Enter。 此行将配置HDMI扩展器,以将捕获的视频广播到您选择的IP,并关闭多播。

我们在Windows中启动VLC。 或者在Linux终端中我们编写:

 socat UDP-RECV:5004 - | mplayer – 

阿布拉·卡达布拉! 这是我们的(或不是我们的)实时桌面。



因此,通过一些黑客方法,我们强制设备执行所需的操作。

随着视频传输的整理,转到遥控器。

控制权转移


元件选择


因为 HDMI Extender的成本很低,大约为1800卢布,而且由于他们说有点贵,我提出了一个口号:“给2000 KR的IP KVM!”。 卢布汇率将在很大程度上影响这一声明的真实性,但让我们不要谈论可悲的事情,我想相信美好的未来。 为了实现这一目标,我们需要非常便宜的元件,我选择的是ESP8266作为控制器,所有相同的Atmega(8/16/32)u2作为执行器。

当然,您可以考虑其他候选人来担任执行设备(键盘)的角色。 模拟键盘的固件写入LUFA库(某种程度上)。 该库可通过USB连接用于整个AVR系列。 因此,作为键盘仿真器,您可以购买可能更便宜的微控制器。 这是需要考虑的信息,但是现在我们继续。

您可以以约90卢布的价格购买ESP8266,以约100卢布的价格购买Atmega(8/16/32)u2,如果小批量购买5或更多,则价格更便宜。 当然,将需要捆绑微控制器的耗材,但是它们的成本非常小,因此我将不考虑它们。

ESP8266


这个中国工业的奇迹不需要介绍,因此我只想说在这个项目中我使用了ESP8266-12e版本。 当然,您可以使用其他版本,只需要考虑端口的位置,因为 在此版本中,ESP8266端口之一用于接通Atmega(8/16/32)u2的电源,如下图所示。

韧体


ESP8266的固件是在ArduinoIDE环境中编写的,因此让我们从开发人员网站下载最新版本。 接下来,您需要添加对ESP8266的支持-最简单的方法可以在此链接中找到。 在同一页面上,您可以找到许多有用的信息,例如,电源连接方案和TTL。 对于那些不是最新的人,ESP8266 严格使用3.3伏! 如果您不确定自己的能力,最好使用适合USB的板选件,例如NodeMCU:



如果一切准备就绪,请打开ArduinoIDE并复制我的草图:

草绘
 #include <ESP8266WiFi.h> #include <WiFiClient.h> #include <ESP8266WebServer.h> #include <ESP8266mDNS.h> #include <SoftwareSerial.h> #include <HIDKeyboard.h> #define MAX_SRV_CLIENTS 3 HIDKeyboard keyboard; const char* host = "esp8266"; const char* ssid = ""; const char* pass = ""; int rebootdev = 0; int modeswitch = 0; // ,    ESP8266    #define Port1 15 #define Port2 14 #define Port3 12 #define Port4 4 #define Port5 5 //  String ColorB1; String ColorB2; String ColorB3; String ColorB4; String ColorB5; ESP8266WebServer server(80); WiFiClient serverClients[MAX_SRV_CLIENTS]; const char* serverIndex = "<form method='POST' action='/update' enctype='multipart/form-data'><input type='file' name='update'><input type='submit' value='Update'></form><a href='/'>BACK</a>";//Update //    void handleRedirect(){ String content = "<html><head><meta http-equiv='refresh' content='0;/'><head></html>"; server.send(200, "text/html", content); } //  WI-FI void handleLogin(){ String msg = ""; if (server.hasArg("SSID") && server.hasArg("PASSAP")){ if ((server.arg("SSID") != NULL) && (server.arg("PASSAP") != NULL)){ String header = "HTTP/1.1 301 OK\r\r\nLocation: /\r\nCache-Control: no-cache\r\n\r\n"; server.sendContent(header); String web_ssid = server.arg("SSID"); String web_pass = server.arg("PASSAP"); ssid = web_ssid.c_str();//       C pass = web_pass.c_str(); Serial.println(); Serial.print("SSID "); Serial.println(ssid); Serial.print("Pass "); Serial.println(pass); WiFi.begin(ssid, pass); digitalWrite(LED_BUILTIN, LOW); ESP.reset(); return; } msg = "Wrong ssid/password! try again."; Serial.println("Login Failed"); } String content = "<html><body><form action='/' method='POST'>Enter the access point name and password <br>";//  SSID   content += "Name AP:<input type='text' name='SSID' placeholder='SSID'><br>"; content += "Password:<input type='password' name='PASSAP' placeholder='password'><br><br>"; content += "<input type='submit' name='SUBMIT' value='Connect to WI-FI'></form><b><font color='red'>" + msg + "</font></b><br>"; content += "Firmware update <a href='/upload'>UPDATE</a></body></html>"; server.send(200, "text/html", content); } void handleNotFound(){ String message = "File Not Found\n\n"; message += "URI: "; message += server.uri(); message += "\nMethod: "; message += (server.method() == HTTP_GET)?"GET":"POST"; message += "\nArguments: "; message += server.args(); message += "\n"; for (uint8_t i=0; i<server.args(); i++){ message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; } server.send(404, "text/plain", message); } //  int controlPin(int UsePin){ if (UsePin > 0){ int StPort; if (digitalRead(UsePin) == 1) {//      digitalWrite(UsePin, LOW); StPort = 0; } else { digitalWrite(UsePin, HIGH); StPort = 1; } digitalWrite(LED_BUILTIN, HIGH);//    Serial.print("Port "); Serial.print(UsePin); Serial.print("="); Serial.println(StPort); delay(500); digitalWrite(LED_BUILTIN, LOW); return(StPort); } return(-1); } //  int clientConnect(int Seconds){ Serial.print("connection "); for (int i=0; i <= Seconds; i++){ WiFi.begin(ssid, pass); digitalWrite(LED_BUILTIN, LOW); delay(250); digitalWrite(LED_BUILTIN, HIGH); delay(250); Serial.print(" "); Serial.print("."); if (WiFi.status() == WL_CONNECTED) return(0); } return(1); Serial.println(); } void setup(void){ Serial.begin(115200); delay(1000); pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, HIGH); //uint8_t i = 0; if (modeswitch == 0) WiFi.mode(WIFI_STA);//  modeswitch = 0     Serial.println(); Serial.println(); if (clientConnect(30) != 0) modeswitch = 1;//            if (modeswitch == 1){ Serial.println(""); Serial.println("WiFi switch AP mode"); //WiFi.mode(WIFI_AP_STA);    +  .     WiFi.mode(WIFI_AP); WiFi.softAP("TD", "testtest"); Serial.print("AP mode ip adress "); Serial.println(WiFi.softAPIP()); digitalWrite(LED_BUILTIN, LOW); } if (modeswitch != 1){ WiFiServer server(23); Serial.println(); Serial.print("Client mod ip address: "); Serial.println(WiFi.localIP()); digitalWrite(LED_BUILTIN, LOW); } MDNS.begin(host); pinMode(Port1, OUTPUT); pinMode(Port2, OUTPUT); pinMode(Port3, OUTPUT); pinMode(Port4, OUTPUT); pinMode(Port5, OUTPUT); if (modeswitch == 1){ //     server.on("/", handleLogin);//  (SSID)   //  server.on("/upload", HTTP_GET, [](){ server.sendHeader("Connection", "close"); server.send(200, "text/html", serverIndex); }); server.on("/update", HTTP_POST, [](){ server.sendHeader("Connection", "close"); int uperror = Update.hasError(); Serial.printf("UPERR %u\nRebooting...\n",Update.hasError()); if (uperror == 0) server.send(200, "text/html", "Firmware update successfully <a href='/'>BACK</a>"); else server.send(200, "text/html", "Update error <a href='/'>BACK</a>"); ESP.restart(); },[](){ HTTPUpload& upload = server.upload(); if(upload.status == UPLOAD_FILE_START){ Serial.setDebugOutput(true); WiFiUDP::stopAll(); Serial.printf("Update: %s\n", upload.filename.c_str()); if (upload.filename == NULL) { Serial.printf("ERROR: zero file size"); server.send(200, "text/html", "<html> zero file size <a href='/upload'>BACK</a></html>"); return(-1); } uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; if(!Update.begin(maxSketchSpace)){//start with max available size Update.printError(Serial); } } else if(upload.status == UPLOAD_FILE_WRITE){ if(Update.write(upload.buf, upload.currentSize) != upload.currentSize){ Update.printError(Serial); } } else if(upload.status == UPLOAD_FILE_END){ if(Update.end(true)){ //true to set the size to the current progress Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); } else { Update.printError(Serial); } Serial.setDebugOutput(false); } yield(); }); //  server.on("/PoRt1", [] { controlPin(Port1); handleRedirect();//    }); server.on("/PoRt2", [] { controlPin(Port2); handleRedirect(); }); server.on("/PoRt3", [] { controlPin(Port3); handleRedirect(); }); server.on("/PoRt4", [] { controlPin(Port4); handleRedirect(); }); server.on("/PoRt5", [] { controlPin(Port5); handleRedirect(); }); server.on("/reboot", [] { rebootdev = 1;//      handleRedirect(); }); server.onNotFound(handleNotFound);//    //server.begin(); MDNS.addService("http", "tcp", 80); Serial.println(); Serial.println("HTTP server started"); } server.begin(); } void loop(void){ uint8_t i; if (modeswitch == 1) server.handleClient(); delay(100); if (modeswitch != 1){ if (WiFi.status() != WL_CONNECTED) clientConnect(30); else { digitalWrite(5, HIGH);//  ATMEGA16U2 digitalWrite(LED_BUILTIN, LOW); } } WiFiServer server(23); server.setNoDelay(true); server.begin(); keyboard.begin(); while(WiFi.status() == WL_CONNECTED) { if (server.hasClient()){ for(i = 0; i < MAX_SRV_CLIENTS; i++){ //find free/disconnected spot if (!serverClients[i] || !serverClients[i].connected()){ if(serverClients[i]) serverClients[i].stop(); serverClients[i] = server.available(); Serial.println("New client: "); Serial.print(i); continue; } } //no free/disconnected spot so reject WiFiClient serverClient = server.available(); serverClient.stop(); } //check clients for data for(i = 0; i < MAX_SRV_CLIENTS; i++){ if (serverClients[i] && serverClients[i].connected()){ if(serverClients[i].available()){ //get data from the telnet client and push it to the UART String bufkey; while(serverClients[i].available()) bufkey += (serverClients[i].read());//     if (bufkey != 0) { bufkey = bufkey.substring(0, 8);//  int key = bufkey.toInt();//   switch (key){ case 277980: keyboard.pressSpecialKey(F1); break; case 277981: keyboard.pressSpecialKey(F2); break; case 277982: keyboard.pressSpecialKey(F3); break; case 277983: keyboard.pressSpecialKey(F4); break; case 27914953: keyboard.pressSpecialKey(F5); break; case 27914955: keyboard.pressSpecialKey(F6); break; case 27914956: keyboard.pressSpecialKey(F7); break; case 27914957: keyboard.pressSpecialKey(F8); break; case 27915048: keyboard.pressSpecialKey(F9); break; case 27915049: keyboard.pressSpecialKey(F10); break; case 27915051: keyboard.pressSpecialKey(F11); break; case 27915052: keyboard.pressSpecialKey(F12); break; case 1310: keyboard.pressSpecialKey(ENTER); break; case 130: keyboard.pressSpecialKey(ENTER); break; case 27: keyboard.pressSpecialKey(ESCAPE); break; case 8: keyboard.pressSpecialKey(BACKSPACE); break; case 9: keyboard.pressSpecialKey(TAB); break; case 32: keyboard.pressSpecialKey(SPACEBAR); break; case 27915012: keyboard.pressSpecialKey(INSERT); break; case 27914912: keyboard.pressSpecialKey(HOME); break; case 27915312: keyboard.pressSpecialKey(PAGEUP); break; case 27915212: keyboard.pressSpecialKey(END); break; case 27915412: keyboard.pressSpecialKey(PAGEDOWN); break; case 279167: keyboard.pressSpecialKey(RIGHTARROW); break; case 279168: keyboard.pressSpecialKey(LEFTARROW); break; case 279166: keyboard.pressSpecialKey(DOWNARROW); break; case 279165: keyboard.pressSpecialKey(UPARROW); break; case 127: keyboard.pressSpecialKey(DELETE); break; case 27915112: keyboard.pressSpecialKey(DELETE); break; case 4: keyboard.pressSpecialKey((LCTRL | ALT), DELETE); break; //CTRL+ALT+DELETE  Ctrl + d case 6: keyboard.pressSpecialKey(ALT, F4); break; //alt+f4  Ctrl + f case 19: keyboard.pressSpecialKey(ALT | SHIFT); break;//   Ctrl+s case 2: keyboard.pressSpecialKey(LCTRL | SHIFT); break;//   Ctrl+b //    case 17: controlPin(Port1); break;//Ctrl+q case 23: controlPin(Port2); break;//Ctrl+w case 5: controlPin(Port3); break;//Ctrl+e case 18: controlPin(Port4); break;//Ctrl+r case 20: controlPin(Port5); break;//Ctrl+t default: keyboard.pressKey(key); break;//  } keyboard.releaseKey();//  Serial.print(" string: "); Serial.print(key);//  Serial.print(" KEY: "); Serial.write(bufkey.toInt()); bufkey = '0';//    } } } } //check UART for data if(Serial.available()){ size_t len = Serial.available(); uint8_t sbuf[len]; Serial.readBytes(sbuf, len); //push UART data to all connected telnet clients for(i = 0; i < MAX_SRV_CLIENTS; i++){ if (serverClients[i] && serverClients[i].connected()){ serverClients[i].write(sbuf, len); delay(1); } } } } } 


连接所需的库:

选择“ Sketch”选项卡→“ Connect library”→“ Add .ZIP library”。选择名为“ UNO-HIDKeyboard-Library-master(mod).zip”的库,该库是从github下载的。 我们编译并填写固件。 要下载固件,请连接ESP8266 TTL,在ArduinoIDE中设置所需的端口。 设置应如下所示:



要下载固件,您需要将GPIO0端口接地,并通过将Reset端口接地也重启微控制器。 我不会详细介绍,以免给这篇文章增色,Google会为您提供帮助。

ESP8266的逻辑如下:通电后,微控制器会尝试连接Wi-Fi接入点约30秒钟:

如果连接成功 :打开端口23,您可以使用telnet连接到该端口并传输击键。 除按键外,您还可以传输基于“ Ctrl +键”的组合,这些组合将按某些组合。 例如,如果您传递“ Ctrl + d”,则将在受管计算机上按CTRL + ALT + DELETE组合键。

还有一些用于控制ESP8266端口的组合,例如,您可以连接中继并使用“ Ctrl + q”组合来打开和关闭中继,从而远程打开和关闭受管计算机。 您可以在源代码中看到这些以及其他组合。

如果失败 :ESP8266切换到名称为“ TD”,密码为“ testtest”的接入点模式,并打开一个小型Web界面,可通过192.168.4.1进行访问,您可以在其中配置通过WI-FI连接的设置。



因此,该设备可以很容易地连接到另一个接入点。 是的,这里隐藏着美中不足的地方,为了操作我们的IP KVM,我们将需要LAN电缆和Wi-Fi。 这将是设备便宜的成本。

我们处理的是ESP8266,使用的是Atmega16u2,与之前的文章中的所有内容一样,我们刷新了Flip程序,固件在从github下载的存档中。

组件连接


为了正确连接组件,我连接了电路。 请记住,这是一个有条件的方案,它只是为了清楚起见,并不声称是理想的方案。 每个人都可以自由地“提供”她喜欢的东西。



让我解释一些要点:加载ESP8266之后,电路中的晶体管需要打开Atmega16u2,因为当ESP8266打开时,调试信息会传输到所有TX端口,如果Atmega16u2打开并连接到计算机,则会传输具有其量的数据流司机无法应付。 目前尚不知道会发生什么,驱动程序缓冲区可能溢出,效果非常不愉快:按下了数百个键,如果打开了文本编辑器,则会倒出一堆乱码,所有服务键都会卡住,从而无法使用键盘进行操作。 为避免这种情况,需要在加载ESP8266之后给Atmega16u2上电。 在固件中考虑了这一刻。

当然,该方案还有替代方案,但是对于富人或懒惰者来说,这是一个选择。 顺便说一句,此选项不会取消以上操作:



在图中,不带Atmega328p芯片的Arduino UNO连接到NodeMCU的中文版本。 3.3伏特线连接到Arduino上相同电压电平的线,地线和GPIO2引脚(ESP8266)连接到Arduino的TX引脚。

最终检查


我们通过telnet连接到端口23并检查性能。 您可以执行以下操作:在Windows上,使用telnet [ip- ESP8266]命令telnet [ip- ESP8266]

在Linux上,情况会稍微复杂一些:

telnet [ip- ESP8266]命令telnet [ip- ESP8266]然后按控制键,默认为“ Ctrl +”,telnet进入命令模式,然后按“ l”和“ Enter”。 通过这些操作,我们将telnet切换到符号模式。

 $ telnet 192.168.***.*** Trying 192.168.***.***... Connected to 192.168.***.***. Escape character is '^]'. ^] telnet> l 

一切就绪,我们可以检查所创建设备的操作。 让我提醒您,按下“ Alt + Tab”,“ Ctrl + Alt + Del”等可能的组合。 可以在草图中看到。 到此为止,第三个KVM DIY IP版本已经准备就绪。

总结一下


优点:


  • 也许最重要的好处是价格,原来价格适合2000卢布
  • 不应对视频质量有任何抱怨,您可以毫无问题地流式传输到Full HD
  • 通过添加多达四个继电器或其他执行器来扩展功能的能力。

缺点:


  • 需要通过Wi-Fi和Lan电缆连接
  • 与VGA一起使用时,需要使用适配器,这自然会影响成本

总的来说,该设备值得关注,当然,它并不是具有所有“优点”的串行IP KVM的竞争者,但其价格却高得多。对于家庭使用,也许不仅如此,它非常适合。

我想借此机会感谢用户DaylightIsBurning这位善良的人建议挖掘的正确方向。

谢谢您的关注。 待会见!

Source: https://habr.com/ru/post/zh-CN406059/


All Articles