物联网

在iOS和iPadOS、Android以及Windows中开发基于Wi-Fi、BLE、MQTT等的物联网技术。
万物可以互联,万物也必须互联。

Wi-Fi

无线网络( Wireless )技术被广泛地使用在智能机器、健康监测、共享单车、智能家电等设备的通讯中,使智能设备轻松地与手机、平板和电脑等交换数据信息。
1895年,意大利无线电工程师、企业家伽利尔摩·马可尼( Guglielmo Marconi ),在意大利的博洛尼亚( Bologna )成功地把无线电信号发送到了1.5英里外的地方,他成为了世界上第一台实用的无线电报系统的发明者,并于1909年获得诺贝尔物理学奖( In recognition of his contributions to the development of wireless telegraphy ),被称作无线电之父。
20世纪90年代,电气和电子工程师协会 Institute of Electrical and Electronics Engineers 成立了802.11工作组。1991年,美国 National Cash Register 公司和其合资伙伴 American Telephone & Telegraph 公司(其前身就是美国贝尔电话 Bell Telephone 公司)在荷兰 Neuwegein 开发出了 WaveLAN 技术,即 Wi-Fi 的雏形。于此同时,悉尼大学的研究机构 Commonwealth Scientific and Industrial Research Organisation 也发明了一种无线网技术,于1996年取得了用于无线局域网标准的底层技术专利,该技术专利跟 IEEE 目前的 802.11a 与 802.11g 无线标准有相当密切的关系。
Intersil 、 3Com 、 Nokia 、 Aironet 、 Symbol 和 Alcatel-Lucent 6家公司,共同组成了无线以太网路相容性联盟( Wireless Ethernet Compatibility Alliance ),对不同厂家的产品进行兼容性认证,实现不同厂家设备间的互操作性。联盟成立之后,为了便于市场推广,于2002年10月选取 Wi-Fi 作为其名称, Wi-Fi 有着 wireless fidelity (无线保真)的含义。WECA 改名为 Wi-Fi 联盟( Wi-Fi Alliance )。
Wi-Fi 通常采用 2.4GHz 和 5GHz 超高频波段。

Camellia 微型控制器可以轻松切换,选择工作在 Access Point 或 Station 模式。
Camellia 微型控制器内部的无线芯片设置为 Access Point 模式,手机、平板、电脑等作为 Station 。
Camellia 微型控制器可以进行路由连接参数或 IP 地址设置。
将手机、平板、电脑连接到安装了 Camellia 微型控制器的智能设备作为服务器的无线网络中,即可直接与通讯。这本质是一种无线局域网。
建立上述物理层的连接后,采用 Transmission Control Protocol 的通讯方式,把Camellia 微型控制器作为服务器,电脑、 Surface 等作为客户端,客户端通过 Internet Protocol 地址和应用程序端口号连接服务器。

把办公室或家中连接到 Internet 的路由器作为 Access Point ,而手机、平板、电脑等作为 Station ,Camellia 微型控制器也作为 Station 。 Camellia 微型控制器可以轻松与任何路由器或手机热点自动连接,连接到互联网络。
这种模式下,手机、平板、电脑与智能设备的通讯,必须经过路由器转发。
如果把手机、平板、电脑和 Camellia 微型控制器设置在不同的局域网络中,即各自连接到不同的路由器。例如 Camellia 微型控制器连接到家中的路由器,在数公里外的办公室中,手机、平板、电脑连接到办公室的路由器,两个路由器又同时连接到 Internet ,或是手机、平板、电脑直接通过 4G 或 5G 蜂窝通讯技术连接到 Internet ,就可以通过 MQTT 技术实现远程控制,这样无论你坐在办公室中,或是漫步在公园中,你都可以轻点手机,轻松操作在家中的安装了 Camellia 微型控制器的机器人或智能设备,打造了自己的远程物联网络。


下面详细介绍分别采用 Android 、 Windows 、 iOS 和 iPadOS 等平台的设备作为客户端的软件开发流程。
采用 Socket ,并基于 Transmission Control Protocol 协议发送和接收数据信息。 Socket 即套接字,是应用层与 TCP/IP 协议族通信的中间软件抽象层,表现为一个封装了 TCP/IP 协议族的编程接口( Application Programming Interface ),一个 Socket 实例代表一个主机上的一个应用程序的通信链路。

Station in Android with Java to control Camellia Mini Controller which is an Access Point using socket

1. 检查 Wi-Fi 硬件
Code in Activity:
MainActivity activity = (MainActivity) getActivity();
Context mcontext = activity.getApplicationContext();
ConnectivityManager mConnectivityManager = (ConnectivityManager) mcontext.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo mWiFiNetworkInfo = mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);

