Parcourir la source

MQTT和零星物料入库

cjb il y a 1 mois
Parent
commit
212e5c6793

+ 184 - 0
entry/src/main/ets/common/util/mqtt.ets

@@ -0,0 +1,184 @@
+// src/main/ets/utils/MqttManager.ts
+import { MqttAsync, MqttClient, MqttClientOptions, MqttConnectOptions,MqttQos, MqttMessage, MqttPublishOptions } from '@ohos/mqtt';
+import emitter from '@ohos.events.emitter';
+
+const TAG = 'MqttManager';
+export enum EventId {
+  MQTT_MESSAGE = 1001,
+}
+// 1. 定义事件ID常量(必须为number)
+type MessageCallback = (topic: string, payload: string) => void;
+
+interface TagValuePair {
+  tag: string;
+  value: number;
+}
+
+class MqttManager {
+  static  readonly  MQTT_SERVICE = '192.168.1.10'
+  private static instance: MqttManager;
+  private client: MqttClient | null = null;
+  private callbacks: Map<string, MessageCallback[]> = new Map();
+
+  private constructor() {}
+
+  public static getInstance(): MqttManager {
+    if (!MqttManager.instance) {
+      MqttManager.instance = new MqttManager();
+    }
+    return MqttManager.instance;
+  }
+
+  public init(options: MqttClientOptions): void {
+    try {
+      this.client = MqttAsync.createMqtt(options);
+      this.registerCallbacks();
+      console.info(TAG, 'MQTT client initialized');
+    } catch (err) {
+      console.error(TAG, `Initialization failed: ${JSON.stringify(err)}`);
+    }
+  }
+
+  private registerCallbacks(): void {
+    if (!this.client) return;
+
+    this.client.messageArrived((err, message) => {
+      if (err) {
+        console.error(TAG, `Message error: ${err.message}`);
+        return;
+      }
+      this.handleMessage(message);
+    });
+
+    this.client.connectLost((err) => {
+      if (err) {
+        console.error(TAG, `Connection lost: ${err.message}`);
+      }
+      this.reconnect();
+    });
+  }
+
+  // 修改后的 handleMessage 方法片段
+  private handleMessage(message: MqttMessage): void {
+    const topic = message.topic;
+    const payload = message.payload.toString();
+
+    // 触发回调
+    const topicCallbacks = this.callbacks.get(topic) || [];
+    topicCallbacks.forEach(cb => cb(topic, payload));
+
+    // 正确传递参数:事件对象 + 数据对象(修正点1)
+    emitter.emit({
+      eventId: EventId.MQTT_MESSAGE
+    }, {
+      data: {  // 按照 EventData 接口要求包裹数据
+        topic: topic,
+        payload: payload
+      }
+    });
+
+  }
+
+  public async connect(options: MqttConnectOptions): Promise<boolean> {
+    if (!this.client) return false;
+
+    try {
+      const res = await this.client.connect(options);
+      if (res.code === 0) {
+        console.info(TAG, 'Connected to broker');
+        return true;
+      }
+      console.error(TAG, `Connect failed: ${res.message}`);
+      return false;
+    } catch (err) {
+      console.error(TAG, `Connect error: ${err.message}`);
+      return false;
+    }
+  }
+
+  public async subscribe(topic: string, callback?: MessageCallback): Promise<void> {
+    if (!this.client) return;
+    try {
+      const res = await this.client.subscribe({ topic, qos: 1 });
+      if (res.code === 0) {
+        console.info(TAG, `Subscribed to ${topic}`);
+        if (callback) {
+          this.addCallback(topic, callback);
+        }
+      }
+    } catch (err) {
+      console.error(TAG, `Subscribe error: ${err.message}`);
+    }
+  }
+
+  private addCallback(topic: string, callback: MessageCallback): void {
+    const callbacks = this.callbacks.get(topic) || [];
+    callbacks.push(callback);
+    this.callbacks.set(topic, callbacks);
+  }
+
+  private reconnect(): void {
+    setTimeout(() => {
+      this.client?.reconnect().then(success => {
+        if (success) {
+          console.info(TAG, 'Reconnected successfully');
+        }
+      });
+    }, 5000);
+  }
+
+  public async disconnect(): Promise<void> {
+    if (!this.client) return;
+
+    try {
+      await this.client.disconnect();
+      console.info(TAG, 'Disconnected');
+    } catch (err) {
+      console.error(TAG, `Disconnect error: ${err.message}`);
+    }
+  }
+  public async publish(
+    topic: string,
+    payload: string | object,
+    qos: MqttQos = 1,
+    retained: boolean = false
+  ): Promise<void> {
+    if (!this.client) {
+      console.error(TAG, 'MQTT客户端未初始化');
+      return;
+    }
+
+    try {
+      // 统一处理 payload 类型
+      const payloadStr = typeof payload === 'string' ? payload : JSON.stringify(payload);
+
+      const options: MqttPublishOptions  = {
+        topic: topic,
+        payload: payloadStr,
+        qos: qos,
+        retained: retained
+      };
+
+      const res = await this.client.publish(options);
+      if (res.code === 0) {
+        console.info(TAG, `消息发布成功: ${topic}`);
+      } else {
+        console.error(TAG, `发布失败: ${res.message}`);
+      }
+    } catch (err) {
+      console.error(TAG, `发布异常: ${JSON.stringify(err)}`);
+    }
+  }
+}
+
+
+export default MqttManager.getInstance();
+
+
+export interface MQTTPublishData {
+  w: TagValuePair[];
+}
+
+export interface MQTTReceiveData {
+  d: TagValuePair[];
+}

