# MrkDeviceDemo **Repository Path**: williamOsChina/mrk-device-demo ## Basic Information - **Project Name**: MrkDeviceDemo - **Description**: MrkDeviceDemo - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-06-25 - **Last Updated**: 2024-09-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 麦瑞克设备连接文档 ## 使用须知 - SDK只提供搜索,连接,运动数据回调等设备的功能,设备产生的运动数据请自行处理。 - 所有设备类型只允许同时连接一个设备。如跑步机已经连接了一台,再连接跑步机时需要先断开之前所连接的跑步机。 - 在使用SDK的功能前,请先确保所需权限以及蓝牙开关是否打开,设备是否唤醒且未被他人连接。 ## 引入&配置SDK 麦瑞克为开发者提供了两种引入SDK方式:自动集成和手动集成。自动集成是指Android的maven依赖,手动集成是指在下载SDK后导入App工程。 #### maven自动集成(推荐) * 在项目根目录`build.gradle`文件的`allprojects.repositories`块中,添加maven库地址配置。 ``` allprojects { repositories { maven { url 'https://maven.aliyun.com/repository/public' } maven { credentials { username '66505af78138868a60f57f91' password '31xhdA(s2-kl' } url 'https://packages.aliyun.com/maven/repository/2160068-snapshot-25MJti' } maven { credentials { username '66505af78138868a60f57f91' password '31xhdA(s2-kl' } url 'https://packages.aliyun.com/maven/repository/2160068-release-Q0kcTH' } } } ``` * 在`app/build.gradle`文件的`dependencies`块中添加SDK依赖。 ``` dependencies { implementation 'com.mrk.device.small:mrkDeviceSmall:1.0.3' } ``` #### 手动集成 * [下载此sdk](app%2Flibs),在Android Studio的项目工程libs目录中拷入相关组件aar包,右键Android Studio的项目工程 —> 选择Open Module Settings —> 在 Project Structure弹出框中 —> 选择 Dependencies选项卡 —> 点击左下方“+” —> 选择组件包类型 —> 引入相应的包。 ``` repositories{ flatDir{ dirs 'libs' }} dependencies { implementation fileTree(include:['*.jar'], dir:'libs') implementation (name:'MrkDevice_1.0.3', ext:'aar') implementation 'com.android.support:appcompat-v7:23.1.0' implementation "org.jetbrains.kotlin:kotlin-reflect:1.3.21" implementation "org.jetbrains.kotlin:kotlin-stdlib:1.3.21" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.0" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.0" implementation 'com.alibaba:fastjson:1.2.83' } ``` ## 使用说明 #### 权限声明 使用过程中需要您打开蓝牙,打开定位开关,需确保开启和授权必要的权限, 精准定位权限和附近的设备权限 。可以查看官方蓝牙权限文档,文档地址:[Google开发者网站关于Bluetooth permissions说明](https://developer.android.com/guide/topics/connectivity/bluetooth/permissions)。 权限说明: - Android 6 至 Android 11 需要动态获取 ACCESS\_FINE\_LOCATION 和 ACCESS\_COARSE\_LOCATION 权限。 - Android 10 至 Android 11 蓝牙扫描需要开启 定位开关。 - Android 12 及以上 需要动态获取 BLUETOOTH\_CONNECT 和BLUETOOTH\_SCAN 权限。 为了搜索与连接设备。需要向您申请以下权限: ``` ``` 代码示例 ``` public String[] getBLEPermissions() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { return new String[]{ Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_ADVERTISE, Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.ACCESS_FINE_LOCATION // 添加这行确保所有情况下蓝牙扫描都能正常工作 }; } else { return new String[]{ Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION }; } } ``` #### SDK初始化 在您应用的启动入口调用SDK的初始化代码:`application`为你项目的Application,`sign`为签名,请联系麦瑞克获取,`isDebug`表示是否是调试模式,调试模式下会打印关键日志。日志TAG为:`BluetoothManager`。 ##### sign分为debugSign与releaseSign。 - debugSign:可连接SDK所支持的所有设备,方便测试以及选品,此sign有效期为30天,过期后请联系麦瑞克重新获取。 - releaseSign:只可连接与麦瑞克协商合作的设备,避免连接未合作的设备,引起不必要的问题。 ``` MrkDeviceManger.INSTANCE.init(application,sign,isDebug); ``` #### 设置搜索配置与连接配置 - 此方法非必须,SDK会有默认的配置 - 以下示例为默认配置 ##### 设备搜索配置 ``` SearchRequest searchConfig = new SearchRequest.Builder() .searchBluetoothLeDevice(3000, 3) // 先扫BLE设备3次,每次3s .searchBluetoothClassicDevice(3000) // 再扫经典蓝牙3s .searchBluetoothLeDevice(3000) // 再扫BLE设备3s .build(); ``` ##### 设备连接配置 - 此配置为SDK内部重试机制,不会影响设备连接状态回调。 ``` BleConnectOptions connectConfig = new BleConnectOptions.Builder() .setConnectRetry(3)// 重试次数 .setConnectTimeout(8000) // 连接超时 .setServiceDiscoverRetry(3)// 发现服务重试次数 .setServiceDiscoverTimeout(10000)// 发现服务超时 .build(); ``` ##### 设置配置 ``` MrkDeviceManger.INSTANCE.setConfig(searchConfig, connectConfig); ``` #### 注册一个全局唯一的蓝牙状态监听器(重新注册会覆盖) ``` MrkDeviceManger.INSTANCE.registerBluetoothStateListener(new BluetoothStatusListener() { @Override public void onBluetoothStatus(BluetoothEnum bluetoothEnum) { //蓝牙开关状态 } }); //释放 MrkDeviceManger.INSTANCE.unRegisterBluetoothStateListener(); ``` #### 添加一个全局的蓝牙状态监听器(可添加多个,注意在不需要的时候请释放掉) ``` MrkDeviceManger.INSTANCE.addBluetoothStateListener(new BluetoothStatusListener() { @Override public void onBluetoothStatus(BluetoothEnum bluetoothEnum) { //蓝牙开关状态 } }); //释放 MrkDeviceManger.INSTANCE.removeBluetoothStateListener(BluetoothStatusListener) ``` #### 注册一个全局唯一的设备监听器(重新注册会覆盖) ``` MrkDeviceManger.INSTANCE.registerDeviceListener(this, new DeviceListener() { @Override public void onConnectStatus(boolean isAutoReconnect, DeviceMangerBean bean) { switch (bean.getConnectEnum()) { case ON: //连接成功 break; case OFF: //断开连接 break; case ING: //连接中 break; case ERROR: //连接失败 break; default: break; } } }); //释放 MrkDeviceManger.INSTANCE.unRegisterDeviceListener(); ``` #### 添加一个全局的监听器(可添加多个,注意在不需要的时候请释放掉) ``` MrkDeviceManger.INSTANCE.addDeviceListener(this, new DeviceListener() { @Override public void onConnectStatus(boolean isAutoReconnect, DeviceMangerBean bean) { switch (bean.getConnectEnum()) { case ON: //连接成功 break; case OFF: //断开连接 break; case ING: //连接中 break; case ERROR: //连接失败 break; default: break; } } }); //释放 MrkDeviceManger.INSTANCE.removeDeviceListener(Context,DeviceListener); ``` #### 判断蓝牙是否开启 ``` MrkDeviceManger.INSTANCE.isBluetoothOpened() ``` #### 设备搜索 - 设备开始搜索,会回调搜索开始`BluetoothEnum.START`。 - 设备搜索中,会回调搜索中`BluetoothEnum.ING`。 - 搜索会默认会持续16秒,16秒以后会回调搜索结束`BluetoothEnum.STOP` 调用搜索结束或点击连接设备也会回调。 ``` MrkDeviceManger.INSTANCE.startSearch(this, new BluetoothSearchListener() { @Override public void onSearchStatus(BluetoothEnum bluetoothEnum) { //设备搜索状态 } @Override public void onSearchDevice(DeviceSearchBean bean) { //搜索到的设备对象 } }); ``` #### 停止搜索 SDK内部会在设备连接的时候也会同步调用此方法。 ``` MrkDeviceManger.INSTANCE.stopSearch(); ``` #### 设备连接 - 连接超时时间为默认为35秒,35秒以后还未连接会回调`DeviceConnectEnum.OFF`。 **方法1:通过搜索对象连接。** - 把搜索到的设备对象`DeviceSearchBean`传入,即可连接设备。 ``` MrkDeviceManger.INSTANCE.create(context,DeviceSearchBean).connect(); ``` **方法2:通过必要参数连接。** - 把搜索到的设备对象`DeviceSearchBean`保存`mac`mac地址,`productId`产品Id,`bluetoothName`蓝牙广播名, `modelId`型号id, `uniqueModelIdentify` 设备特征值。通过这些参数去连接。 ``` MrkDeviceManger.INSTANCE.create(context,mac,productId,bluetoothName,modelId,uniqueModelIdentify).connect(); ``` #### 获取控制 - 设备连接成功后即可通过以下方法获取控制,`params`可以是设备大类  也可以mac地址。 ``` MrkDeviceManger.INSTANCE.getDeviceControl(this, params) ``` #### 断开连接 把需要断开连接的`mac`地址传入,即可断开连接设备。 ``` MrkDeviceManger.INSTANCE.disConnect(mac) ``` #### 判断设备是否连接 `params`可以是设备大类  也可以mac地址。 ``` MrkDeviceManger.INSTANCE.isConnect(params) ``` #### 获取所有已经连接的设备对象 ``` MrkDeviceManger.INSTANCE.getConnectList() ``` #### 清空数据 `mac`可传入null或者设备的mac地址,传入null则表示清除掉当前所有的连接设备的数据,传入mac地址,则清除此mac地址设备的数据。 ``` MrkDeviceManger.INSTANCE.clear(Context,mac) ``` #### 清除已经匹配的设备信息(去除系统蓝牙所匹配的数据) ``` MrkDeviceManger.INSTANCE.removeBondedDevice(mac) ``` #### 通过设备大类id返回对象的设备名称 `productId`设备大类id ``` MrkDeviceManger.INSTANCE.getTypeName(productId) ``` ``` fun getTypeName(productId: String): String { return when (productId) { DeviceConstants.D_BICYCLE -> "动感单车" DeviceConstants.D_TREADMILL -> "跑步机" DeviceConstants.D_OTHER -> "其他" DeviceConstants.D_ROW -> "划船机" DeviceConstants.D_TECHNOGYM -> "椭圆机" DeviceConstants.D_FASCIA_GUN -> "筋膜枪" DeviceConstants.D_SKIPPING -> "跳绳" DeviceConstants.D_POWER -> "力量站" DeviceConstants.D_HULA_HOOP -> "呼啦圈" DeviceConstants.D_HEART -> "心率带" DeviceConstants.D_FAT_SCALE -> "体脂秤" DeviceConstants.D_PHYLLISRODS -> "飞力士棒" DeviceConstants.D_SCALE_LF -> "乐福体脂秤" DeviceConstants.D_SCALE_WL -> "沃莱体脂秤" DeviceConstants.D_GAME_PAD -> "游戏手柄" else -> { "未知设备" } } } ``` #### 设备类型productId对应解释 ``` const val D_BICYCLE = "1" //动感单车 const val D_TREADMILL = "2" //跑步机 const val D_OTHER = "3" //其他 const val D_ROW = "5" //划船机 const val D_TECHNOGYM = "6" //椭圆机 const val D_FASCIA_GUN = "9" //筋膜枪 const val D_SKIPPING = "10" //跳绳 const val D_POWER = "11" //力量站 const val D_HULA_HOOP = "21" //呼啦圈 const val D_HEART = "100" //心率带 const val D_FAT_SCALE = "41"//体脂秤 const val D_PHYLLISRODS = "27" //飞力士棒 const val D_SCALE_LF = "410"//乐福体脂秤 const val D_SCALE_WL = "420"//沃莱体脂秤 const val D_GAME_PAD = "200" //游戏手柄 const val D_GAME_KEYBOARD = "210" ``` ## 控制设备 #### 创建控制类 - 设备连接成功后,即可控制设备。 - 有多种创建控制类的方式,请根据需求选择。 **1.通过搜索对象创建** ``` deviceControl = MrkDeviceManger.INSTANCE.create(this, DeviceSearchBean)//搜索对象 .setOnDeviceListener(deviceListener)//设置该设备的状态监听 ``` **2.通过必要参数创建** - `mac`mac地址,`productId`产品Id,`bluetoothName`蓝牙广播名, `modelId`型号id, `uniqueModelIdentify` 设备特征值。 ``` deviceControl = MrkDeviceManger.INSTANCE.create(context,mac,productId,bluetoothName,modelId,uniqueModelIdentify) .setOnDeviceListener(deviceListener)//设置改设备的状态监听 ``` **3.通过已经连接的设备控制** - 设备连接成功后即可通过以下方法获取控制,`params`可以是设备大类  也可以mac地址。 ``` deviceControl = MrkDeviceManger.INSTANCE.getDeviceControl(this, params) .setOnDeviceListener(deviceListener)//设置改设备的状态监听 ``` #### 开启自动重连 - 特定情况下,设备会断开连接。如长时间未使用设备,电源断开等。 - 开启此功能后,非人为主动断开即会触发设备重连。 - 此功能建议在运动模式下开启。 ``` //开启一次自动重连 deviceControl.autoConnect() //开启一直自动重连 deviceControl.autoConnectAlways() ``` #### 关闭自动重连 ``` deviceControl.unAutoConnect() ``` #### 发送控制指令 - 控制指令,SDK内部有做设备最大最小指令限制。如设备最大阻力15,下发了16,SDK内部会直接发送设备的最大阻力。坡度,速度亦是如此。 ``` //发送设备控制指令 阻力(如果发错了,设备不会响应) //resistance 阻力 deviceControl.sendCommandResistance(resistance: Int) //发送设备控制指令 坡度(如果发错了,设备不会响应) //slope 坡度 deviceControl.sendCommandSlope(slope: Int) //发送跑步机控制指令 速度与坡度(不论是发送速度还是坡度都要使用此方法,请不要使用单独的sendCommandSlope) deviceControl.sendCommandTreadmill(speed: Int, slope: Int) ``` **根据麦瑞克提供的课程教案发送教案** ``` { "id": "1604735072183595009", "name": "拉伸", "describeInfo": "", "kcal": 10.0, "time": 2, "beginTime": 1350, "endTime": 1470, "sustainTime": 120, "courseLinks": [ { "id": "1604735072187789313", "catalogueId": "1604735072183595009", "name": "股四头肌-臀大肌-腘绳肌", "crux": "", "beginTime": 1350, "sustainTime": 120, "endTime": 1470, "minNum": 0.0, "maxNum": 0.0, "adviseNum": 0, "slopeNum": 0, "distance": 0.0, "kcal": 10.0, "isExistFlame": 0 } ], "isContinue": 0, "maxNum": 0.0, "speedDesc": "最高踏频(rpm)" } ``` - 当设备为:动感单车,椭圆机,划船机时(阻力对应以上教案中的`adviseNum`,坡度对应以上教案中的`slopeNum`) ``` //动感单车,椭圆机,划船机发送阻力 deviceControl.sendCommandResistance(adviseNum) //动感单车,椭圆机,划船机发送坡度 deviceControl.sendCommandSlope(slopeNum) ``` - 当设备为:跑步机时(速度对应以上教案中的 `maxNum`,坡度对应以上教案中的 `adviseNum`) ``` deviceControl.sendCommandTreadmill(maxNum, adviseNum) ``` #### 其他控制 ``` //当前设备连接 deviceControl.connect() //当前设备断开连接 deviceControl.disConnect() //设备开始(只有跑步机可用) deviceControl.deviceStart() //设备暂停(只有跑步机可用) deviceControl.devicePause() //设备停止(只有跑步机可用,会清除当前的运动数据) deviceControl.deviceStop(); //设备数据清除 deviceControl.clearData() //是否开启设备数据回调返回 deviceControl.setNotifyData(true) ``` ## 对象参数说明 - 用不上的可以直接无视 #### 蓝牙状态`BluetoothEnum` ``` /** * @param OPEN 蓝牙开关打开 * @param CLOSE 蓝牙开关关闭 * @param START 开始搜索 * @param STOP 停止搜索 * @param ING 搜索中 */ enum class BluetoothEnum { OPEN, CLOSE, START, STOP, ING } ``` #### 设备连接状态 `DeviceConnectEnum` ``` /** * 设备连接状态 * @param ON 设备连接成功 * @param OFF 设备未连接 * @param ING 设备连接中 * @param ERROR 设备连接错误(接口没请求通) */ enum class DeviceConnectEnum { ON, OFF, ING, ERROR } ``` #### 跑步机设备状态  `DeviceTreadmillEnum` ``` /** * @param START 跑步机 设备开始 运行中 * @param PAUSE 跑步机 设备暂停 * @param STOP 跑步机 设备停止 * @param COUNT_TIME 跑步机 启动中 * @param SLOW_DOWN 跑步机 减速中 * @param DISABLE 故障 * @param WAIT 待机 */ enum class DeviceTreadmillEnum { START, PAUSE, STOP, COUNT_TIME, SLOW_DOWN, WAIT, DISABLE } ``` #### 整个设备对象  `DeviceMangerBean`  ``` data class DeviceMangerBean( var connectBean: DeviceGoConnectBean,//当前连接的对象 var deviceOtaBean: DeviceOtaBean? = null,//当前设备的ota对象 var deviceDetails: DeviceDetailsBean? = null,//当前设备的设备详情(需要先连接) var connectEnum: DeviceConnectEnum = DeviceConnectEnum.OFF, //当前设备的连接状态 var deviceControl: DeviceControl? = null,//当前设备的设备控制类 var autoReconnectEnum: AutoReconnectEnum = AutoReconnectEnum.DEFAULT//重连枚举 ) ``` #### 搜索对象  `DeviceSearchBean`  ``` data class DeviceSearchBean( val modelId: String = "", //型号id 1630452448293478401 val originModelId: String = "", //起源型号id(老型号ID) 1630452448293478401 val modelName: String = "", //型号名称 S06单车 val bluetoothName: String = "", //蓝牙广播名 MRK-S06-07BD val mac: String = "", //mac地址 24:00:0C:A0:DA:27 val modelDescription: String = "", //型号描述 val productId: String = "", //产品Id 1 val cover: String = "", //型号封面 https://static.merach.com/other/20230228/24843ca5b8cd4b9ebba0106cb667f161_1560x840.png val communicationProtocol: Int = 0, //通信协议 1:麦瑞克,2:FTMS,3:智健,4:柏群,5:FTMS+智健 val isOta: Int = 0, //是否支持ota 1 val isMerit: Int = 0, //是否merit设备 1 val characteristicValue: String="",//唯一型号确认json val isTrust: Int = 0,//是否可以信任的数据,0不可信任,1可信任,当数据不可信任时不展示具体信息 1 var connectEnum: DeviceConnectEnum? //自定义的连接状态 ) ``` #### 设备详情  `DeviceDetailsBean`  ``` data class DeviceDetailsBean( val productId: String = "", //产品Id // 1 val modelId: String = "", //型号id 1744914228939124742 val modelName: String = "",//型号名称 val cover: String = "", //型号封面 https://static.merach.com/other/20240201/c70321dd6842484f8855d369449e4e19_1200x900.jpg val communicationProtocol: Int = 0, //通信协议 1:麦瑞克,2:FTMS,3:智健,4:柏群,5:FTMS+智健 3 val isOta: Int = 0, //是否支持ota,不推荐使用,建议使用productModelTsl中的isOta 「已废弃」 1 val otaProtocol: Int = 0, //ota协议 1:泰凌威 智建设备;2:博通; 3:DFU 4:新向远 5富芮坤 6 凌思威 0 val versionEigenValue: Int = 0, //版本特征值 2 val deviceUserRelId: String = "", //用户设备关联id 1752946454528196610 val deviceAlias: String = "",//设备别名 var bluetoothName: String = "", //蓝牙广播名 MRK-S12-07BD var mac: String = "", //mac地址 24:00:0C:A0:DA:27 val productName: String = "",//产品名称 val productType: Int = 0, //产品分类 0 val helpCenter: String = "",//帮助中心 val productManual: String = "",//产品说明 val isMerit: Int = 0, //是否merit设备,不推荐使用,建议使用productModelTsl中的isElectromagneticControl 1 val deviceId: String = "", //设备id 0 val firmwareVersion: String = "",//当前固件版本 val isBind: Int = 0, //是否绑定操作 0 val productModelTsl: ProductModelTsl = ProductModelTsl() ) { data class ProductModelTsl( val isOta: Int = 0, //是否支持ota:1是0否 1 val isClean: Int = 0, //是否支持清理运动数据:1是0否 1 val isElectromagneticControl: Int = 0, //是否是电磁控设备 1是0否 1 val versionEigenValue: Int = 0, //版本特征值 2 val controlResistance: Int = 0, //是否控制阻力,1是0否 0 val minResistance: Int = 0, //最小阻力 0 val maxResistance: Int = 0, //最大阻力 0 val controlSpeed: Int = 0, //是否控制速度,1是0否 0 val minSpeed: Int = 0, //最小速度 0 val maxSpeed: Int = 0, //最大速度 0 val controlSlope: Int = 0, //是否控制坡度,1是0否 0 val minSlope: Int = 0, //最小坡度 0 val maxSlope: Int = 0, //最大坡度 0 val controlGear: Int = 0, //是否可调节档位,1是0否 0 val minGear: Int = 0, //最小档位 0 val maxGear: Int = 0, //最大档位 0 val isSupportResistanceEcho: Int = 0, //是否支持阻力回显 1 val guide: String = "",//引导信息 val electrode: Int = 0, //电极信息 0 val unitVal: Int = 0, //设备单位:1-公制,2-英制 1 val isSupportSlopeEcho: Int = 0 //是否支持坡度回显:0-否,1-是 0 ) } ``` #### 设备数据回调  `DeviceTrainBO`  ``` data class DeviceTrainBO( var dataType: Int = 0, // var linkId: Long = 0, //小节ID var speed: Float = 0f, //速度 var avgSpeed: Float = 0f, //平均速度 var distance: Int = 0, //距离:米 var spm: Int = 0, //踏频/桨频 var count: Int = 0, //总踏数/总桨数/总个数/总圈数 var avgSpm: Float = 0f, //平均踏频/平均桨频 var drag: Int = 0, //阻力 var power: Float = 0f, //功率 var avgPower: Float = 0f, //平均功率 var energy: Float = 0f, //消耗:kcal var rateKcal: Float = 0f, //消耗:kcal var deviceTime: Long = 0,//设备时长:秒 var deviceRate: Int = 0, //设备心率 var rate: Int = 0,//心率设备心率 var gradient: Int = 0, //坡度 var grade: Int = 0, //挡位 var electric: Int = 0, //电量 var timestamp: Long = 0, var status: Int = -1, //设备状态用来规避x1彩屏跑步机暂停也会给数据; var model: Int = 0, var deviceTimestamp: Long = 0,//设备收到数据的时间戳 var direction: Int = -1,//摇摆方向 00H:直线 01H:左 02H:右 var press: Int = -1,//00H:存在按压信号 var originalData: String = "", var mac: String = "",//设备mac地址 var type: String = "",//设备类型 var name: String = "", //蓝牙名称k60, var unitDistance: Int = -1,//设备类型0:公制,1:英制 var treadmillModel: Int = -1//跑步机模式 1:手动模式 2:内置程式 3:模式(倒计时/倒计数/倒计卡路⾥) ) ``` ## 文件下载 #### [SDK](app%2Flibs) #### [Demo源码](https://gitee.com/williamOsChina/mrk-device-demo) #### [Apk下载](https://www.pgyer.com/967QYXBh) ## 常见问题 #### [Wiki](https://gitee.com/williamOsChina/mrk-device-demo/wikis/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98?sort_id=11220949) ## 版本更新记录 #### [版本更新记录](https://gitee.com/williamOsChina/mrk-device-demo/wikis/%E7%89%88%E6%9C%AC%E6%9B%B4%E6%96%B0%E8%AE%B0%E5%BD%95/) ## 联系我们 #### [联系我们](https://www.merach.com/)