// 首先检查手机等设备的 Wi-Fi 功能是否开启,并通过检查手机 IP 地址等方式检测是否与路由器相连通。
if( mWiFiNetworkInfo != null && mWiFiNetworkInfo.isConnected() ) {
   WifiManager mWifiManager = (WifiManager) mcontext.getSystemService(Context.WIFI_SERVICE);
   WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
   // ...
}
else{
   // ...
}

2. 定义变量
Code in Activity:
private Socket mSocket = new Socket();
private OutputStream mOutputStream = null;
private InputStream mInputStream = null;
private InetAddress mIPAddress;
public int mDevicePortWiFi;
// 服务器 IP 地址
public String mDeviceAddressWiFi = "192.168.4.1"; 

private byte[] bufWiFi = new byte[64];
Thread for Connect, Send and Receive:
// 定义线程设置,这里对于连接操作、发送数据操作、接收数据操作分别采用不同的子线程。
private ConnectSeverThread mConnectThread;
private SendtoSeverThread mSendThread;
private ReceivefromSeverThread mReceiveThread;

3. 连接到 Access Point
Code in Activity:
public void ConnecttoServer(){
      mConnectThread = new ConnectSeverThread();
      mConnectThread.start();
}
Code in Thread:
private class ConnectSeverThread extends Thread {
   public void run() {
      try
      {
         mIPAddress = InetAddress.getByName(mDeviceAddressWiFi);
         mSocket = new Socket(mIPAddress, mDevicePortWiFi);
		 
         // 可以设置连接超时限制
         // mSocket = new Socket();
         // SocketAddress socAddress = new InetSocketAddress(mIPAddress, mDevicePortWiFi);
         // mSocket.connect(socAddress, 5000);
		 
		 runOnUiThread(new Runnable() {
            public void run() {
            }
         });
      }
      catch(IOException e)
      {
         e.printStackTrace();
      }
   }
}

4. 发送数据
Code in Activity:
public void sendDataWiFi(byte[] data) {
   if( mSocket.isClosed() == false ) {
      for( i = 0 ; i < 64 ; i++ ){
         bufWiFi[i] = data[i];
      }
      mSendThread = new SendtoSeverThread();
      mSendThread.start();
   }
}
Code in Thread:
// 触发发送数据线程:
private class SendtoSeverThread extends Thread {
   public void run() {
      if( mSocket.isClosed() == false ) {
         try
         {
            mOutputStream = mSocket.getOutputStream();
            mOutputStream.write(bufWiFi);
         }
         catch(IOException e)
         {
             e.printStackTrace();
         }
      }
   }
}

5. 接收数据
Code in Activity:
public void receiveDataWiFi( ) {
   if( mSocket.isClosed() == false ) {
      mReceiveThread = new ReceivefromSeverThread();
      mReceiveThread.start();
   }
}
Code in Thread:
// 触发接收数据线程:
private class ReceivefromSeverThread extends Thread {
   public void run() {
      if( mSocket.isClosed() == false ) {
         try
         {
            mInputStream = mSocket.getInputStream();
            // 中文 GBK 编码
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(mInputStream,"GB18030"));
            // UTF-8 编码
            // BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(mInputStream,"UTF-8"));  
         }
         catch(UnsupportedEncodingException e)
         {
            e.printStackTrace();
         }
      }
   }
}
注意: Android 接收数据时,是以 "\n" 作为每次接收结束的标志,因此其他设备发送数据时,请在数据信息结尾增加 "\n" ,以防止 Android 接收端无休止地等待。


6. 线程间的数据通讯
Code in Activity:
private Handler handler = new Handler() {
   @Override
   public void handleMessage(Message msg) {
      // ...
   }
}
Code in Thread:
Message msg = new Message();
handler.sendMessage(msg);
Android 中不允许子线程操作主界面,因此如何把主界面的数据传递给发送数据子线程,或是如何把接收数据子线程中的数据显示到主界面上,就需要采用线程中的消息传递方法。

7. 断开连接
Android 的 Socket 通讯属于长连接,客户端侧不需要手动关闭。
有时考虑到功耗等问题,服务器端会设置休眠等功能,长时间接收不到客户端的信息时,会自动断开连接,此时在客户端侧可以建立心跳( Heart Beat )函数,定期向服务器发送简短的、无关实际操作的数据信息。




Station in Windows with C# to control Camellia Mini Controller which is an Access Point using socket

1. 检查 Wi-Fi 硬件
Code in Main Class:
NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();
string ipAddress = null;

