zhoutao 3 meses atrás
commit
98328288be

+ 31 - 0
.eslintrc.js

@@ -0,0 +1,31 @@
+/*
+ * Eslint config file
+ * Documentation: https://eslint.org/docs/user-guide/configuring/
+ * Install the Eslint extension before using this feature.
+ */
+module.exports = {
+  env: {
+    es6: true,
+    browser: true,
+    node: true,
+  },
+  ecmaFeatures: {
+    modules: true,
+  },
+  parserOptions: {
+    ecmaVersion: 2018,
+    sourceType: 'module',
+  },
+  globals: {
+    wx: true,
+    App: true,
+    Page: true,
+    getCurrentPages: true,
+    getApp: true,
+    Component: true,
+    requirePlugin: true,
+    requireMiniProgram: true,
+  },
+  // extends: 'eslint:recommended',
+  rules: {},
+}

+ 312 - 0
app.js

@@ -0,0 +1,312 @@
+// app.js
+App({
+    data: {//数据
+        worker: null,//线程句柄
+        recorder: null,//录音设备句柄
+        wavFilePath: '',//wav文件路径
+        readFileTimes: 0,//读取本地文件次数
+        imuStartTimestamp: 0,//IMU开始时间戳
+    },
+    onLaunch() {
+        // 展示本地存储能力
+        const logs = wx.getStorageSync('logs') || []
+        logs.unshift(Date.now())
+        wx.setStorageSync('logs', logs)
+
+        // TODO登录
+        wx.login({
+            success: (res) => {
+                // 发送 res.code 到后台换取 openId, sessionKey, unionId
+            }
+        })
+    },
+    //创建线程(listener:线程监听服务程序)
+    createWorker(listener) {
+        if (this.data.worker == null) {
+            this.data.worker = wx.createWorker("worker/worker.js")
+
+            //设置线程监听服务程序
+            if (typeof (listener) === 'function') {
+                this.setWorkerListener(listener)
+
+                //发消息给worker,加载wasm
+                this.data.worker.postMessage({
+                    message: 'MAIN_WORKER_LOAD_WX_WASM',
+                    data: ''
+                })
+            }
+        }
+    },
+    //设置线程监听服务程序
+    setWorkerListener(listener) {
+        if (this.data.worker != null) {
+            this.data.worker.onMessage(listener)
+        }
+    },
+    //创建录音设备,并监听录音完成事件
+    createRecorder() {
+        const self = this
+
+        //获得录音机句柄
+        this.data.recorder = wx.getRecorderManager()
+
+        //注册录音回调函数(录音长度达到设定值后执行)
+        this.data.recorder.onFrameRecorded((res) => {
+            //从返回参数中解构变量isLastFrame和frameBuffer, 其中frameBuffer是二进制数据数组,不能直接读写
+            const { isLastFrame, frameBuffer } = res
+
+            //录音时长到了最大值,重新开始录音
+            if (isLastFrame) {
+                self.startRecord()
+            }
+
+            //将数据发送给worker
+            self.data.worker.postMessage({
+                message: 'MAIN_WORKER_AUDIO_DATA',
+                data: frameBuffer
+            })
+
+            // //将数据发送给worker
+            // self.data.worker.postMessage({
+            //     message: 'MAIN_WORKER_SAVE_AUDIO_DATA',
+            //     data: frameBuffer
+            // })
+        })
+
+        //开始录音
+        setTimeout(this.startRecord, 2000)
+    },
+    //开始录音
+    startRecord() {
+        if (this.data.recorder != null) {
+            let options = {}
+
+            //组织录音参数
+            options.duration = 600000 //最长连续录音10分钟
+            options.sampleRate = 48000 //采样频率
+            options.encodeBitRate = 320000 //TODO,编码率,不知道干什么用的
+            options.numberOfChannels = 1 //单通道采样
+            options.format = "pcm"
+            options.frameSize = "22" //单位kB(数据不会太准确,因此需要缓存数据以实施成帧操作)
+
+            //启动录音
+            this.data.recorder.start(options)
+        }
+    },
+    //保存音频文件
+    //buffer:录音数据,为二进制单字节数据数组(byte)
+    saveAduioDataToBin(buffer) {
+        console.log("收到保存消息")
+        let fileSystemManager = wx.getFileSystemManager()
+
+        try {
+            fileSystemManager.appendFileSync(wx.env.USER_DATA_PATH + "/IPS8000.bin", buffer, 'binary')
+            return { code: 1, data: 'ok' }
+        } catch (e) {
+            return { code: 0, data: e }
+        }
+    },
+    //打开蓝牙适配器
+    openBleAdapter() {
+        let self = this
+
+        wx.openBluetoothAdapter({
+            mode: "central",
+            success: function (res) {//打开成功,开始扫描周边设备
+                wx.startBluetoothDevicesDiscovery({
+                    allowDuplicatesKey: true,
+                    interval: 2000,//2秒扫描一次
+                    powerLevel: "medium",
+                    success() {
+                        self.monitorBleBtsScanResult()
+                    }
+                })
+            },
+            fail: function (err) {//打开失败
+                console.log("open ble fail", err)
+                setTimeout(() => {
+                    self.openBleAdapter()
+                }, 3000)
+            }
+        })
+
+        //this.monitorBleBtsScanResult();
+
+
+        // // 监听扫描到新设备事件
+        // wx.onBluetoothDeviceFound((res) => {
+        //     console.log("扫描到了蓝牙设备")
+        //     res.devices.forEach((device) => {
+        //         console.log('Device Found', device)
+        //     })
+        // })
+    },
+    //开始扫描周边蓝牙设备
+    startScanNearbyBleBts() {
+        let self = this
+
+        //扫描周边设备
+        wx.startBluetoothDevicesDiscovery({
+            allowDuplicatesKey: true,//允许重复上报已扫描到的设备
+            interval: 3000,//3秒扫描一次
+            powerLevel: "medium",
+            success: function () {
+                self.monitorBleBtsScanResult()//监听蓝牙基站扫描结果
+            },
+            fail: function () {
+                //500毫秒之后重新扫描
+                setTimeout(self.startScanNearbyBleBts, 500)
+            }
+        })
+    },
+    //监听蓝牙基站扫描结果
+    monitorBleBtsScanResult() {
+        let self = this
+        wx.onBluetoothDeviceFound((res) => {
+            let findFlag = 0//发现系统基站标识符
+            let bleBtsDataList = []//蓝牙基站数据队列
+
+            //遍历蓝牙设备队列
+            res.devices.forEach((element) => {
+                if (element.name && element.name.length == 23) {
+
+                    let bleName = element.name
+                    let subName = bleName.slice(-10)
+
+                    //该设备为本系统设备
+                    if (subName === "IPS8000BLE") {
+                        if (!findFlag) {
+                            findFlag = 1//发现系统基站
+                        }
+
+                        let macAddress = bleName.substring(0, 12);//BLE的mac地址
+                        let rssi = -10000;
+                        if (element.RSSI) {
+                            rssi = element.RSSI//接收电平强度
+                        }
+
+                        //保存扫描结果
+                        let item = {}
+                        item['mac'] = macAddress;
+                        item['rssi'] = rssi
+                        bleBtsDataList.push(item)
+                    }
+                }
+            })
+
+            //成功监听到本系统蓝牙设备
+            if (findFlag) {
+                //组织本次监听到的蓝牙基站数据
+                self.organizeBleBtsData(bleBtsDataList)
+            }
+        })
+    },
+    //按照wasm模块所需格式组织蓝牙基站数据
+    organizeBleBtsData(bleBtsList) {
+        let listLen = 0
+        let bleBtsDataList = []//蓝牙基站数据队列
+        let rssi = 0
+        let strMac = ""
+        let macLen = 0
+        let oneHexChar = ""
+        let oneHexCharValue = 0
+
+        //没有蓝牙基站
+        if (!bleBtsList || bleBtsList.length <= 0) {
+            return
+        }
+
+        //队列长度
+        listLen = bleBtsList.length
+        if (listLen > 9) {
+            listLen = 9//最多9个蓝牙基站
+        }
+
+        ////按照WASM模块所需数据格式组织蓝牙基站数据
+        bleBtsDataList.push(1)//蓝牙基站有效标识符置1
+        bleBtsDataList.push(listLen)//蓝牙基站个数
+
+        //组织RSSI和MAC
+        for (let i = 0; i < listLen; i++) {
+            rssi = bleBtsList[i].rssi
+            bleBtsDataList.push(rssi)//RSSI
+
+            strMac = bleBtsList[i].mac//mac地址16进制字符串
+            macLen = strMac.length
+            if (macLen == 12) {
+                for (let j = 0; j < macLen; j++) {
+                    oneHexChar = strMac[j]
+                    oneHexCharValue = this.hexCharToValue(oneHexChar)
+                    bleBtsDataList.push(oneHexCharValue)//MAC
+                }
+            } else {
+                console.log("长度错误")
+            }
+        }
+
+        //把数据发送给worker
+        if (bleBtsDataList.length > 3) {
+            if (this.data.worker != null) {
+                this.data.worker.postMessage({
+                    message: 'MAIN_WORKER_BLE_BTS_DATA',
+                    data: bleBtsDataList
+                })
+            }
+        }
+    },
+    hexCharToValue(char) {
+        // 定义16进制字符到数值的映射  
+        const hexValues = {
+            '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7,
+            '8': 8, '9': 9, 'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14, 'F': 15,
+            'a': 10, 'b': 11, 'c': 12, 'd': 13, 'e': 14, 'f': 15
+        };
+
+        // 查找字符对应的值  
+        return hexValues[char];
+    },
+    //停止扫描蓝牙设备
+    stopScanNearbyBle() {
+        let self = this
+        wx.stopBluetoothDevicesDiscovery({
+            success: function () {
+                wx.offBluetoothDeviceFound(function () { })
+            },
+            fail: function () {
+                console.log("没有扫描到基站")
+                setTimeout(self.stopScanNearbyBle, 3000)
+            }
+        })
+    },
+    //启动加速度计
+    startImu() {
+        wx.stopAccelerometer()//停止加速度传感器
+
+        //启动加速度传感器,20毫秒采集一次加速度
+        wx.startAccelerometer({ interval: "game" })
+
+        //监听加速度传感器
+        wx.onAccelerometerChange(this.onAccelerometerChange)
+
+        //生成IMU开始时间戳
+        this.data.imuStartTimestamp = new Date().getTime()
+    },
+    //监测三轴加速度计的变化
+    onAccelerometerChange(res) {
+        let timestamp = new Date().getTime() - this.data.imuStartTimestamp; //生成时间戳
+
+        let item = {}
+        item.timestamp = timestamp
+        item.x = res.x
+        item.y = res.y
+        item.z = res.z
+
+        //把数据发送给线程
+        if (this.data.worker != null) {
+            this.data.worker.postMessage({
+                message: 'MAIN_WORKER_ONE_ACC_DATA',//加速度数据
+                data: item
+            })
+        }
+    },
+})

