更新时间:2022-02-17 07:05:43下载pdf
涂鸦蓝牙有三条技术线路。蓝牙设备与手机一对一相连的蓝牙单点,涂鸦自研的蓝牙拓扑通信 涂鸦 Mesh 和蓝牙技术联盟发布的蓝牙拓扑通信 蓝牙 Mesh。除了以上三种之外,还有一些多协议设备也会使用到蓝牙技术,比如同时具备 Wi-Fi 能力和蓝牙能力的 双模设备,也可以使用蓝牙进行配网,当然 Wi-Fi 设备原本的配网仍然可用。
蓝牙技术分类 | 产品举例 |
---|---|
蓝牙 | 体脂秤、手环、温控器、电动牙刷、门锁、防丢器等 |
蓝牙 Mesh | 一路、二路、五路等灯泡、插座、传感器等蓝牙 Mesh 子设备 |
涂鸦 Mesh | 与蓝牙 Mesh 产品类似,协议为涂鸦 自研 |
双模设备 | 蓝牙 Mesh 网关、IPC 设备以及新版多协议 Wi-Fi 设备等均有可能 双模配网的蓝牙配网部分,使用的是蓝牙技术为设备配网,将放到蓝牙章节进行说明。 |
注意: 开发蓝牙 Mesh 时,请先熟悉 SDK 本身。Mesh 的所有操作都建立在站点数据已经初始化的基础上。一个站点里可以拥有多个 Mesh(建议一个站点只创建一个)。
手机系统要求
蓝牙使用需要 Android 4.3 以及以上版本,TuyaHomesdk 从 Android 4.4 开始支持。
Manifest 的权限
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
使用蓝牙前需要检测权限已被授予
APP 在使用蓝牙连接或者扫描操作前 需要检查 APP 定位权限是否被允许。
检查蓝牙状态是否为开启。
说明:该部分检查逻辑,TuyaHomeSdk 未提供 API,您可自行检测。每次扫描和连接前都要进行检测,否则 APP 无法正常使用蓝牙。
设备类别值由 设备类型 + 大类 + 小类组成后小端排列。
标准型 Tuya 设备 : 1
透传型 Tuya 设备 : 2
……
灯大类(01): 1-5 路 RGBWC 彩灯
电工类(02): 1-6 路插座
遥控器(05): 1-6 个按键
1-5路灯(1-5)
1-6路排插(1-6)
1-6个遥控器按键(1-6)
……
产品 | 类别值 | 说明 |
---|---|---|
标准五路灯 | 1510 | 小端转换后得到 1015:1 :tuya 设备01 :灯5 :5 路灯 |
标准四路插座 | 2410 | 小端转换后得到 1024:1 :tuya设备02 :插座5 :4路插座 |
透传型设备 | xx20 | 小端转换后得到 20xx:2 :透传设备 |
NodeId 用于区分每个 Mesh 设备在 Mesh 网中的「唯一标识」,比如想控制某个设备就向 Mesh 网发此设备对应的 NodeId 命令即可。
LocalId 用于区分每个 Mesh 群组在 Mesh 网中的「唯一标识」,比如想控制某个群组中的设备就向 Mesh 网发此群组对应的 LocalId 命令即可。
因为设备的操作,例如增删操作、群组操作,都需要本地蓝牙命令执行一次、云端记录一次 向本地 Mesh 网同步操作信息的同时也需要向云端同步操作信息。
本地连接:已配网设备通过蓝牙连接,来控制 Mesh 和指令操作。
网关连接:已配网设备通过网关连接(网关需和设备在一起,距离不能太远),来控制 Mesh 和指令操作。
由云端分配 MeshId,一个站点下只能创建一个 Mesh。
接口方法
void createSigMesh(ITuyaResultCallback<SigMeshBean> callback);
示例代码
TuyaHomeSdk.newHomeInstance(123xxx) // 参数为 long 类型的 homeId
.createSigMesh(new ITuyaResultCallback<SigMeshBean>() {
@Override
public void onError(String errorCode, String errorMsg) {
}
@Override
public void onSuccess(SigMeshBean sigMeshBean) {
}
});
删除 SIGMesh MeshId
接口方法
void removeMesh(IResultCallback callback);
示例代码
TuyaHomeSdk.newSigMeshDeviceInstance(meshId).removeMesh(new IResultCallback() {
@Override
public void onError(String errorCode, String errorMsg) {
}
@Override
public void onSuccess() {
}
});
接口说明
List<SigMeshBean> getSigMeshList();
示例代码
ITuyaHome mTuyaHome = TuyaHomeSdk.newHomeInstance(123xxx); // 参数为 homeId
if (mTuyaHome.getHomeBean() != null){
List<SigMeshBean> meshList = TuyaHomeSdk.getSigMeshInstance().getSigMeshList()
}
接口说明
List<DeviceBean> getMeshSubDevList();
示例代码
List<DeviceBean> meshSubDevList = TuyaHomeSdk.newSigMeshDeviceInstance("meshIdxxxxx").getMeshSubDevList();
建议在站点切换的时候,销毁当前 Mesh,然后重新初始化站点中的 Mesh。
接口说明
void initMesh(String meshId);
示例代码
TuyaHomeSdk.getTuyaSigMeshClient().initMesh("meshIdxxxxx");
接口说明
void destroyMesh();
示例代码
TuyaHomeSdk.getTuyaSigMeshClient().destroyMesh();
ITuyaBlueMeshClient
提供 开始连接、断开连接、开启扫描、停止扫描
代码示例
// 开启连接
TuyaHomeSdk.getTuyaSigMeshClient().startClient(mSigMeshBean);
// 开启连接,指定扫描的时间
TuyaHomeSdk.getTuyaSigMeshClient().startClient(mSigMeshBean,searchTime);
// 断开连接
TuyaHomeSdk.getTuyaSigMeshClient().stopClient();
// 开启扫描
TuyaHomeSdk.getTuyaSigMeshClient().startSearch()
// 停止扫描
TuyaHomeSdk.getTuyaSigMeshClient().stopSearch();
注意事项
startClient(mSigMeshBean)
开启连接后,会在后台不断的去扫描周围可连接设备,直到连接成功为止。startClient(mSigMeshBean,searchTime)
开启连接后, searchTime
时间内没有扫描到设备就停止扫描.startClient()
时候,调用 startSearch()
和 stopSearch()
是没有效果的startSearch
和 stopSearch
是没有效果的扫描前需要检查蓝牙和位置权限 ;扫描附近符合 SIG 标准的蓝牙设备
接口说明
// 开启扫描
void startSearch();
// 停止扫描
void stopSearch();
示例代码
ITuyaBlueMeshSearchListener iTuyaBlueMeshSearchListener=new ITuyaBlueMeshSearchListener() {
@Override
public void onSearched(SearchDeviceBean deviceBean) {
}
@Override
public void onSearchFinish() {
}
};
// 待配网的 SIGMesh 设备 UUID 是固定的
UUID[] MESH_PROVISIONING_UUID = {UUID.fromString("00001827-0000-1000-8000-00805f9b34fb")};
SearchBuilder searchBuilder = new SearchBuilder()
.setServiceUUIDs(MESH_PROVISIONING_UUID) // SIGMesh 的 UUID 是固定值
.setTimeOut(100) // 扫描时长 单位秒
.setTuyaBlueMeshSearchListener(iTuyaBlueMeshSearchListener).build();
ITuyaBlueMeshSearch mMeshSearch = TuyaHomeSdk.getTuyaBlueMeshConfig().newTuyaBlueMeshSearch(searchBuilder);
// 开启扫描
mMeshSearch.startSearch();
// 停止扫描
mMeshSearch.stopSearch();
扫描到目标设备以后,可以通过查询显示产品配置的名称和图标。
接口说明
void getActivatorDeviceInfo(String productId, String uuid, String mac, ITuyaDataCallback<ConfigProductInfoBean> callback);
参数说明
参数 | 类型 | 说明 |
---|---|---|
productId | String | SearchDeviceBean.productId ,注意需要将 byte[] 转换为 String 之后传入 |
uuid | String | Mesh 设备为 null |
mac | String | SearchDeviceBean.productId |
示例代码
TuyaHomeSdk.getActivatorInstance().getActivatorDeviceInfo(
// btye[] to String
new String(bean.getProductId(), StandardCharsets.UTF_8),
// uuid
null,
// mac
scanDeviceBean.getMacAdress(),
// callback
new ITuyaDataCallback<ConfigProductInfoBean>() {
@Override
public void onSuccess(ConfigProductInfoBean result) {
}
@Override
public void onError(String errorCode, String errorMessage) {
}
});
回调说明
ConfigProductInfoBean
说明
属性 | 类型 | 说明 |
---|---|---|
name | String | 产品名称,云端配置,一般是客户首次创建产品时的名称 |
icon | String | 产品图标 |
子设备入网有两种方式,一种是通过 App 蓝牙入网,另一种是通过网关直接配子设备入网。
接口说明
// 开启配网
void startActivator();
// 停止配网
void stopActivator();
示例代码
TuyaSigMeshActivatorBuilder tuyaSigMeshActivatorBuilder = new TuyaSigMeshActivatorBuilder()
.setSearchDeviceBeans(mSearchDeviceBeanList)
.setSigMeshBean(sigMeshBean) // SIGMesh 基本信息
.setTimeOut(100) // 超时时间
.setTuyaBlueMeshActivatorListener(new ITuyaBlueMeshActivatorListener() {
@Override
public void onSuccess(String mac, DeviceBean deviceBean) {
L.d(TAG, "subDevBean onSuccess: " + deviceBean.getName());
}
@Override
public void onError(String mac, String errorCode, String errorMsg) {
L.d(TAG, "config mesh error" + errorCode + " " + errorMsg);
}
@Override
public void onFinish() {
L.d(TAG, "config mesh onFinish");
});
ITuyaBlueMeshActivator iTuyaBlueMeshActivator = TuyaHomeSdk.getTuyaBlueMeshConfig().newSigActivator(tuyaSigMeshActivatorBuilder);
// 开启配网
iTuyaBlueMeshActivator.startActivator();
// 停止配网
iTuyaBlueMeshActivator.stopActivator();
入参说明
参数 | 说明 |
---|---|
mSearchDeviceBeanList | 待配网的设备集合,通过 startSearch 扫描得到的 |
timeout | 配网的超时时间设置,默认是 100s(配网设备过多时,需要增加时间) |
sigMeshBean | SigMeshBean |
出参说明
DeviceBean
: 见 DeviceBean数据模型
errorCode
: 见错误码
SigMesh 网关 本质上为双模设备;
使用 Wi-Fi设备的配网,同 Wi-Fi EZ 配网模式;
使用单点蓝牙双模配网 双模设备配网。
SIGMesh 子设备可以通过网关直接给子设备配网,配网的方式与 Zigbee 子设备入网方法一致,参考 Zigbee 子设备配网
Code | MSG |
---|---|
21002 | invite 失败 |
21004 | provision 失败 |
21006 | send public key 失败 |
21008 | conform 失败 |
210010 | random conform 失败 |
210014 | send data 失败 |
210016 | composition data 失败 |
210018 | add appkey 失败 |
210020 | bind model 失败 |
210022 | publication model 失败 |
210024 | network transmit 失败 |
210026 | 云端注册失败 |
210027 | 设备地址分配已满 |
210034 | notify 失败 |
20021 | 配网超时 |
ITuyaBlueMeshDevice 类封装了对指定 Mesh 内所有设备的操作
ITuyaBlueMeshDevice mTuyaBlueMeshDevice = TuyaHomeSdk.newSigMeshDeviceInstance("meshIdxxxx");
示例代码
DeviceBean deviceBean=TuyaHomeSdk.getDataInstance().getDeviceBean(mDevId);
DeviceBean gwBean=TuyaHomeSdk.getDataInstance().getDeviceBean(deviceBean.getParentId());
// 综合在线状态 (包括本地在线和网关在线)
boolean online=deviceBean.getIsOnline()
// 设备本地蓝牙在线状态
boolean localOnline=deviceBean.getIsLocalOnline()
// 设备网关在线状态 (需要网关在线且子设备在线 才认为网关真实在线)
boolean wifiOnline=deviceBean.isCloudOnline() && gwBean.getIsOnline()
示例代码
DeviceBean deviceBean=TuyaHomeSdk.getDataInstance().getDeviceBean(mDevId);
// 判读是否是 sigmesh 设备 (子设备 + 网关)
if(deviceBean.isSigMesh()){
// This device is sigmesh device"
}
// 判读是否是 sigmesh 网关设备
if(deviceBean.isSigMeshWifi()){
// This device is sigmesh Wi-Fi device
}
接口说明
void renameMeshSubDev(String devId, String name, IResultCallback callback);
参数说明
参数 | 说明 |
---|---|
devId | 设备 Id |
name | 重命名名称 |
callback | 回调 |
示例代码
ITuyaBlueMeshDevice mTuyaBlueMeshDevice= TuyaHomeSdk.newSigMeshDeviceInstance("meshIdxxxx");
mTuyaBlueMeshDevice.renameMeshSubDev("devIdxxxx","设备新名称", new IResultCallback() {
@Override
public void onError(String code, String errorMsg) {
// 重命名失败
}
@Override
public void onSuccess() {
// 重命名成功
}
});
云端获取到的 dp 点数据可能不是当前设备实时的数据,可以通过该命令去查询设备的当前数据值,结果通过 IMeshDevListener
的 onDpUpdate
方法返回
接口说明
void querySubDevStatusByLocal(String pcc, final String nodeId, final IResultCallback callback);
参数说明
参数 | 说明 |
---|---|
pcc | 设备大小类 |
nodeId | 设备 nodeId |
callback | 回调 |
示例代码
ITuyaBlueMeshDevice mTuyaBlueMeshDevice = TuyaHomeSdk.newSigMeshDeviceInstance("meshIdxxxx");
mTuyaBlueMeshDevice.querySubDevStatusByLocal(devBean.getCategory(), devBean.getNodeId(), new IResultCallback() {
@Override
public void onError(String code, String errorMsg) {
// 查询错误
}
@Override
public void onSuccess() {
// 查询成功,见 IMeshDevListener 的回调
}
});
接口说明
void removeMeshSubDev(String devId, IResultCallback callback);
参数说明
参数 | 说明 |
---|---|
devId | 设备 Id |
pcc | 设备大小类 |
callback | 回调 |
示例代码
ITuyaBlueMeshDevice mTuyaBlueMeshDevice = TuyaHomeSdk.newSigMeshDeviceInstance("meshIdxxxx");
mTuyaBlueMeshDevice.removeMeshSubDev(devBean.getDevId(), devBean.getCategory(), new IResultCallback() {
@Override
public void onError(String code, String errorMsg) {
}
@Override
public void onSuccess() {
}
});
ITuyaGroup
类提供了对 SIGMesh 群组的操作
描述
可以通过群组中是否具备 MeshId 来区分 Mesh 群组和普通 Wi-Fi 群组
代码示例
GroupBean groupBean=TuyaHomeSdk.getDataInstance().getGroupBean("groupId");
if(!TextUtils.isEmpty(groupBean.getMeshId())){
// This group is mesh group"
}
描述
一个 Mesh 网内支持创建 16128 个群组 返回时 id 范围 C000 - FEFF (16 进制) 由本地进行维护
接口说明
void addGroup(String name, String pcc, String localId,IAddGroupCallback callback);
参数说明
参数 | 说明 |
---|---|
name | 群组名称 |
pcc | 群组中设备的大小类 (支持跨小类创建) |
localId | 群组的 localId (范围 C000 - FEFF 16 进制字符串) |
callback | 回调 |
代码示例
ITuyaBlueMeshDevice mTuyaSigMeshDevice= TuyaHomeSdk.newSigMeshDeviceInstance("meshId");
mTuyaSigMeshDevice.addGroup("群组名称","大小类", "C001", new IAddGroupCallback() {
@Override
public void onError(String errorCode, String errorMsg) {
// 创建群组失败
}
@Override
public void onSuccess(long groupId) {
// 创建群组成功
}
});
接口说明
void addDevice(String devId,IResultCallback callback);
参数说明
参数 | 说明 |
---|---|
devId | 设备 Id |
callback | 回调 |
代码示例
ITuyaGroup mGroup = TuyaHomeSdk.newSigMeshGroupInstance(groupId);
mGroup.addDevice("devId", new IResultCallback() {
@Override
public void onError(String code, String errorMsg) {
// 添加设备到群组失败
}
@Override
public void onSuccess() {
// 添加设备到群组成功
}
});
接口说明
void removeDevice(String devId,IResultCallback callback);
参数说明
参数 | 说明 |
---|---|
devId | 设备 Id |
callback | 回调 |
代码示例
ITuyaGroup mGroup = TuyaHomeSdk.newSigMeshGroupInstance(groupId);
mGroup.removeDevice("devId", new IResultCallback() {
@Override
public void onError(String code, String errorMsg) {
// 移除子设备失败
}
@Override
public void onSuccess() {
// 移除子设备成功
}
});
接口说明
void dismissGroup(IResultCallback callback);
参数说明
参数 | 说明 |
---|---|
callback | 回调 |
代码示例
ITuyaGroup mGroup = TuyaHomeSdk.newSigMeshGroupInstance(groupId);
mGroup.dismissGroup(new IResultCallback() {
@Override
public void onError(String code, String errorMsg) {
// 解散群组失败
}
@Override
public void onSuccess() {
// 解散群组成功
}
});
接口说明
void renameGroup(String groupName,IResultCallback callback);
参数说明
参数 | 说明 |
---|---|
groupName | 重命名名称 |
callback | 回调 |
代码示例
ITuyaGroup mGroup = TuyaHomeSdk.newSigMeshGroupInstance(groupId);
mGroup.renameGroup("群组名称",new IResultCallback() {
@Override
public void onError(String code, String errorMsg) {
// 重命名群组失败
}
@Override
public void onSuccess() {
// 重命名群组成功
}
});
ITuyaBlueMeshDevice
类提供了对 Mesh 设备的操作
描述
发送控制指令按照以下格式:
{
"(dpId1)":(dpValue1),
"(dpId2)":(dpValue2)
}
接口说明
void publishDps(String nodeId, String pcc, String dps, IResultCallback callback);
参数说明
参数 | 说明 |
---|---|
nodeId | 子设备本地编号 |
pcc | 设备产品大小类 |
dps | dps |
callback | 回调 |
代码示例
String dps = {"1":false};
ITuyaBlueMeshDevice mTuyaSigMeshDevice=TuyaHomeSdk.newSigMeshDeviceInstance("meshIdxxxx");
mTuyaSigMeshDevice.publishDps(devBean.getNodeId(), devBean.getCategory(), dps, new IResultCallback() {
@Override
public void onError(String s, String s1) {
// 发送 Dps 失败
}
@Override
public void onSuccess() {
// 发送 Dps 成功
}
});
接口说明
void multicastDps(String localId, String pcc, String dps, IResultCallback callback)
参数说明
参数 | 说明 |
---|---|
localId | 群组本地编号 |
pcc | 设备产品大小类 |
dps | dps |
callback | 回调 |
代码示例
String dps = {"1":false};
ITuyaBlueMeshDevice mTuyaSigMeshDevice= TuyaHomeSdk.newSigMeshDeviceInstance("meshId");
mTuyaSigMeshDevice.multicastDps(groupBean.getLocalId(), devBean.getCategory(), dps, new IResultCallback() {
@Override
public void onError(String errorCode, String errorMsg) {
// 发送 Dps 失败
}
@Override
public void onSuccess() {
// 发送 Dps 成功
}
});
描述
Mesh 网内相关信息( dp 数据、状态变更、设备名称、设备移除)会实时同步到 IMeshDevListener
代码示例
mTuyaSigMeshDevice.registerMeshDevListener(new IMeshDevListener() {
/**
* 数据更新
* @param nodeId 更新设备的 nodeId
* @param dps dp 数据
* @param isFromLocal 数据来源 true 表示从本地蓝牙 false 表示从云端
*/
@Override
public void onDpUpdate(String nodeId, String dps,boolean isFromLocal) {
// 可以通过node来找到相对应的DeviceBean
DeviceBean deviceBean = mTuyaBlueMeshDevice.getMeshSubDevBeanByNodeId(nodeId);
}
/**
* 设备状态的上报
* @param online 在线设备列表
* @param offline 离线设备列表
*/
@Override
public void onStatusChanged(List<String> online, List<String> offline,String gwId) {
}
/**
* 网络状态变化
* @param devId
* @param status
*/
@Override
public void onNetworkStatusChanged(String devId, boolean status) {
}
/**
* raw类型数据上报
* @param bytes
*/
@Override
public void onRawDataUpdate(byte[] bytes) {
}
/**
* 设备信息变更(名称等)
* @param bytes
*/
@Override
public void onDevInfoUpdate(String devId) {
}
/**
* 设备移除
* @param devId
*/
@Override
public void onRemoved(String devId) {
}
});
可以通过 OTA 的方式给 SIGMesh 子设备进行升级。
Sigmesh 低功耗设备升级之前需要唤醒,具体的唤醒方式不同设备唤醒方式不同。
接口说明
void requestUpgradeInfo(String devId, IRequestUpgradeInfoCallback callback);
参数说明
参数 | 说明 |
---|---|
devId | 需要升级的设备 devId |
callback | 检查回调 |
示例代码
TuyaHomeSdk.getMeshInstance().requestUpgradeInfo(mDevID, new IRequestUpgradeInfoCallback() {
@Override
public void onSuccess(ArrayList<BLEUpgradeBean> bleUpgradeBeans) {
}
@Override
public void onError(String errorCode, String errorMsg) {
}
});
BLEUpgradeBean
返回固件升级的信息,提供以下信息
字段 | 类型 | 描述 |
---|---|---|
upgradeStatus | int | 升级状态,0:无新版本 1:有新版本 2:在升级中 |
version | String | 最新版本 |
currentVersion | String | 当前版本 |
timeout | int | 超时时间,单位:秒 |
upgradeType | int | 0:app 提醒升级 2:app 强制升级 3:检测升级 |
type | int | 0:Wi-Fi设备 1:蓝牙设备 2:GPRS设备 3:zigbee设备 9:MCU |
typeDesc | String | 模块描述 |
lastUpgradeTime | long | 上次升级时间,单位:毫秒 |
url | String | 新固件下载地址,type = 1 或者 9 的时候 有值 |
fileSize | long | 新固件大小 |
md5 | String | 新固件 MD5 值 |
升级接口
void startOta();
示例代码
private MeshUpgradeListener mListener = new MeshUpgradeListener() {
@Override
public void onUpgrade(int percent) {
// 升级进度
}
@Override
public void onSendSuccess() {
// 固件数据发送成功
}
@Override
public void onUpgradeSuccess() {
// 升级成功
mMeshOta.onDestroy();
}
@Override
public void onFail(String errorCode, String errorMsg) {
// 升级失败
mMeshOta.onDestroy();
}
};
// 获取指定文件的字节流
byte data[] = getFromFile(path);
TuyaBlueMeshOtaBuilder build = new TuyaBlueMeshOtaBuilder()
.setData(data)
.setMeshId(mDevBean.getMeshId())
.setMac(mDevBean.getMac())
.setProductKey(mDevBean.getProductId())
.setNodeId(mDevBean.getNodeId())
.setDevId(mDevID)
.setVersion("version")
.setTuyaBlueMeshActivatorListener(mListener)
.bulid();
ITuyaBlueMeshOta = TuyaHomeSdk.newMeshOtaManagerInstance(build);
// 开始升级
mMeshOta.startOta();
入参说明
TuyaBlueMeshOtaBuilder
参数 | 类型 | 说明 |
---|---|---|
data | byte[] | 待升级固件的字节流 |
meshId | String | 设备 MeshId |
mac | String | 设备 Mac 地址 |
productKey | String | 设备产品 Id |
mNodeId | String | 设备 NodeId |
devId | String | 设备 Id |
version | String | 待升级固件的版本号 |
SIGMesh 网关升级和普通的 Wi-Fi 设备升级一样。以下是实例代码。
示例代码
private IOtaListener iOtaListener = new IOtaListener() {
@Override
public void onSuccess(int otaType) {
// 升级成功
}
@Override
public void onFailure(int otaType, String code, String error) {
// 升级失败
}
@Override
public void onProgress(int otaType, final int progress) {
// 升级进度
}
};
ITuyaOta iTuyaOta = TuyaHomeSdk.newOTAInstance(devId);
iTuyaOta.setOtaListener(mOtaListener);
// 开始升级
iTuyaOta.startOta();
// 销毁升级
iTuyaOta.onDestroy();
该内容对您有帮助吗?
是意见反馈该内容对您有帮助吗?
是意见反馈