天天看点

HarmonyOS参赛作品-智能打蒜器,全解密!

与去年不同,今年第二届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的。

手机App端:

因为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));
        }
    }

}

           

Java端的代码如下:

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商业生态。

回头见!

附件链接:

打蒜器App端控制模块

打蒜器的Neptune模块镜像

继续阅读