+ 17 - 0
app.json

@@ -0,0 +1,17 @@
+{
+    "pages": [
+        "pages/home/home",
+        "pages/index/index",
+        "pages/logs/logs"
+    ],
+    "window": {
+        "navigationBarTextStyle": "white",
+        "navigationBarTitleText": "秒寻定位系统",
+        "navigationBarBackgroundColor": "#FC0000"
+    },
+    "style": "v2",
+    "componentFramework": "glass-easel",
+    "sitemapLocation": "sitemap.json",
+    "lazyCodeLoading": "requiredComponents",
+    "workers": "worker"
+}

+ 10 - 0
app.wxss

@@ -0,0 +1,10 @@
+/**app.wxss**/
+.container {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: space-between;
+  padding: 200rpx 0;
+  box-sizing: border-box;
+} 

+ 493 - 0
pages/home/home.js

@@ -0,0 +1,493 @@
+
+const app = getApp()
+// pages/home/home.js
+Page({
+    /**
+     * 页面的初始数据
+     */
+    data: {
+        bestFrame: 0,
+        bestMultiFrame: 0,
+        bleDataList: [],//蓝牙设备队列,包括mac和rssi项
+
+        installBtsList: [],//安装基站列表
+        btsMinCoordX: 0,
+        btsMaxCoordX: 0,
+        btsMinCoordY: 0,
+        btsMaxCoordY: 0,
+
+        canvasCtx: null, //画布
+        canvasWidth: 0, //画布宽度
+        canvasHeight: 0, //画布高度
+        canvasPixelRatio: 1,//画布像素比例
+
+        coordDataList: [],//定位坐标数据队列
+
+    },
+    /**
+     * 生命周期函数--监听页面加载
+     */
+    onLoad(options) {
+        //创建线程
+        app.createWorker(this.workerMessageListenerHome)
+
+        app.createRecorder()//创建录音设备
+
+        //TODO,测试用接口,后期改成从服务器上读取安装基站数据
+        this.generateMultiInstallBts() //生成定位基站
+
+        this.initCanvas()//初始化画布
+
+        //启动三轴加速计
+        app.startImu()
+    },
+    /**
+     * 生命周期函数--监听页面初次渲染完成
+     */
+    onReady() {
+
+    },
+    /**
+     * 生命周期函数--监听页面显示
+     */
+    onShow() {
+
+    },
+    /**
+     * 生命周期函数--监听页面隐藏
+     */
+    onHide() {
+
+    },
+    /**
+     * 生命周期函数--监听页面卸载
+     */
+    onUnload() {
+        if (app.data.worker != null) {
+            app.data.worker.postMessage({
+                message: 'MAIN_WORKER_FREE_MEMORY',
+                data: ''
+            })
+        }
+    },
+    /**
+     * 页面相关事件处理函数--监听用户下拉动作
+     */
+    onPullDownRefresh() {
+
+    },
+    /**
+     * 页面上拉触底事件的处理函数
+     */
+    onReachBottom() {
+
+    },
+    /**
+     * 用户点击右上角分享
+     */
+    onShareAppMessage() {
+    },
+    //线程监听服务程序
+    workerMessageListenerHome(res) {
+        switch (res.message) {
+            case 'WORKER_MAIN_START_MONITOR_BLE_BTS':
+                app.openBleAdapter()//打开蓝牙设备
+                break
+            case 'WORKER_MAIN_ORGANIZE_INSTALL_BTS_DATA':
+                this.organizeInstallBtsData()//组织安装基站
+                break
+            case 'WORKER_MAIN_START_REC'://开始录音
+                app.createRecorder()
+                break
+            case 'WORKER_MAIN_CURRENT_LOCATION_COORD'://当前定位坐标
+                this.drawLocationTrace(res.data)
+                break
+            case 'WORKER_MAIN_NOTICE_DOWNLOAD_AUDIO_FILE':
+                this.downloadAudioFile()
+                break
+            case 'WORKER_MAIN_NOTICE_READ_AUDIO_FILE':
+                this.readAudioFile()
+                break
+            case 'WORKER_MAIN_SAVE_AUDIO_DATA':
+                app.saveAduioDataToBin(res.data)
+                break
+            default:
+                break
+        }
+    },
+    //初始化画布
+    initCanvas() {
+        //获得屏幕数据
+        let self = this
+        wx.getSystemInfo({
+            success: (res) => {
+                self.data.canvasPixelRatio = res.pixelRatio
+                let myCanvasWidth = res.windowWidth - 5
+                self.setData({
+                    canvasWidth: myCanvasWidth,
+                    canvasHeight: myCanvasWidth
+                })
+            }
+        })
+
+        //为画布选择一个容器
+        wx.createSelectorQuery().select('#homeCanvas').fields({ node: true, size: true }).exec((res) => {
+            let canvas = res[0].node
+            if (canvas) {
+                this.data.canvasCtx = canvas.getContext('2d')
+                canvas.width = res[0].width * this.data.canvasPixelRatio
+                canvas.height = res[0].height * this.data.canvasPixelRatio
+                this.data.canvasCtx.scale(this.data.canvasPixelRatio, this.data.canvasPixelRatio)
+
+                this.drawPosition(0, 0, 0, 0)
+            }
+        })
+    },
+    //组织安装基站数据
+    organizeInstallBtsData() {
+        let dataList = []//数据队列
+        let oneItem = {}
+
+        //按照wasm模块要求的数据格式组织数据
+        dataList.push(this.data.installBtsList.length)
+        for (let i = 0; i < this.data.installBtsList.length; i++) {
+            oneItem = this.data.installBtsList[i]
+            dataList.push(oneItem.dwBuildId)
+            dataList.push(oneItem.dwLayerId)
+
+            dataList.push(oneItem.dwBtsId)
+            dataList.push(oneItem.dwFreqIndex)
+            dataList.push(oneItem.dwSlot)
+
+            dataList.push(oneItem.dwCoordX)
+            dataList.push(oneItem.dwCoordY)
+            dataList.push(oneItem.dwCoordZ)
+
+            //一维定位数据
+            dataList.push(oneItem.dwLctType)
+            dataList.push(oneItem.dwTwoBtsNum)
+            for (let i = 0; i < oneItem.dwTwoBtsNum; i++) {
+                dataList.push(oneItem.adwTwoBtsList[i])
+            }
+            for (let i = oneItem.dwTwoBtsNum; i < 6; i++) {
+                dataList.push(0)
+            }
+
+            //蓝牙mac
+            for (let j = 0; j < 12; j++) {
+                dataList.push(oneItem.adwBluetoothMac[j])
+            }
+
+            //wifi mac
+            for (let k = 0; k < 12; k++) {
+                dataList.push(oneItem.adwWifiMac[k])
+            }
+        }
+
+        //将基站数据发送给worker
+        if (app.data.worker != null) {
+            app.data.worker.postMessage({
+                message: 'MAIN_WORKER_INSTALL_BTS_DATA',
+                data: dataList//安装基站数据
+            })
+        }
+    },
+    //生成多个安装基站
+    generateMultiInstallBts() {
+        let oneItem = {}
+        let maxTmpX = -10000000
+        let minTmpX = 10000000
+        let maxTmpY = -10000000
+        let minTmpY = 10000000
+        let xTmp = 0
+        let yTmp = 0
+
+        let btsNum = 5;//基站数量
+
+        //清空基站队列
+        this.data.installBtsList = []
+
+        // a81710d94e04 1号基站
+        // a81710d9a1c8 2号基站
+        // a81710d8f9be 3号基站
+        // a81710d91a5e 4号基站
+
+        // 1号基站,基站ID,频率ID(0 - 8),时隙ID(0 - 3),X坐标(单位厘米),Y坐标(单位厘米),MAC地址,定位类型,1维定位基站数,1维定位基站ID队列
+        oneItem = this.generateOneInstallBts(10001, 0, 0, 0, 0, "685377178192", 2, 0, "")
+        this.data.installBtsList.push(oneItem)
+
+        //2号基站
+        oneItem = this.generateOneInstallBts(10002, 1, 1, 2500, 0, "685377183652", 3, 1, "10005")
+        this.data.installBtsList.push(oneItem)
+
+        //3号基站
+        oneItem = this.generateOneInstallBts(10003, 2, 2, 2500, 800, "6853771679b0", 2, 0, "")
+        this.data.installBtsList.push(oneItem)
+
+        //4号基站
+        oneItem = this.generateOneInstallBts(10004, 3, 3, 0, 800, "68537716c686", 2, 0, "")
+        this.data.installBtsList.push(oneItem)
+
+        //5号基站
+        oneItem = this.generateOneInstallBts(10005, 4, 0, 5000, 0, "685377183238", 1, 1, "10002")
+        this.data.installBtsList.push(oneItem)
+
+        // //6号基站
+        // oneItem = this.generateOneInstallBts(10006, 5, 0, 5500, 0, "685377174ea4", 1, 1, "10005")
+        // this.data.installBtsList.push(oneItem)
+
+
+        // //7号基站
+        // oneItem = this.generateOneInstallBts(2097114, 6, 0, 500, 180, "a81710d94198", 2, 1, "0")
+        // this.data.installBtsList.push(oneItem)
+
+        // // //8号基站
+        // // oneItem = this.generateOneInstallBts(2097110, 7, 0, 800, 1800, "a81710d951dc")
+        // // this.data.installBtsList.push(oneItem)
+
+        for (let i = 0; i < btsNum; i++) {
+            xTmp = this.data.installBtsList[i].dwCoordX
+            if (xTmp > maxTmpX) {
+                maxTmpX = xTmp
+            }
+            if (xTmp < minTmpX) {
+                minTmpX = xTmp
+            }
+
+            yTmp = this.data.installBtsList[i].dwCoordY
+            if (yTmp > maxTmpY) {
+                maxTmpY = yTmp
+            }
+            if (yTmp < minTmpY) {
+                minTmpY = yTmp
+            }
+        }
+
+        //保存值
+        this.setData({
+            btsMinCoordX: minTmpX,
+            btsMaxCoordX: maxTmpX,
+            btsMinCoordY: minTmpY,
+            btsMaxCoordY: maxTmpY
+        })
+    },
+    //生成一个安装基站数据
+    generateOneInstallBts(dwBtsId, dwFreqIndex, dwSlot, dwCoordX, dwCoordY, macAddressList, dwLctType, dwTwoBtsNum, adwTwoBtsList) {
+        let installBts = {}
+        let oneCharHex = ''
+        let oneCharHexValue = 0
+
+        installBts.dwBuildId = 1
+        installBts.dwLayerId = 1
+
+        installBts.dwBtsId = dwBtsId
+        installBts.dwFreqIndex = dwFreqIndex
+        installBts.dwSlot = dwSlot
+
+        installBts.dwCoordX = dwCoordX
+        installBts.dwCoordY = dwCoordY
+        installBts.dwCoordZ = 270
+
+        //定位类型,1:一维定位,2:二维定位,3:既支持一维定位也支持二维定位
+        installBts.dwLctType = dwLctType
+
+        //一维定位基站数量
+        installBts.dwTwoBtsNum = dwTwoBtsNum;
+
+        //一维定位基站ID队列
+        installBts.adwTwoBtsList = []
+        let btsIdList = adwTwoBtsList.split(",")
+        let btsId = 0
+
+        // if (btsIdList.length != dwTwoBtsNum) {
+        //     console.log("长度错误")
+        //     return;
+        // }
+        for (let k = 0; k < dwTwoBtsNum; k++) {
+            btsId = parseInt(btsIdList[k])
+            console.log("基站ID:", btsId)
+            installBts.adwTwoBtsList.push(btsId)
+        }
+
+        //蓝牙mac
+        installBts.adwBluetoothMac = []
+        for (let i = 0; i < 12; i++) {
+            oneCharHex = macAddressList[i]
+            oneCharHexValue = app.hexCharToValue(oneCharHex)
+            installBts.adwBluetoothMac.push(oneCharHexValue)
+        }
+
+        //wifi mac
+        installBts.adwWifiMac = []
+        for (let j = 0; j < 12; j++) {
+            oneCharHex = macAddressList[j]
+            oneCharHexValue = app.hexCharToValue(oneCharHex)
+            installBts.adwWifiMac.push(oneCharHexValue)
+        }
+
+        console.log(installBts)
+        return installBts
+    },
+    //TODO,生成蓝牙基站数据
+    generateBleBtsData() {
+        let mac = "123456789ABC"
+        let rssi = -58
+        let item = {}
+        item['mac'] = mac;
+        item['rssi'] = rssi
+
+        let dataList = []
+        dataList.push(item)
+
+        app.data.worker.postMessage({
+            message: 'MAIN_WORKER_BLE_BTS_DATA',
+            code: 100,
+            data: dataList
+        })
+    },
+    //从服务器上下载文件
+    downloadAudioFile() {
+        const url = "https://www.stp.intourism.cn/tdsadmin/line390-zt.wav"
+
+        wx.downloadFile({
+            url: url,
+            success: (res) => {
+                if (res.statusCode == 200) {
+                    app.data.wavFilePath = res.tempFilePath
+                    this.readAudioFile();
+                }
+            }
+        })
+    },
+    // 读取音频文件
+    readAudioFile() {
+        let filePath = app.data.wavFilePath
+
+        if (app.data.readFileTimes == null) {
+            return
+        }
+
+        //90秒数据
+        if (app.data.readFileTimes >= 360) {
+            app.data.readFileTimes = 0
+            return
+        }
+
+        const times = app.data.readFileTimes
+        const fs = wx.getFileSystemManager();
+        fs.readFile({
+            filePath: `${filePath}`,
+            position: 44 + times * 12000 * 2,
+            length: 12000 * 2,//12000点,每点2个字节
+            success: (res) => {
+                app.data.readFileTimes++
+                app.data.worker.postMessage({
+                    message: 'MAIN_WORKER_AUDIO_DATA',
+                    data: res.data
+                })
+            },
+            fail(res) {
+                console.log(res);
+            }
+        })
+    },
+    drawPosition(type, coordinateX, coordinateY, coordDataList) {
+        let i = 0
+        let pos = 0
+        let space = 10
+        let lineCount = 40
+
+        let ctxMinX = this.data.btsMinCoordX - 500
+        let ctxMaxX = this.data.btsMaxCoordX + 500
+        let ctxMinY = this.data.btsMinCoordY - 500
+        let ctxMaxY = this.data.btsMaxCoordY + 500
+        let ctxWidth = ctxMaxX - ctxMinX
+        let ctxHeight = ctxMaxY - ctxMinY
+
+        let radio = this.data.canvasWidth / ctxWidth
+        let radioHeight = this.data.canvasHeight / ctxHeight
+        if (radioHeight < radio) {
+            radio = radioHeight
+        }
+
+        let positionCtx = this.data.canvasCtx
+        positionCtx.fillStyle = "#0066FF"
+        positionCtx.clearRect(0, 0, this.data.canvasWidth, this.data.canvasHeight)
+        positionCtx.lineWidth = 0.5
+
+        //// 画网格
+        positionCtx.beginPath()
+        positionCtx.strokeStyle = '#CCCCCC'
+
+        //1、画网格横线
+        space = parseFloat(this.data.canvasWidth) / lineCount
+        for (i = 0; i < lineCount; i++) {
+            pos = i * space
+            positionCtx.moveTo(0, pos)
+            positionCtx.lineTo(this.data.canvasWidth, pos)
+        }
+
+        //2、画网格竖线
+        space = parseFloat(this.data.canvasHeight) / lineCount
+        for (i = 0; i < lineCount; i++) {
+            pos = i * space
+            positionCtx.moveTo(pos, 0)
+            positionCtx.lineTo(pos, this.data.canvasHeight)
+        }
+        positionCtx.stroke()
+
+        if (type === 0) { //仅画网格
+            return
+        }
+
+        // 画基站
+        positionCtx.beginPath()
+        positionCtx.fillStyle = "#FF0000"
+        positionCtx.strokeStyle = '#FF0000'
+        for (i = 0; i < this.data.installBtsList.length; i++) {
+            let x = (this.data.installBtsList[i].dwCoordX - ctxMinX) * radio
+            let y = this.data.canvasHeight - (this.data.installBtsList[i].dwCoordY - ctxMinY) * radio
+            positionCtx.fillRect(x - 5, y - 5, 6, 6);
+            // if (masterFreqFrequency === app.store.signal.ubsList[i].device_frequency) {
+            //     positionCtx.fillRect(x - 8, y - 8, 16, 16);
+            // } else {
+            //     positionCtx.fillRect(x - 5, y - 5, 10, 10);
+            // }
+        }
+
+        // 画轨迹线
+        positionCtx.lineWidth = 1.2
+        positionCtx.beginPath()
+        positionCtx.strokeStyle = '#0A0A0A'
+        for (i = 2; i < coordDataList.length; i++) {
+            let point = coordDataList[i - 1]
+            let x = (point.x - ctxMinX) * radio
+            let y = this.data.canvasHeight - (point.y - ctxMinY) * radio
+            positionCtx.moveTo(x, y)
+
+            point = coordDataList[i]
+            x = (point.x - ctxMinX) * radio
+            y = this.data.canvasHeight - (point.y - ctxMinY) * radio
+            positionCtx.lineTo(x, y)
+        }
+        positionCtx.stroke()
+
+        // 画当前位置
+        positionCtx.beginPath()
+        positionCtx.fillStyle = "#0000FF"
+        positionCtx.strokeStyle = '#0000FF'
+        let x = (coordinateX - ctxMinX) * radio
+        let y = this.data.canvasHeight - (coordinateY - ctxMinY) * radio
+        positionCtx.arc(x, y, 3, 0, 2 * Math.PI);
+        positionCtx.fill()
+    },
+    drawLocationTrace(coord) {
+        let x = coord.x
+        let y = coord.y
+
+        //保存定位坐标
+        this.data.coordDataList.push(coord)
+
+        this.drawPosition(1, x, y, this.data.coordDataList)
+    }
+})

