1.背景
之前做过一个蓝牙共享充电宝项目 ,这里是详细对接流程记录开发蓝牙充电宝小程序的历程_小程序 蓝牙充电源码-CSDN博客。
昨天某音 有人突然联系,需要做一个网吧的项目,桌子上放个充电宝,客人扫码计时充电。
一聊,发现是个学生,考察了一段时间了,说是想去加盟,被我成功劝退!感兴趣的可以去搜一下,这是我总结的几个坑:
- 被隔空充电宝加盟割韭菜的不计其数.....
- 很多厂家技术不成熟、根本无法满足极端条件...
- 用户缺乏项目运营经验,很多都是小白,被镰刀一一忽悠就迷失自我...
2.言归正传
和对方沟通后,对方找了几个厂家,基本没有靠谱的,基本都是在收割,卖垃圾充电宝,走一单算一单:
关键是,他们的接口我之前对接过,贴一部分:
BLE 通信数据
主服务 UUID 0000F040-0000-1000-8000-000000000000
写属性 UUID 0000F041-0000-1000-8000-008000000000
读属性(非通知)UUID 0000F042-0000-1000-8000-0080000000000
一、发送充电指令,小程序通过 UUID 0000F041-0000-1000-8000-0080000000000 写属性,发送数据给电路板
命令字节 0XD5
小时字节 充电时间的小时 0x00~0x30 0~48 小时 最大支持 48 小时
分钟字节 充电时间的分钟 0x00~0x3c 0~60 分钟
状态字节 0X45
其它字节为 0x00
例子发送充电 1 小时:
FE D5 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 45
二、读取充电状态,小程序通过 UUID 0000F042-0000-1000-8000-0080500000000000000 读属性,获取电路板充电状态
状态字节 0X46 充电中 0X00 没有充电
其它字节为小程序发送过来的数据
例子成功充电状态:
FE D5 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 46
失败充电状态:
FE D5 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
三、电池电量读取
备注:为了信息安全,数据做了处理。
经过和这个小伙伴一交流,其实项目是好项目,但是需要略作修改,主要有如下几点:
- 解决隔空充电宝使用上的不规范:市场根本无法很顺畅的去满足客户需求;
- 硬件采购、需要可以对接自己的平台,否则 利润无法保证;
3.解决方案
只有经历过,才知道哪里有坑,然后早上我就决定用另一个方案来解决这个问题:
- 换成有线扫码充电线,代替隔空(隔空充电就是个噱头);
- 适配器接通电源,外接扫码、支付选择套餐,进行充电;
- 赠送5分钟;
经过以上整改之后,优势很明显:
- 价格便宜,有线充电线成本只要10块钱左右;
- 稳定可靠,通过扫码直连充电,不会闪断;
- 运营方便,无需频频售后,频繁售后;
4.平台对接
4.1.准备工作
我们只需要从某宝购买可二开的硬件即可,然后自己对接平台,看:我已经拿到了api接口文档:
4.2.系统流程
接下来就是对接系统了,照样沿用我之前的平台,只需更换接口即可,界面如此:
4.3.界面UI
4.4.核心代码
蓝牙对接,关键在调试,就需要对微信开发工具很熟悉了,这个是我弄的一个简单的代码,没有优化的,可以修改一下自己用,
我用的是uniapp,所以这里是片段,不要喷、不要喷、不要喷,我是建议自己看懂逻辑,然后根据自己的业务逻辑去写一份,照搬的,我建议换行吧,哈哈:
<template>
<view class="warp">
<!--充电流程-->
<u-navbar :border-bottom="false" :custom-back="gotoindex" :is-back="true" back-icon-color="#ffffff" title-color="#ffffff" title="无线充电" :background="background">
</u-navbar>
<view class="u-p-20" style="background: linear-gradient(180deg, #0AA79E 5%, #ffffff);;border-radius: 0px 0px 30px 0px;">
<view class="u-m-b-20 u-m-t-20" style="font-weight: bold;">
操作步骤
</view>
<u-steps active-color="#0AA79E" :list="numList" :current="currentstepindex" mode="number"></u-steps>
<view class=" u-m-t-20 u-m-l-20" style="color: #ff5500;" v-if="errormsg !== ''" >
<u-icon name="info-circle" color="#ff5500"></u-icon><text class="u-m-l-10">{{errormsg}}</text>
</view>
</view>
<!--显示我的充电动态-->
<view class="u-p-20" v-if="myorder.endtime > 0 ">
<view class="u-flex u-m-b-20 " style="justify-content: center; font-size: 18px;">
倒计时:<u-count-down :timestamp="myorder.endtime" @end="chargerend" font-size="36"></u-count-down>
</view>
<u-image width="100%" height="300rpx" src="https://btc.jxh101.cn/uploads/20230924/3853e8d0d114e505e84485a9c2c2cc90.gif"></u-image>
</view>
<!--开始选择支付选项-->
<view v-else class="u-p-20">
<view class="u-m-b-20 u-m-t-20" style="font-weight: bold;">
欢迎使用无线充电器:
</view>
<view v-if="currentstepindex == 2 " class="u-flex" style="justify-content: center;">
<view class="u-m-t-40" style="width: 75%">
<view v-for="(item, index) in projectslists" :key="index" @click="setprojectId(index)" >
<view v-if="item.status == 0" class="u-flex u-p-20 u-m-t-20 "
style="border: 1px solid #929292;;justify-content: space-around;background-color: #ffffff; border-radius: 30px;height: 40px;">
<view class="u-flex" style="justify-content: center;color:#929292;width: 30%;">无线充电</view>
<view class="u-flex" style="justify-content: center;color:#929292;width: 30%;">{{item.name}}</view>
<view class="u-flex u-font-18" style="justify-content: flex-start;color:#ff0000;width: 40%;">
<text>{{item.price}}元</text>
</view>
</view>
<view v-if="item.status == 1" class="u-flex u-p-20 u-m-t-20 "
style="border: 1px solid #929292;;justify-content: space-around;background-color: #0AA79E;color:#ffffff; border-radius: 30px;height: 40px;">
<view class="u-flex" style="justify-content: center;width: 30%;">无线充电</view>
<view class="u-flex" style="justify-content: center;width: 30%;">{{item.name}}</view>
<view class="u-flex u-font-18" style="justify-content: flex-start;width: 40%;">
<text>{{item.price}}元</text>
</view>
</view>
</view>
<view class="u-m-t-80">
<u-button type="success" @click="submitsorder()">
<u-icon name="weixin-fill" color="#ffffff" size="32"></u-icon>
<text class="u-m-l-10">
微信支付
</text>
</u-button>
</view>
<view class="u-flex u-m-t-30 " style="justify-content: flex-start;">
<view>
<u-switch active-color="#19be6b" v-model="agreenreadme" size="40" ></u-switch>
</view>
<view class="u-m-l-10 u-flex" style="justify-content: flex-start;">
<text>
同意接受
</text>
<text @click="readmeshow=true" class="u-m-r-10" style="color:#87b6e7" >
《用户须知》
</text>
</view>
</view>
</view>
</view>
<view v-else>
<view v-if="currentstepindex == 0" class="u-font-16 u-p-20" style="font-weight: bold;">
<view>
请打开蓝牙后,再扫码链接充电宝
</view>
</view>
<view v-else-if="currentstepindex == 1" class="u-font-16 u-p-20" style="font-weight: bold;">
<view>{{errormsg}}</view>
<u-button type="success" @click="scanCode()">
<u-icon name="weixin-fill" color="#ffffff" size="32"></u-icon>
<text class="u-m-l-10">
打开蓝牙后,扫码使用
</text>
</u-button>
</view>
</view>
</view>
<view class="u-p-20">
<view class="u-m-b-20 u-m-t-20" style="font-weight: bold;">
温馨提示
</view>
<view class="u-m-b-20 u-m-t-20">
<view class="u-p-20 content" v-html="chargconfig.note.replace(/(\r\n|\n|\r)/gm, '<br />')"></view>
</view>
</view>
<u-modal confirm-color="#0AA79E" v-model="successhow" confirm-text="好的" :title="modaltitle" @confirm="clossuccess" :content="successcontent"></u-modal>
<u-toast ref="uToast" />
</view>
</template>
下面是 js
<script>
export default {
data() {
return {
charge_time:0,
timer: null, //定时器名称
chargconfig:{
note:''
},
timestamp:'20',
modaltitle:'',
successhow:false,
successcontent:'提交成功',
projectId:0,
agreenreadme:false,
numList: [
{
name: '扫码进入'
},
{
name: '开启蓝牙'
}, {
name: '连接蓝牙'
}, {
name: '支付下单'
}, {
name: '开始充电'
},
],
errormsg:'',
currentstepindex:0,
background: {
backgroundColor: '#0AA79E'
},
myorder:{
endtime:0
},
projectslists:[],
bluetooth_name:'',
userInfo:[],
islogin:false,
bluetoothlist:[],
deviceId:'',
serviceId:'0000F040-0000-1000-8000-001111111111111',//主服务
characteristics:[],
characteristicId:'0000F041-0000-1000-8000-001111111111111',//写属性
readcharacteristicId:'0000F042-0000-1000-8000-001111111111111'//读属性
}
},
onLoad(options) {
/*if(!options.bluetooth){
this.$refs.uToast.show({
title: '参数错误',
type:'success',
duration:'1000'
})
return false;
}*/
//options.bluetooth = 'AKPXGA0271';
//options.bluetooth = '0000180D-0000-1000-8000-001111111111111';
this.bluetooth_name = options.bluetooth;
console.log('options:',options);
if(uni.getStorageSync('userInfo')){
this.userInfo = uni.getStorageSync('userInfo');
this.islogin = true;
}
},
onShow() {
this.errormsg = '';
clearInterval(this.timer);
this.init();
console.log('bluetooth_name:',this.bluetooth_name);
uni.openBluetoothAdapter({
success:(res)=> { //已打开
uni.getBluetoothAdapterState({//蓝牙的匹配状态
success:(res1)=>{
console.log(res1,'本机设备的蓝牙已打开')
// 开始搜索蓝牙设备
this.startBluetoothDeviceDiscovery();
if(this.currentstepindex ==0){
this.currentstepindex = 1;
}
this.errormsg = '';
},
fail(error) {
uni.showToast({icon:'none',title: '查看手机蓝牙是否打开'});
this.errormsg = '查看手机蓝牙是否打开';
}
});
},
fail:err=>{ //未打开
uni.showToast({icon:'none',title: '查看手机蓝牙是否打开'});
this.errormsg = '查看手机蓝牙是否打开';
}
})
//this.asyncorderQuery('20231002171318937723');
},
onUnload() {
clearInterval(this.timer);
uni.closeBluetoothAdapter({
success(res) {
console.log(res)
}
})
},
methods: {
scanCode(){
uni.closeBluetoothAdapter({
success(res) {
console.log('closeBluetoothAdapter:',res)
},
})
var that = this;
uni.scanCode({
onlyFromCamera: true,
success: function (res) {
console.log('条码类型:' + res.scanType);
console.log('条码内容:' + res.result);
console.log(res);
uni.closeBluetoothAdapter({
success(r) {
console.log('closeBluetoothAdapter:',r)
},
complete(r){
console.log('@closeBluetoothAdapter:',r)
}
})
if(res.scanType !== 'WX_CODE'){
that.$refs.uToast.show({
title: '请使用微信扫一扫',
type: 'error'
})
}
//return false;
if(res.path){
var path = res.path;
uni.navigateTo({
url:'/'+path
})
}else{
that.$refs.uToast.show({
title: '请微信扫桌上二维码',
type: 'error'
})
}
},
fail:function (res) {
that.$refs.uToast.show({
title: '请微信扫桌上二维码',
type: 'error'
})
}
});
},
async chargerend(){
console.log('end。。。');
let data = await this.$api.endcharger({id:this.myorder.id});
if(data){
this.myorder = [];
this.init();
this.currentstepindex = 2;
}
},
clossuccess(){
console.log(2222);
this.successhow = false;
},
async submitsorder(){
var that = this;
if(!this.islogin){
this.login();
return false;
}
let data = {};
//获取图片列表
if(!this.agreenreadme){
uni.showToast({icon:'none',title: '请阅读并同意用户协议'});
return false;
}
if(this.projectId == 0){
this.$refs.uToast.show({
title: '请选择充值时间',
type:'error',
duration:'1000'
})
return false;
}
data.projectId = this.projectId;
data.bluetooth_name = this.bluetooth_name;
console.log(data)
//提交
//return false;
let payData = await this.$api.submitsorder(data);
console.log('payData:',payData);
if(payData){
uni.requestPayment({
provider: 'wxpay',
timeStamp: payData.timeStamp,
nonceStr: payData.nonceStr,
package: payData.package,
signType: payData.signType,
paySign: payData.paySign,
success: function (res) {
console.log(res);
//uni.navigateBack();
that.modaltitle = '支付成功';
that.successcontent ='请将手机放置到充电板,或直接使用数据线充电!';
that.successhow = true;
//that.getBLEDeviceServices();
// clearInterval(that.timer);
//that.writeBLECharacteristicValue();
},
fail: function (err) {
uni.showToast({
title:'支付失败,请重新进行支付!',
icon:'none'
})
clearInterval(that.timer); // 清除定时器
that.timer = null;
},
complete:function(res){
console.log('pay_success:',res);
}
});
that.timer = setInterval(() => {
that.asyncorderQuery(payData.orderid); // 每3秒查询订单
}, 3000)
}else{
}
},
async asyncorderQuery(orderid){
let orderinfo = await this.$api.getorderbyOrderno({orderno:orderid});
console.log('orderinfo:',orderinfo);
if(orderinfo){
if(orderinfo.status == '1'){
this.charge_time = orderinfo.charge_time;
this.currentstepindex = 3;
//支付成功
this.getBLEDeviceServices();
clearInterval(this.timer);
}else{
console.log("orderinfo.status:",orderinfo.status);
}
}else{
}
},
setprojectId(index){
this.projectId = this.projectslists[index].id;
for(var i = 0;i < this.projectslists.length;i++){
this.projectslists[i].status = 0;
}
this.projectslists[index].status = 1;
},
gotoindex(){
uni.switchTab({
url:"/pages/index/index"
})
},
async init(){
let data = await this.$api.projects({bluetooth_name:this.bluetooth_name});
if(data){
this.projectslists = data.projectslists;
this.myorder = data.my_orders;
if(data.my_orders && data.my_orders.endtime > 0){
this.currentstepindex = 4;
}
this.chargconfig = data.chargconfig;
}else{
}
console.log(data)
},
// 开始搜索蓝牙设备
startBluetoothDeviceDiscovery(){
console.log('bluetooth:',this.bluetooth_name);
uni.startBluetoothDevicesDiscovery({
//services:this.bluetooth,//['0000180D-0000-1000-8000-001111111111111'],
success: (res) => {
console.log('startBluetoothDevicesDiscovery success', res)
// 发现外围设备
this.onBluetoothDeviceFound()
},fail:err=>{
console.log(err,'错误信息')
}
})
console.log('sha');
},
// 发现外围设备
onBluetoothDeviceFound() {
var that = this ;
uni.onBluetoothDeviceFound((res) => {
console.log('onBluetoothDeviceFound:',JSON.stringify(res.devices));
console.log('res.devices[0].name :this.bluetooth_name',res.devices[0].name +':'+ this.bluetooth_name);
//if(res.devices[0].name == this.bluetooth_name){
if(this.bluetoothlist.indexOf(res.devices[0].deviceId)==-1 && res.devices[0].name == this.bluetooth_name){
this.bluetoothlist.push({
name:res.devices[0].name,
deviceId:res.devices[0].deviceId,
status:0
})
that.deviceId = res.devices[0].deviceId;
that.createBLEConnection(res.devices[0].deviceId);
}else{
console.log('neq');
}
///that.deviceId = res.devices[0].deviceId;
//that.createBLEConnection(res.devices[0].deviceId);
console.log('Bluetooth Lists:',this.bluetoothlist);
//}else{
// console.log('名字不想等');
//}
})
},
//选择设备连接吧deviceId传进来
createBLEConnection(deviceId){
//data里面建立一个deviceId,存储起来
//this.deviceId = deviceId;
var that = this;
console.log(this.bluetoothlist);
console.log('deviceId',deviceId);
//连接蓝牙
uni.createBLEConnection({
// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
deviceId:deviceId,
success(res) {
//that.bluetoothlist[index].status = 1;
console.log(res)
console.log("蓝牙连接成功");
if(that.myorder && parseInt(that.myorder.endtime) > 0){
that.currentstepindex = 4;
}else{
that.currentstepindex = 2;
}
that.errMsg="";
},fail(res) {
that.errMsg="蓝牙连接失败,请扫码重试"
console.log("蓝牙连接失败",res);
that.currentstepindex = 1;
that.errMsg="蓝牙连接失败,请扫码重试"
}
})
this.stopBluetoothDevicesDiscovery();
//this.writeBLECharacteristicValue();
},
//获取蓝牙的所有服务
getBLEDeviceServices(){
setTimeout(()=>{
uni.getBLEDeviceServices({
// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
deviceId:this.deviceId,
success:(res)=>{
console.log('device services:', res)
//E95D93AF-251D-470A-A062-FA1922DFA9A8
//6E400001-B5A3-F393-E0A9-E50E24DCCA9E
res.services.forEach((item)=>{
if(item.uuid !=-1 && item.uuid=='0000F040-0000-1000-8000-00805F9B34FB'){
this.serviceId = item.uuid;
console.log(this.serviceId)
//获取特征
this.getBLEDeviceCharacteristics()
}
})
}
})
},1000)
},
//获取蓝牙特征
getBLEDeviceCharacteristics(){
console.log("进入特征");
setTimeout(()=>{
uni.getBLEDeviceCharacteristics({
// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
deviceId:this.deviceId,
// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
serviceId:this.serviceId,
success:(res)=>{
console.log(res,'特征getBLEDeviceCharacteristics')
this.characteristics = res.characteristics
console.log(this.characteristics)
res.characteristics.forEach((item)=>{
if(item.uuid != -1 && item.uuid == '0000F041-0000-1000-8000-001111111111111'){
this.characteristicId = item.uuid;
this.writeBLECharacteristicValue();
//console.log('characteristicId:', item.uuid)
//利用传参的形势传给下面的notify,这里的uuid如果都需要用到,就不用做判断了,建议使用setTimeout进行间隔性的调用此方法
//this.notifyBLECharacteristicValueChange(item.uuid)
}
})
},
fail:(res)=>{
console.log(res)
}
})
},1000)
},
//写入蓝牙,写成功之后就可以充电了。
writeBLECharacteristicValue() {
var that = this;
//例如:充电一个小时,buff 的值如下:FED5010000000000000000000000000000000045 ;'FED5000200000000000000000000000000000045'
var charge_time = this.charge_time;
var buff = this.string2buffer(charge_time); //9.0
console.log('这里了1:',this.deviceId);
console.log('这里了2:',this.serviceId);
console.log('这里了3:',this.characteristicId);
console.log('buff:',buff);
uni.writeBLECharacteristicValue({
deviceId: this.deviceId,//设备ID
serviceId: this.serviceId,//主服务
characteristicId: this.characteristicId,//写属性
value:buff,
success(res) {
console.log('writeBLECharacteristicValue success', res.errMsg);
that.init();
that.bluetoothlist[0].status = 1;
that.currentstepindex = 4;
},
fail(res) {
console.log(JSON.stringify(res))
console.log(JSON.stringify(buff))
}
})
},
//字符串转 Buffer
string2buffer(str) {
let val = ""
if (!str) return;
let length = str.length;
let index = 0;
let array = []
while (index < length) {
array.push(str.substring(index, index + 2));
index = index + 2;
}
val = array.join(",");
// 将16进制转化为ArrayBuffer
return new Uint8Array(val.match(/[\da-f]{2}/gi).map(function(h) {
return parseInt(h, 16)
})).buffer
},
// 停止搜寻蓝牙设备
stopBluetoothDevicesDiscovery(){
uni.stopBluetoothDevicesDiscovery({
success: e => {
this.loading = false
console.log('停止搜索蓝牙设备:' + e.errMsg);
},
fail: e => {
console.log('停止搜索蓝牙设备失败,错误码:' + e.errCode);
}
});
},
login(){
let that = this;
// #ifndef MP-TOUTIAO
uni.getUserProfile({
desc:'用于完善会员资料',
success:async (res) => {
console.log(res)
if (res.errMsg = 'getUserInfo:ok') {
let user_info = res.userInfo;
console.log(user_info);
//用户注册 或 登录
let result = await that.$api.wxGetUserInfo(user_info,'wechat');
console.log(result)
if(result){
uni.setStorageSync('token',result.userInfo.token);
uni.setStorageSync('userInfo',result.userInfo);
uni.setStorageSync('isLogin',1);
that.islogin = true;
that.userInfo = result.userInfo;
that.submitsorder();
//that.init();
//console.log(result.userInfo);
}
}else{
uni.openSetting({
success(res) {
console.log(res)
}
})
}
},
fail:(res)=>{
console.log('您选择了取消');
console.log(res);
}
})
// #endif
},
}
}
</script>
5.运营模式
本项目之所以写出来,是因为每个环节都没有瓶颈,如果有想法的可以去做,主要点有:
- 直接收益:用户直接充电收益,和商户谈好分层比例即可;
- 流量主收益:可以通过周边广告进行二次收益,这部分才是大头;
- 异业合作:流量上去后,可以扩展其他功能,比如学校周边的可以增加,周边的悠闲娱乐;
- 合作商户:酒吧、网吧、公交大巴、餐饮公共区域等.....
就到此吧,感兴趣的可以沟通,交流.....................