// 程序中首先检查电脑或Surface的 Wi-Fi 功能是否开启,并通过检查电脑或Surface的 IP 地址等方式检测是否与路由器相连通。
foreach (NetworkInterface adapter in nics)
{
   /// Wi-Fi
   if (adapter.NetworkInterfaceType == NetworkInterfaceType.Wireless80211)
   {
      IPInterfaceProperties ip = adapter.GetIPProperties();
      UnicastIPAddressInformationCollection ipCollection = ip.UnicastAddresses;
      foreach (UnicastIPAddressInformation ipadd in ipCollection)
      {
         if (ipadd.Address.AddressFamily == AddressFamily.InterNetwork)
         { 
            ipAddress = ipadd.Address.ToString();
         }
      }
   }
}

2. 定义变量
Code in Main Class:
// 定义 Socket 变量,同时定义服务器的 IP 地址和端口号变量,不同的应用程序采用不同的端口。
public Socket clientSocket;
public IPAddress wifiserverIP = IPAddress.Parse("192.168.4.1");
public int wifiPort = 14615;

3. 连接到 ACCESS POINT
Code in Main Class:
try
{
   clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
   clientSocket.Connect(wifiserverIP, wifiPort);
}
catch (Exception error)
{
}

4. 发送数据
Code in Main Class:
public bool SocketSendMsg(byte[] data)
{
   if ( (clientSocket == null)&&(clientSocket.Connected) )
   {
      clientSocket.Send(data);
   }
}

5. 接收数据
Code in Main Class:
Thread receiveThread = new Thread(SocketReceiveMsg);
receiveThread.Start();
Code in Thread:
public bool SocketSendMsg()
{
   byte[] result = new byte[1024];
   if ( (clientSocket == null)&&(clientSocket.Connected) )
   {
      int receiveLength = clientSocket.Receive(result);
      string resultStr = Encoding.Default.GetString(result, 0, receiveLength);
      /// 其他编码方式
      // Encoding.ASCII
      // Encoding.UTF8
   }
}
Socket 在接收与发送时都是以字节流的形式传输的,所以发送方与接收方的字节编码一定要保持一致。


6. 线程间的数据通讯
Code in Main Class:
string resultStr = "My dear Camellia!";
mqttInvoke mi = new mqttInvoke(messageSetText);
BeginInvoke(mi, resultStr);
Code in Thread:
public void messageSetText(string str)
{
   /// str is resultStr in Main Class
}

7. 断开连接
有时考虑到功耗等问题,服务器端会设置休眠等功能,长时间接收不到客户端的信息时,会自动断开连接,此时在客户端侧可以建立心跳( Heart Beat )函数,定期向服务器发送简短的、无关实际操作的数据信息。




Station in iOS and iPadOS with Swift to control Camellia Mini Controller which is an Access Point using socket

1. 检查 Wi-Fi 硬件
Code in Main Class:
// 程序中首先检查 iPhone 或 iPad 的 Wi-Fi 功能是否开启,并通过检查 iPhone 或 iPad 的 IP 地址等方式检测是否与路由器相连通。
let reachability = try? Reachability() 
if(reachability != nil){
   if(reachability?.connection == .wifi){
       print("Reachable via WiFi!")
   }
}

let mresult = getMAC()
if((mresult.success)){
   mdevice.text = mresult.ssid
   print("Wi-Fi is OK!")
}

2. 定义变量
Code in Main Class:
let msocket = try? Socket.create()

3. 连接到 Access Point
Code in Main Class:
// 根据服务器的 IP 地址和端口号,进行连接,不同的应用程序采用不同的端口。
if(msocket != nil){
   if(msocket!.isConnected == false){
      do{
         try msocket?.connect(to: "192.168.4.1", port: 14615)
         print("Wi-Fi Connect successfully!")
      }
      catch{
         print("Wi-Fi Connect error: ")
         print(error)
      }
   }
}

4. 发送数据
Code in Main Class:
if( (msocket != nil)&&(msocket!.isConnected) ){
   do{
      try msocket?.write(from: writedata)
   }
   catch{
      print("Message Write error:")
      print(error)
   }
 }

5. 接收数据
Code in Main Class:
if( (msocket != nil)&&(msocket!.isConnected) ){
   var readdata : String = "noresponse"
   do{
      readdata = try (self.msocket?.readString() ?? "noresponse")
      print(readdata)
   }
   catch{
      print("Read error:")
      print(error)
   }
}

6. 断开连接
有时考虑到功耗等问题,服务器端会设置休眠等功能,长时间接收不到客户端的信息时,会自动断开连接,此时在客户端侧可以建立心跳( Heart Beat )函数,定期向服务器发送简短的、无关实际操作的数据信息。





所有核心代码