+ 3 - 0
pages/home/home.json

@@ -0,0 +1,3 @@
+{
+  "usingComponents": {}
+}

+ 41 - 0
pages/home/home.scss

@@ -0,0 +1,41 @@
+.home {
+    width: 100%;
+    font-size: 13px;
+
+    &__top {
+        padding: 0px 10px;
+        height: 40px;
+        text-align: center;
+
+        &_recordbtn {
+            width: 100px;
+        }
+    }
+
+    &__bottom {
+        width: 100%;
+    }
+
+    &__param {
+        border: 1px solid;
+        margin: 5px;
+        padding: 10px;
+
+        &_txt {
+            width: 100%;
+            height: 25px;
+            line-height: 25px;
+            color: #000;
+            font-size: 15px;
+            display: flex;
+
+            &_item1 {
+                width: 100%;
+            }
+
+            &_item2 {
+                width: 50%;
+            }
+        }
+    }
+}

+ 9 - 0
pages/home/home.wxml

@@ -0,0 +1,9 @@
+<view class="container">
+    <view class="home">
+        <view class="home__bottom">
+            <canvas id="homeCanvas" type="2d" canvas-id="homeCanvas" style="width:{{canvasWidth}}px; height:{{canvasHeight}}px; border: 1px solid; margin-left: 5px; margin-right: 5px;" disable-scroll="true" />
+        </view>
+    </view>
+</view>
+
+

