前言
记录自己独自完成的一个完整的展厅中控系统。整体实现思路:灯光线路接上网络继电器,网络继电器接上串口服务器,串口服务器接上路由器,电脑接上路由器,华为paid连接路由器无线网络,实现所有设备连接在一个局域网内。 中控的主要功能: 1.控制灯光的开关 2.控制电脑开关机 3.控制电视机的开机和关机 4.控制内容: a、PPT的上下翻页(有动态效果的) b、视频的暂停、播放、停止 c、系统声音的加减一、硬件环境
交换机:光电信息转换
路由器:创建局域网络,连接不同的网络设备
AC控制器:管理所有AP设备(无线WIFI)
串口服务器:可以直连传统串口设备(不带IP的模块),通过网络协议发送指令给模块
时序电源:提送稳定可控的电源(可通过IP或串口控制各个电源开闭)
网络继电器:通过网络控制灯光开关
电脑:每台电脑作为单独的服务端
电视机:这里用的是电视机作为显示器
华为paid:安卓系统,作为客户端给每台电脑发送指令
二、整体构造
1.灯光控制模块
灯光连接智能照明模块(其实就是一个继电器),智能照明模块再与串口服务器连接
实现:串口服务器作为服务端,自己开发一个客户端,通过网络协议(我这里是UDP和端口号)连接到串口服务器上,发送指令,实现智能照明模块的开口 开闭合控制灯光的开关
注意事项:智能照明模块的波特率要与串口服务器的波特率设置一致,网络协议(在串口服 务器内也可设置)要确定好(UDP或者是TCP);
确定好智能照明模块的指令(每个模块都有自己的指令,说明书或者供应商提供)
代码如下:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;
public class SendInstructionsToLamp : MonoBehaviour
{
public Socket socket;
public int _port = 9600;
public string _ip = "192.168.0.7";
void Start()
{
ConnectLight();
}
/// <summary>
/// 通过TCP协议连接智能照明模块(灯组控制部分)
/// </summary>
public void ConnectLight()
{
try
{
//创建客户端Socket,获得远程ip和端口号
socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
IPAddress ip = IPAddress.Parse(_ip);
IPEndPoint point = new IPEndPoint(ip, _port);
socket.Connect(point);
Debug.Log("连接成功!");
}
catch (Exception)
{
Debug.Log("IP或者端口号错误...");
}
}
bool quankai;
List<byte[]> instructions_temp;
/// <summary>
/// 给智能照明模块发送指令(灯组控制部分)
/// </summary>
/// <param name="instructions">发送指令的list集合</param>
/// <param name="timeSpan">发送指令的时间间隔</param>
public void SendInstructions(List<byte[]> instructions, float timeSpan) {
StartCoroutine(SendInstructions_TimeSpan(instructions, timeSpan));
}
IEnumerator SendInstructions_TimeSpan(List<byte[]> instructions, float timeSpan) {
for (int k = 0; k < 10; k++)//这里为什么要做一个循环发送指令,是因为在实际测试中发现,发送一两次指令不稳定,多发送几次确保指令能被网络继电器接收到
{
for (int i = 0; i < instructions.Count; i++)
{
byte[] b = new byte[8];
socket.Send(instructions[i]);
yield return new WaitForSeconds(0.1f);
}
}
yield return new WaitForSeconds(timeSpan);
//StopAllCoroutines();
}
//连接关闭
void SocketQuit() {
socket.Close();
}
private void OnApplicationQuit()
{
Debug.Log(123456);
SocketQuit();
}
/// <summary>
/// 一键照明指令
/// </summary>
/// <param name="b">true为全开、false为全关</param>
public void One_KeyLightSwitch(bool b) {
if (b)
{
List<byte[]> list_byte1 = new List<byte[]>();
byte[] bt1 = new byte[8] { 0x04, 0x06, 0x00, 0x02, 0x00, 0x01, 0xE9, 0x9F };
list_byte1.Add(bt1);
SendInstructions(list_byte1, 0);
Debug.Log("照明全开!");
}
else {
List<byte[]> list_byte2 = new List<byte[]>();
byte[] bt1 = new byte[8] { 0x04, 0x06, 0x00, 0x01, 0x00, 0x00, 0xD8, 0x5F };
list_byte2.Add(bt1);
SendInstructions(list_byte2, 0);
Debug.Log("照明全关!");
}
}
}
2.主中控模块
代码如下:
客户端
using UnityEngine;
using System;
//using System.IO.Ports;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.Text;
using UnityEngine.UI;
public class SpSend : MonoBehaviour
{
public static SpSend _instance;
public Toggle[] toggles;
public Toggle toggle1;
public Socket socketSend;
Socket socketSend_Tcp_Udp;
string ip;
private void Awake()
{
_instance = this;
}
private void Start()
{
bt_connect_Click("192.168.0.101");
}
public QiehuanVideoPPT_1 qiehuanVideoPPT_1;
public void Send_Udp_String(string s)
{
byte[] b = Encoding.ASCII.GetBytes(s.ToCharArray());
socketSend_Tcp_Udp.Send(b);
}
public void bt_connect_Click(string _ip)
{
if (socketSend != null)
socketSend = null;
try
{
int _port = 8888;
socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
IPAddress ip = IPAddress.Parse(_ip);
IPEndPoint point = new IPEndPoint(ip, _port);
socketSend.Connect(point);
Debug.Log("连接成功!");
}
catch (Exception)
{
Debug.Log("IP或者端口号错误...");
}
}
public void SendMassageToClient(string s) {
//string s = "u:video1.mp4";
Debug.Log(s);
byte[] b = Encoding.ASCII.GetBytes(s.ToCharArray());
socketSend.Send(b);
//socketSend.Close();
}
}
服务端
using UnityEngine;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine.Video;
using RenderHeads.Media.AVProVideo;
public class UDPServer : MonoBehaviour
{
public static UDPServer _instance;
private void Awake()
{
_instance = this;
}
/// <summary>
/// 模拟按键 按键对应表:http://www.doc88.com/p-895906443391.html
/// </summary>
/// <param name="bvk">虚拟键值 </param>
/// <param name="bScan">0</param>
/// <param name="dwFlags">0为按下,1按住,2释放</param>
/// <param name="dwExtraInfo">0</param>
[System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "keybd_event")]
public static extern void Keybd_event(byte bvk, byte bScan, int dwFlags, int dwExtraInfo);
//控制键盘事件
public void KeybdWait(byte t)
{
Keybd_event(t, 0, 0, 0);
Keybd_event(t, 0, 2, 0);
}
public string ipAddress = "192.168.0.10";
public int ConnectPort = 8888;
public string recvStr;
public ReadConfigJson configJson;
public VideoPlayer videoPlayer;
public MediaPlayer mediaPlayer;
public SoundManager soundManager;
Socket socket;
EndPoint clientEnd;
IPEndPoint ipEnd;
string sendStr;
byte[] recvData = new byte[1024];
byte[] sendData = new byte[1024];
int recvLen;
Thread connectThread;
bool isPlayer,baohu=true;
//初始化
void InitSocket()
{
ipEnd = new IPEndPoint(IPAddress.Parse(ipAddress), ConnectPort);
socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
socket.Bind(ipEnd);
//定义客户端
IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
clientEnd = (EndPoint)sender;
print("等待连接数据");
//开启一个线程连接
connectThread = new Thread(new ThreadStart(SocketReceive));
connectThread.Start();
}
public void SocketSend(string sendStr)
{
sendData = new byte[1024];
sendData = Encoding.UTF8.GetBytes(sendStr);
socket.SendTo(sendData, sendData.Length, SocketFlags.None, clientEnd);
}
//服务器接收
void SocketReceive()
{
while (true)
{
recvData = new byte[1024];
recvLen = socket.ReceiveFrom(recvData, ref clientEnd);
recvStr = Encoding.UTF8.GetString(recvData, 0, recvLen);
Debug.Log("收到得信息 " + recvStr);
//控制object要放在主线程里 控制系统虚拟按键要放在线程里——切记切记
if (recvStr == "F5")
{
//recvStr = "";
KeybdWait(116);
}
else if (recvStr == "prve")
{
//recvStr = "";
KeybdWait(37);
}
else if (recvStr == "next")
{
//recvStr = "";
KeybdWait(39);
}
else if (recvStr == "frist")
{
// recvStr = "";
WindowMod._instance.SetFrist("中控播放器");
}
//else if (recvStr == "stop"|| recvStr.Contains(".mp4"))
//{
// KeybdWait(27);
//}
}
}
//连接关闭
void SocketQuit()
{
//关闭线程
if (connectThread != null)
{
connectThread.Interrupt();
connectThread.Abort();
}
//最后关闭socket
if (socket != null)
socket.Close();
Debug.LogWarning("断开连接");
}
// Use this for initialization
void Start()
{
ipAddress = IPManager.GetIP(ADDRESSFAM.IPv4);
ConnectPort = configJson.configInfo.port;
InitSocket(); //在这里初始化server
ToolControlTaskBar.HideTaskBar();
}
public void PlayerInstruction()
{
if (recvStr == "frist")
{
recvStr = "";
WindowMod._instance.SetFrist("ZKPlayer");
KillProcess("PowerPoint 幻灯片放映 - [ppt1.pptx]");
}
else if (recvStr.Contains(".ppsx")&&baohu)
{
baohu = false;
isPlayer = false;
KillProcess("POWERPNT.EXE");
FunctionControl._instance.shiping.SetActive(false);
//videoPlayer.Stop();
//videoPlayer.targetTexture.Release();
mediaPlayer.Stop();
Application.OpenURL(Application.streamingAssetsPath + "/ppt/" + recvStr);
//StartCoroutine(WaitLookForPPT());
//WindowMod._instance.SetFrist(recvStr+" - PowerPoint");
recvStr = "";
baohu = true;
}
else if (recvStr.Contains(".mp4"))
{
isPlayer = true;
mediaPlayer.m_VideoPath = Application.streamingAssetsPath + "/mp4/" + recvStr;
//mediaPlayer.
//videoPlayer.url = Application.streamingAssetsPath + "/mp4/" + recvStr;
//videoPlayer.Play();
mediaPlayer.OpenVideoFromFile(MediaPlayer.FileLocation.RelativeToStreamingAssetsFolder, mediaPlayer.m_VideoPath);
mediaPlayer.Play();
recvStr = "";
FunctionControl._instance.shiping.SetActive(true);
WindowMod._instance.SetFrist("ZKPlayer");
KillProcess("POWERPNT.EXE");
}
else if (recvStr == "play"&&isPlayer)
{
recvStr = "";
//videoPlayer.Play();
mediaPlayer.Play();
}
else if (recvStr == "pause")
{
//videoPlayer.Pause();
mediaPlayer.Pause();
recvStr = "";
}
else if (recvStr == "stop")
{
isPlayer = false;
//videoPlayer.Stop();
//videoPlayer.targetTexture.Release();
mediaPlayer.Stop();
WindowMod._instance.SetFrist("ZKPlayer");
FunctionControl._instance.shiping.SetActive(false);
KillProcess("POWERPNT.EXE");
recvStr = "";
}
//else if (recvStr!=null && recvStr.Contains(".mp4"))
//{
// videoPlayer.url= Application.streamingAssetsPath + "/mp4/" + recvStr;
// videoPlayer.Stop();
// videoPlayer.Play();
// recvStr = "";
//}
if (recvStr == "volumeup")
{
soundManager.SystemVolumeUp();
recvStr = "";
}
else if (recvStr == "volumedown")
{
soundManager.SystemVolumeDown();
recvStr = "";
}
else if (recvStr == "close")
{
CloseComputer();
recvStr = "";
}
}
private void Update()
{
PlayerInstruction();
if (Screen.width != ReadConfigJson._instance.configInfo.screenWidth)
{
Screen.SetResolution(ReadConfigJson._instance.configInfo.screenWidth, ReadConfigJson._instance.configInfo.screenHeigth, true);
}
}
void OnApplicationQuit()
{
SocketQuit();
ToolControlTaskBar.ShowTaskBar();
}
public void CloseComputer()
{
System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = "/c " + "shutdown -s -t 0";
p.Start();
}
/// <summary>
/// 杀死进程
/// </summary>
/// <param name="processName">应用程序名</param>
void KillProcess(string processName)
{
System.Diagnostics.Process[] processes = System.Diagnostics.Process.GetProcesses();
foreach (System.Diagnostics.Process process in processes)
{
Debug.Log(process.ProcessName);
try
{
if (!process.HasExited)
{
if (process.ProcessName == processName)
{
process.Kill();
UnityEngine.Debug.Log("已杀死进程");
}
}
}
catch (System.InvalidOperationException)
{
//UnityEngine.Debug.Log("Holy batman we've got an exception!");
}
}
}
}
本来视频播放用的是unity自带组件videoplayer,但是展厅的主机没有独立显卡,视频播放起来会卡顿,于是便换成了AVpro。
3.电脑主机的开关机控制
开机:通过网络给电脑发开机指令
注意事项:你的电脑允许网络唤醒,在boss主板设置勾选允许网络唤醒
网卡驱动也要设置允许网络唤醒
如果你发现你的你没有电源管理,请更新网卡驱动
网络唤醒代码
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;
public class WakeUpComputer : MonoBehaviour
{
//通过正则表达式设定MAC地址筛选标准,关于正则表达式请自行百度
const string macCheckRegexString = @"^([0-9a-fA-F]{2})(([/\s:-][0-9a-fA-F]{2}){5})$";
private static readonly Regex MacCheckRegex = new Regex(macCheckRegexString);
//唤醒主要逻辑方法
public static bool WakeUp(string mac)
{
//查看该MAC地址是否匹配正则表达式定义,(mac,0)前一个参数是指mac地址,后一个是从指定位置开始查询,0即从头开始
if (MacCheckRegex.IsMatch(mac, 0))
{
byte[] macByte = FormatMac(mac);
WakeUpCore(macByte);
return true;
}
return false;
}
private static void WakeUpCore(byte[] mac)
{
//发送方法是通过UDP
UdpClient client = new UdpClient();
//Broadcast内容为:255,255,255,255.广播形式,所以不需要IP
client.Connect(IPAddress.Broadcast, 50000);
//下方为发送内容的编制,6遍“FF”+17遍mac的byte类型字节。
byte[] packet = new byte[17 * 6];
for (int i = 0; i < 6; i++)
packet[i] = 0xFF;
for (int i = 1; i <= 16; i++)
for (int j = 0; j < 6; j++)
packet[i * 6 + j] = mac[j];
//唤醒动作
client.Send(packet, packet.Length);
}
private static byte[] FormatMac(string macInput)
{
byte[] mac = new byte[6];
string str = macInput;
//消除MAC地址中的“-”符号
string[] sArray = str.Split('-');
//mac地址从string转换成byte
for (var i = 0; i < 6; i++)
{
var byteValue = Convert.ToByte(sArray[i], 16);
mac[i] = byteValue;
}
return mac;
}
public void Button_Click_WakeUp(string s)
{
WakeUp(s);
//print(WakeUp("4A-BB-6D-61-75-AE"));
//print(WakeUp("E4-3A-6E-36-38-AA"));
}
}
关机:发送指令给电脑关机
注意事项:关机没啥主意事项,很简单
关机代码:
public void CloseComputer()
{
System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = "/c " + "shutdown -s -t 0";
p.Start();
}
4.控制电视机的开关
开关机:RS232红外学习模块+串口服务器,
实现:串口服务器做服务端,RS232红外学习模块与串口服务器直连,自己开发客户端通过网络协议(我这里是UDP和端口号)连接到串口服务器上,发送指令给服务端,从而控制RS232红外学习模块。
(连接方式和设置同智能照明模块一样,接线可能有所区别)
注意事项:红外学习模块学习电视机的开关机频率,同时设置指令;
学习的时候,电视遥控器对准红外学习指示灯,按住遥控器开机键,直到红外学习指示灯 快速连闪3下,学习结束