文章目录
- 1.手动反复拉的那种,用了几次感觉比刀切还累,这就尴尬了。 花费20。 2.电动蒜泥机,要一直按着开关,然后手就很酸,按了几次居然失灵了还是某个大牌电器(做电冰箱起家的),然后洗了一下,居然进水。前后用坏了5个蒜泥机,花费30×5=150。 6个月内,直接损失了170元。受不了啦,直接投降。 我来做一个App遥控的吧,这样总不会坏了吧。可能遇到此情况的用户很多,如果能做得出来,是不是也可以占领一小部分市场份额,从拼多多上那么大的销量算来,看起来挺有前景。 废话少说,经过对比,发现Neptune模块比Hi3861的成本更低,而且很容易购买,仅9.9元。嵌入一块Neptune到购买来的蒜泥原型机(主要含直流电机、3.7v电池、充放电模块)。电路设计比较简单,但暂时不会画电路图所以无法制作自定义PCB电路板,先使用简单的导线连接。 在这里感谢董昱老师的大力帮助。主要控制代码如下: #include <stdio.h>#include <unistd.h>#include <string.h>#include "ohos_init.h"#include "cmsis_os2.h"#include "wifiiot_gpio.h"#include "wifiiot_gpio_ex.h"#include "wifiiot_i2c.h"#include "wifiiot_gpio_w800.h"#include "net_params.h"#include "wifi_connecter.h"#include "net_common.h"#include <errno.h>#define LED_TASK_STACK_SIZE 512#define LED_TASK_PRIO 25enum LedState{ LED_ON = 0, LED_OFF, LED_SPARK,};enum LedState g_ledState = LED_SPARK;static void *GpioTask(const char *arg){ (void)arg; while (1) { switch (g_ledState) { case LED_ON: printf(" LED_ON! \n"); GpioSetOutputVal(WIFI_IOT_GPIO_PB_00, WIFI_IOT_GPIO_VALUE0); osDelay(1500); break; case LED_OFF: printf(" LED_OFF! \n"); GpioSetOutputVal(WIFI_IOT_GPIO_PB_00, WIFI_IOT_GPIO_VALUE1); osDelay(1500); break; case LED_SPARK: printf(" LED_SPARK! Low \n"); GpioSetOutputVal(WIFI_IOT_GPIO_PB_00, WIFI_IOT_GPIO_VALUE0); osDelay(1500); printf(" LED_SPARK! High \n"); GpioSetOutputVal(WIFI_IOT_GPIO_PB_00, WIFI_IOT_GPIO_VALUE1); osDelay(1500); break; default: osDelay(1500); break; } } return NULL;}static void GpioIsr(char *arg){ (void)arg; enum LedState nextState = LED_SPARK; printf(" GpioIsr entry\n"); GpioSetIsrMask(WIFI_IOT_GPIO_PB_09, 0); switch (g_ledState) { case LED_ON: nextState = LED_OFF; break; case LED_OFF: nextState = LED_ON; break; case LED_SPARK: nextState = LED_OFF; break; default: break; } g_ledState = nextState;}void Broardcast(void *arg){ (void)arg; int retval = 0; // 建立UDP连接,这里充当了UDP的客户端 int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // UDP socket struct sockaddr_in toAddr = {0}; toAddr.sin_family = AF_INET; toAddr.sin_port = htons(PARAM_SERVER_PORT); // 端口号,从主机字节序转为网络字节序 if (inet_pton(AF_INET, PARAM_SERVER_ADDR, &toAddr.sin_addr) <= 0) { // 将主机IP地址从“点分十进制”字符串 转化为 标准格式(32位整数) printf("inet_pton failed!\r\n"); goto do_cleanup; } // Broadcast me is online while (1) { // 将online数据作为UDP的消息发送给手机 static char udpmessage[] = "live"; // UDP socket 是 “无连接的” ,因此每次发送都必须先指定目标主机和端口,主机可以是多播地址 retval = sendto(sockfd, udpmessage, sizeof(udpmessage), 0, (struct sockaddr *)&toAddr, sizeof(toAddr)); if (retval < 0) { printf("sendto failed!\r\n"); goto do_cleanup; } printf("send online message {%s} %ld done!\r\n", udpmessage, retval); // 延时1秒 osDelay(1000); }do_cleanup: printf("do_cleanup...\r\n"); close(sockfd);}void Server(void *arg){ (void)arg; int retval = 0; int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // UDP socket struct sockaddr_in clientAddr = {0}; socklen_t clientAddrLen = sizeof(clientAddr); struct sockaddr_in serverAddr = {0}; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(PARAM_SERVER_PORT); serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); retval = bind(sockfd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)); if (retval < 0) { printf("bind failed, %ld!\r\n", retval); goto do_cleanup; } printf("bind to port %d success!\r\n", PARAM_SERVER_PORT); static char message[2] = {0}; while (1) { retval = recvfrom(sockfd, message, sizeof(message), 0, (struct sockaddr *)&clientAddr, &clientAddrLen); printf("recv end\r\n"); if (retval < 0) { printf("recvfrom failed, %ld!\r\n", retval); goto do_cleanup; } printf("recv message {%s} %ld done!\r\n", message, retval); printf("peer info: ipaddr = %s, port = %d\r\n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port)); if (strcmp(message, "on") == 0) { printf(" LED_ON! \n"); GpioSetOutputVal(WIFI_IOT_GPIO_PB_01, WIFI_IOT_GPIO_VALUE0); // GpioSetOutputVal(WIFI_IOT_GPIO_PB_08, WIFI_IOT_GPIO_VALUE0); } if (strcmp(message, "of") == 0) { printf(" LED_OFF! \n"); GpioSetOutputVal(WIFI_IOT_GPIO_PB_01, WIFI_IOT_GPIO_VALUE1); // GpioSetOutputVal(WIFI_IOT_GPIO_PB_08, WIFI_IOT_GPIO_VALUE1); } retval = sendto(sockfd, message, strlen(message), 0, (struct sockaddr *)&clientAddr, sizeof(clientAddr)); if (retval <= 0) { printf("send failed, %ld!\r\n", retval); goto do_cleanup; } printf("send message {%s} %ld done!\r\n", message, retval); }do_cleanup: printf("do_cleanup...\r\n"); close(sockfd);}void connectWiFI(){ WifiDeviceConfig config = {0}; // 准备AP的配置参数, 连接WiFi strcpy(config.ssid, PARAM_HOTSPOT_SSID); strcpy(config.preSharedKey, PARAM_HOTSPOT_PSK); config.securityType = PARAM_HOTSPOT_TYPE; osDelay(100); int netId = ConnectToHotspot(&config);}void HT30Test(void){ GpioInit(); // GpioSetDir(WIFI_IOT_GPIO_PB_08, WIFI_IOT_GPIO_DIR_OUTPUT); // output is 0 PB08 control led // GpioSetOutputVal(WIFI_IOT_GPIO_PB_08, WIFI_IOT_GPIO_VALUE0); GpioSetDir(WIFI_IOT_GPIO_PB_01, WIFI_IOT_GPIO_DIR_OUTPUT); GpioSetOutputVal(WIFI_IOT_GPIO_PB_01, WIFI_IOT_GPIO_VALUE1); //High level to block elec GpioSetDir(WIFI_IOT_GPIO_PB_09, WIFI_IOT_GPIO_DIR_INPUT); // input is PB09 IoSetPull(WIFI_IOT_GPIO_PB_09, WIFI_IOT_GPIO_ATTR_PULLHIGH); GpioRegisterIsrFunc(WIFI_IOT_GPIO_PB_09, WIFI_IOT_INT_TYPE_EDGE, WIFI_IOT_GPIO_EDGE_FALL_LEVEL_LOW, GpioIsr, NULL); connectWiFI(); // Broardcast UDP 线程 osThreadAttr_t attr; attr.name = "Broardcast"; attr.attr_bits = 0U; attr.cb_mem = NULL; attr.cb_size = 0U; attr.stack_mem = NULL; attr.stack_size = 4096; attr.priority = osPriorityNormal; if (osThreadNew(Broardcast, NULL, &attr) == NULL) { printf("[Broardcast] Failed to create Broardcast!\n"); } // Server UDP 线程 osThreadAttr_t attrRecv; attrRecv.name = "Server"; attrRecv.attr_bits = 0U; attrRecv.cb_mem = NULL; attrRecv.cb_size = 0U; attrRecv.stack_mem = NULL; attrRecv.stack_size = 4096; attrRecv.priority = osPriorityNormal; if (osThreadNew(Server, NULL, &attrRecv) == NULL) { printf("[Server] Failed to create Server!\n"); } // GPIO闪烁线程 osThreadAttr_t attr2; attr2.name = "GpioTask"; attr2.attr_bits = 0U; attr2.cb_mem = NULL; attr2.cb_size = 0U; attr2.stack_mem = NULL; attr2.stack_size = 4096; attr2.priority = osPriorityNormal; // if (osThreadNew(GpioTask, NULL, &attr2) == NULL) // { // printf("[GpioTask] Failed to create GpioTask!\n"); // }}APP_FEATURE_INIT(HT30Test); 代码解析如下,使用UDP协议分别在Neptune上建立Server和Client两个端,Server用于监听从UDP广播地址(手机App)发送来的蒜泥机控制消息(开和关)。Client用于向UDP广播地址发送蒜泥机的在线活动信号,以便让手机App“发现”设备。 连接WiFi使用的是固定数字,真正做到产品可以开启热点和DHCP服务器,让手机主动连接设备的热点即可。 这里遇到的坑很大,主要是Neptune的控制代码,一定要从润和官方的gitee上下载,使用DevEco Tools自带的代码是无法开启Wifi热点和连接到Wifi的。
- 因为ArkUI的js和ets接口在HarmonyOS 2.0上均不支持UDP通讯,所以必须要用Java来进行桥接。出于ets目前的稳定性,暂时舍弃使用,改用JS来写UI。 这里要感谢Soon_L的大力帮助。
- // 定义常量 0-Ability、1-Internal Abilityconst ABILITY_TYPE_EXTERNAL = 0;//Ability调用方式const ABILITY_TYPE_INTERNAL = 1;//Internal Ability调用方式// 接口调用同步或者异步const ACTION_SYNC = 0;//同步方式const ACTION_ASYNC = 1;//异步方式// 业务码const ACTION_MESSAGE_UDP_UPDATE = 1001;// 主动更新const ACTION_MESSAGE_UDP_SUBSCRIBE = 1002;// 订阅Subscribeconst ACTION_MESSAGE_UDP_UNSUBSCRIBE = 1003;// 取消订阅Unsubscribeconst ACTION_MESSAGE_UDP_SEND_ORDER = 1004;// 发送命令const SUCCESS = 0;var timer = null;export default { data: { title: "", message: "", connected: false, on: false, }, environmentStatus: function () { this.getEnvironmentStatus(); }, environmentStatusSubscribe: function () { this.startEnvironmentStatusSubscribe(); }, environmentStatusUnSubscribe: function () { this.startEnvironmentStatusUnSubscribe(); }, sendOrder: async function (order) { var action = {}; action.bundleName = "com.example.mixerjs"; action.abilityName = "com.example.mixerjs.UpdateDataServiceAbility"; action.messageCode = ACTION_MESSAGE_UDP_SEND_ORDER action.data = order; action.abilityType = ABILITY_TYPE_EXTERNAL; action.syncOption = ACTION_SYNC; var result = await FeatureAbility.callAbility(action); var ret = JSON.parse(result); if (ret.code == 0) { console.info('发送UDP命令结果:' + JSON.stringify(ret.abilityResult)); } else { console.error('发送UDP命令错误:' + JSON.stringify(ret.code)); } }, switchMixer(){ if (this.on) { this.sendOrder("of") } else { this.sendOrder("on") } this.on = !this.on }, initAction: function (code) { var actionData = {}; var action = {}; action.bundleName = "com.example.mixerjs"; action.abilityName = "com.example.mixerjs.UpdateDataServiceAbility"; action.messageCode = code; action.data = actionData; action.abilityType = ABILITY_TYPE_EXTERNAL; action.syncOption = ACTION_SYNC; return action; }, onInit() { console.info('Demo App onInit') this.environmentStatusSubscribe(); }, onDestroy(){ console.info('Demo App onDestroy') this.environmentStatusUnSubscribe(); }, getEnvironmentStatus: async function() { var action = this.initAction(ACTION_MESSAGE_UDP_UPDATE); //给封装好的初始化函数传递操作码,确定要调用的业务 var result = await FeatureAbility.callAbility(action); var ret = JSON.parse(result); if (ret.code == SUCCESS) { this.message = ret.message; console.info('getEnvironmentStatus message is:' + JSON.stringify(ret.message)); } else { this.message = "NA"; console.error('getEnvironmentStatus error code:' + JSON.stringify(ret.code)); this.connected = false } }, startEnvironmentStatusSubscribe: async function () { try { var action = this.initAction(ACTION_MESSAGE_UDP_SUBSCRIBE); //给封装好的初始化函数传递操作码,确定要调用的业务 var that = this;//that没改变之前仍然是指向当时的this,这样就不会出现找不到原来的对象 var result = await FeatureAbility.subscribeAbilityEvent(action,function (requestEnvironmentStatus) { //调用订阅服务API var envInfo = JSON.parse(requestEnvironmentStatus).data; //将Json字符串转换为对象,并获取接口返回数据 that.message = envInfo.message; let str = that.message.substring(0,4) console.info('startEnvironmentStatusSubscribe message is:' + JSON.stringify(str)); if( str == 'live' ) { that.connected = true } }); console.info("startEnvironmentStatusSubscribe result = " + result); } catch (pluginError) { console.error("startEnvironmentStatusSubscribe error : result= " + result + JSON.stringify(pluginError)); } }, startEnvironmentStatusUnSubscribe: async function () { try { var action = this.initAction(ACTION_MESSAGE_UDP_UNSUBSCRIBE); var result = await FeatureAbility.unsubscribeAbilityEvent(action); FeatureAbility.callAbility(action); } catch (pluginError) { console.error("startEnvironmentStatusUnSubscribe error : " + JSON.stringify(pluginError)); } }}
- package com.example.mixerjs;// ohos相关接口包import ohos.aafwk.ability.Ability;import ohos.aafwk.content.Intent;import ohos.aafwk.content.Operation;import ohos.event.commonevent.*;import ohos.hiviewdfx.HiLog;import ohos.hiviewdfx.HiLogLabel;import ohos.rpc.*;import ohos.utils.zson.ZSONObject;import java.io.IOException;import java.net.DatagramPacket;import java.net.DatagramSocket;import java.net.InetAddress;import java.net.InetSocketAddress;import java.nio.charset.StandardCharsets;import java.util.HashMap;import java.util.Map;public class UpdateDataServiceAbility extends Ability { private static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0, "Mixer UpdateDataServiceAbility"); private MyRemote remote = new MyRemote(); private CommonEventSubscriber subscriber; public static String recv_string; private DatagramSocket server_sock; private DatagramSocket client_sock; private DatagramPacket pac; private byte recv_buffer[]; private byte send_buffer[]; private String message; private String order; //待发送的命令 on/of private static final int DEFAULT_TYPE = 0; private static final String COMMON_UDP_INFO_CHANGED = "UDP_INFO_CHANGED"; @Override protected void onStart(Intent intent) { HiLog.info(LABEL, "DemoApp onStart"); UpdateDataServiceAbility.GetStatusInfo getstatusinfo = new UpdateDataServiceAbility.GetStatusInfo(); Thread t = new Thread(getstatusinfo, "getstatusinfoThread"); t.start(); HiLog.info(LABEL, "线程开始执行--> " + t.isAlive());//判断是否启动 super.onStart(intent); } // FA在请求PA服务时会调用Ability.connectAbility连接PA,连接成功后,需要在onConnect返回一个remote对象,供FA向PA发送消息 @Override protected IRemoteObject onConnect(Intent intent) { super.onConnect(intent); return remote.asObject(); } class MyRemote extends RemoteObject implements IRemoteBroker { private static final int SUCCESS = 0; private static final int ERROR = 1; private static final int ACTION_MESSAGE_UDP_UPDATE = 1001; private static final int ACTION_MESSAGE_UDP_SUBSCRIBE = 1002; private static final int ACTION_MESSAGE_UDP_UNSUBSCRIBE = 1003; private static final int ACTION_MESSAGE_UDP_SEND_ORDER = 1004; MyRemote() { super("MyService_MyRemote"); } @Override public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) { switch (code) { case ACTION_MESSAGE_UDP_UPDATE: { reply.writeString(getEnvironmentInfo()); break; } case ACTION_MESSAGE_UDP_SEND_ORDER: { order = data.readString(); HiLog.info(LABEL, "发送命令--> " + order); try { HiLog.info(LABEL, "发送广播数据"); client_sock = new DatagramSocket(); client_sock.setBroadcast(true); InetAddress addr1 = InetAddress.getByName("255.255.255.255"); pac = new DatagramPacket(order.getBytes(), order.getBytes().length, addr1, 63060);//构造一个packet client_sock.send(pac); } catch (Exception e) { e.printStackTrace(); } // 返回结果当前仅支持String,对于复杂结构可以序列化为ZSON字符串上报 Map<String, Object> result = new HashMap<String, Object>(); result.put("code", SUCCESS); result.put("abilityResult", order); reply.writeString(ZSONObject.toZSONString(result)); break; } case ACTION_MESSAGE_UDP_SUBSCRIBE: { subscribeEvent(data, reply, option); break; } case ACTION_MESSAGE_UDP_UNSUBSCRIBE: { unSubscribeEnvironmentEvent(reply); break; } default: { Map<String, Object> result = new HashMap<String, Object>(); result.put("abilityError", ERROR); reply.writeString(ZSONObject.toZSONString(result)); return false; } } return true; } private void subscribeEvent(MessageParcel data, MessageParcel reply, MessageOption option) { MatchingSkills matchingSkills = new MatchingSkills(); matchingSkills.addEvent(COMMON_UDP_INFO_CHANGED); IRemoteObject notifier = data.readRemoteObject(); CommonEventSubscribeInfo subscribeInfo = new CommonEventSubscribeInfo(matchingSkills); subscriber = new CommonEventSubscriber(subscribeInfo) { @Override public void onReceiveEvent(CommonEventData commonEventData) { replyMsg(notifier); } }; if (option.getFlags() == MessageOption.TF_SYNC) { reply.writeString("subscribe common event success"); } try { CommonEventManager.subscribeCommonEvent(subscriber); reply.writeString(" subscribe common event success"); } catch (RemoteException e) { HiLog.info(LABEL, "%{public}s", "RemoteException in subscribeNotificationEvents!"); } } private void replyMsg(IRemoteObject notifier) { MessageParcel notifyData = MessageParcel.obtain(); notifyData.writeString(getEnvironmentInfo()); try { notifier.sendRequest(DEFAULT_TYPE, notifyData, MessageParcel.obtain(), new MessageOption()); } catch (RemoteException exception) { HiLog.info(LABEL, "%{public}s", "replyMsg RemoteException !"); } finally { notifyData.reclaim(); } } private String getEnvironmentInfo() { // 返回结果当前仅支持String,对于复杂结构可以序列化为ZSON字符串上报 Map<String, Object> result = new HashMap<String, Object>(); result.put("code", SUCCESS); result.put("message", message); return ZSONObject.toZSONString(result); } private void unSubscribeEnvironmentEvent(MessageParcel reply) { try { CommonEventManager.unsubscribeCommonEvent(subscriber); reply.writeString("Unsubscribe common event success!"); } catch (RemoteException | IllegalArgumentException exception) { reply.writeString("Unsubscribe failed!"); HiLog.info(LABEL, "%{public}s", "Unsubscribe failed!"); } subscriber = null; } @Override public IRemoteObject asObject() { return this; } } class GetStatusInfo implements Runnable { @Override public void run() { try { //监听端口 server_sock = new DatagramSocket(63060); recv_buffer = new byte[1024];//接收缓冲区,byte型 pac = new DatagramPacket(recv_buffer, recv_buffer.length);//构造一个packet recv_string = "offline"; HiLog.info(LABEL, "开始等待消息 "); //循环接受数据 while (true) { server_sock.receive(pac);//阻塞式接收数据 //将byte[]转化成string recv_string = new String(recv_buffer, 0, pac.getLength()); HiLog.info(LABEL, "接受到UDP数据:" + recv_string + "长度:" + recv_string.length());// if (recv_string.length() >= 0) { System.out.println("Received UDP:" + recv_string); message = recv_string; HiLog.info(LABEL, "message:" + message); try { Intent intent = new Intent(); Operation operation = new Intent.OperationBuilder() .withAction(COMMON_UDP_INFO_CHANGED)//自定义字符串类型的action .build(); intent.setOperation(operation); intent.setParam("result", "commonEventData"); intent.setParam("isCommonEvent", true); CommonEventData eventData = new CommonEventData(intent); CommonEventManager.publishCommonEvent(eventData); HiLog.info(LABEL, "PublishCommonEvent SUCCESS"); } catch (RemoteException e) { HiLog.info(LABEL, "Exception occurred during publishCommonEvent invocation."); }// } } } catch (IOException e) { System.out.println("Socket Exception"); e.printStackTrace(); } } }} 最终实现了这个我期待已久的智能打蒜器1.0,也作为本地大赛的参赛作品提交,希望有机会走到商业化的阶段。本人也是因为这个小项目,从头开始学硬件、电路、甚至回忆Java有关的知识,确实遭遇了巨大的困难和挫折,不过好在坚持下来,虽然很菜,但也略有信心了。 还需要进行的工作,去除导线连接Neptune模块,使用自定义的电路板,以便小型化,USB充电口改造成磁吸式(西安的同学提出的建议)。 代码全部附上,希望大家也能积极开始,构建自己心目中有用的商业化产品,丰富HarmonyOS和Open Harmony商业生态。 想了解更多关于开源的内容,请访问: 51CTO 开源基础软件社区 https://ost.51cto.com。