+ 1 - 0
pages/home/home.wxss

@@ -0,0 +1 @@
+/* pages/home/home.wxss */

+ 49 - 0
pages/index/index.js

@@ -0,0 +1,49 @@
+// index.js
+const defaultAvatarUrl = 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0'
+
+Page({
+  data: {
+    motto: 'Hello World',
+    userInfo: {
+      avatarUrl: defaultAvatarUrl,
+      nickName: '',
+    },
+    hasUserInfo: false,
+    canIUseGetUserProfile: wx.canIUse('getUserProfile'),
+    canIUseNicknameComp: wx.canIUse('input.type.nickname'),
+  },
+  bindViewTap() {
+    wx.navigateTo({
+      url: '../logs/logs'
+    })
+  },
+  onChooseAvatar(e) {
+    const { avatarUrl } = e.detail
+    const { nickName } = this.data.userInfo
+    this.setData({
+      "userInfo.avatarUrl": avatarUrl,
+      hasUserInfo: nickName && avatarUrl && avatarUrl !== defaultAvatarUrl,
+    })
+  },
+  onInputChange(e) {
+    const nickName = e.detail.value
+    const { avatarUrl } = this.data.userInfo
+    this.setData({
+      "userInfo.nickName": nickName,
+      hasUserInfo: nickName && avatarUrl && avatarUrl !== defaultAvatarUrl,
+    })
+  },
+  getUserProfile(e) {
+    // 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认,开发者妥善保管用户快速填写的头像昵称,避免重复弹窗
+    wx.getUserProfile({
+      desc: '展示用户信息', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
+      success: (res) => {
+        console.log(res)
+        this.setData({
+          userInfo: res.userInfo,
+          hasUserInfo: true
+        })
+      }
+    })
+  },
+})