+ 121 - 5
entry/src/main/ets/component/InBoundView.ets

@@ -1,6 +1,74 @@
+import MqttManager from '../common/util/mqtt';
+import emitter from '@ohos.events.emitter';
+import {EventId,MQTTPublishData,MQTTReceiveData} from '../common/util/mqtt';
 @Component
 export struct InBoundView {
   @Prop messages: string[] = []
+  @State drawerPositionStatus: number = 1
+  @State materialBoxID: string = ''
+  //料箱重量
+  @State materialBoxWeight: number = 0
+
+  refreshTag=()=>{
+    emitter.on({
+      eventId: EventId.MQTT_MESSAGE
+    }, (eventData: emitter.EventData) => {
+
+      try {
+        const payload = eventData.data?.['payload'] as string;
+        const valueJson: MQTTReceiveData = JSON.parse(payload);
+        const stationItem = valueJson?.d?.find(item => item.tag === 'Station2Set');
+        const stationWeight = valueJson?.d?.find(item => item.tag === 'Station2Weight');
+        const rfidString = decodeRfidString(
+          valueJson?.d?.find(item => item.tag === 'RFID3Data1')?.value as number,
+          valueJson?.d?.find(item => item.tag === 'RFID3Data2')?.value as number,
+          valueJson?.d?.find(item => item.tag === 'RFID3Data3')?.value as number,
+          valueJson?.d?.find(item => item.tag === 'RFID3Data4')?.value as number
+        );
+        this.materialBoxID = rfidString;
+
+        //this.materialBoxID = "XF-00002";
+        if (stationItem?.value !== undefined) {
+          this.drawerPositionStatus = Number(stationItem.value);
+          console.log(`更新抽屉状态: ${this.drawerPositionStatus} (${typeof this.drawerPositionStatus})`);
+        }
+        if (stationWeight?.value !== undefined) {
+          this.materialBoxWeight =decodeWeight(stationWeight.value)
+          console.log(`更新重量状态: ${this.materialBoxWeight} (${typeof this.materialBoxWeight})`);
+        }
+      } catch (e) {
+        console.error("MQTT消息处理异常:", e);
+      }
+    });
+  }
+  //抽屉推出
+  pushOut =()=>{
+    const drawerCommand: MQTTPublishData = {
+      w: [
+        {
+          tag: "Station2Set",
+          value: 0
+        }
+      ]
+    };
+    MqttManager.publish('station100/cmd/devices', drawerCommand, 1, true);
+  }
+
+  //抽屉伸入
+  pushIn =()=>{
+    const drawerCommand: MQTTPublishData = {
+      w: [
+        {
+          tag: "Station2Set",
+          value: 1
+        }
+      ]
+    };
+    MqttManager.publish('station100/cmd/devices', drawerCommand, 1, true);
+  }
+  aboutToAppear(): void {
+    this.refreshTag()
+  }
   build() {
     Column() {
       Text('出入库料口操作')
@@ -9,7 +77,11 @@ export struct InBoundView {
         .margin({ top: '3%' })
       Column() {
         Stack() {
-          Image($r('app.media.drawer_in_box'))// 替换为您的电梯图片资源
+          Image(
+            this.drawerPositionStatus === 1
+              ? (this.materialBoxWeight >0 ? $r('app.media.drawer_in_box') : $r('app.media.drawer_in_no_box'))
+              : (this.materialBoxWeight >0 ? $r('app.media.drawer_out_box') : $r('app.media.drawer_out_no_box'))
+          )// 替换为您的电梯图片资源
             .width('100%')
             .height('100%')
             .borderRadius($r('app.float.virtualSize_6_4'))
@@ -21,7 +93,7 @@ export struct InBoundView {
                 .width('5%')
                 .height('30%')
                 .fillColor($r('app.color.30D158'))
-              Text("15Kg")
+              Text(String(this.materialBoxWeight)+"kg")
                 .margin({ left: 10 })
                 .fontSize($r('app.float.fontSize_15_2'))
                 .fontColor($r('app.color.30D158')) // 绿色文字
@@ -33,7 +105,7 @@ export struct InBoundView {
                 .height('30%')
                 .fillColor($r('app.color.30D158'))
 
-              Text("LX-00010")
+              Text(this.materialBoxID)
                 .margin({ left: 10 })
                 .fontSize($r('app.float.fontSize_15_2'))
                 .fontColor($r('app.color.30D158')) // 绿色文字
@@ -87,7 +159,7 @@ export struct InBoundView {
         .backgroundColor($r('app.color.20FFFFFF'))
         .borderRadius($r('app.float.virtualSize_6_4'))
         .onClick(() => {
-          // 按钮点击事件处理
+          this.pushOut()
         })
 
         Button({ type: ButtonType.Normal }) {
@@ -100,7 +172,7 @@ export struct InBoundView {
         .backgroundColor($r('app.color.20FFFFFF'))
         .borderRadius($r('app.float.virtualSize_6_4'))
         .onClick(() => {
-          // 按钮点击事件处理
+          this.pushIn()
         })
       }
       .width('95%')
@@ -128,3 +200,47 @@ export struct InBoundView {
     .justifyContent(FlexAlign.Start)
   }
 }
+
+
+function decodeRegister(regValue: number | undefined): string {
+  if (regValue === undefined) return ''; // 处理undefined
+
+  // 确保是16位无符号整数
+  const value = regValue & 0xFFFF;
+
+  // 提取高8位和低8位
+  const highByte = (value >> 8) & 0xFF;
+  const lowByte = value & 0xFF;
+
+  // 转换为ASCII字符
+  return String.fromCharCode(highByte) + String.fromCharCode(lowByte);
+}
+
+// 完整RFID解码函数
+function decodeRfidString(
+  rfidData1?: number,
+  rfidData2?: number,
+  rfidData3?: number,
+  rfidData4?: number
+): string {
+  return [
+    decodeRegister(rfidData1),
+    decodeRegister(rfidData2),
+    decodeRegister(rfidData3),
+    decodeRegister(rfidData4)
+  ].join('');
+}
+
+function decodeWeight(regValue: number | undefined): number {
+  if (regValue === undefined) return 0; // 处理undefined
+
+  // 确保是16位无符号整数
+  const value = regValue & 0xFFFF;
+
+  // 提取高8位和低8位
+  const highByte = (value >> 8) & 0xFF;
+  const lowByte = value & 0xFF;
+
+  // 转换为ASCII字符
+  return highByte+lowByte/100
+}

+ 9 - 8
entry/src/main/ets/component/MenuView.ets

@@ -1,7 +1,8 @@
-import {InOrder} from "../params/OrderMaterialsStorageParams"
+import { BoundOrder} from "../params/OrderMaterialsStorageParams"
+import PageRes from '../viewmodel/wms/InBoundOrderInfo'
 @Component
 export struct StorageList {
-  @Prop storageData: InOrder[] = []
+  @Prop storageData: BoundOrder[] = []
   @Prop title: string = ''
   private scrollerForList: Scroller = new Scroller()
   build() {
@@ -15,22 +16,22 @@ export struct StorageList {
         .height('15%')
 
       // 列表部分
-      List() {
-        ForEach(this.storageData, (item:InOrder) => {
+      List({scroller:this.scrollerForList}) {
+        ForEach(this.storageData, (item:BoundOrder) => {
           ListItem() {
             Row() {
               // 左侧信息列
               Column() {
-                Text(item.id)
+                Text(item.batchCode)
                   .fontSize($r('app.float.fontSize_9_6'))
                   .fontColor($r('app.color.FFFFFF'))
 
-                Text(item.orderNumber)
+                Text(` ${item.materialName} ${item.materialNo}`)
                   .fontSize($r('app.float.fontSize_7'))
                   .fontColor($r('app.color.60FFFFFF'))
                   .margin({ top: 4 })
 
-                Text(item.date)
+                Text(item.created)
                   .fontSize($r('app.float.fontSize_7'))
                   .fontColor($r('app.color.60FFFFFF'))
                   .margin({ top: 4 })
@@ -39,7 +40,7 @@ export struct StorageList {
 
               // 右侧状态列
               Column() {
-                Text(item.progress)
+                Text(String(item.num))
                   .fontSize($r('app.float.fontSize_15_2'))
                   .fontColor($r('app.color.FFFFFF'))
 

+ 0 - 2
entry/src/main/ets/component/OrderMaterialsStorageView.ets

@@ -1,6 +1,4 @@
 import {DemandMaterial,OrderParams,MaterialItem,MaterialBox,EmptyBox} from "../params/OrderMaterialsStorageParams"
-import { ON } from '@ohos.UiTest'
-
 @Component
 export struct ProcessFlow {
   @Prop currentStep:number =0

+ 3 - 0
entry/src/main/ets/pages/EmptyBoxStorage.ets

@@ -1,3 +1,6 @@
+/*
+ * 空箱入库
+ * */
 import {NavigationBar} from '../component/NavigationBar'
 import {TimeAndTitle} from "../component/TimeAndTitle"
 import {InBoundView} from '../component/InBoundView'

+ 5 - 3
entry/src/main/ets/pages/LittleMaterialsStorage.ets

@@ -1,6 +1,8 @@
-import {ProcessFlow,MaterialList,SingleOrder,BoxGrid,
-  OrderListComponent,MaterialListComponent} from "../component/OrderMaterialsStorageView"
-import {OrderParams,DemandMaterial,MaterialBox, EmptyBox} from "../params/OrderMaterialsStorageParams"
+/*
+ * 零星物料入库
+ * */
+import {ProcessFlow,BoxGrid,MaterialListComponent} from "../component/OrderMaterialsStorageView"
+import {OrderParams,MaterialBox, EmptyBox} from "../params/OrderMaterialsStorageParams"
 import router from '@ohos.router';
 import {NavigationBar} from '../component/NavigationBar'
 import {TimeAndTitle} from "../component/TimeAndTitle"

+ 68 - 42
entry/src/main/ets/pages/Menu.ets

@@ -1,51 +1,77 @@
 import {TimeAndTitle} from "../component/TimeAndTitle"
 import {NavigationBar} from '../component/NavigationBar'
 import {StorageList} from '../component/MenuView'
-import {InOrder} from '../params/OrderMaterialsStorageParams'
+import {BoundOrder} from '../params/OrderMaterialsStorageParams'
+import RequestParamModel from '../viewmodel/wms/RequestParamModel'
+import WmsRequest from '../common/util/request/WmsRequest'
+import PageRes from "../viewmodel/wms/InBoundOrderInfo"
 import router from '@ohos.router';
+import MqttManager from '../common/util/mqtt';
+import { MqttClientOptions, MqttConnectOptions } from '@ohos/mqtt';
+const TAG: string = 'MENU'
 @Entry
 @Component
 struct menu {
   @State materialBoxID: string = 'LX-00010';
-  @State inboundOrderList: InOrder [] = [
-  // 第一行物料箱
-    {
-      id: "电路板(ASFSDFSADFASDFSADFDDASFASDFA...",
-      orderNumber: "仓储机器人生产订单1231231231231231",
-      date: "2025/11/11",
-      progress: "400",
-      status: "入库/计划数量"
-    },{
-    id: "电路板(ASFSDFSADFASDFSADFDDASFASDFA...",
-    orderNumber: "仓储机器人生产订单1231231231231231",
-    date: "2025/11/11",
-    progress: "400",
-    status: "入库/计划数量"
-  },{
-    id: "电路板(ASFSDFSADFASDFSADFDDASFASDFA...",
-    orderNumber: "仓储机器人生产订单1231231231231231",
-    date: "2025/11/11",
-    progress: "400",
-    status: "入库/计划数量"
-  },{
-    id: "电路板(ASFSDFSADFASDFSADFDDASFASDFA...",
-    orderNumber: "仓储机器人生产订单1231231231231231",
-    date: "2025/11/11",
-    progress: "400",
-    status: "入库/计划数量"
-  },{
-    id: "电路板(ASFSDFSADFASDFSADFDDASFASDFA...",
-    orderNumber: "仓储机器人生产订单1231231231231231",
-    date: "2025/11/11",
-    progress: "400",
-    status: "入库/计划数量"
-  },{
-    id: "电路板(ASFSDFSADFASDFSADFDDASFASDFA...",
-    orderNumber: "仓储机器人生产订单1231231231231231",
-    date: "2025/11/11",
-    progress: "400",
-    status: "入库/计划数量"
-  }]
+  @State pages: PageRes = {}
+  @State inBoundOrders: BoundOrder[]=[]
+  @State outBoundOrders: BoundOrder[]=[]
+
+  requestBoundOrder = async () => {
+    const loadOrders = async (type: number, targetArray: BoundOrder[]) => {
+      const pages = await WmsRequest.post('/api/v1/wmsOrder/page', { type } as RequestParamModel) as PageRes;
+      if (pages?.records) {
+        for (const record of pages.records) {
+          targetArray.push({
+            batchCode: record.batchCode,
+            materialName: record.materialName,
+            materialNo: record.materialNo || '',
+            num: record.num,
+            created: record.created
+          });
+        }
+      }
+    };
+    await Promise.all([
+      loadOrders(1, this.inBoundOrders),
+      loadOrders(2, this.outBoundOrders)
+    ]);
+  }
+
+  connectMQTT=async ()=>{
+    const clientOptions: MqttClientOptions = {
+      url: 'mqtt://192.168.1.3:1883',   // 替换实际IP
+      clientId: `ohos_client_${Date.now()}`,
+      persistenceType: 1,              // 使用英文逗号       // 建议开启自动重连
+    };
+    // MQTT连接配置
+    const connectOptions: MqttConnectOptions = {
+      cleanSession: true,
+      connectTimeout: 30,
+      keepAliveInterval: 60,
+      userName: 'optional_username',
+      password: 'optional_password'
+    };
+    try {
+      MqttManager.init(clientOptions);
+      const isConnected = await MqttManager.connect(connectOptions);
+      if (isConnected) {
+        console.info(TAG, 'MQTT connected successfully');
+        await MqttManager.subscribe('station100/data/devices');
+      } else {
+        console.error(TAG, 'MQTT connection failed');
+      }
+    } catch (err) {
+      console.error(TAG, `MQTT error: ${JSON.stringify(err)}`);
+    }
+  }
+
+  async aboutToAppear() {
+    this.requestBoundOrder();
+    this.connectMQTT();
+  }
+
+
   build() {
     Row() {
       Column() {
@@ -207,8 +233,8 @@ struct menu {
             Text("  ")
               .fontColor($r('app.color.FFFFFF'))
               .fontSize($r('app.float.fontSize_15_2'))
-            StorageList({title:"入库单",storageData: this.inboundOrderList }).height('46%')
-            StorageList({title:"出库单",storageData: this.inboundOrderList }).height('46%');
+            StorageList({title:"入库单",storageData: this.inBoundOrders }).height('46%')
+            StorageList({title:"出库单",storageData: this.outBoundOrders }).height('46%');
           }.alignItems(HorizontalAlign.Start).justifyContent(FlexAlign.SpaceAround).height('95%').width('30%')
         }
         .height('88.6%')

+ 3 - 0
entry/src/main/ets/pages/OrderMaterialsStorage.ets

@@ -1,3 +1,6 @@
+/*
+ * 订单物料入库
+ * */
 import {ProcessFlow} from "../component/OrderMaterialsStorageView"
 import {OrderParams,DemandMaterial,MaterialBox, EmptyBox} from "../params/OrderMaterialsStorageParams"
 import router from '@ohos.router';

+ 6 - 6
entry/src/main/ets/params/OrderMaterialsStorageParams.ets

@@ -30,12 +30,12 @@ export interface MaterialBox {
   position: string;           // X-1 Y-2
 }
 
-export interface InOrder{
-  id: string;
-  orderNumber: string;
-  date: string;
-  progress: string;
-  status: string;
+export interface BoundOrder{
+  batchCode: string;
+  materialName: string;
+  materialNo: string
+  num: number;
+  created: string;
 }
 
 export interface EmptyBox {

+ 38 - 0
entry/src/main/ets/viewmodel/wms/InBoundOrderInfo.ets

@@ -0,0 +1,38 @@
+export default class PageRes {
+  pageNo?: number;
+  pageSize?: number;
+  //出入库退料单详情出参
+  records?: Records[]
+  scrollId?: string;
+  totalCount?: number;
+  totalPages?: number;
+}
+interface Records{
+  //批次号
+  batchCode: string;
+  created:string;
+  creator: string;
+  deleted: number;
+  deptId: string;
+  houseNo: string;
+  id: string;
+  locationNo: string;
+  //物料名称
+  materialName: string;
+  //物料编号
+  materialNo: string;
+  message: string;
+  //出入库数量
+  num: number;
+  orgId: string;
+  planNo: string;
+  seqNo: string;
+  spec: string;
+  state: string;
+  taskNo: string;
+  type: string;
+  unit: string;
+  updated: string;
+  updator: string;
+  vehicleCode: string;
+}

+ 2 - 1
entry/src/main/ets/viewmodel/wms/RequestParamModel.ets

@@ -2,5 +2,6 @@
 export default class RequestParamModel {
   // 工单状态 0 未完成 1已完成
   queryComplete?: number
-
+  //工单类型 1 入库单 2 出库单
+  type?: number
 }