這是Hello, Lithosphere Tutorials系列教程中的其中一篇。
感覺介紹用C/C++,用Python來開發(fā)物聯(lián)網(wǎng)應(yīng)用的文章比較多,用Java來做物聯(lián)網(wǎng)的文章比較少。
這篇文章,介紹如何使用Java技術(shù)來開發(fā)一個物聯(lián)網(wǎng)的簡單例子。我們從手機App上,遙控硬件板上的LED燈亮燈、熄燈、閃燈。
我們學習如何將樹莓派硬件板連接到服務(wù)器,并使用Java代碼來控制硬件板的GPIO。
當一切連通,我們通過手機App來遙控硬件板LED燈。
我們使用100%的純Java代碼來完成這一切。
以下,為教程的具體內(nèi)容。
?
Hello, Actuator!!!
歡迎進入IoT的世界?。?!讓我們來學習如何讓硬件板上的LED燈閃起來
在這第一篇教程中,讓我們使用一塊樹莓派硬件板,通過GPIO連接LED燈。
然后,我們使用Lithosphere IoT平臺提供的Actuator插件,只需要做簡單的開發(fā),我們就可以用App遙控IoT硬件板設(shè)備來讓LED閃燈了。
?文章來源:http://www.zghlxwxcb.cn/news/detail-770201.html
1 前置條件:
Java = 8(樹莓派端)
Java >= 11(服務(wù)器端)
Granite Lite IoT XMPP Server
點擊這里下載Granite Lite IoT XMPP Server
Raspberry Pi Zero W硬件板
LED模塊
幾顆杜邦線
下圖是這個教程中使用到的硬件。
?
2 概念
這篇教程,會涉及到IoT設(shè)備的硬件控制接口,以及IoT終端設(shè)備的分發(fā)部署。簡單介紹相關(guān)概念如下。
?
2.1 GPIO
GPIO是英文General-Purpose Input/Output的縮寫。
簡單來說,GPIO就是硬件板上一組引針,這些引針可以用來控制電信號輸入輸出。
由于這些引針是可以編程來控制的,從而可以實現(xiàn)和外部的電路模塊板通訊。
下圖是Raspberry Pi Zero W上的GPIO接口。
?
在本教程中,我們使用GPIO來連接外部的LED模塊,從而實現(xiàn)LED小燈的程序控制。
?
2.2 設(shè)備注冊
在真實的IoT應(yīng)用中,當一個IoT設(shè)備從庫房中拿出來時,它并不能接通電源后立馬就開始工作。
基于安全性和利于管理的考慮,IoT設(shè)備要能夠開始工作,需要經(jīng)過一個“設(shè)備注冊”的步驟。
在這個“設(shè)備注冊”的過程中,應(yīng)用一般會:
- 檢查設(shè)備的合法性
- 檢查設(shè)備是否在此時被允許進入網(wǎng)絡(luò)
- 登記設(shè)備相關(guān)信息到系統(tǒng)中
- 配置設(shè)備 - 例如對設(shè)備進行網(wǎng)絡(luò)配置;發(fā)放安全token等。
在不同的應(yīng)用和不同標準中,“設(shè)備注冊”可能會有不同的術(shù)語和叫法。
例如,在LoRaWAN標準中,“設(shè)備注冊”被叫做"End Device Activation"。官方文檔里,這樣描述End Device Activation:
All end devices that participate in a LoRaWAN network must be activated. There are two methods of activation you can choose: over-the-air activation (OTAA) or activation by personalization (ABP).
Lithosphere IoT Platform基于XMPP通訊協(xié)議。在標準XMPP協(xié)議擴展XEP-0077 (In-Band Registration)里,定義了如何實現(xiàn)IM用戶在線注冊的功能。
遵從XMPP的習慣,Lithosphere IoT平臺里的“設(shè)備注冊”,被稱為IBTR(In-Band Thing Registration),即智能物件在線注冊。
Lithosphere IoT平臺,已經(jīng)內(nèi)置實現(xiàn)了IBTR功能?;诜?wù)器端和客戶端的IBTR插件,可以快速開發(fā)及靈活定制“設(shè)備注冊”功能。
在本篇教程中,我們會看到相關(guān)的內(nèi)容。
?
3 安裝和配置Raspberry Pi
樹莓派Zero W,我在淘寶上買153元RMB,記住要買一個SD卡來插上。
我們需要做的第一件事,是給樹莓派裝上系統(tǒng)。
在沒有監(jiān)視器和鍵盤的情況下,給樹莓派裝上OS。在Raspberry Pi的官方的文檔里,把這叫做Headless Setting Up??梢詤⒖枷旅娴逆溄樱私馊绾巫鯤eadless Setting Up。
官方Headless Setting Up文檔
如果你不想閱讀英文,可以看XDongger這篇關(guān)于樹莓派安裝和配置的文章。
安裝和配置樹莓派
?
4 連接硬件
我們先來看看LED模塊長啥樣。
?
這個LED模塊在淘寶上買,5塊錢一個。
我們可以看到,它有3個引腳:
引腳名 | 作用 |
---|---|
GND | 地線 |
VCC | 5V電源 |
IN | LED控制 |
我們需要將LED模塊,通過樹莓派硬件板上的GPIO接口,連接到硬件板上。
如何對接呢?讓我們我們來看看樹莓派的GPIO接口。
?
我們將LED的VCC接口,接在GPIO的5V Powerd引腳上。
我們將LED的GND接口,接在GPIO的Ground引腳上。
我們將LED的IN接口,接在GPIO 8引腳上。
接好后,看上去是這樣的。
?
5 配置硬件板基礎(chǔ)軟件環(huán)境
在2 安裝和配置Raspberry Pi步驟中,我們只是得到了一個初始化版本的RaspBerry Pi OS操作系統(tǒng)。要想在上面能運行Lithosphere IoT平臺的終端程序,還得做以下的配置。
?
5.1 安裝OpenJDK
登錄到樹莓派硬件板。
ssh pi@192.168.1.180
注:
- 192.168.1.180是樹莓派板的網(wǎng)絡(luò)地址。請改為你配置樹莓派板時,指定的靜態(tài)IP地址。
- pi為樹莓派用戶。請改為你配置樹莓派時,初始化創(chuàng)建的用戶名。
執(zhí)行以下指令安裝OpenJDK 8。
sudo apt-get install openjdk-8-jdk
注:
- 為何要安裝OpenJDK 8版本?
Raspberry Pi OS默認自帶的OpenJDK版本是OpenJDK 11。這意味著,如果你執(zhí)行以下指令。系統(tǒng)會默認安裝OpenJDK 11。
sudo apt-get install default-jdk
這個默認版本的JDK,在Raspberry Pi Zero W上不能正常工作。原因是因為Raspberry Zero W使用ARMv6版本CPU。而Raspberry Pi OS默認帶的OPenJDK 11,僅適用于ARMv7和ARMv8版本的CPU。
當然,有一些其它辦法可以在Raspberry Pi Zero W上來安裝OpenJDK 11。
在這篇教程里,我選擇改裝OpenJDK 8的解決方案??瓷先?,這是一個簡單有效的解決方案。
?
5.2 安裝WiringPi
我們在后續(xù)開發(fā)中,會使用Pi4J庫V1.3版本來控制GPIO。Pi4J是一個開源Java庫,它使用JNI來調(diào)用C程序編寫的WiringPi庫來控制GPIO。
所以,我們需要安裝WiringPi。
Raspberry Pi OS并沒有自帶WiringPi軟件包。我們通過以下指令安裝WiringPi。
wget https://github.com/WiringPi/WiringPi/releases/download/2.61-1/wiringpi-2.61-1-armhf.deb
sudo dpkg -i wiringpi-2.61-1-armhf.deb
?
6 開發(fā)協(xié)議包
為何我們需要單獨開發(fā)一個包含協(xié)議對象的協(xié)議包?
這是因為我們使用叫OXM(Object-XMPP Mapping)的技術(shù)。簡單來說,我們希望在開發(fā)中,能夠屏蔽掉XMPP協(xié)議實現(xiàn)的細節(jié),簡化開發(fā)過程。
關(guān)于OXM,可以參考概念文檔中的OXM章節(jié)
使用OXM,我們會定義的協(xié)議對象,這些協(xié)議對象,在客戶端和服務(wù)器端,都會被使用。協(xié)議對象是可復(fù)用的。
所以,我們最好開發(fā)一個單獨的協(xié)議包,已便于在后面客戶端和服務(wù)器端復(fù)用這些協(xié)議對象。
?
6.1 創(chuàng)建協(xié)議工程
創(chuàng)建hello-actuator-protocol工程,pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.thefirstlineofcode.basalt</groupId>
<artifactId>com.thefirstlineofcode.basalt</artifactId>
<version>1.1.0-RELEASE</version>
</parent>
<groupId>com.thefirstlineofcode.lithosphere.tutorials.helloactuator</groupId>
<artifactId>hello-actuator-protocol</artifactId>
<name>Hello actuator protocol</name>
<version>0.0.1-RELEASE</version>
<dependencies>
<dependency>
<groupId>com.thefirstlineofcode.basalt</groupId>
<artifactId>basalt-oxm</artifactId>
</dependency>
</dependencies>
<repositories>
<repository>
<id>com.thefirstlineofcode.releases</id>
<name>TheFirstLineOfCode Repository - Releases</name>
<url>http://120.25.166.188:9090/repository/maven-releases/</url>
</repository>
</repositories>
</project>
代碼說明
- 指定parent為com.thefirstlineofcode.basalt:com.thefirstlineofcode.basalt,這樣可以直接引用basalt parent pom的依賴管理配置。
?- 因為要使用OXM(Object-XMPP Mapping),所以依賴basalt-oxm庫。
?- 目前,Lithosphere的開源庫,僅被部署在TheFirstLineOfCode的私有的maven服務(wù)器上。為了構(gòu)建時能夠正確找到開源依賴庫,需要配置com.thefirstlineofcode.releases的repository。
<repositories> <repository> <id>com.thefirstlineofcode.releases</id> <name>TheFirstLineOfCode Repository - Releases</name> <url>http://120.25.166.188:9090/repository/maven-releases</url> </repository> </repositories>
?
6.2 定義協(xié)議對象
在Hello,Actuator例子里,我們想用App來遙控IoT設(shè)備上的LED燈。我們希望可以遙控LED執(zhí)行:亮燈、熄燈、閃燈。
為此,我們使用以下3個協(xié)議對象:
TurnOn
@ProtocolObject(namespace="urn:leps:things:simple-light", localName="turn-on")
public class TurnOn {
public static final Protocol PROTOCOL = new Protocol("urn:leps:things:simple-light", "turn-on");
}
TurnOff
@ProtocolObject(namespace="urn:leps:things:simple-light", localName="turn-off")
public class TurnOff {
public static final Protocol PROTOCOL = new Protocol("urn:leps:things:simple-light", "turn-off");
}
Flash
@ProtocolObject(namespace="urn:leps:things:simple-light", localName="flash")
public class Flash {
public static final Protocol PROTOCOL = new Protocol("urn:leps:things:simple-light", "flash");
private int repeat;
public Flash() {
this(1);
}
public Flash(int repeat) {
setRepeat(repeat);
}
public int getRepeat() {
return repeat;
}
public void setRepeat(int repeat) {
if (repeat < 1)
throw new IllegalArgumentException("Attribute repeat must be a non-zero positive integer.");
this.repeat = repeat;
}
@Override
public String toString() {
return String.format("Flash[repeat=%d]", repeat);
}
}
代碼說明
- OXM框架會幫我們將Project Object轉(zhuǎn)換成XMPP協(xié)議文檔。當Flash對象的repeat屬性值設(shè)置成5時,會生成下面的XMPP協(xié)議文檔:
<flash xmlns="urn:leps:things:simple-light" repeat=5/>
?
- 不需要擔心"urn:leps:things:simple-light"字符串太長了,會帶來傳輸效率的低下。在Lithosphere IoT平臺下,如果使用BXMPP技術(shù),ProtocolObject(namespace="urn:leps:things:simple-light", localName="flash")這段協(xié)議頭信息,會被轉(zhuǎn)化成3字節(jié)長度的二進制協(xié)議信息。
?- TurnOn和TurnOff,表達一個不帶任何參數(shù)的指令,所以它們被設(shè)計成不帶任何實例屬性的空對象。
?
6.3 定義Model Descriptor
讓我們來給要控制的IoT設(shè)備,定義一個Model Descriptor。
為何需要定義Model Descriptor?
這是因為Lithosphere完全基于插件架構(gòu)。在插件架構(gòu)下,系統(tǒng)功能并不是寫死固化的。當一個插件被部署時,它為系統(tǒng)提供了一組應(yīng)用功能。當這個插件被卸載掉后,它所提供的功能就消失了。
正是因為這種插件架構(gòu)的特性,默認系統(tǒng)是不認識任何IoT設(shè)備的,也不能識別任何IoT控制/數(shù)據(jù)協(xié)議。
當我們基于插件技術(shù),使用XMPP擴展協(xié)議,開發(fā)了某種IoT設(shè)備的一組應(yīng)用功能。我們需要使用平臺提供的擴展點,將插件提供的應(yīng)用功能注冊到系統(tǒng)中。我們需要明確的告知系統(tǒng):現(xiàn)在,我們有這些功能了,相關(guān)功能由XXX插件提供。
最好是能有一個從設(shè)備維度專門描述IoT設(shè)備的對象,能夠便利我們將相關(guān)信息提供給系統(tǒng)。
這個事說起來啰嗦,實操起來其實比較簡單。我們需要在協(xié)議包里,定義一個Model Descriptor類。
public class HatModelDescriptor extends SimpleThingModelDescriptor {
public static final String MODEL_NAME = "HAT";
public static final String DESCRIPTION = "Hello Acuator Thing";
public HatModelDescriptor() {
super(MODEL_NAME, DESCRIPTION, false, null, null, createSupportedActions());
}
private static Map<Protocol, Class<?>> createSupportedActions() {
Map<Protocol, Class<?>> supportedActions = new HashMap<>();
supportedActions.put(Flash.PROTOCOL, Flash.class);
supportedActions.put(TurnOn.PROTOCOL, TurnOn.class);
supportedActions.put(TurnOff.PROTOCOL, TurnOff.class);
return supportedActions;
}
}
代碼說明
- 繼承SimpleThingModelDescriptor,這個基類實現(xiàn)了IThingModelDescriptor接口,提供了一些復(fù)用代碼,可以讓我們編寫Model Descriptor時更省事。
?- IoT設(shè)備的型號名。在這里,我們把這個被遙控閃燈的IoT設(shè)備型號叫做HAT,Hello Actuator Thing的縮寫。
?- 這個IoT設(shè)備是一個Actuator(執(zhí)行器)設(shè)備,它接受Action指令,然后執(zhí)行指令對應(yīng)的操作。它不是Sensor,不需要上報數(shù)據(jù)。它也不觸發(fā)事件,不需要事件通知功能。所以構(gòu)造器里的參數(shù)都是false,null。只有最后一個構(gòu)造器參數(shù),我們用createSupportedActions(),將這個Actuator設(shè)備支持的Action都登記到Model Descriptor中。當然,在這個例子里,IoT設(shè)備只支持3個指令,TurnOn,TurnOff,F(xiàn)lash。
?
6.4 構(gòu)建安裝協(xié)議包
因為需要在后續(xù)開發(fā)客戶端和服務(wù)器端插件包時,引用協(xié)議包,所以我們在hello-actuator-protocol工程里,執(zhí)行構(gòu)建安裝指令,把協(xié)議包安裝到本地maven倉庫。
cd hello-actuator-protocol
mvn clean install
協(xié)議包已經(jīng)開發(fā)完成,你可以參考官方開源倉庫代碼hello-actuator-protocol協(xié)議包工程源碼
?
7 開發(fā)服務(wù)器端插件
7.1 服務(wù)器端插件工程
創(chuàng)建hello-actuator-server目錄,添加pom.xml文件。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.thefirstlineofcode.sand</groupId>
<artifactId>sand-server</artifactId>
<version>1.0.0-BETA3</version>
</parent>
<groupId>com.thefirstlineofcode.lithosphere.tutorials.helloactuator</groupId>
<artifactId>hello-actuator-server</artifactId>
<version>0.0.1-RELEASE</version>
<name>Hello actuator server plugin</name>
<dependencies>
<dependency>
<groupId>com.thefirstlineofcode.sand.server</groupId>
<artifactId>sand-server-things</artifactId>
</dependency>
<dependency>
<groupId>com.thefirstlineofcode.lithosphere.tutorials.helloactuator</groupId>
<artifactId>hello-actuator-protocol</artifactId>
<version>0.0.1-RELEASE</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>com.thefirstlineofcode.releases</id>
<name>TheFirstLineOfCode Repository - Releases</name>
<url>http://120.25.166.188:9090/repository/maven-releases/</url>
</repository>
</repositories>
</project>
代碼說明
- parent設(shè)置為com.thefirstlineofcode.sand:sand-server,可引用parent POM里的以來配置管理。
?- 依賴com.thefirstlineofcode.sand.server:sand-server-things庫,因為我們要使用這個庫里的接口IThingProvider和IThingRegistrationCustomizer來實現(xiàn)相關(guān)功能。
- 依賴hello-actuator-protocol協(xié)議包。
?
7.2 實現(xiàn)IThingProvider
@Extension
public class ThingModelsProvider implements IThingModelsProvider {
@Override
public IThingModelDescriptor[] provide() {
return new IThingModelDescriptor[] {
new HatModelDescriptor()
};
}
}
代碼說明
- 實現(xiàn)IThingProvider接口,在接口方法里,我們將前面開發(fā)的HatModelDescripotor登記到服務(wù)器中。
?- 請注意@Extension標注,它申明了這個類是PF4J的插件擴展。
?
7.3 實現(xiàn)IThingRegistrationCustomizer
在這篇教程的前面章節(jié),我們提到了“設(shè)備注冊”。在Lithosphere平臺里,“設(shè)備注冊”被實現(xiàn)為IBTR(In-Band Thing Registration)協(xié)議。
Lithosphere IoT平臺,提供一個IThingRegistrationCustomizer的接口,來允許開發(fā)人員定制設(shè)備注冊的過程。
下面,我們來看看怎么使用IThingRegistrationCustomizer。
@Extension
public class ThingRegistrationCustomizer extends ThingRegistrationCustomizerAdapter {
private static final String HARD_CODED_REGISTRATION_CODE = "abcdefghijkl";
@Override
public boolean isUnregisteredThing(String thingId, String registrationCode) {
if (!super.isUnregisteredThing(thingId, registrationCode))
return false;
return HARD_CODED_REGISTRATION_CODE.equals(registrationCode);
}
@Override
public boolean isAuthorizationRequired() {
return false;
}
}
代碼說明
- 繼承ThingRegistrationCustomizerAdapter,這個類提供了IThingRegistrationCustomizer接口的默認實現(xiàn)。
?- 重載了isUnregisteredThing方法,這個方法檢查IoT設(shè)備的 Thing ID和Registration Code的合法性。我們看到,實現(xiàn)檢查一個硬編碼寫死的Registration Code。
?- isAuthorizationRequired(),返回false。我們在這個例子中,只檢查Thing ID和Registration Code的合法性。不做人工的設(shè)備注冊授權(quán)。所以我們關(guān)掉Authorization功能。
?- @Extension標注申明這個類是PF4J的插件擴展。
?
7.4 編寫插件配置文件
在src/main/resources目錄下,創(chuàng)建plugin.properties。
plugin.id=hello-actuator-server
plugin.provider=TheFirstLineOfCode
plugin.version=0.0.1-RELEASE
plugin.dependencies=sand-server-things
non-plugin.dependencies=hello-actuator-protocol
代碼說明
- plugin.dependencies,依賴sand-server-things插件。
?- non-plugin.dependencies,依賴hello-actuator-protocol協(xié)議包(非插件格式j(luò)ar)。
?
7.5 構(gòu)建部署服務(wù)器端插件
構(gòu)建hello-actuator-server插件包
cd hello-actuator-server
mvn clean package
將hello-actuator-server插件包和它依賴的hello-actuator-protocol包,把這兩個jar包,copy到服務(wù)器的plugins目錄下。
cp hello-actuator-protocol/target/hello-actuator-protocol-0.0.1-RELEASE.jar granite-lite-iot-1.0.4-RELEASE/plugins
cp hello-actuator-server/target/hello-actuator-server-0.0.1-RELEASE.jar granite-lite-iot-1.0.4-RELEASE/plugins
服務(wù)器端插件已經(jīng)開發(fā)完成,你可以參考官方開源倉庫代碼hello-actuator-server服務(wù)器端插件包工程源碼
?
7.6 檢查Granite Lite XMPP Server狀態(tài)
啟動Granite Lite XMPP Server
cd granite-lite-iot-1.0.4
java -jar granite-server-1.0.4-RELEASE.jar -console
帶-console參數(shù)啟動Granite Lite XMPP Server之后,能夠看到Granite Server Console的界面。
我們可以在Console輸入services命令來檢查Granite XMPP Server的狀態(tài)。
$services
如果能看到所有的services的狀態(tài)都是available,說明granite lite server已經(jīng)被正常的啟動了。
?
可以用plugins命令,來檢查可用的plugins。
$plugins
我們可以看到,hello-actuator-server插件已經(jīng)被部署。
?
7.7 創(chuàng)建測試用戶
在這里,我們還需要解決一個問題。
?
我們想使用手機App來遙控IoT設(shè)備閃燈,我們使用XMPP技術(shù)來做這件事情。
?
在XMPP標準里,要在XMPP網(wǎng)絡(luò)里進行通訊,你需要一個XMPP賬戶。無論你是人還是物,你都需要一個賬戶。你是人,需要一個User賬戶。你是物,你需要一個Thing(IoT)賬戶。
我們需要一個User賬戶,這樣,我們才能夠從手機App,連接到XMPP網(wǎng)絡(luò)中,這才有可能通過XMPP網(wǎng)絡(luò)去遙控IoT設(shè)備。
有一個簡單的辦法,可以創(chuàng)建User用戶。
我們在Granite Lite IoT XMPP Server里,默認部署了sand-demo-server插件。這個插件用來支撐完整的sand-demo演示程序。使用plugins指令,可以看到Granite Lite IoT XMPP Server部署了sand-demo-server插件。
?
sand-demo-server插件,在Granite Server Console里提供了一個創(chuàng)建測試用戶的指令,我們可以用它來創(chuàng)建測試用戶。在Granite Server Console里,我們執(zhí)行以下指令。
sand-demo create-test-users
我們會看到,測試用戶已經(jīng)被創(chuàng)建。
我們在這個教程后面,會使用測試用戶sand-demo來登錄手機App,進行遙控閃燈操作。
使用exit指令,可以退出Granite Server Console,并關(guān)閉Granite XMPP Server。
$exit
?
8 開發(fā)設(shè)備端程序
8.1 設(shè)備端工程
創(chuàng)建hello-actuator-thing目錄,添加pom.xml文件。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.thefirstlineofcode.sand</groupId>
<artifactId>sand-client</artifactId>
<version>1.0.0-BETA3</version>
</parent>
<groupId>com.thefirstlineofcode.lithosphere.tutorials.helloactuator</groupId>
<artifactId>hello-actuator-thing</artifactId>
<version>0.0.1-RELEASE</version>
<name>Hello actuator thing</name>
<dependencies>
<dependency>
<groupId>com.thefirstlineofcode.sand.client</groupId>
<artifactId>sand-client-edge</artifactId>
</dependency>
<dependency>
<groupId>com.pi4j</groupId>
<artifactId>pi4j-core</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>com.thefirstlineofcode.lithosphere.tutorials.helloactuator</groupId>
<artifactId>hello-actuator-protocol</artifactId>
<version>0.0.1-RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>libs/</classpathPrefix>
<mainClass>com.thefirstlineofcode.lithosphere.tutorials.helloactuator.thing.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<descriptors>
<descriptor>src/assembly/descriptor.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>com.thefirstlineofcode.releases</id>
<name>TheFirstLineOfCode Repository - Releases</name>
<url>http://120.25.166.188:9090/repository/maven-releases/</url>
</repository>
</repositories>
</project>
代碼說明
- POM繼承com.thefirstlineofcode.sand:sand-client,以便復(fù)用父POM里的依賴配置管理。
?- hello-actuator-thing是一個獨立運行的Java程序。我們使用maven-assembly-plugin和maven-jar-plugin來打包和配置可這個可運行程序。
?- 依賴com.thefirstlineofcode.sand.client:sand-client-edge庫,我們使用Edge庫來幫助終端設(shè)備進行設(shè)備注冊和連接服務(wù)器。
<dependency> <groupId>com.thefirstlineofcode.sand.client</groupId> <artifactId>sand-client-edge</artifactId> </dependency>
?
- 依賴pi4j-core庫。我們用Pi4J庫來訪問控制硬件板的GPIO接口。
<dependency> <groupId>com.pi4j</groupId> <artifactId>pi4j-core</artifactId> <version>1.3</version> </dependency>
注:我們使用1.3版本Pi4J。因為Pi4J v1.4和Pi4J v2.x需要JDK 11。而我們在樹莓派Zero W上,只安裝了JDK 8。所以,我們使用Pi4J v1.3。
- 依賴hello-actautor-protocol協(xié)議包。
<dependency> <groupId>com.thefirstlineofcode.lithosphere.tutorials.helloactuator</groupId> <artifactId>hello-actuator-protocol</artifactId> <version>0.0.1-RELEASE</version> </dependency>
?
8.2 硬件控制
程序設(shè)計一個重要原則,責任原則。簡單來說,我們將不同的責任劃分到不同的接口和實現(xiàn)中,讓它們各行其事。
在這里,我們也來劃分責任。
我們先來關(guān)注硬件控制部分,我們想要控制IoT設(shè)備做亮燈、熄燈、閃燈。
Ok,先不管其它,我們來定義這個IoT設(shè)備的硬件控制的接口。
?
public interface ISimpleLight {
void turnOn();
void turnOff();
void flash(int repeat) throws ExecutionException;
}
注:
這種控制硬件的接口,在概念里,我們把它叫做Thing Controller,智能物件控制器。
hello-actuator-thing的核心類HelloActuatorThing,我們讓它實現(xiàn)ISimpleLight接口。
public class HelloActuatorThing implements ISimpleLight {
private GpioController gpio;
private GpioPinDigitalOutput ledPin;
public HelloActuatorThing() {
configureGpio();
}
private void configureGpio() {
gpio = GpioFactory.getInstance();
ledPin = gpio.provisionDigitalOutputPin(RaspiPin.GPIO_08, "MyLED", PinState.LOW);
ledPin.setShutdownOptions(true, PinState.LOW);
}
@Override
public void turnOn() {
ledPin.high();
}
@Override
public void turnOff() {
ledPin.low();
}
@Override
public void flash(int repeat) throws ExecutionException {
if (repeat <= 0 || repeat > 8)
throw new ExecutionException(-1);
for (int i = 0; i < repeat; i++) {
flash();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new ExecutionException(-2);
}
}
}
private void flash() throws ExecutionException {
turnOn();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
throw new ExecutionException(-2);
}
turnOff();
}
}
**代碼說明
- 在configureGpio()方法里,我們使用Pi4J庫對GPIO進行配置。我們配置使用ledPin變量來對LED燈的IN接口進行控制。
?- 在turnOn方法中,我們調(diào)用ledPin.high(),拉高GPIO引針電壓,這會讓LED燈亮燈。對應(yīng)的,在turnOff方法中,我們調(diào)用ledPin.low()來讓LED燈熄燈。
Ok,現(xiàn)在,我們已經(jīng)實現(xiàn)硬件控制了。
?
8.3 IoT通訊
讓我們來給IoT設(shè)備添加通訊能力。
我們使用sand-client-edge庫,可以簡化設(shè)備端程序的編寫。
讓HelloActuatorThing繼承AbstractEdgeThing。
public class HelloActuatorThing extends AbstractEdgeThing implements ISimpleLight {
public static final String THING_MODEL = HatModelDescriptor.MODEL_NAME;
public static final String SOFTWARE_VERSION = "0.0.1-RELEASE";
private IActuator actuator;
private GpioController gpio;
private GpioPinDigitalOutput ledPin;
public HelloActuatorThing() {
super(THING_MODEL, streamConfig, true);
configureGpio();
}
@Override
public String getSoftwareVersion() {
return SOFTWARE_VERSION;
}
@Override
protected void registerIotPlugins() {
chatClient.register(ActuatorPlugin.class);
}
@Override
protected void startIotComponents() {
startActuator();
}
private void configureGpio() {
... ...
}
private void startActuator() {
if (actuator == null) {
actuator = chatClient.createApi(IActuator.class);
registerExecutors(actuator);
}
actuator.start();
}
private void registerExecutors(IActuator actuator) {
actuator.registerExecutor(TurnOn.class, TurnOnExecutor.class, this);
actuator.registerExecutor(TurnOff.class, TurnOffExecutor.class, this);
actuator.registerExecutorFactory(createFlashExecutorFactory());
}
private IExecutorFactory<?> createFlashExecutorFactory() {
return new IExecutorFactory<Flash>() {
@Override
public Protocol getProtocol() {
return Flash.PROTOCOL;
}
@Override
public Class<Flash> getActionType() {
return Flash.class;
}
@Override
public IExecutor<Flash> create() {
return new FlashExecutor(HelloActuatorThing.this);
}
};
}
@Override
protected void stopIotComponents() {
if (actuator != null) {
actuator.stop();
actuator = null;
}
}
@Override
public void turnOn() {
... ...
}
@Override
public void turnOff() {
... ...
}
@Override
public void flash(int repeat) throws ExecutionException {
... ...
}
private void flash() throws ExecutionException {
... ...
}
@Override
protected String loadThingId() {
return THING_MODEL + "-" + ThingsUtils.generateRandomId(8);
}
@Override
protected String loadRegistrationCode() {
return "abcdefghijkl";
}
}
代碼說明
- AbstractEdgeThing留下了一些抽象方法給子類來實現(xiàn)。這些抽象方法名字很直白,我們遵從方法名給出對應(yīng)實現(xiàn)細節(jié)即可。
?- registerIotPlugins()方法,在這里登記我們要使用的插件。因為這個IoT設(shè)備是一個可執(zhí)行指令的Actuator。所以我們只需要注冊ActuatorPlugin插件。
protected void registerIotPlugins() { chatClient.register(ActuatorPlugin.class); }
?
- startIotComponents()方法,Edge Thing連接到服務(wù)器后,會調(diào)用這個方法來啟動設(shè)備。在這里,我們需要把Actuator組件啟動起來,Actuator組件來自ActuatorPlugin。
我們在startActuator()方法里,創(chuàng)建IActuator實例,并注冊Executors,最后調(diào)用Actuator的start()方法。protected void startIotComponents() { startActuator(); } private void startActuator() { if (actuator == null) { actuator = chatClient.createApi(IActuator.class); registerExecutors(actuator); } actuator.start(); }
?
- 在registerExecutors()方法里,我們給TurnOn,TurnOff,F(xiàn)lash指令,注冊它們對應(yīng)的執(zhí)行器。
有兩種注冊執(zhí)行器的API。第一種API,我們直接將Action指令類,和Executor指令類,登記到Actuator。
注:
這里registerExecutor()方法的最后一個參數(shù),是Thing Controller。
依據(jù)設(shè)計原則中的責任原則,Thing Controller負責直接控制IoT設(shè)備的硬件接口。在這里,Thing Controller是ISimpleLigh。
HelloActuatorThing實現(xiàn)了這個接口(ISimpleLight),提供控制硬件的turnOn(),TurnOff(),flash()方法。
我們把HelloActuatorThing當做第三個參數(shù)傳給Actuator,Thing Controller會被注入給Executor使用。actuator.registerExecutor(TurnOn.class, TurnOnExecutor.class, this); actuator.registerExecutor(TurnOff.class, TurnOffExecutor.class, this);
?
- 第二種API,我們注冊一個Executor Factory,由這個Factory來提供相關(guān)信息以及創(chuàng)建Executor,這樣可以更靈活的處理Executor的創(chuàng)建。我們使用這種API來創(chuàng)建FlashExecutor。
... ... actuator.registerExecutorFactory(createFlashExecutorFactory()); ... ... private IExecutorFactory<?> createFlashExecutorFactory() { return new IExecutorFactory<Flash>() { @Override public Protocol getProtocol() { return Flash.PROTOCOL; } @Override public Class<Flash> getActionType() { return Flash.class; } @Override public IExecutor<Flash> create() { return new FlashExecutor(HelloActuatorThing.this); } }; }
?
- 我們來處理一下設(shè)備注冊的對應(yīng)邏輯。還記得嗎,我們在服務(wù)器端的Registration Customizer里,在檢查Registration Code合法性時,使用的是一個硬編碼的Registration Code。
所以,在IoT設(shè)備端,我們需要使用這個硬編碼的Registration Code去注冊。
我們重載loadRegistrationCode()方法,用寫死的"abcdefghijkl"作為Registration Code去進行注冊。protected String loadRegistrationCode() { return "abcdefghijkl"; }
?
8.4 主程序
我們編寫一個主程序來啟動hello-actuator-thing。
public class Main {
private HelloActuatorThing simpleLight;
public static void main(String[] args) {
new Main().run(args);
}
private void run(String[] args) {
if (args.length == 1 && args[0].equals("--help")) {
printUsage();
return;
}
... ...
try {
simpleLight = new HelloActuatorThing();
} catch (IllegalArgumentException e) {
e.printStackTrace();
printUsage();
return;
}
... ...
simpleLight.start();
}
... ...
}
代碼說明
- 很簡單直白的代碼,重要的是調(diào)用HelloActuatorThing的start()方法,將Edge Thing啟動起來。
設(shè)備端程序已經(jīng)開發(fā)完成,你可以參考官方開源倉庫代碼hello-actuator-thing設(shè)備端程序工程源碼
?
8.5 構(gòu)建部署hello-actuator-thing
用maven構(gòu)建hello-actuator-thing
cd hello-actuator-thing
mvn clean package
將構(gòu)建成功的設(shè)備端程序,copy到樹莓派硬件板上。
scp target/hello-actuator-thing-0.0.1-RELEASE.tar.gz pi@192.168.1.180:/home/pi
注:
請將pi用戶名,和樹莓派網(wǎng)絡(luò)ip 192.168.1.180,改為你自己環(huán)境的配置值。
?
8.6 啟動thing程序
登錄到樹莓派上
ssh pi@192.168.1.180
運行thing程序
tar -xzvf hello-actuator-thing-0.0.1-RELEASE.tar.gz
cd hello-actuator-thing-0.0.1-RELEASE
java -jar hello-actuator-thing-0.0.1-RELEASE.jar --host=192.168.1.80
說明
- 啟動thing程序之前,記得要先啟動Granite XMPP Server。
? - 第一次運行thing程序時,需要使用--host參數(shù)指定服務(wù)器地址。AbstractEdgeThing會記住程序啟動參數(shù),后續(xù)再啟動thing程序,不需要再指定host。
? - 程序啟動后,會連接到服務(wù)器進行設(shè)備注冊,注冊成功后,登錄到服務(wù)器
一切就緒后,可以看到Thing thing has started的提示。
可以使用exit命令來退出hello-actuator-thing程序。
?
9 使用手機App遙控IoT設(shè)備
從頭開發(fā)一個手機App比較繁瑣,我們可以直接用Lithosphere平臺提供的sand-demo App來遙控我們的IoT小燈。
你可以自己來構(gòu)建sand-demo App,這是一個標準的Andriod工程,請用Andriod Studio來打開它。sand-demo App源碼位于sand工程的demo/app-android子目錄下。
你可以直接下載構(gòu)建好的sand-demo App安裝使用。
sand-demo App里大部分是常規(guī)的Android開發(fā)。創(chuàng)建菜單,畫界面... ...
和IoT通訊相關(guān)的部分,是App使用Chalk的remoting插件來遙控IoT設(shè)備。
remoting插件的功能,是可以在遠程設(shè)備上執(zhí)行action指令。
remoting插件的使用較簡單,以下代碼,來自sand-demo App源碼。
... ...
public void turnOn(JabberId target) {
logger.info("Turn on light {}.", target);
controlThing(target, new TurnOn(), "Turn on");
}
... ...
private void controlThing(JabberId target, Object action, String actionDescription) {
IChatClient chatClient = ChatClientSingleton.get(this);
IRemoting remoting = chatClient.createApi(IRemoting.class);
remoting.execute(target, action, new RemotingCallback(this, actionDescription));
}
... ...
private static class RemotingCallback implements IRemoting.Callback {
private final Activity activity;
private final String actionDescription;
public RemotingCallback(Activity activity, String actionDescription) {
this.activity = activity;
this.actionDescription = actionDescription;
}
@Override
public void executed(Object xep) {
activity.runOnUiThread(() -> Toast.makeText(activity,
actionDescription + " executed.",
Toast.LENGTH_LONG).show());
}
@Override
public void occurred(StanzaError error) {
String errorText = actionDescription + " execution error: " +
(error.getText() == null ? error.toString() : error.getText().getText());
remotingErrorOccurred(activity, error, errorText);
}
@Override
public void timeout() {
activity.runOnUiThread(() -> Toast.makeText(activity,
actionDescription + " execution timeout.",
Toast.LENGTH_LONG).show());
}
}
... ...
代碼說明
- 用chatClient創(chuàng)建IRemoting實例,然后調(diào)用IRemoting的execute()方法。方法3個參數(shù),第一個參數(shù)是遠程設(shè)備的地址;第二個參數(shù)是要執(zhí)行的action對象(Protocol Object);第三個參數(shù),是RemotingCallback,我們用這個回調(diào)接口來處理遠程指令執(zhí)行結(jié)果。
IChatClient chatClient = ChatClientSingleton.get(this); IRemoting remoting = chatClient.createApi(IRemoting.class); remoting.execute(target, action, new RemotingCallback(this, actionDescription));
?
- 在RemotingCallback里,如果遠程指令正常執(zhí)行,會回調(diào)executed()方法;遠程指令執(zhí)行出錯,會回調(diào)occurred()方法;遠程指令執(zhí)行超時(執(zhí)行端未返回響應(yīng),不能確認指令是否執(zhí)行成功),會調(diào)用timeout()方法。我們在這些回調(diào)方法里,做對應(yīng)的處理。
... ... @Override public void executed(Object xep) { activity.runOnUiThread(() -> Toast.makeText(activity, actionDescription + " executed.", Toast.LENGTH_LONG).show()); } @Override public void occurred(StanzaError error) { String errorText = actionDescription + " execution error: " + (error.getText() == null ? error.toString() error.getText().getText()); remotingErrorOccurred(activity, error, errorText); } @Override public void timeout() { activity.runOnUiThread(() -> Toast.makeText(activity, actionDescription + " execution timeout.", Toast.LENGTH_LONG).show()); } ... ...
如果想了解sand-demo App更多細節(jié),請參考開源倉庫里的sand-demo App程序源碼
將構(gòu)建好或直接下載的sand-demo App安裝到安卓手機上。
啟動sand-demo App。點擊配置傳輸通道鏈接,進入stream配置頁面。
?
在傳輸通道配置頁里,填寫Granite XMPP Server的正確地址。
?
配置好傳輸通道后,回到登錄頁,使用sand-demo用戶名來登錄App。用戶密碼也是"sand-demo"。
登錄后,可以看到注冊成功后hello-actuator-thing。點擊“控制這個智能物件”,會看到下拉菜單里,有Flash,Turn On,Turn Off三個子菜單。
?
現(xiàn)在可以用手機App來控制hello-actuator-thing了。
?
10 總結(jié)
通過這篇教程,我們可以了解到以下的內(nèi)容:文章來源地址http://www.zghlxwxcb.cn/news/detail-770201.html
- 我們可以通過IoT硬件板上的GPIO接口,來控制IoT設(shè)備的外接硬件模塊。
? - 開源的Pi4J庫,可以幫助我們使用Java語言來訪問樹莓派上的GPIO接口。
? - 在IoT的世界里,有不同的通訊協(xié)議可以選擇。在互聯(lián)網(wǎng)端,XMPP協(xié)議是一個很好的選擇。
? - 如果使用Lithosphere,基于OXM(Object/XMPP Mapping)技術(shù),我們不需要去處理XML,也不需要了解XMPP協(xié)議的細節(jié)。我們簡單的創(chuàng)建Protocol Object來表達通訊指令。
? - Lithosphere基于插件架構(gòu),我們可以通過創(chuàng)建服務(wù)器端插件,將我們要管理的IoT設(shè)備注冊到系統(tǒng)中。我們還可以定制設(shè)備注冊過程,以應(yīng)對真實項目的需求。
? - 在IoT設(shè)備端,我們開發(fā)Thing程序。使用Sand的sand-client-edge庫,可以簡化設(shè)備注冊,服務(wù)器連接過程。我們只要注冊需要使用的插件,編寫Thing端處理邏輯。
? - 對于Actuator(執(zhí)行器)類型的IoT設(shè)備,我們使用actuator插件來簡化開發(fā)。我們登記設(shè)備可執(zhí)行的指令,并且注冊指令所對應(yīng)的Executor(執(zhí)行器)。
? - 我們在手機App端,使用remoting插件來遠程執(zhí)行指令,遙控IoT設(shè)備。
? - 這篇教程里的內(nèi)容,全部基于Java平臺,只有Java,不涉及其它編程語言。
到了這里,關(guān)于使用Java來開發(fā)物聯(lián)網(wǎng)應(yīng)用的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!