+ 4 - 0
pages/index/index.json

@@ -0,0 +1,4 @@
+{
+  "usingComponents": {
+  }
+}

+ 27 - 0
pages/index/index.wxml

@@ -0,0 +1,27 @@
+<!--index.wxml-->
+<scroll-view class="scrollarea" scroll-y type="list">
+  <view class="container">
+    <view class="userinfo">
+      <block wx:if="{{canIUseNicknameComp && !hasUserInfo}}">
+        <button class="avatar-wrapper" open-type="chooseAvatar" bind:chooseavatar="onChooseAvatar">
+          <image class="avatar" src="{{userInfo.avatarUrl}}"></image>
+        </button>
+        <view class="nickname-wrapper">
+          <text class="nickname-label">昵称</text>
+          <input type="nickname" class="nickname-input" placeholder="请输入昵称" bind:change="onInputChange" />
+        </view>
+      </block>
+      <block wx:elif="{{!hasUserInfo}}">
+        <button wx:if="{{canIUseGetUserProfile}}" bindtap="getUserProfile"> 获取头像昵称 </button>
+        <view wx:else> 请使用2.10.4及以上版本基础库 </view>
+      </block>
+      <block wx:else>
+        <image bindtap="bindViewTap" class="userinfo-avatar" src="{{userInfo.avatarUrl}}" mode="cover"></image>
+        <text class="userinfo-nickname">{{userInfo.nickName}}</text>
+      </block>
+    </view>
+    <view class="usermotto">
+      <text class="user-motto">{{motto}}</text>
+    </view>
+  </view>
+</scroll-view>

+ 62 - 0
pages/index/index.wxss

@@ -0,0 +1,62 @@
+/**index.wxss**/
+page {
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+}
+.scrollarea {
+  flex: 1;
+  overflow-y: hidden;
+}
+
+.userinfo {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  color: #aaa;
+  width: 80%;
+}
+
+.userinfo-avatar {
+  overflow: hidden;
+  width: 128rpx;
+  height: 128rpx;
+  margin: 20rpx;
+  border-radius: 50%;
+}
+
+.usermotto {
+  margin-top: 200px;
+}
+
+.avatar-wrapper {
+  padding: 0;
+  width: 56px !important;
+  border-radius: 8px;
+  margin-top: 40px;
+  margin-bottom: 40px;
+}
+
+.avatar {
+  display: block;
+  width: 56px;
+  height: 56px;
+}
+
+.nickname-wrapper {
+  display: flex;
+  width: 100%;
+  padding: 16px;
+  box-sizing: border-box;
+  border-top: .5px solid rgba(0, 0, 0, 0.1);
+  border-bottom: .5px solid rgba(0, 0, 0, 0.1);
+  color: black;
+}
+
+.nickname-label {
+  width: 105px;
+}
+
+.nickname-input {
+  flex: 1;
+}

+ 18 - 0
pages/logs/logs.js

@@ -0,0 +1,18 @@
+// logs.js
+const util = require('../../utils/util.js')
+
+Page({
+  data: {
+    logs: []
+  },
+  onLoad() {
+    this.setData({
+      logs: (wx.getStorageSync('logs') || []).map(log => {
+        return {
+          date: util.formatTime(new Date(log)),
+          timeStamp: log
+        }
+      })
+    })
+  }
+})

+ 4 - 0
pages/logs/logs.json

@@ -0,0 +1,4 @@
+{
+  "usingComponents": {
+  }
+}

+ 6 - 0
pages/logs/logs.wxml

@@ -0,0 +1,6 @@
+<!--logs.wxml-->
+<scroll-view class="scrollarea" scroll-y type="list">
+  <block wx:for="{{logs}}" wx:key="timeStamp" wx:for-item="log">
+    <view class="log-item">{{index + 1}}. {{log.date}}</view>
+  </block>
+</scroll-view>

+ 16 - 0
pages/logs/logs.wxss

@@ -0,0 +1,16 @@
+page {
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+}
+.scrollarea {
+  flex: 1;
+  overflow-y: hidden;
+}
+.log-item {
+  margin-top: 20rpx;
+  text-align: center;
+}
+.log-item:last-child {
+  padding-bottom: env(safe-area-inset-bottom);
+}

+ 28 - 0
project.config.json

@@ -0,0 +1,28 @@
+{
+  "compileType": "miniprogram",
+  "libVersion": "trial",
+  "packOptions": {
+    "ignore": [],
+    "include": []
+  },
+  "setting": {
+    "coverView": true,
+    "es6": true,
+    "postcss": true,
+    "minified": true,
+    "enhance": true,
+    "showShadowRootInWxmlPanel": true,
+    "packNpmRelationList": [],
+    "babelSetting": {
+      "ignore": [],
+      "disablePlugins": [],
+      "outputPath": ""
+    }
+  },
+  "condition": {},
+  "editorSetting": {
+    "tabIndent": "auto",
+    "tabSize": 2
+  },
+  "appid": "wx6057c2a6b6474a01"
+}

+ 7 - 0
project.private.config.json

@@ -0,0 +1,7 @@
+{
+  "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
+  "projectname": "apls8000wx",
+  "setting": {
+    "compileHotReLoad": true
+  }
+}

+ 7 - 0
sitemap.json

