📜 ⬆️ ⬇️

With remote control for life or laziness - the engine of progress

image
Picture to attract attention, the similarity with real life distant

I'll write another article. About one of your projects from the previously mentioned folder "Projects / 4Fun". This project started as 4Fun, and ended as 4Use. That is, it is used periodically to this day. And it was like that ...


')

Problem one


We all love to watch TV. Well, almost all love. I'm not an exception. But to watch TV, you need to have it. But with this I had some problems. I did not have his (telly). And it was not because usually the TV sets were attached to the rented apartments in which I lived. But then there was one apartment without TV. And this problem had to be solved somehow.

Solution one


Being a miser for a living , quite an economical person, I decided not to buy a telly, but a TV tuner - this is probably the first thought that should come to my head in a similar situation. Thought - done. One of my colleagues just wanted to sell a TV tuner. Here is such, approximately:
image

In general, I bought it. Yes, just was ...

Problem two


It turned out that the remote control was irretrievably lost and could not be restored. And I would like to lie on the couch to switch channels and adjust the sound (ordinary and understandable joys). Then connected to the brain department responsible for finding solutions. Immediately it seemed obvious - to find another remote control and somehow make them friends with the tuner. But it did not fit, because along with the native remote, the signal receiver was also lost (it seems that it was a long time ago). Well, somehow it is not in programmer, perhaps, “we have our own way” (c). Therefore, it was suggested ...

Solution two


To watch TV, I use my native tuner card - BeholdTV. You can switch channels in it using the “up” and “down” keys, adjust the sound “right” / “left”, etc. Therefore, I thought of the following: write a server on a computer that will emulate key presses, and a client on a mobile phone will send the codes of the necessary keys to the server, and everything will be fine. So in the end it turned out (good).

The server was written under Windows, in C ++ and WinAPI. It's simple: we start a stream for Broadcast over UDP messages of the type “I am a server for managing a TV set” and are waiting for the clients to connect. So any client will be able to find out about the location of the server, and no IP hardcode will be needed. And so do the right thing (I think).
The client connects, the server starts listening to incoming commands. As soon as I heard something, it emulates pressing a key. Everything is simple and fit in one file:
Server code
// Roco.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <winsock2.h> #pragma comment(lib, "Ws2_32.lib") void broadcastThreadFunction(void *context) { const SOCKET *broadcastSocket = (SOCKET*)context; sockaddr_in broadcastSocketServiceInfo; ZeroMemory(&broadcastSocketServiceInfo, sizeof(broadcastSocketServiceInfo)); broadcastSocketServiceInfo.sin_family = AF_INET; broadcastSocketServiceInfo.sin_addr.s_addr = htonl(INADDR_BROADCAST); broadcastSocketServiceInfo.sin_port = htons(28777); static const char broadcastMessage[] = "ROCO-BROADCAST-MESSAGE"; do { const int result = sendto(*broadcastSocket, broadcastMessage, sizeof(broadcastMessage), 0, (SOCKADDR*)&broadcastSocketServiceInfo, sizeof(broadcastSocketServiceInfo)); if (result == SOCKET_ERROR && ::WSAGetLastError() == WSAENOTSOCK) { break; } ::Sleep(300); } while (true); _endthread(); } int _tmain(int argc, _TCHAR* argv[]) { if (argc >= 2 && _tcscmp(argv[1], _T("/silent")) == 0) { ::ShowWindow(::GetConsoleWindow(), SW_HIDE); } WSADATA wsaData; ZeroMemory(&wsaData, sizeof(wsaData)); printf("Initializing network... "); int result = ::WSAStartup(MAKEWORD(2,2), &wsaData); if (result == NO_ERROR) { printf("Done.\n"); printf("Creating broadcast socket... "); const SOCKET broadcastSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (broadcastSocket != INVALID_SOCKET) { printf("Done.\n"); static const BOOL onValue = TRUE; setsockopt(broadcastSocket, SOL_SOCKET, SO_BROADCAST, (const char*)&onValue, sizeof(onValue)); printf("Starting broadcast thread... "); HANDLE broadcastThreadHandle =(HANDLE)_beginthread(broadcastThreadFunction, 0, (void*)&broadcastSocket); if (broadcastThreadHandle != INVALID_HANDLE_VALUE) { printf("Done.\n"); printf("Creating listen socket... "); const SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (listenSocket != INVALID_SOCKET) { printf("Done.\n"); printf("Binding listen socket... "); sockaddr_in listenSocketServiceInfo; ZeroMemory(&listenSocketServiceInfo, sizeof(listenSocketServiceInfo)); listenSocketServiceInfo.sin_family = AF_INET; listenSocketServiceInfo.sin_addr.s_addr = htonl(INADDR_ANY); listenSocketServiceInfo.sin_port = htons(28666); result = bind(listenSocket, (SOCKADDR*)&listenSocketServiceInfo, sizeof(listenSocketServiceInfo)); if (result != SOCKET_ERROR) { printf("Done.\n"); printf("Listening for incoming connection... "); result = listen(listenSocket, SOMAXCONN); if (result != SOCKET_ERROR) { printf("Done.\n"); unsigned connectionIndex = 0; do { printf("Accepting incoming connection #%d... ", connectionIndex + 1); ::ResumeThread(broadcastThreadHandle); SOCKET commandSocket = accept(listenSocket, NULL, NULL); if (commandSocket != INVALID_SOCKET) { printf("Done.\n"); ::SuspendThread(broadcastThreadHandle); printf("Sending PING to command socket... "); static const char ping[] = "PING"; result = send(commandSocket, ping, sizeof(ping), 0); if (result != SOCKET_ERROR && result == sizeof(ping)) { printf("Done.\n"); printf("Receiving PONG from command socket... "); static char pong[sizeof("PONG")]; pong[0] = '\0'; result = recv(commandSocket, pong, sizeof(pong), 0); if (result != SOCKET_ERROR && result == sizeof(pong) && strcmp(pong, "PONG") == 0) { printf("Done.\n"); unsigned commandIndex = 0; do { printf("Waiting for command #%d...\n", commandIndex + 1); static char command[2]; ZeroMemory(command, sizeof(command)); result = recv(commandSocket, command, sizeof(command), 0); if (result != SOCKET_ERROR && result == sizeof(command)) { enum { CC_KEY_DOWM = 1, CC_KEY_UP = 0 }; const char commandCode = command[0]; const char keyCode = command[1]; static const char res = 1; switch (commandCode) { case CC_KEY_DOWM: { printf("KEY_DOWN(%d)\n", keyCode); keybd_event(keyCode, 0, 0, 0); send(commandSocket, &res, sizeof(res), 0); } break; case CC_KEY_UP: { printf("KEY_UP(%d)\n", keyCode); keybd_event(keyCode, 0, KEYEVENTF_KEYUP, 0); send(commandSocket, &res, sizeof(res), 0); } break; default: { printf("Invalid command received - %d!\n", commandCode); } break; } } else { printf("Could not receive command from socket (error - %d)!\n", ::WSAGetLastError()); break; } ++commandIndex; } while (true); } else { printf("\nCould not receive PONG from command socket (error - %d)!\n", ::WSAGetLastError()); } } else { printf("\nCould not sent PING to command socket (error - %d)!\n", ::WSAGetLastError()); } } else { printf("\nCould not accept incoming connection (error - %d)!\n", ::WSAGetLastError()); } ++connectionIndex; } while (true); } else { printf("\nCould not listen for incoming connection (error - %d)!\n", ::WSAGetLastError()); } } else { printf("\nCould not bind listen socket (error - %d)!\n", ::WSAGetLastError()); } closesocket(listenSocket); } else { printf("\nCould not create listen socket (error - %d)!\n", ::WSAGetLastError()); } } else { printf("\nCould not start broadcast thread!\n"); } ::ResumeThread(broadcastThreadHandle); closesocket(broadcastSocket); ::WaitForSingleObject(broadcastThreadHandle, INFINITE); } else { printf("\nCould not create broadcast socket (error - %d)!\n", ::WSAGetLastError()); } ::WSACleanup(); } else { printf("\nWSAStartup failed (error - %d)!", result); } return 0; } 