与去年不同,今年第二届HarmonyOS创新大赛要求很显然要求是比较高的,要想获得比较大的青睐,可能需要软硬件结合,南北向通吃的作品,而且最好是可以商业化落地的产品级解决方案。
这个挑战很大。
个人的想法是,寻找真正有商业价值的,可以结合现有HarmonyOS和Open Harmony技术特征的东西。幸运的是,我找到了这样的切入口,智能打蒜器。
缘由非常简单,平时喜欢吃水饺,虽然是南方人,不过偏爱北方的蘸酱和蒜泥吃法。这就遇到一个问题,蒜泥如何制作,用刀切太累了。。。作为一个程序员和纯种的懒人,就合计着如何自动化这一点。
打开拼多多,搜索蒜泥,搜到一大把的打蒜器。有手动的、有电动的,都下单买了来,居然销量超过10w+,这玩意这么受欢迎的吗?
1.手动反复拉的那种,用了几次感觉比刀切还累,这就尴尬了。 花费20。
2.电动蒜泥机,要一直按着开关,然后手就很酸,按了几次居然失灵了还是某个大牌电器(做电冰箱起家的),然后洗了一下,居然进水。前后用坏了5个蒜泥机,花费30×5=150。
6个月内,直接损失了170元。受不了啦,直接投降。
我来做一个App遥控的吧,这样总不会坏了吧。可能遇到此情况的用户很多,如果能做得出来,是不是也可以占领一小部分市场份额,从拼多多上那么大的销量算来,看起来挺有前景。
废话少说,经过对比,发现Neptune模块比Hi3861的成本更低,而且很容易购买,仅9.9元。嵌入一块Neptune到购买来的蒜泥原型机(主要含直流电机、3.7v电池、充放电模块)。电路设计比较简单,但暂时不会画电路图所以无法制作自定义PCB电路板,先使用简单的导线连接。
在这里感谢董昱老师的大力帮助。主要控制代码如下:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include "ohos_init.h"
#include "cmsis_os2.h"
#include "wifiiot_gpio.h"
#include "wifiiot_gpio_ex.h"
#include "wifiiot_i2c.h"
#include "wifiiot_gpio_w800.h"
#include "net_params.h"
#include "wifi_connecter.h"
#include "net_common.h"
#include <errno.h>
#define LED_TASK_STACK_SIZE 512
#define LED_TASK_PRIO 25
enum LedState
{
LED_ON = 0,
LED_OFF,
LED_SPARK,
};
enum LedState g_ledState = LED_SPARK;
static void *GpioTask(const char *arg)
{
(void)arg;
while (1)
{
switch (g_ledState)
{
case LED_ON:
printf(" LED_ON! \n");
GpioSetOutputVal(WIFI_IOT_GPIO_PB_00, WIFI_IOT_GPIO_VALUE0);
osDelay(1500);
break;
case LED_OFF:
printf(" LED_OFF! \n");
GpioSetOutputVal(WIFI_IOT_GPIO_PB_00, WIFI_IOT_GPIO_VALUE1);
osDelay(1500);
break;
case LED_SPARK:
printf(" LED_SPARK! Low \n");
GpioSetOutputVal(WIFI_IOT_GPIO_PB_00, WIFI_IOT_GPIO_VALUE0);
osDelay(1500);
printf(" LED_SPARK! High \n");
GpioSetOutputVal(WIFI_IOT_GPIO_PB_00, WIFI_IOT_GPIO_VALUE1);
osDelay(1500);
break;
default:
osDelay(1500);
break;
}
}
return NULL;
}
static void GpioIsr(char *arg)
{
(void)arg;
enum LedState nextState = LED_SPARK;
printf(" GpioIsr entry\n");
GpioSetIsrMask(WIFI_IOT_GPIO_PB_09, 0);
switch (g_ledState)
{
case LED_ON:
nextState = LED_OFF;
break;
case LED_OFF:
nextState = LED_ON;
break;
case LED_SPARK:
nextState = LED_OFF;
break;
default:
break;
}
g_ledState = nextState;
}
void Broardcast(void *arg)
{
(void)arg;
int retval = 0;
// 建立UDP连接,这里充当了UDP的客户端
int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // UDP socket
struct sockaddr_in toAddr = {0};
toAddr.sin_family = AF_INET;
toAddr.sin_port = htons(PARAM_SERVER_PORT); // 端口号,从主机字节序转为网络字节序
if (inet_pton(AF_INET, PARAM_SERVER_ADDR, &toAddr.sin_addr) <= 0)
{ // 将主机IP地址从“点分十进制”字符串 转化为 标准格式(32位整数)
printf("inet_pton failed!\r\n");
goto do_cleanup;
}
// Broadcast me is online
while (1)
{
// 将online数据作为UDP的消息发送给手机
static char udpmessage[] = "live";
// UDP socket 是 “无连接的” ,因此每次发送都必须先指定目标主机和端口,主机可以是多播地址
retval = sendto(sockfd, udpmessage, sizeof(udpmessage), 0, (struct sockaddr *)&toAddr, sizeof(toAddr));
if (retval < 0)
{
printf("sendto failed!\r\n");
goto do_cleanup;
}
printf("send online message {%s} %ld done!\r\n", udpmessage, retval);
// 延时1秒
osDelay(1000);
}
do_cleanup:
printf("do_cleanup...\r\n");
close(sockfd);
}
void Server(void *arg)
{
(void)arg;
int retval = 0;
int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // UDP socket
struct sockaddr_in clientAddr = {0};
socklen_t clientAddrLen = sizeof(clientAddr);
struct sockaddr_in serverAddr = {0};
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(PARAM_SERVER_PORT);
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
retval = bind(sockfd, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
if (retval < 0)
{
printf("bind failed, %ld!\r\n", retval);
goto do_cleanup;
}
printf("bind to port %d success!\r\n", PARAM_SERVER_PORT);
static char message[2] = {0};
while (1)
{
retval = recvfrom(sockfd, message, sizeof(message), 0, (struct sockaddr *)&clientAddr, &clientAddrLen);
printf("recv end\r\n");
if (retval < 0)
{
printf("recvfrom failed, %ld!\r\n", retval);
goto do_cleanup;
}
printf("recv message {%s} %ld done!\r\n", message, retval);
printf("peer info: ipaddr = %s, port = %d\r\n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));
if (strcmp(message, "on") == 0)
{
printf(" LED_ON! \n");
GpioSetOutputVal(WIFI_IOT_GPIO_PB_01, WIFI_IOT_GPIO_VALUE0);
// GpioSetOutputVal(WIFI_IOT_GPIO_PB_08, WIFI_IOT_GPIO_VALUE0);
}
if (strcmp(message, "of") == 0)
{
printf(" LED_OFF! \n");
GpioSetOutputVal(WIFI_IOT_GPIO_PB_01, WIFI_IOT_GPIO_VALUE1);
// GpioSetOutputVal(WIFI_IOT_GPIO_PB_08, WIFI_IOT_GPIO_VALUE1);
}
retval = sendto(sockfd, message, strlen(message), 0, (struct sockaddr *)&clientAddr, sizeof(clientAddr));
if (retval <= 0)
{
printf("send failed, %ld!\r\n", retval);
goto do_cleanup;
}
printf("send message {%s} %ld done!\r\n", message, retval);
}
do_cleanup:
printf("do_cleanup...\r\n");
close(sockfd);
}
void connectWiFI()
{
WifiDeviceConfig config = {0};
// 准备AP的配置参数, 连接WiFi
strcpy(config.ssid, PARAM_HOTSPOT_SSID);
strcpy(config.preSharedKey, PARAM_HOTSPOT_PSK);
config.securityType = PARAM_HOTSPOT_TYPE;
osDelay(100);
int netId = ConnectToHotspot(&config);
}
void HT30Test(void)
{
GpioInit();
// GpioSetDir(WIFI_IOT_GPIO_PB_08, WIFI_IOT_GPIO_DIR_OUTPUT); // output is 0 PB08 control led
// GpioSetOutputVal(WIFI_IOT_GPIO_PB_08, WIFI_IOT_GPIO_VALUE0);
GpioSetDir(WIFI_IOT_GPIO_PB_01, WIFI_IOT_GPIO_DIR_OUTPUT);
GpioSetOutputVal(WIFI_IOT_GPIO_PB_01, WIFI_IOT_GPIO_VALUE1); //High level to block elec
GpioSetDir(WIFI_IOT_GPIO_PB_09, WIFI_IOT_GPIO_DIR_INPUT); // input is PB09
IoSetPull(WIFI_IOT_GPIO_PB_09, WIFI_IOT_GPIO_ATTR_PULLHIGH);
GpioRegisterIsrFunc(WIFI_IOT_GPIO_PB_09, WIFI_IOT_INT_TYPE_EDGE, WIFI_IOT_GPIO_EDGE_FALL_LEVEL_LOW, GpioIsr, NULL);
connectWiFI();
// Broardcast UDP 线程
osThreadAttr_t attr;
attr.name = "Broardcast";
attr.attr_bits = 0U;
attr.cb_mem = NULL;
attr.cb_size = 0U;
attr.stack_mem = NULL;
attr.stack_size = 4096;
attr.priority = osPriorityNormal;
if (osThreadNew(Broardcast, NULL, &attr) == NULL)
{
printf("[Broardcast] Failed to create Broardcast!\n");
}
// Server UDP 线程
osThreadAttr_t attrRecv;
attrRecv.name = "Server";
attrRecv.attr_bits = 0U;
attrRecv.cb_mem = NULL;
attrRecv.cb_size = 0U;
attrRecv.stack_mem = NULL;
attrRecv.stack_size = 4096;
attrRecv.priority = osPriorityNormal;
if (osThreadNew(Server, NULL, &attrRecv) == NULL)
{
printf("[Server] Failed to create Server!\n");
}
// GPIO闪烁线程
osThreadAttr_t attr2;
attr2.name = "GpioTask";
attr2.attr_bits = 0U;
attr2.cb_mem = NULL;
attr2.cb_size = 0U;
attr2.stack_mem = NULL;
attr2.stack_size = 4096;
attr2.priority = osPriorityNormal;
// if (osThreadNew(GpioTask, NULL, &attr2) == NULL)
// {
// printf("[GpioTask] Failed to create GpioTask!\n");
// }
}
APP_FEATURE_INIT(HT30Test);
代码解析如下,使用UDP协议分别在Neptune上建立Server和Client两个端,Server用于监听从UDP广播地址(手机App)发送来的蒜泥机控制消息(开和关)。Client用于向UDP广播地址发送蒜泥机的在线活动信号,以便让手机App“发现”设备。
连接WiFi使用的是固定数字,真正做到产品可以开启热点和DHCP服务器,让手机主动连接设备的热点即可。
这里遇到的坑很大,主要是Neptune的控制代码,一定要从润和官方的gitee上下载,使用DevEco Tools自带的代码是无法开启Wifi热点和连接到Wifi的。
因为ArkUI的js和ets接口在HarmonyOS 2.0上均不支持UDP通讯,所以必须要用Java来进行桥接。出于ets目前的稳定性,暂时舍弃使用,改用JS来写UI。
这里要感谢Soon_L的大力帮助。
// 定义常量 0-Ability、1-Internal Ability
const ABILITY_TYPE_EXTERNAL = 0;//Ability调用方式
const ABILITY_TYPE_INTERNAL = 1;//Internal Ability调用方式
// 接口调用同步或者异步
const ACTION_SYNC = 0;//同步方式
const ACTION_ASYNC = 1;//异步方式
// 业务码
const ACTION_MESSAGE_UDP_UPDATE = 1001;// 主动更新
const ACTION_MESSAGE_UDP_SUBSCRIBE = 1002;// 订阅Subscribe
const ACTION_MESSAGE_UDP_UNSUBSCRIBE = 1003;// 取消订阅Unsubscribe
const ACTION_MESSAGE_UDP_SEND_ORDER = 1004;// 发送命令
const SUCCESS = 0;
var timer = null;
export default {
data: {
title: "",
message: "",
connected: false,
on: false,
},
environmentStatus: function () {
this.getEnvironmentStatus();
},
environmentStatusSubscribe: function () {
this.startEnvironmentStatusSubscribe();
},
environmentStatusUnSubscribe: function () {
this.startEnvironmentStatusUnSubscribe();
},
sendOrder: async function (order) {
var action = {};
action.bundleName = "com.example.mixerjs";
action.abilityName = "com.example.mixerjs.UpdateDataServiceAbility";
action.messageCode = ACTION_MESSAGE_UDP_SEND_ORDER
action.data = order;
action.abilityType = ABILITY_TYPE_EXTERNAL;
action.syncOption = ACTION_SYNC;
var result = await FeatureAbility.callAbility(action);
var ret = JSON.parse(result);
if (ret.code == 0) {
console.info('发送UDP命令结果:' + JSON.stringify(ret.abilityResult));
} else {
console.error('发送UDP命令错误:' + JSON.stringify(ret.code));
}
},
switchMixer(){
if (this.on) {
this.sendOrder("of")
} else {
this.sendOrder("on")
}
this.on = !this.on
},
initAction: function (code) {
var actionData = {};
var action = {};
action.bundleName = "com.example.mixerjs";
action.abilityName = "com.example.mixerjs.UpdateDataServiceAbility";
action.messageCode = code;
action.data = actionData;
action.abilityType = ABILITY_TYPE_EXTERNAL;
action.syncOption = ACTION_SYNC;
return action;
},
onInit() {
console.info('Demo App onInit')
this.environmentStatusSubscribe();
},
onDestroy(){
console.info('Demo App onDestroy')
this.environmentStatusUnSubscribe();
},
getEnvironmentStatus: async function() {
var action = this.initAction(ACTION_MESSAGE_UDP_UPDATE); //给封装好的初始化函数传递操作码,确定要调用的业务
var result = await FeatureAbility.callAbility(action);
var ret = JSON.parse(result);
if (ret.code == SUCCESS) {
this.message = ret.message;
console.info('getEnvironmentStatus message is:' + JSON.stringify(ret.message));
} else {
this.message = "NA";
console.error('getEnvironmentStatus error code:' + JSON.stringify(ret.code));
this.connected = false
}
},
startEnvironmentStatusSubscribe: async function () {
try {
var action = this.initAction(ACTION_MESSAGE_UDP_SUBSCRIBE); //给封装好的初始化函数传递操作码,确定要调用的业务
var that = this;//that没改变之前仍然是指向当时的this,这样就不会出现找不到原来的对象
var result = await FeatureAbility.subscribeAbilityEvent(action,function (requestEnvironmentStatus) { //调用订阅服务API
var envInfo = JSON.parse(requestEnvironmentStatus).data; //将Json字符串转换为对象,并获取接口返回数据
that.message = envInfo.message;
let str = that.message.substring(0,4)
console.info('startEnvironmentStatusSubscribe message is:' + JSON.stringify(str));
if( str == 'live' ) {
that.connected = true
}
});
console.info("startEnvironmentStatusSubscribe result = " + result);
} catch (pluginError) {
console.error("startEnvironmentStatusSubscribe error : result= " + result + JSON.stringify(pluginError));
}
},
startEnvironmentStatusUnSubscribe: async function () {
try {
var action = this.initAction(ACTION_MESSAGE_UDP_UNSUBSCRIBE);
var result = await FeatureAbility.unsubscribeAbilityEvent(action);
FeatureAbility.callAbility(action);
} catch (pluginError) {
console.error("startEnvironmentStatusUnSubscribe error : " + JSON.stringify(pluginError));
}
}
}
const ABILITY_TYPE_EXTERNAL = 0;//Ability调用方式
const ABILITY_TYPE_INTERNAL = 1;//Internal Ability调用方式
// 接口调用同步或者异步
const ACTION_SYNC = 0;//同步方式
const ACTION_ASYNC = 1;//异步方式
// 业务码
const ACTION_MESSAGE_UDP_UPDATE = 1001;// 主动更新
const ACTION_MESSAGE_UDP_SUBSCRIBE = 1002;// 订阅Subscribe
const ACTION_MESSAGE_UDP_UNSUBSCRIBE = 1003;// 取消订阅Unsubscribe
const ACTION_MESSAGE_UDP_SEND_ORDER = 1004;// 发送命令
const SUCCESS = 0;
var timer = null;
export default {
data: {
title: "",
message: "",
connected: false,
on: false,
},
environmentStatus: function () {
this.getEnvironmentStatus();
},
environmentStatusSubscribe: function () {
this.startEnvironmentStatusSubscribe();
},
environmentStatusUnSubscribe: function () {
this.startEnvironmentStatusUnSubscribe();
},
sendOrder: async function (order) {
var action = {};
action.bundleName = "com.example.mixerjs";
action.abilityName = "com.example.mixerjs.UpdateDataServiceAbility";
action.messageCode = ACTION_MESSAGE_UDP_SEND_ORDER
action.data = order;
action.abilityType = ABILITY_TYPE_EXTERNAL;
action.syncOption = ACTION_SYNC;
var result = await FeatureAbility.callAbility(action);
var ret = JSON.parse(result);
if (ret.code == 0) {
console.info('发送UDP命令结果:' + JSON.stringify(ret.abilityResult));
} else {
console.error('发送UDP命令错误:' + JSON.stringify(ret.code));
}
},
switchMixer(){
if (this.on) {
this.sendOrder("of")
} else {
this.sendOrder("on")
}
this.on = !this.on
},
initAction: function (code) {
var actionData = {};
var action = {};
action.bundleName = "com.example.mixerjs";
action.abilityName = "com.example.mixerjs.UpdateDataServiceAbility";
action.messageCode = code;
action.data = actionData;
action.abilityType = ABILITY_TYPE_EXTERNAL;
action.syncOption = ACTION_SYNC;
return action;
},
onInit() {
console.info('Demo App onInit')
this.environmentStatusSubscribe();
},
onDestroy(){
console.info('Demo App onDestroy')
this.environmentStatusUnSubscribe();
},
getEnvironmentStatus: async function() {
var action = this.initAction(ACTION_MESSAGE_UDP_UPDATE); //给封装好的初始化函数传递操作码,确定要调用的业务
var result = await FeatureAbility.callAbility(action);
var ret = JSON.parse(result);
if (ret.code == SUCCESS) {
this.message = ret.message;
console.info('getEnvironmentStatus message is:' + JSON.stringify(ret.message));
} else {
this.message = "NA";
console.error('getEnvironmentStatus error code:' + JSON.stringify(ret.code));
this.connected = false
}
},
startEnvironmentStatusSubscribe: async function () {
try {
var action = this.initAction(ACTION_MESSAGE_UDP_SUBSCRIBE); //给封装好的初始化函数传递操作码,确定要调用的业务
var that = this;//that没改变之前仍然是指向当时的this,这样就不会出现找不到原来的对象
var result = await FeatureAbility.subscribeAbilityEvent(action,function (requestEnvironmentStatus) { //调用订阅服务API
var envInfo = JSON.parse(requestEnvironmentStatus).data; //将Json字符串转换为对象,并获取接口返回数据
that.message = envInfo.message;
let str = that.message.substring(0,4)
console.info('startEnvironmentStatusSubscribe message is:' + JSON.stringify(str));
if( str == 'live' ) {
that.connected = true
}
});
console.info("startEnvironmentStatusSubscribe result = " + result);
} catch (pluginError) {
console.error("startEnvironmentStatusSubscribe error : result= " + result + JSON.stringify(pluginError));
}
},
startEnvironmentStatusUnSubscribe: async function () {
try {
var action = this.initAction(ACTION_MESSAGE_UDP_UNSUBSCRIBE);
var result = await FeatureAbility.unsubscribeAbilityEvent(action);
FeatureAbility.callAbility(action);
} catch (pluginError) {
console.error("startEnvironmentStatusUnSubscribe error : " + JSON.stringify(pluginError));
}
}
}
package com.example.mixerjs;
// ohos相关接口包
import ohos.aafwk.ability.Ability;
import ohos.aafwk.content.Intent;
import ohos.aafwk.content.Operation;
import ohos.event.commonevent.*;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.rpc.*;
import ohos.utils.zson.ZSONObject;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public class UpdateDataServiceAbility extends Ability {
private static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0, "Mixer UpdateDataServiceAbility");
private MyRemote remote = new MyRemote();
private CommonEventSubscriber subscriber;
public static String recv_string;
private DatagramSocket server_sock;
private DatagramSocket client_sock;
private DatagramPacket pac;
private byte recv_buffer[];
private byte send_buffer[];
private String message;
private String order; //待发送的命令 on/of
private static final int DEFAULT_TYPE = 0;
private static final String COMMON_UDP_INFO_CHANGED = "UDP_INFO_CHANGED";
@Override
protected void onStart(Intent intent) {
HiLog.info(LABEL, "DemoApp onStart");
UpdateDataServiceAbility.GetStatusInfo getstatusinfo = new UpdateDataServiceAbility.GetStatusInfo();
Thread t = new Thread(getstatusinfo, "getstatusinfoThread");
t.start();
HiLog.info(LABEL, "线程开始执行--> " + t.isAlive());//判断是否启动
super.onStart(intent);
}
// FA在请求PA服务时会调用Ability.connectAbility连接PA,连接成功后,需要在onConnect返回一个remote对象,供FA向PA发送消息
@Override
protected IRemoteObject onConnect(Intent intent) {
super.onConnect(intent);
return remote.asObject();
}
class MyRemote extends RemoteObject implements IRemoteBroker {
private static final int SUCCESS = 0;
private static final int ERROR = 1;
private static final int ACTION_MESSAGE_UDP_UPDATE = 1001;
private static final int ACTION_MESSAGE_UDP_SUBSCRIBE = 1002;
private static final int ACTION_MESSAGE_UDP_UNSUBSCRIBE = 1003;
private static final int ACTION_MESSAGE_UDP_SEND_ORDER = 1004;
MyRemote() {
super("MyService_MyRemote");
}
@Override
public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) {
switch (code) {
case ACTION_MESSAGE_UDP_UPDATE: {
reply.writeString(getEnvironmentInfo());
break;
}
case ACTION_MESSAGE_UDP_SEND_ORDER: {
order = data.readString();
HiLog.info(LABEL, "发送命令--> " + order);
try {
HiLog.info(LABEL, "发送广播数据");
client_sock = new DatagramSocket();
client_sock.setBroadcast(true);
InetAddress addr1 = InetAddress.getByName("255.255.255.255");
pac = new DatagramPacket(order.getBytes(), order.getBytes().length, addr1, 63060);//构造一个packet
client_sock.send(pac);
} catch (Exception e) {
e.printStackTrace();
}
// 返回结果当前仅支持String,对于复杂结构可以序列化为ZSON字符串上报
Map<String, Object> result = new HashMap<String, Object>();
result.put("code", SUCCESS);
result.put("abilityResult", order);
reply.writeString(ZSONObject.toZSONString(result));
break;
}
case ACTION_MESSAGE_UDP_SUBSCRIBE: {
subscribeEvent(data, reply, option);
break;
}
case ACTION_MESSAGE_UDP_UNSUBSCRIBE: {
unSubscribeEnvironmentEvent(reply);
break;
}
default: {
Map<String, Object> result = new HashMap<String, Object>();
result.put("abilityError", ERROR);
reply.writeString(ZSONObject.toZSONString(result));
return false;
}
}
return true;
}
private void subscribeEvent(MessageParcel data, MessageParcel reply, MessageOption option) {
MatchingSkills matchingSkills = new MatchingSkills();
matchingSkills.addEvent(COMMON_UDP_INFO_CHANGED);
IRemoteObject notifier = data.readRemoteObject();
CommonEventSubscribeInfo subscribeInfo = new CommonEventSubscribeInfo(matchingSkills);
subscriber = new CommonEventSubscriber(subscribeInfo) {
@Override
public void onReceiveEvent(CommonEventData commonEventData) {
replyMsg(notifier);
}
};
if (option.getFlags() == MessageOption.TF_SYNC) {
reply.writeString("subscribe common event success");
}
try {
CommonEventManager.subscribeCommonEvent(subscriber);
reply.writeString(" subscribe common event success");
} catch (RemoteException e) {
HiLog.info(LABEL, "%{public}s", "RemoteException in subscribeNotificationEvents!");
}
}
private void replyMsg(IRemoteObject notifier) {
MessageParcel notifyData = MessageParcel.obtain();
notifyData.writeString(getEnvironmentInfo());
try {
notifier.sendRequest(DEFAULT_TYPE, notifyData, MessageParcel.obtain(), new MessageOption());
} catch (RemoteException exception) {
HiLog.info(LABEL, "%{public}s", "replyMsg RemoteException !");
} finally {
notifyData.reclaim();
}
}
private String getEnvironmentInfo() {
// 返回结果当前仅支持String,对于复杂结构可以序列化为ZSON字符串上报
Map<String, Object> result = new HashMap<String, Object>();
result.put("code", SUCCESS);
result.put("message", message);
return ZSONObject.toZSONString(result);
}
private void unSubscribeEnvironmentEvent(MessageParcel reply) {
try {
CommonEventManager.unsubscribeCommonEvent(subscriber);
reply.writeString("Unsubscribe common event success!");
} catch (RemoteException | IllegalArgumentException exception) {
reply.writeString("Unsubscribe failed!");
HiLog.info(LABEL, "%{public}s", "Unsubscribe failed!");
}
subscriber = null;
}
@Override
public IRemoteObject asObject() {
return this;
}
}
class GetStatusInfo implements Runnable {
@Override
public void run() {
try {
//监听端口
server_sock = new DatagramSocket(63060);
recv_buffer = new byte[1024];//接收缓冲区,byte型
pac = new DatagramPacket(recv_buffer, recv_buffer.length);//构造一个packet
recv_string = "offline";
HiLog.info(LABEL, "开始等待消息 ");
//循环接受数据
while (true) {
server_sock.receive(pac);//阻塞式接收数据
//将byte[]转化成string
recv_string = new String(recv_buffer, 0, pac.getLength());
HiLog.info(LABEL, "接受到UDP数据:" + recv_string + "长度:" + recv_string.length());
// if (recv_string.length() >= 0) {
System.out.println("Received UDP:" + recv_string);
message = recv_string;
HiLog.info(LABEL, "message:" + message);
try {
Intent intent = new Intent();
Operation operation = new Intent.OperationBuilder()
.withAction(COMMON_UDP_INFO_CHANGED)//自定义字符串类型的action
.build();
intent.setOperation(operation);
intent.setParam("result", "commonEventData");
intent.setParam("isCommonEvent", true);
CommonEventData eventData = new CommonEventData(intent);
CommonEventManager.publishCommonEvent(eventData);
HiLog.info(LABEL, "PublishCommonEvent SUCCESS");
} catch (RemoteException e) {
HiLog.info(LABEL, "Exception occurred during publishCommonEvent invocation.");
}
// }
}
} catch (IOException e) {
System.out.println("Socket Exception");
e.printStackTrace();
}
}
}
}
// ohos相关接口包
import ohos.aafwk.ability.Ability;
import ohos.aafwk.content.Intent;
import ohos.aafwk.content.Operation;
import ohos.event.commonevent.*;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.rpc.*;
import ohos.utils.zson.ZSONObject;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public class UpdateDataServiceAbility extends Ability {
private static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0, "Mixer UpdateDataServiceAbility");
private MyRemote remote = new MyRemote();
private CommonEventSubscriber subscriber;
public static String recv_string;
private DatagramSocket server_sock;
private DatagramSocket client_sock;
private DatagramPacket pac;
private byte recv_buffer[];
private byte send_buffer[];
private String message;
private String order; //待发送的命令 on/of
private static final int DEFAULT_TYPE = 0;
private static final String COMMON_UDP_INFO_CHANGED = "UDP_INFO_CHANGED";
@Override
protected void onStart(Intent intent) {
HiLog.info(LABEL, "DemoApp onStart");
UpdateDataServiceAbility.GetStatusInfo getstatusinfo = new UpdateDataServiceAbility.GetStatusInfo();
Thread t = new Thread(getstatusinfo, "getstatusinfoThread");
t.start();
HiLog.info(LABEL, "线程开始执行--> " + t.isAlive());//判断是否启动
super.onStart(intent);
}
// FA在请求PA服务时会调用Ability.connectAbility连接PA,连接成功后,需要在onConnect返回一个remote对象,供FA向PA发送消息
@Override
protected IRemoteObject onConnect(Intent intent) {
super.onConnect(intent);
return remote.asObject();
}
class MyRemote extends RemoteObject implements IRemoteBroker {
private static final int SUCCESS = 0;
private static final int ERROR = 1;
private static final int ACTION_MESSAGE_UDP_UPDATE = 1001;
private static final int ACTION_MESSAGE_UDP_SUBSCRIBE = 1002;
private static final int ACTION_MESSAGE_UDP_UNSUBSCRIBE = 1003;
private static final int ACTION_MESSAGE_UDP_SEND_ORDER = 1004;
MyRemote() {
super("MyService_MyRemote");
}
@Override
public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) {
switch (code) {
case ACTION_MESSAGE_UDP_UPDATE: {
reply.writeString(getEnvironmentInfo());
break;
}
case ACTION_MESSAGE_UDP_SEND_ORDER: {
order = data.readString();
HiLog.info(LABEL, "发送命令--> " + order);
try {
HiLog.info(LABEL, "发送广播数据");
client_sock = new DatagramSocket();
client_sock.setBroadcast(true);
InetAddress addr1 = InetAddress.getByName("255.255.255.255");
pac = new DatagramPacket(order.getBytes(), order.getBytes().length, addr1, 63060);//构造一个packet
client_sock.send(pac);
} catch (Exception e) {
e.printStackTrace();
}
// 返回结果当前仅支持String,对于复杂结构可以序列化为ZSON字符串上报
Map<String, Object> result = new HashMap<String, Object>();
result.put("code", SUCCESS);
result.put("abilityResult", order);
reply.writeString(ZSONObject.toZSONString(result));
break;
}
case ACTION_MESSAGE_UDP_SUBSCRIBE: {
subscribeEvent(data, reply, option);
break;
}
case ACTION_MESSAGE_UDP_UNSUBSCRIBE: {
unSubscribeEnvironmentEvent(reply);
break;
}
default: {
Map<String, Object> result = new HashMap<String, Object>();
result.put("abilityError", ERROR);
reply.writeString(ZSONObject.toZSONString(result));
return false;
}
}
return true;
}
private void subscribeEvent(MessageParcel data, MessageParcel reply, MessageOption option) {
MatchingSkills matchingSkills = new MatchingSkills();
matchingSkills.addEvent(COMMON_UDP_INFO_CHANGED);
IRemoteObject notifier = data.readRemoteObject();
CommonEventSubscribeInfo subscribeInfo = new CommonEventSubscribeInfo(matchingSkills);
subscriber = new CommonEventSubscriber(subscribeInfo) {
@Override
public void onReceiveEvent(CommonEventData commonEventData) {
replyMsg(notifier);
}
};
if (option.getFlags() == MessageOption.TF_SYNC) {
reply.writeString("subscribe common event success");
}
try {
CommonEventManager.subscribeCommonEvent(subscriber);
reply.writeString(" subscribe common event success");
} catch (RemoteException e) {
HiLog.info(LABEL, "%{public}s", "RemoteException in subscribeNotificationEvents!");
}
}
private void replyMsg(IRemoteObject notifier) {
MessageParcel notifyData = MessageParcel.obtain();
notifyData.writeString(getEnvironmentInfo());
try {
notifier.sendRequest(DEFAULT_TYPE, notifyData, MessageParcel.obtain(), new MessageOption());
} catch (RemoteException exception) {
HiLog.info(LABEL, "%{public}s", "replyMsg RemoteException !");
} finally {
notifyData.reclaim();
}
}
private String getEnvironmentInfo() {
// 返回结果当前仅支持String,对于复杂结构可以序列化为ZSON字符串上报
Map<String, Object> result = new HashMap<String, Object>();
result.put("code", SUCCESS);
result.put("message", message);
return ZSONObject.toZSONString(result);
}
private void unSubscribeEnvironmentEvent(MessageParcel reply) {
try {
CommonEventManager.unsubscribeCommonEvent(subscriber);
reply.writeString("Unsubscribe common event success!");
} catch (RemoteException | IllegalArgumentException exception) {
reply.writeString("Unsubscribe failed!");
HiLog.info(LABEL, "%{public}s", "Unsubscribe failed!");
}
subscriber = null;
}
@Override
public IRemoteObject asObject() {
return this;
}
}
class GetStatusInfo implements Runnable {
@Override
public void run() {
try {
//监听端口
server_sock = new DatagramSocket(63060);
recv_buffer = new byte[1024];//接收缓冲区,byte型
pac = new DatagramPacket(recv_buffer, recv_buffer.length);//构造一个packet
recv_string = "offline";
HiLog.info(LABEL, "开始等待消息 ");
//循环接受数据
while (true) {
server_sock.receive(pac);//阻塞式接收数据
//将byte[]转化成string
recv_string = new String(recv_buffer, 0, pac.getLength());
HiLog.info(LABEL, "接受到UDP数据:" + recv_string + "长度:" + recv_string.length());
// if (recv_string.length() >= 0) {
System.out.println("Received UDP:" + recv_string);
message = recv_string;
HiLog.info(LABEL, "message:" + message);
try {
Intent intent = new Intent();
Operation operation = new Intent.OperationBuilder()
.withAction(COMMON_UDP_INFO_CHANGED)//自定义字符串类型的action
.build();
intent.setOperation(operation);
intent.setParam("result", "commonEventData");
intent.setParam("isCommonEvent", true);
CommonEventData eventData = new CommonEventData(intent);
CommonEventManager.publishCommonEvent(eventData);
HiLog.info(LABEL, "PublishCommonEvent SUCCESS");
} catch (RemoteException e) {
HiLog.info(LABEL, "Exception occurred during publishCommonEvent invocation.");
}
// }
}
} catch (IOException e) {
System.out.println("Socket Exception");
e.printStackTrace();
}
}
}
}
最终实现了这个我期待已久的智能打蒜器1.0,也作为本地大赛的参赛作品提交,希望有机会走到商业化的阶段。本人也是因为这个小项目,从头开始学硬件、电路、甚至回忆Java有关的知识,确实遭遇了巨大的困难和挫折,不过好在坚持下来,虽然很菜,但也略有信心了。
还需要进行的工作,去除导线连接Neptune模块,使用自定义的电路板,以便小型化,USB充电口改造成磁吸式(西安的同学提出的建议)。 代码全部附上,希望大家也能积极开始,构建自己心目中有用的商业化产品,丰富HarmonyOS和Open Harmony商业生态。