@@ -0,0 +1,7 @@
+{
+  "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
+  "rules": [{
+  "action": "allow",
+  "page": "*"
+  }]
+}

+ 19 - 0
utils/util.js

@@ -0,0 +1,19 @@
+const formatTime = date => {
+  const year = date.getFullYear()
+  const month = date.getMonth() + 1
+  const day = date.getDate()
+  const hour = date.getHours()
+  const minute = date.getMinutes()
+  const second = date.getSeconds()
+
+  return `${[year, month, day].map(formatNumber).join('/')} ${[hour, minute, second].map(formatNumber).join(':')}`
+}
+
+const formatNumber = n => {
+  n = n.toString()
+  return n[1] ? n : `0${n}`
+}
+
+module.exports = {
+  formatTime
+}

BIN
wasm/aplm8000sdk.wasm


+ 450 - 0
worker/wasmapi.js

@@ -0,0 +1,450 @@
+var printCharBuffers = [null, [], []]
+var UTF8Decoder = typeof TextDecoder !== 'undefined' ? new TextDecoder('utf8') : undefined
+var HEAP8 = null // Int8Array
+var HEAPU8 = null // Uint8Array
+var HEAP16 = null // Int16Array
+var HEAPU16 = null // Uint16Array
+var HEAP32 = null // Int32Array
+var HEAPU32 = null // Uint32Array
+var HEAPF32 = null // Float32Array
+var HEAPF64 = null // Float64Array
+
+//注入函数,打印字符串,C代码中打印字符串需要以\n结束
+function printChar(stream, curr) {
+    var buffer = printCharBuffers[stream];
+    //assert(buffer);
+    if (curr === 0 || curr === 10) {
+        //(stream === 1 ? out : err)(this.UTF8ArrayToString(buffer, 0));
+        //buffer.length = 0;
+        let str = UTF8ArrayToString(buffer, 0)
+        console.log(str)//打印字符串
+        buffer.length = 0;
+    } else {
+        buffer.push(curr);
+    }
+}
+
+//注入函数,将字节数组转成字符数组
+function UTF8ArrayToString(heapOrArray, idx, maxBytesToRead) {
+    var endIdx = idx + maxBytesToRead;
+    var endPtr = idx;
+    // TextDecoder needs to know the byte length in advance, it doesn't stop on
+    // null terminator by itself.  Also, use the length info to avoid running tiny
+    // strings through TextDecoder, since .subarray() allocates garbage.
+    // (As a tiny code save trick, compare endPtr against endIdx using a negation,
+    // so that undefined means Infinity)
+    while (heapOrArray[endPtr] && !(endPtr >= endIdx)) ++endPtr;
+
+    if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) {
+        return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr));
+    }
+    var str = '';
+    // If building with TextDecoder, we have already computed the string length
+    // above, so test loop end condition against that
+    while (idx < endPtr) {
+        // For UTF8 byte structure, see:
+        // http://en.wikipedia.org/wiki/UTF-8#Description
+        // https://www.ietf.org/rfc/rfc2279.txt
+        // https://tools.ietf.org/html/rfc3629
+        var u0 = heapOrArray[idx++];
+        if (!(u0 & 0x80)) { str += String.fromCharCode(u0); continue; }
+        var u1 = heapOrArray[idx++] & 63;
+        if ((u0 & 0xE0) == 0xC0) { str += String.fromCharCode(((u0 & 31) << 6) | u1); continue; }
+        var u2 = heapOrArray[idx++] & 63;
+        if ((u0 & 0xF0) == 0xE0) {
+            u0 = ((u0 & 15) << 12) | (u1 << 6) | u2;
+        } else {
+            //if ((u0 & 0xF8) != 0xF0) warnOnce('Invalid UTF-8 leading byte ' + ptrToString(u0) + ' encountered when deserializing a UTF-8 string in wasm memory to a JS string!');
+            u0 = ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | (heapOrArray[idx++] & 63);
+        }
+
+        if (u0 < 0x10000) {
+            str += String.fromCharCode(u0);
+        } else {
+            var ch = u0 - 0x10000;
+            str += String.fromCharCode(0xD800 | (ch >> 10), 0xDC00 | (ch & 0x3FF));
+        }
+    }
+    return str;
+}
+
+const wasmapi = {
+    wasmInstance: null,//wasm实例
+    wasmFuncs: null,//wasm导出函数
+    wasmDataPtr: {//wasm模块的数据指针
+        installBtsDataPtr: 0,//安装基站数据指针
+        bleBtsDataPtr: 0,//蓝牙基站数据指针
+        audioDataPtr: 0,//音频数据指针
+        currentLocationCoordPtr: 0,//定位坐标数据指针
+        accDataPtr: 0,//加速度指针
+    },
+    data: {
+        audioDataList: [],//音频数据队列
+        oneFrameLen: 12000,//一帧音频数据长度
+
+        bleBtsMonitorSuccessFlag: false,//蓝牙基站监听成功标识符
+        bleBtsDataList: [], //蓝牙基站队列
+
+        installBtsInjectFlag: false,//安装基站已注入标识符
+
+        accDataList: [],//加速度数据队列
+    },
+    ////wasm注入函数,将数据从源地址src拷贝长度为num的数据到目的地址dest
+    _emscripten_memcpy_js(dest, src, num) {
+        HEAPU8.copyWithin(dest, src, src + num)
+    },
+    _emscripten_resize_heap(requestedSize) {
+        let oldSize = HEAPU8.length
+        // With CAN_ADDRESS_2GB or MEMORY64, pointers are already unsigned.
+        requestedSize >>>= 0
+        this.abortOnCannotGrowMemory(requestedSize)
+    },
+    abortOnCannotGrowMemory(requestedSize) {
+        abort(`Cannot enlarge memory arrays to size ${requestedSize} bytes (OOM). Either (1) compile with -sINITIAL_MEMORY=X with X higher than the current value ${HEAP8.length}, (2) compile with -sALLOW_MEMORY_GROWTH which allows increasing the size at runtime, or (3) if you want malloc to return NULL (0) instead of this abort, compile with -sABORTING_MALLOC=0`);
+    },
+    _fd_write(fd, iov, iovcnt, pnum) {
+        let num = 0
+        for (let i = 0; i < iovcnt; i++) {
+            let ptr = HEAP32[((iov) >> 2)]
+            let len = HEAP32[(((iov) + (4)) >> 2)]
+            iov += 8
+            for (let j = 0; j < len; j++) {
+                printChar(fd, HEAPU8[ptr + j])
+            }
+            num += len
+        }
+        HEAP32[((pnum) >> 2)] = num
+
+        return 0
+    },
+    _fd_close(fd) {
+        abort('fd_close called without SYSCALLS_REQUIRE_FILESYSTEM');
+        return 0
+    },
+    _fd_seek(fd, offset_low, offset_high, whence, newOffset) {
+        abort('it should not be possible to operate on streams when !SYSCALLS_REQUIRE_FILESYSTEM')
+        return 0;
+    },
+    _emscripten_date_now() {
+        return new Date().getTime()
+    },
+    // __emscripten_get_now_is_monotonic() {
+    //     return 1
+    // },
+    // _emscripten_get_now() {
+    //     return new Date().getTime()
+    // },
+    //加载微信wasm模块
+    loadWxWasm() {
+        let self = this
+        let configParam = {}
+        configParam.emscripten_memcpy_js = this._emscripten_memcpy_js
+        configParam.emscripten_resize_heap = this._emscripten_resize_heap
+        configParam.fd_write = this._fd_write
+        configParam.fd_close = this._fd_close
+        configParam.fd_seek = this._fd_seek
+        configParam.emscripten_date_now = this._emscripten_date_now
+        // configParam._emscripten_get_now_is_monotonic = this.__emscripten_get_now_is_monotonic
+        // configParam.emscripten_get_now = this._emscripten_get_now
+
+        let importObject = {}
+        importObject.env = configParam
+        importObject.wasi_snapshot_preview1 = configParam
+
+        //返回值res:{instance{}, Module{}}:包含instance和Module对象,instance中也包含Module。
+        //js使用instance或者Module中Exports对象中的函数(包括用户自定义的函数和系统函数)完成既定功能
+        //instantiate的第一个参数为绝对路径
+        WXWebAssembly.instantiate("wasm/aplm8000sdk.wasm", importObject).then((res) => {
+            self.wasmInstance = res.instance
+            self.wasmFuncs = this.wasmInstance.exports
+            if (self.wasmInstance.exports['memory']) {//初始化内存
+                let buf = self.wasmInstance.exports['memory'].buffer
+                HEAP8 = new Int8Array(buf)
+                HEAPU8 = new Uint8Array(buf)
+                HEAP16 = new Int16Array(buf)
+                HEAPU16 = new Uint16Array(buf)
+                HEAP32 = new Int32Array(buf)
+                HEAPU32 = new Uint32Array(buf)
+                HEAPF32 = new Float32Array(buf)
+                HEAPF64 = new Float64Array(buf)
+            }
+
+            //wasm模块初始化
+            self.wasmFuncs.main()
+
+            //获得wasm模块路由基站数据指针
+            self.wasmDataPtr.bleBtsDataPtr = self.wasmFuncs.exchange_js_get_route_bts_save_address()
+
+            //获得wasm模块安装基站的数据指针
+            self.wasmDataPtr.installBtsDataPtr = self.wasmFuncs.exchange_js_get_install_bts_save_address()
+
+            //获得wasm模块音频数据指针
+            self.wasmDataPtr.audioDataPtr = self.wasmFuncs.exchange_js_get_audio_data_save_address()
+
+            //获得wasm模块定位坐标数据指针
+            self.wasmDataPtr.currentLocationCoordPtr = self.wasmFuncs.exchange_js_get_current_location_coord_save_address()
+
+            //获得wasm模块加速度数据指针
+            self.wasmDataPtr.accDataPtr = self.wasmFuncs.exchange_js_get_acc_save_address()
+
+            //通知主控模块开始监听蓝牙基站
+            worker.postMessage({
+                message: 'WORKER_MAIN_START_MONITOR_BLE_BTS',
+                data: ''
+            })
+
+            // //通知主控模块开始录音
+            // worker.postMessage({
+            //     message: 'WORKER_MAIN_START_REC',
+            //     data: ''
+            // })
+
+            // //通知主控模块开始读取本地文件
+            // worker.postMessage({
+            //     message: 'WORKER_MAIN_NOTICE_DOWNLOAD_AUDIO_FILE',
+            //     data: ''
+            // })
+        }).catch((err) => {
+            console.log("加载wasm对象出错", err)
+        })
+    },
+    //保存蓝牙基站数据至本地
+    saveBleBtsDataToLocal(dataList) {
+        if (!dataList || dataList.length < 4) {
+            return
+        }
+
+        //蓝牙基站监听成功,保存数据
+        this.data.bleBtsMonitorSuccessFlag = true//蓝牙基站监听成功
+        this.data.bleBtsDataList.splice(0, this.data.bleBtsDataList.length)
+        this.data.bleBtsDataList = dataList
+
+        //通知主控模块注入安装基站数据(只注入一次)
+        if (!this.data.installBtsInjectFlag) {
+            worker.postMessage({
+                message: 'WORKER_MAIN_ORGANIZE_INSTALL_BTS_DATA',//通知主线程组织安装基站数据
+                data: ''
+            })
+        }
+    },
+    //清除蓝牙基站设备(没有监听到本系统的蓝牙基站)
+    clearBleBtsData() {
+        this.data.bleBtsMonitorSuccessFlag = false//蓝牙基站监听失败
+        this.data.bleBtsDataList.splice(0, this.data.bleBtsDataList.length)
+    },
+    //发送蓝牙基站数据至wasm模块
+    sendBleBtsDataToWasm() {
+        let dataList = []
+
+        //监听蓝牙基站失败
+        if (!this.data.bleBtsMonitorSuccessFlag) {
+            return
+        }
+
+        //将数据保存至wasm模块
+        dataList = this.data.bleBtsDataList
+        let dataLen = dataList.length
+        if (this.wasmDataPtr.bleBtsDataPtr > 0) {
+            let ptr = this.wasmDataPtr.bleBtsDataPtr / 4//蓝牙基站数据在wasm模块按4字节存储
+            for (let i = 0; i < dataLen; i++) {
+                HEAP32[ptr + i] = dataList[i]//写入数据
+            }
+        } else {
+            console.log("蓝牙指针尚未初始化")
+        }
+
+        //清除本地蓝牙基站数据
+        this.data.bleBtsDataList.splice(0, this.data.bleBtsDataList.length)
+        this.data.bleBtsMonitorSuccessFlag = false
+
+        // // //TODO,打印一下看看效果,正式版本要去掉
+        // if (this.wasmFuncs != null) {
+        //     this.wasmFuncs.exchange_js_notice_wasm_show_route_bts();
+        // }
+    },
+    //发送安装基站数据至wasm模块
+    sendInstallBtsDataToWasm(dataList) {
+        //将数据保存至wasm模块
+        if (this.wasmDataPtr.installBtsDataPtr > 0) {
+            this.data.installBtsInjectFlag = true//注入标识符置1
+
+            //向wasm模块注入数据
+            let ptr = this.wasmDataPtr.installBtsDataPtr / 4//蓝牙基站数据在wasm模块按4字节存储
+            for (let i = 0; i < dataList.length; i++) {
+                HEAP32[ptr + i] = dataList[i]
+            }
+
+            ////TODO 打印看看效果
+            //this.wasmFuncs.exchange_js_notice_wasm_show_install_bts()
+
+            //向主线程发消息开始录音
+            worker.postMessage({
+                message: 'WORKER_MAIN_START_REC',
+                data: ''
+            })
+        }
+
+        // // TODO,打印一下看看效果,正式版本要去掉
+        // if (this.wasmFuncs != null) {
+        //     this.wasmFuncs.exchange_js_notice_wasm_show_install_bts()
+        // }
+    },
+    //处理音频数据,实施定位操作
+    //buffer:录音数据,为二进制单字节(byte)数据数组
+    processAudioData(buffer) {
+        //注入蓝牙基站数据
+        this.sendBleBtsDataToWasm()
+
+        ////创建数据视图。
+        //录音数据是arraryBuffer类型,该类型数据为二进制单字节(byte)数据数组,不能直接读写,需要在buffer上创建数据视图后方可操作数据
+        const dataView = new DataView(buffer);//创建一个数据视图
+
+        //遍历数据视图
+        let sample = 0
+        let dataLen = Math.floor(dataView.byteLength / 2) //一个采样点为两个字节
+        if (dataLen > 0) {
+            //把数据插入全局队列,以便实施成帧处理
+            for (let i = 0; i < dataLen; i++) {
+                sample = dataView.getInt16(2 * i, true)//数据大小端模式,true:小端模式,false:大端模式
+                this.data.audioDataList.push(sample)
+            }
+
+            //计算全局队列中数据的帧数
+            let frameCounter = Math.floor(this.data.audioDataList.length / this.data.oneFrameLen)
+            if (frameCounter) {//至少有一帧数据
+                //截取音频数据帧(可能是1帧,也可能是多帧。若是多帧,有可能导致处理超时)
+                const dataList = this.data.audioDataList.splice(0, frameCounter * this.data.oneFrameLen)
+
+                //把音频数据逐帧注入wasm模块
+                let ptr = this.wasmDataPtr.audioDataPtr / 2//音频数据在wasm模块中按2字节存储
+                for (let i = 0; i < frameCounter; i++) {
+                    //let beginTime = new Date().getTime()//开始计时
+
+                    //1、把数据注入wasm模块
+                    let baseIndex = i * this.data.oneFrameLen
+                    for (let j = 0; j < this.data.oneFrameLen; j++) {
+                        let item = dataList[baseIndex + j]
+                        HEAP16[ptr + j] = item//把音频数据注入wasm模块
+                    }
+
+                    //2、注入加速度数据
+                    let oneFrameAccDataList = []
+                    oneFrameAccDataList = this.organizeOneFrameAccData()//组织一秒加速度数据
+                    this.injectAccDataToWasm(oneFrameAccDataList)//注入数据
+
+
+                    //3、通知wasm模块处理音频数据
+                    let result = this.wasmFuncs.exchange_js_notice_wasm_start_work()
+
+                    //let stopTime = new Date().getTime() - beginTime//结束计时
+                    //console.log("耗时:", stopTime)
+
+                    //4、获取wasm模块处理音频数据的结果
+                    if (result != 1) {
+                        console.log("音频数据处理错误")
+                    } else {
+                        let item = []
+
+                        //定位坐标数据在wasm模块中按4字节存储
+                        let locationCoordPtr = this.wasmDataPtr.currentLocationCoordPtr / 4
+
+                        //a、从wasm模块读取音频数据处理结果
+                        for (let i = 0; i < 18; i++) {
+                            item.push(HEAP32[locationCoordPtr + i])
+                        }
+
+                        //TODO,b、使用定位结果这部分,需要二次开放商根据业务需求,做定制开发
+                        //使用定位结果
+                        if (item[0] === 1 && item[1] === 1) {
+                            let coord = {}
+                            coord.x = item[2]
+                            coord.y = item[3]
+
+                            //imu移动标识符
+                            const imuMoveFlag = item[17]//0:惯导计算出错,1:惯导计算成功,但手机未移动,50:惯导计算能成功,手机有移动
+                            if (imuMoveFlag == 50) {//手机有移动
+                                //将定位坐标发送至主线程
+                                worker.postMessage({
+                                    message: 'WORKER_MAIN_CURRENT_LOCATION_COORD',
+                                    data: coord
+                                })
+                            } else {//手机未移动或者出错
+                                console.log("imu move flag:", imuMoveFlag)
+                            }
+                        }
+
+                        // //向主线程发消息,通知读取音频文件
+                        // worker.postMessage({
+                        //     message: 'WORKER_MAIN_NOTICE_READ_AUDIO_FILE',//读取音频文件
+                        //     data: ''
+                        // })
+                    }
+                }
+            }
+        }
+        return
+    },
+    //缓存加速度数据
+    storeAccData(oneAccData) {
+        let item = {}
+        item.timestamp = oneAccData.timestamp
+        item.x = oneAccData.x
+        item.y = oneAccData.y
+        item.z = oneAccData.z
+
+        this.data.accDataList.push(item)//缓存加速度
+    },
+    //组织一帧加速度数据(约250毫秒)
+    organizeOneFrameAccData() {
+        let oneFrameAccData = [] //一帧加速度数据
+
+        if (this.data.accDataList.length > 0) {
+            let stopTimestamp = this.data.accDataList[0].timestamp + 260//250毫秒的数据
+            let accCounter = 0//计数器
+
+            for (let k = 0; k < this.data.accDataList.length; k++) {
+                let oneAcc = {}
+
+                oneAcc = this.data.accDataList[k]
+                if (oneAcc.timestamp <= stopTimestamp) {
+                    oneFrameAccData.push(oneAcc)
+                    accCounter++
+                } else {
+                    break
+                }
+            }
+            this.data.accDataList = this.data.accDataList.slice(accCounter)//截取加速度队列
+        }
+        return oneFrameAccData
+    },
+    //注入加速度到wasm模块
+    injectAccDataToWasm(dataList) {
+        let ptr = this.wasmDataPtr.accDataPtr / 4//加速度数据在wasm模块中按4字节存储
+        let wLen = dataList.length;
+        let item = {}
+
+        if (wLen > 0) {
+            //注入队列长度
+            HEAPU32[ptr] = wLen //加速度队列长度
+            ptr++
+
+            //注入3轴加速度
+            for (let i = 0; i < wLen; i++) {
+                item = dataList[i]
+                ptr = ptr + i * 3//修改数据指针
+                HEAPF32[ptr] = item.x//x轴加速度
+                HEAPF32[ptr + 1] = item.y//y轴加速度
+                HEAPF32[ptr + 2] = item.z//z轴加速度
+            }
+
+            //保存数据
+            this.wasmFuncs.exchange_js_notice_wasm_save_acc()
+        }
+    },
+    //通知wasm模块释放内存
+    noticeWasmFreeMemory() {
+        this.wasmFuncs.exchange_js_notice_wasm_free_memory()
+    }
+}
+
+export default wasmapi