Starts the server along with the prominent. Server console utility (convenient for viewing logs, if that), so this line is needed right after launch:
 ::ShowWindow(::GetConsoleWindow(), SW_HIDE); 


I have a mobile phone on Android, so the client wrote a native one, on the edge. It turned out such a super-mega interface:


The client source is also pretty simple. We generate the interface programmatically, on each button hangs the sending of the key code on the server. When the client starts, we look for the location of the server, connect. It looks like this:
Client code
 package com.dummy.roco; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetSocketAddress; import java.net.Socket; import java.util.Timer; import java.util.TimerTask; import android.net.wifi.WifiManager; import android.net.wifi.WifiManager.MulticastLock; import android.os.Bundle; import android.os.StrictMode; import android.os.Vibrator; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup.LayoutParams; import android.widget.Button; import android.widget.LinearLayout; public class RemoteControlActivity extends Activity { protected static class ButtonInfo { public final String text_; public final int code_; public ButtonInfo(final String text, final int code) { text_ = text; if (code != 0) { code_ = code; } else { code_ = text.codePointAt(0); } } } protected static class CommandButton extends Button { protected ButtonInfo buttonInfo_; protected Socket commandSocket_; protected Vibrator vibrator_; protected Timer commandTimer_; protected final int COMMAND_DELAY = 200; public CommandButton(final Context context, final ButtonInfo buttonInfo, final Socket commandSocket, final Vibrator vibrator) { super(context); buttonInfo_ = buttonInfo; commandSocket_ = commandSocket; vibrator_ = vibrator; setText(buttonInfo_.text_); setTextSize(getTextSize()); setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: startCommandTimer(); break; case MotionEvent.ACTION_UP: stopCommandTimer(); break; } return false; } }); } protected void sendCommand(final int commandCode, final int buttonCode) { final byte command[] = { (byte) commandCode, (byte) buttonCode }; try { commandSocket_.getOutputStream().write(command); } catch (Exception exception) { exception.printStackTrace(); } } public void startCommandTimer() { vibrator_.vibrate(10); sendCommand(CC_KEY_DOWM, buttonInfo_.code_); commandTimer_ = new Timer(); commandTimer_.schedule(new TimerTask() { @Override public void run() { sendCommand(CC_KEY_DOWM, buttonInfo_.code_); } }, COMMAND_DELAY, COMMAND_DELAY); } public void stopCommandTimer() { commandTimer_.cancel(); commandTimer_.purge(); commandTimer_ = null; sendCommand(CC_KEY_UP, buttonInfo_.code_); vibrator_.vibrate(10); } } protected static final ButtonInfo buttonInfos_[][] = { { new ButtonInfo("1", 0), new ButtonInfo("2", 0), new ButtonInfo("3", 0) }, { new ButtonInfo("4", 0), new ButtonInfo("5", 0), new ButtonInfo("6", 0) }, { new ButtonInfo("7", 0), new ButtonInfo("8", 0), new ButtonInfo("9", 0) }, { new ButtonInfo("¾", 8), new ButtonInfo("↑", 38), new ButtonInfo("¤", 77) }, { new ButtonInfo("←", 37), new ButtonInfo("®", 13), new ButtonInfo("→", 39) }, { new ButtonInfo("§", 32), new ButtonInfo("↓", 40), new ButtonInfo("«", 27) } }; protected static final int CC_KEY_DOWM = 1; protected static final int CC_KEY_UP = 0; protected final Socket commandSocket_ = new Socket(); protected Vibrator vibrator_; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); vibrator_ = (Vibrator) getSystemService(VIBRATOR_SERVICE); final LinearLayout mainLayout = new LinearLayout(this); mainLayout.setOrientation(LinearLayout.VERTICAL); for (int i = 0; i < buttonInfos_.length; ++i) { final LinearLayout rowLayout = new LinearLayout(this); rowLayout.setOrientation(LinearLayout.HORIZONTAL); for (int j = 0; j < buttonInfos_[i].length; ++j) { final CommandButton button = new CommandButton(this, buttonInfos_[i][j], commandSocket_, vibrator_); final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); layoutParams.weight = 1.0f; rowLayout.addView(button, layoutParams); } final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); layoutParams.weight = 1.0f; mainLayout.addView(rowLayout, layoutParams); } setContentView(mainLayout, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); MulticastLock multicastLock = null; DatagramSocket broadcastSocket = null; try { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .permitAll().build()); final WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); if (wifiManager != null) { multicastLock = wifiManager .createMulticastLock("ROCO-MulticastLock"); } if (multicastLock != null) { multicastLock.acquire(); } broadcastSocket = new DatagramSocket(28777); broadcastSocket.setBroadcast(true); broadcastSocket.setSoTimeout(1000); final byte[] datagramPacketData = new byte["ROCO-BROADCAST-MESSAGE\0" .length()]; final DatagramPacket datagramPacket = new DatagramPacket( datagramPacketData, datagramPacketData.length); broadcastSocket.receive(datagramPacket); if (new String(datagramPacketData) .compareTo("ROCO-BROADCAST-MESSAGE\0") != 0) { throw new Exception("Could not get ROCO server address!"); } commandSocket_.setSoTimeout(500); commandSocket_.connect(new InetSocketAddress(datagramPacket .getAddress().getHostAddress(), 28666), commandSocket_ .getSoTimeout()); final byte ping[] = new byte["PING\0".length()]; commandSocket_.getInputStream().read(ping); if (new String(ping).compareTo("PING\0") != 0) { throw new Exception( "Could not receive PING from command socket!"); } commandSocket_.getOutputStream().write( new String("PONG\0").getBytes()); } catch (Exception exception) { final AlertDialog alertDialog = new AlertDialog.Builder(this) .create(); alertDialog.setCancelable(false); alertDialog.setTitle("Roco: Error"); alertDialog .setMessage("Could not connect to the server!\nError - '" + exception.toString() + "'\n\nExiting..."); alertDialog.setButton(AlertDialog.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); finish(); } }); alertDialog.show(); } finally { if (broadcastSocket != null) { broadcastSocket.close(); } if (multicastLock != null && multicastLock.isHeld()) { multicastLock.release(); } } } @Override protected void onDestroy() { try { commandSocket_.close(); } catch (Exception exception) { exception.printStackTrace(); } super.onDestroy(); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if ((keyCode == KeyEvent.KEYCODE_BACK)) { finish(); } return super.onKeyDown(keyCode, event); } } 



The project was written for a long time, and professionalism does not stand still. Now, perhaps, I would write everything is not so clumsy (perhaps even using the official API , which I learned about after writing the project). However, everything works stably and periodically used.

There is an interesting side effect - if you watch not a TV set, but, say, YouTube, then the player can be paused. And otpauzit too.

In general, it turned out cool, useful, cheap and cheerful.

The project is called “Roco”. Who can guess why this is so - write in the comments. Guessing glory and respect.

PS By the way, I rarely watch TV recently. Mostly downloaded movies or online. Good thing I did not buy it then. But now I'm thinking about buying. Some paradox turns out ...

Source: https://habr.com/ru/post/224429/


All Articles