物联网

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


下面详细介绍分别采用 AndroidWindowsiOS 和 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 )函数,定期向服务器发送简短的、无关实际操作的数据信息。





所有核心代码