+ 49 - 0
worker/worker.js

@@ -0,0 +1,49 @@
+//线程消息转发模块
+//注意,worker是主线程创建worker后,系统注入的全局对象,可直接使用
+
+import wasmapi from './wasmapi.js'
+
+// if (console.log) {
+//     var old = console.log
+//     let self = this
+//     console.log = function () {
+//         worker.postMessage({ message: 'console', data: arguments }) //日志消息
+//     }
+// }
+console.log("hello worker")
+
+//线程监听服务程序
+worker.onMessage((res) => {
+    switch (res.message) {
+        case 'MAIN_WORKER_LOAD_WX_WASM'://加载wxWasm模块
+            wasmapi.loadWxWasm()
+            break
+        case 'MAIN_WORKER_BLE_BTS_DATA'://蓝牙基站数据
+            wasmapi.saveBleBtsDataToLocal(res.data)
+            break
+        case 'MAIN_WORKER_CLEAR_BLE_BTS_DATA'://清除蓝牙基站数据
+            wasmapi.clearBleBtsData(res.data)
+            break
+        case 'MAIN_WORKER_INSTALL_BTS_DATA'://安装基站数据
+            wasmapi.sendInstallBtsDataToWasm(res.data)
+            break
+        case 'MAIN_WORKER_AUDIO_DATA'://音频采样数据
+            wasmapi.processAudioData(res.data)
+            break
+        case 'MAIN_WORKER_FREE_MEMORY'://释放内存
+            wasmapi.noticeWasmFreeMemory()
+            break
+        case 'MAIN_WORKER_SAVE_AUDIO_DATA'://保存录音文件
+            worker.postMessage({
+                message: 'WORKER_MAIN_SAVE_AUDIO_DATA',
+                data: res.data//录音数据
+            })
+            break
+        case 'MAIN_WORKER_ONE_ACC_DATA'://保存加速度
+            wasmapi.storeAccData(res.data)
+            break
+        default:
+            break
+    }
+})
+

+ 7 - 0
wxmini.code-workspace

@@ -0,0 +1,7 @@
+{
+	"folders": [
+		{
+			"path": "."
+		}
+	]
+}