项目名称: 阳康宝典

简介:

2022年12月26日,深夜二十三点零一分,国家卫健委发布2022年第7号公告:将新型冠状病毒肺炎更名为新型冠状病毒感染。与此同时,国务院联防联控机制发布对新型冠状病毒感染实施“乙类乙管”总体方案,宣告全民抗疫的时代画上了终止符。

随着国家的逐步放开,人口流动量也逐渐增大,许多人因为个人防护不到位而不幸中招,这也给其他人带来了一丝焦虑与恐慌。

没变“羊”却不知如何科学保护自己?

不幸变“羊”后不知如何调理,或是因此产生的负能量不知向谁诉说?

少年,特此为你准备了一本《阳康宝典》,助力未“羊”人时时刻刻远离病毒侵扰,助力“羊”们早日康复!

创意过程(思维导图):

四象限法:

商业模式画布:

  • 客户细分:未“羊”人、“羊”、“阳康”患者
  • 价值主张:为用户提供防疫科普、心理疏导服务
  • 渠道通路:用户自发推广
  • 客户关系:相互依赖型
  • 收入来源:公益性质,用户可以自发支持
  • 核心资源:用户使用量、医疗资讯的准确度
  • 关键业务:小程序为弱运营,只需要对功能进行及时维护,及时debug,吸取用户意见开发新功能
  • 重要伙伴:心理咨询机构、专业医疗机构
  • 成本结构:主要集中于维护费用

用户分析:

目标用户:未“羊”人、“羊”、“阳康”患者

未“羊”人:身边亲友大部分已经感染,自己会因此紧张焦虑,担心自己也会中招,甚至因此出现”幻阳症”。

“羊”:康复过程十分难受,易产生许多负能量,如不及时排遣就不利于自身心理健康。

“阳康患者”:作为过来人,可以为身边的小“羊”人提供暖心的安慰以及战胜病魔的经验之谈。

用户画像:

所听:在与父母及同学交流时,他们经常担心自己也中招,比如身体出现不适症状后就担心是感染的前兆。

所见:

  • 观看新闻得知现在处于疫情高峰期,心中不由得产生恐慌。
  • 微博超话中有人分享自己的阳康经历,也目睹了评论区中大家的相互安慰抱团去年。
  • 同时被部分超话中有人带病坚持学习的精神感动,自己也想加入其中与大家共勉,一起战胜奥密克戎,一起进步。

所想&所感:

  • 自己身体状况不好带来的情绪低落
  • 食欲不振
  • 学习效率低下
  • 焦虑感、恐慌感

所说&所做:

  • 尽己所能去安慰身边亲友

痛点&需求:

痛点 需求
对如何进行自我防护、感染后如何自我调理毫无头绪 急需一款程序来为自己提供贴身的健康指导
康复期间有千种愁绪,更与何人说? 需要一个释放自己负面情绪、接受他人温暖关怀的窗口
感染期间学习效率低下,自控力不足,可患病时身体不适,情绪不稳定 需要一位无形的伙伴督促自己每天适量完成任务,并时不时给自己输入鼓励的话语,帮助稳定情绪,渡过难关

场景分析(用户的使用路径图):

系统功能拆解:

分析:

小程序核心功能 技术模块
用户登录/注册 使用微信小程序提供的userinfo获得用户微信名称头像,并判断在云数据库是否有相应用户:1.无,则在数据库中为其创建一个新的用户,名称与头像默认与微信一致(可后续在小程序中修改)2.有,则调用数据库中该用户信息,实现登录功能。
论坛 需用到云开发数据库功能,网上有大量微信小程序版论坛免费模型,可以借鉴使用,大体功能为发帖、浏览、评论、搜索、删除。
打卡功能 外接蜂鸣器、开关等硬件,并配备有OLED显示屏实时报时,当到达用户设定时间时,蜂鸣器开始工作,响铃1分钟,在此期间用户按下按钮开关即可停止。从而实现提醒功能(比如提醒用户到达喝水的时间了,此刻畅饮一杯凉开水!)
个人防护&生活监测 提前搜集好防护小知识上传至服务器,在该模块用户可以查看防护知识,并可以自己设置按时喝水提醒,每隔多长时间提醒一次喝水,与打卡提醒实现差不多。

系统功能架构:

流程图:

功能实现

  • 小程序

文件结构:

登陆界面:

  • Wxml:
<!--pages/login/login.wxml-->

<view class='top_tip'>

<view class="welcome">欢迎您来到阳康宝典</view>

<view class="welcome">一个病友交流论坛</view>

<form catchsubmit="formSubmit">

<view class='login'>

<view class="user">用户名

<view class="hold"><input class="input" name='username' type='text' placeholder='请输入用户名'></input></view>

</view>

<view class="password">密码

<view class="hold2"><input class="input" name='password' password placeholder='请输入密码'></input></view>

</view>

</view>

<button style="width:80vw" class="click" plain="true" type="default" form-type='submit'>登录</button>

<button style="width:80vw" class="click" plain="true" type="default" bindtap="youke">游客登录</button>

</form>

</view>

<view class="reg" bindtap="toReg">没有账号?</view>`
  • Js:
// pages/login/login.js

const app = getApp()

Page({

/\*\*

\* 页面的初始数据

\*/

data: {

},

formSubmit(e) {

console.log('form发生了submit事件,携带数据为:', e.detail.value)

wx.request({

// url: 'http://127.0.0.1:5000/cms/user/login',

url: 'http://192.168.137.1:5000/cms/user/login',

method: 'POST',

data: {

username: e.detail.value.username,

password: e.detail.value.password

},

header: {

'content-type': 'application/json' // 默认值

},

success:function(res) {

var obj = res.data

console.log(obj)

wx.setStorage({

key:"token",

data:obj.access_token

})

wx.setStorage({

key:"username",

data:e.detail.value.username

})

if(obj.code == 10031) {

wx.showToast({

title: '用户名或密码错误',

icon: 'error'

});

}

else if(obj.code == 10030) {

wx.showToast({

title: '用户名或密码不能为空',

icon: 'error'

});

}

else {

wx.showToast({

title: '登录成功',

})

wx.switchTab({

url: '/pages/allshudong_page/allshudong',

})

}

}

})

},

toReg(){

wx.navigateTo({

url: '/pages/register_page/home1',

})

},

youke(){{

wx.switchTab({

url: '/pages/allshudong_page/allshudong',

})

}}

})

注册界面:

  • Wxml:
<!--index.wxml-->

<view class='top_tip'>

<view class="welcome">欢迎您来到阳康宝典</view>

<view class="welcome">但一个账号是必不可少的</view>

<form catchsubmit="onSubmit">

<view class='login'>

<view class="user">用户名

<view class="hold"><input class="input" name='username' type='text' placeholder='请输入用户名'></input></view>

</view>

<view class="password">密码

<view class="hold2"><input class="input" name='password' password placeholder='请输入密码'></input></view>

</view>

<view class="confirm">请确认

<view class="hold"><input class="input" name='confirm_password' password placeholder='请重复密码'></input></view>

</view>

</view>

<button style="width:80vw" class="click" plain="true" type="default" form-type='submit'>注册</button>

</form>

</view>

<view class="reg" bindtap="toLogin">已有账号?</view>
  • Js:
// index.js

// const app = getApp()

const app = getApp()

Page({

data: {

},

onSubmit: function (e) {

console.log(e.detail.value);

var that= this

wx.request({

// url: 'http://127.0.0.1:5000/cms/user/register',

url: 'http://192.168.137.1:5000/cms/user/register',

method: 'POST',

data: {

username: e.detail.value.username,

password: e.detail.value.password,

confirm_password: e.detail.value.confirm_password

},

header: {

'content-type': 'application/json' // 默认值

},

success:function(res) {

var obj = res.data

console.log(obj)

if(obj.code == 10030) {

wx.showToast({

title: JSON.stringify(obj.message),

icon: 'error'

});

}else if(obj.code == 10071) {

wx.showToast({

title: JSON.stringify(obj.message),

icon: 'error'

});

}else {

wx.showToast({

title: '注册成功',

icon: 'success'

})

}

}

})

},

toLogin(e) {

wx.reLaunch({

url:'/pages/login_page/login',

});

}

});

广场界面:

  • Wxml:
<!-- pages/allshudong_page/allshudong.wxml-->

<view class='container'>

<view class="comment-list">

<block wx:for="{{array}}">

<view class="line"></view>

<navigator url='/pages/oneshudong_page/oneshudong?id={{item.id}}'>

<view class="comment-item">

<view class="Onetitle">{{item.title}}</view>

<view class="Oneauthor">{{item.author}}</view>

</view>

</navigator>

</block>

</view>

</view>

Js:

// pages/allshudong_page/allshudong.js

Page({

/\*\*

\* 页面的初始数据

\*/

data: {

array:[]

},

onLoad(options) {

var that= this

wx.request({

//url: 'http://127.0.0.1:5000/v1/book/',

url: 'http://192.168.137.1:5000/v1/book/',

method:'GET',

data: {

//无

},

header: {

'Authorization': 'Bearer '+wx.getStorageSync('token')

},

success (res) {

console.log(res.data)

that.setData({

array: (res.data).reverse()

})

}

})

},

onPullDownRefresh() {

this.onLoad()

wx.showToast({

title: '刷新成功',

icon: 'success'

})

wx.stopPullDownRefresh()

},

})

帖子详情及评论:

  • Wxml:
<!--pages/demo02/text/text.wxml-->

<view class='tiezi'>

<view class='demo-box'>

<view class="Theauthor">{{author}}</view>

<view style="display: flex;">

<view class="Thetitle">{{title}}</view>

<mp-icon style="margin-right: 20rpx;" type="field" icon="delete" color="red" size="{{20}}" bindtap= 'delete'></mp-icon>

</view>

<view class="line"></view>

<view class="Thesummary">{{summary}}</view>

</view>

<!-- 分割线 -->

<view class="view_fengexian">

<view ></view>

<text class="text_fengexian">评论</text>

<view ></view>

</view>

<!-- 评论区 -->

<view class="comment-list">

<block wx:for="{{array}}" wx:key="index">

<view class="line"></view>

<view class="comment-item">

<view class="comment-author">{{item.author}}</view>

<view style="display: flex;">

<view class="comment-content">{{item.comment}}</view>

<mp-icon style="margin-right: 20rpx;" type="outline" icon="delete" color="black" size="{{20}}" bindtap= 'deleteComment' data-index="{{item.id}}"></mp-icon>

</view>

</view>

</block>

</view>

</view>

<!-- 底部发送评论栏 -->

<view class="col">

<view class="search"><input confirm-type="done" placeholder="发一条友善的评论~" placeholder-class="input" class="input" bindinput="bindText"></input></view>

<view class="sendButton" bindtap="send">发送</view>

</view>

Js:

// pages/oneshudong_page/oneshudong.js

Page({

data: {

id:null,

author:null,

title:null,

summary:null,

array:null,

text:null,

\_id:null

},

onLoad(options) {

var that= this

this.setData({

id:options.id

})

//console.log(this.data.id)

// 获取帖子详细内容

wx.request({

// url: 'http://127.0.0.1:5000/v1/book/'+that.data.id,

url: 'http://192.168.137.1:5000/v1/book/'+that.data.id,

method:'GET',

data: {

//无

},

header: {

'Authorization': 'Bearer '+wx.getStorageSync('token')

},

success (res) {

console.log(res.data)

that.setData({

title:res.data.title,

author:res.data.author,

summary:res.data.summary

})

}

})

// 获取评论

wx.request({

url: 'http://192.168.137.1:5000/treehole/reply/'+that.data.id,

method: 'GET',

data: {

//无

},

success (res) {

console.log(res)

that.setData({

array:res.data

})

}

})

},

// 下拉刷新

onPullDownRefresh() {

var that = this

wx.request({

// url: 'http://127.0.0.1:5000/v1/book/'+that.data.id,

url: 'http://192.168.137.1:5000/v1/book/'+that.data.id,

method:'GET',

data: {

//无

},

header: {

'Authorization': 'Bearer '+wx.getStorageSync('token')

},

success (res) {

console.log(res.data)

that.setData({

title:res.data.title,

author:res.data.author,

summary:res.data.summary

})

}

})

// 获取评论

wx.request({

url: 'http://192.168.137.1:5000/treehole/reply/'+that.data.id,

method: 'GET',

data: {

//无

},

success (res) {

console.log(res)

that.setData({

array:res.data

})

}

})

wx.showToast({

title: '刷新成功',

icon: 'success'

})

wx.stopPullDownRefresh()

},

// 删除帖子

delete(e){

var that= this

wx.request({

// url: 'http://127.0.0.1:5000/v1/book/'+that.data.id,

url: 'http://192.168.137.1:5000/v1/book/'+that.data.id,

method: 'DELETE',

data: {

author:wx.getStorageSync('username')

},

header: {

'Authorization': 'Bearer '+wx.getStorageSync('token')

},

success (res) {

var obj= res.data

console.log(obj)

if(obj.code == 14){

wx.showToast({

title: '删除成功',

icon: 'success'

})

}else{

wx.showToast({

title: JSON.stringify(obj.message),

icon: 'error'

})

}

}

})

wx.request({

url: 'http://192.168.137.1:5000/treehole/reply/all/'+that.data.id,

method: 'DELETE',

data: {

author:wx.getStorageSync('username')

},

header: {

'Authorization': 'Bearer '+wx.getStorageSync('token')

},

success (res) {

var obj= res.data

console.log(obj)

if(obj.code== 14){

wx.showToast({

title: '删除成功',

icon: 'success'

})

}else{

wx.showToast({

title: JSON.stringify(obj.message),

icon: 'error'

})

}

}

})

},

// 删除评论

deleteComment(e){

var that = this

console.log(e.target.dataset.index)

this.setData({

\_id : e.target.dataset.index

})

wx.request({

url: 'http://192.168.137.1:5000/treehole/reply/'+that.data._id,

method: 'DELETE',

data: {

author:wx.getStorageSync('username')

},

header: {

'Authorization': 'Bearer '+wx.getStorageSync('token')

},

success (res) {

var obj= res.data

console.log(obj)

if(obj.code== 14){

wx.showToast({

title: '删除成功',

icon: 'success'

})

}else{

wx.showToast({

title: JSON.stringify(obj.message),

icon: 'error'

})

}

}

})

onPullDownRefresh() //删除评论后自动刷新

},

// 实时获取发送内容

bindText(e){

this.setData({

text:e.detail.value

})

},

// 发送评论

send(e){

var that = this

wx.request({

url: 'http://192.168.137.1:5000/treehole/reply/',

method: 'POST',

data:{

author:wx.getStorageSync('username'),

comment:that.data.text,

image:'none',

this_id:that.data.id

},

header: {

'Authorization': 'Bearer '+wx.getStorageSync('token')

},

success (res){

var obj= res.data

console.log(obj)

if(obj.code== 12){

wx.showToast({

title: '发表评论成功',

icon: 'success'

})

}else{

wx.showToast({

title: JSON.stringify(obj.message),

icon: 'error'

})

}

}

})

}

})

发布界面:

  • Wxml:
<!--pages/commit/commit.wxml-->

<text class="text">标题</text>

<input placeholder="输入标题" class='down_line' placeholder-style="color:#888888; font-size:38rpx;" bindinput="bindText"></input>

<view class="textarea">

<textarea placeholder="发一条帖子吧~" placeholder-style="color:#888888; font-size:38rpx;" class='textinput' bindinput="bindTextAreaBlur"></textarea>

</view>

<view id="btn" class="click" bindtap='send'>发送</view>
  • Js:
// pages/post_page/post.js

Page({

data: {

title:null,

text:null,

author:null

},

onLoad(options) {

},

bindText(e){

//console.log(e.detail.value)

this.setData({

title:e.detail.value

})

},

bindTextAreaBlur(e){

//console.log(e.detail.value)

this.setData({

text:e.detail.value

})

},

send(e){

var that= this

wx.request({

// url: 'http://127.0.0.1:5000/v1/book/',

url: 'http://192.168.137.1:5000/v1/book/',

method:'POST',

data:{

title:that.data.title,

summary:that.data.text,

author:wx.getStorageSync('username'),

image:'none',

},

header: {

'Authorization': 'Bearer '+wx.getStorageSync('token')

},

success (res) {

var obj= res.data

console.log(obj)

if(obj.code== 10000){

wx.showToast({

title: '您还没有登陆',

icon: 'error'

})

}else if(obj.code== 10030){

wx.showToast({

title: JSON.stringify(obj.message),

icon: 'error'

})

}else if(obj.code== 10240){

wx.showToast({

title: JSON.stringify(obj.message),

icon: 'error'

})

}

else{

wx.showToast({

title: '发送成功',

icon: 'success'

})

}

}

})

}

})

我的界面:

  • Wxml:
<!--pages/mine/mine.wxml-->

<!-- <view class="page">

<image class="image" src="/images/myImage.png"></image>

</view> -->

<view class='hello'>

<text>{{welcome}}~</text>

</view>

<view style="margin-top: 100px;">

<view class="info" bindtap="login">

<mp-icon type="outline" icon="me" color="black" size="{{40}}"></mp-icon>

<view style="font-size: 25px;">用户登录</view>

</view>

<view class="info" bindtap="quit">

<mp-icon type="outline" icon="close" color="black" size="{{40}}"></mp-icon>

<view style="font-size: 25px;">用户登出</view>

</view>

<view class="info" bindtap="esp">

<mp-icon type="outline" icon="link" color="black" size="{{40}}"></mp-icon>

<view style="font-size: 25px;">ESP定时器</view>

</view>

</view>
  • Js:
// pages/myinfo_page/myinfo.js

Page({

data: {

welcome:null,

},

onLoad(options) {

var that= this

if(wx.getStorageSync('username')== 'null'){

that.setData({

welcome: '您还没有登陆哦'

})

}else{

that.setData({

welcome: '欢迎你'+wx.getStorageSync('username')

})

}

},

login(e){

if(wx.getStorageSync('username')== 'null'){

wx.reLaunch({

url: '/pages/login_page/login',

})

}else{

console.log(wx.getStorageSync('username'))

wx.showToast({

title: '您已经登录!',

icon: 'error'

})

}

},

quit(e){

wx.setStorageSync('username','null')

wx.removeStorageSync('token')

this.setData({

welcome: '您还没有登陆哦'

})

wx.showToast({

title: '退出成功!',

icon: 'success'

})

},

esp(e){

wx.navigateTo({

url: '/pages/clock/index',

})

}

})

ESP定时器界面:

  • Wxml:
<!--index.wxml-->

<view style="margin-top:20px;">

<view style="text-align:center;">

<p style="font-size:30px;">ESP远程提醒设置</p>

<view style="margin-top:30px;"> <p>设备状态:</p>

<text style="color: {{statusColor}};">{{device_status}}</text>

</view>

<view style="margin-top:0px;"> <p>ESP目前提醒时间:{{powerstatus}}</p> </view>

<view style="font-size: 50rpx;margin-top: 50rpx;">

点击下方以更改提醒时间

</view>

<picker mode="time" value="{{time}}" start="00:01" end="23:59" bindchange="bindTimeChange">

<view class="flex">

<view class="choose">{{time1}}</view>

<view class="maohao">:</view>

<view class="choose">{{time2}}</view>

</view>

</picker>

<view style="margin-top:30px;">

<view style="margin-top:30px;"> <button bindtap="openclick" >发送</button> </view>

</view>

</view>

\</view>
  • Js:
const app = getApp()

Page({

data: {

uid: 'ea565388aea14b9088ebd3801e5488c0',

topic: "timer",

device_status: "离线", //默认离线

powerstatus:"已关闭", //默认关闭

time:"无",

time1:"00",

time2:"00",

statusColor:"red"

},

onLoad: function () {

var that = this

//请求设备状态

//设备断开不会立即显示离线,由于网络情况的复杂性,离线1分钟左右才判断真离线

wx.request({

url: 'https://api.bemfa.com/api/device/v1/status/', //状态api接口,详见巴法云接入文档

data: {

uid: that.data.uid,

topic: that.data.topic,

},

header: {

'content-type': "application/x-www-form-urlencoded"

},

success (res) {

console.log(res.data)

if(res.data.status === "online"){

that.setData({

device_status:"在线",

statusColor: "green"

})

}else{

that.setData({

device_status:"离线",

statusColor: "red"

})

}

console.log(that.data.device_status)

}

})

//请求询问设备开关/状态

wx.request({

url: 'https://api.bemfa.com/api/device/v1/data/1/', //get接口,详见巴法云接入文档

data: {

uid: that.data.uid,

topic: that.data.topic,

},

header: {

'content-type': "application/x-www-form-urlencoded"

},

success (res) {

console.log(res.data)

that.setData({

powerstatus:res.data.msg

})

console.log(that.data.powerstatus)

}

})

//设置定时器,每五秒请求一下设备状态

setInterval(function () {

console.log("定时请求设备状态,默认五秒");

wx.request({

url: 'https://api.bemfa.com/api/device/v1/status/', //get 设备状态接口,详见巴法云接入文档

data: {

uid: that.data.uid,

topic: that.data.topic,

},

header: {

'content-type': "application/x-www-form-urlencoded"

},

success (res) {

console.log(res.data)

if(res.data.status === "online"){

that.setData({

device_status:"在线",

statusColor: "green"

})

}else{

that.setData({

device_status:"离线",

statusColor: "red"

})

}

console.log(that.data.device_status)

}

})

//请求询问设备开关/状态

wx.request({

url: 'https://api.bemfa.com/api/device/v1/data/1/', //get接口,详见巴法云接入文档

data: {

uid: that.data.uid,

topic: that.data.topic,

},

header: {

'content-type': "application/x-www-form-urlencoded"

},

success (res) {

console.log(res.data)

that.setData({

powerstatus:res.data.msg

})

console.log(that.data.powerstatus)

}

})

}, 5000)

},

openclick: function() {

var that = this

that.setData({

powerstatus:"已打开"

})

//控制接口

wx.request({

url: 'https://api.bemfa.com/api/device/v1/data/1/', //api接口,详见接入文档

method:"POST",

data: { //请求字段,详见巴法云接入文档,http接口

uid: that.data.uid,

topic: that.data.topic,

msg:that.data.time //发送消息为on的消息

},

header: {

'content-type': "application/x-www-form-urlencoded"

},

success (res) {

console.log(res.data)

wx.showToast({

title:'发送成功',

icon:'success',

duration:1000

})

}

})

},

bindTimeChange: function(e) {

console.log('picker发送选择改变,携带值为', e.detail.value)

this.setData({

time: e.detail.value,

time1:e.detail.value.slice(0, 2),

time2:e.detail.value.slice(3, 5),

})

},

})
  • 硬件(ESP8266-NodeMCU 1.0)

#include <Arduino.h>

#include <U8g2lib.h>

#include <Wire.h>

#include <NTPClient.h>

#include <ESP8266WiFi.h>

#include <WiFiUdp.h>

WiFiUDP ntpUDP;

一些包含的头文件

//巴法云服务器地址默认即可

#define TCP_SERVER_ADDR "bemfa.com"

//服务器端口,tcp创客云端口8344

#define TCP_SERVER_PORT "8344"

#define DEFAULT_STASSID "LAO_TIAN" //WIFI名称,区分大小写,不要写错

#define DEFAULT_STAPSW "12345678" //WIFI密码

String UID = "ea565388aea14b9088ebd3801e5488c0"; //用户私钥,可在控制台获取,修改为自己的UID

String TOPIC = "timer"; //主题名字,可在控制台新建

连接巴法云

//最大字节数

#define MAX_PACKETSIZE 512

//设置心跳值30s

#define KEEPALIVEATIME 60*1000

//tcp客户端相关初始化,默认即可

WiFiClient TCPclient;

String TcpClient_Buff = "";

unsigned int TcpClient_BuffIndex = 0;

unsigned long TcpClient_preTick = 0;

unsigned long preHeartTick = 0;//心跳

unsigned long preTCPStartTick = 0;//连接

bool preTCPConnected = false;

// 设置屏幕

U8G2_SSD1306_128X64_NONAME_1_SW_I2C u8g2(U8G2_R0, 14 , 5, U8X8_PIN_NONE); // All Boards without Reset of the Display

NTPClient timeClient(ntpUDP, "cn.ntp.org.cn", 8 * 60 * 60, 60000);

String setTime="10:00";

int button = 0;

int bee = 13;

int value = 1;

int judge;

//相关函数初始化

//连接WIFI

void doWiFiTick();

void startSTA();

//TCP初始化连接

void doTCPClientTick();

void startTCPClient();

void sendtoTCPServer(String p);

void set_time(String get);

void setup(void) {

pinMode(bee, OUTPUT);

pinMode(button, INPUT_PULLUP);

Serial.begin(115200);

Serial.println("Beginning...");

timeClient.begin();

Serial.println ( "网络时钟初始化完成" );

u8g2.begin();

}

void loop(void) {

// wifi

doWiFiTick();

// 网络获取时间

timeClient.update();

String nowtime = timeClient.getFormattedTime();

// Serial.println(nowtime);

String hours = nowtime.substring(0, 2);

String minutes = nowtime.substring(3, 5);

String seconds = nowtime.substring(6, 8);

通过网络获取时间

const char *hour = hours.c_str();

const char *minute = minutes.c_str();

const char *second = seconds.c_str();

// 在屏幕显示时间

u8g2.firstPage();

do {

u8g2.setFont(u8g2_font_unifont_t_chinese2);

u8g2.drawStr(30,15,hour);

u8g2.drawStr(50,15,":");

u8g2.drawStr(60,15,minute);

u8g2.drawStr(80,15,":");

u8g2.drawStr(90,15,second);

u8g2.drawStr(10,30,setTime.substring(0,2).c_str());

u8g2.drawStr(30,30,":");

u8g2.drawStr(40,30,setTime.substring(3,5).c_str());

} while ( u8g2.nextPage() );

OLED显示屏显示时间、已设置的闹铃定时等

// 控制到达设置时间时启动蜂鸣

if(value == 1){ //若没有按过按钮

if(strcmp(hours.c_str(),setTime.substring(0,2).c_str()) == strcmp(minutes.c_str(),setTime.substring(3,5).c_str())){

digitalWrite(bee,HIGH);

Serial.println("Bee On");

value=digitalRead(button);

if(value == 0){

digitalWrite(bee,LOW);

Serial.println("Bee Stop");

judge = minutes.toInt();

}

}

}

if(value == 0){ //按下按钮后判定一分钟内不再响铃

if(minutes.toInt() == judge + 1){

value = 1;

}

}

// 连接tcp

doTCPClientTick();

}

/*

*发送数据到TCP服务器

*/

void sendtoTCPServer(String p){

if (!TCPclient.connected())

{

Serial.println("Client is not readly");

return;

}

TCPclient.print(p);

Serial.println("[Send to TCPServer]:String");

Serial.println(p);

preHeartTick = millis();//心跳计时开始,需要每隔60秒发送一次数据

}

/*

*初始化和服务器建立连接

*/

void startTCPClient(){

if(TCPclient.connect(TCP_SERVER_ADDR, atoi(TCP_SERVER_PORT))){

Serial.print("nConnected to server:");

Serial.printf("%s:%d\r\n",TCP_SERVER_ADDR,atoi(TCP_SERVER_PORT));

String tcpTemp=""; //初始化字符串

tcpTemp = "cmd=1&uid="+UID+"&topic="+TOPIC+"\r\n"; //构建订阅指令

sendtoTCPServer(tcpTemp); //发送订阅指令

tcpTemp="";//清空

preTCPConnected = true;

TCPclient.setNoDelay(true);

}

else{

Serial.print("Failed connected to server:");

Serial.println(TCP_SERVER_ADDR);

TCPclient.stop();

preTCPConnected = false;

}

preTCPStartTick = millis();

}

/*

*检查数据,发送心跳

*/

void doTCPClientTick(){

//检查是否断开,断开后重连

if(WiFi.status() != WL_CONNECTED) return;

if (!TCPclient.connected()) {//断开重连

if(preTCPConnected == true){

preTCPConnected = false;

preTCPStartTick = millis();

Serial.println();

Serial.println("TCP Client disconnected.");

TCPclient.stop();

}

else if(millis() - preTCPStartTick > 1*1000)//重新连接

startTCPClient();

}

else

{

if (TCPclient.available()) {//收数据

char c =TCPclient.read();

TcpClient_Buff +=c;

TcpClient_BuffIndex++;

TcpClient_preTick = millis();

if(TcpClient_BuffIndex>=MAX_PACKETSIZE - 1){

TcpClient_BuffIndex = MAX_PACKETSIZE-2;

TcpClient_preTick = TcpClient_preTick - 200;

}

}

if(millis() - preHeartTick >= KEEPALIVEATIME){//保持心跳

preHeartTick = millis();

Serial.println("--Keep alive:");

sendtoTCPServer("cmd=0&msg=keeprn");

}

}

if((TcpClient_Buff.length() >= 1) && (millis() - TcpClient_preTick>=200))

{//data ready

TCPclient.flush();

Serial.print("Rev string: ");

TcpClient_Buff.trim(); //去掉首位空格

Serial.println(TcpClient_Buff); //打印接收到的消息

String getTopic = "";

String getMsg = "";

if(TcpClient_Buff.length() > 15){

//注意TcpClient_Buff只是个字符串,在上面开头做了初始化StringTcpClient_Buff = "";

//此时会收到推送的指令,指令大概为 cmd=2&uid=xxx&topic=light002&msg=off

int topicIndex = TcpClient_Buff.indexOf("&topic=")+7;

//c语言字符串查找,查找&topic=位置,并移动7位,不懂的可百度c语言字符串查找

int msgIndex = TcpClient_Buff.indexOf("&msg=");

//c语言字符串查找,查找&msg=位置

getTopic = TcpClient_Buff.substring(topicIndex,msgIndex);

//c语言字符串截取,截取到topic,不懂的可百度c语言字符串截取

getMsg = TcpClient_Buff.substring(msgIndex+5);

//c语言字符串截取,截取到消息

Serial.print("topic:------");

Serial.println(getTopic);

//打印截取到的主题值

Serial.print("msg:--------");

Serial.println(getMsg);

//打印截取到的消息值

// set_time(getMsg);

//将获取到的时间转化为时、分,并存储起来

setTime = getMsg;

}

TcpClient_Buff="";

TcpClient_BuffIndex = 0;

}

}

void startSTA(){

WiFi.disconnect();

WiFi.mode(WIFI_STA);

WiFi.begin(DEFAULT_STASSID, DEFAULT_STAPSW);

}

/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*

WIFI

\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/

/*

WiFiTick

检查是否需要初始化WiFi

检查WiFi是否连接上,若连接成功启动TCP Client

控制指示灯

*/

void doWiFiTick(){

static bool startSTAFlag = false;

static bool taskStarted = false;

static uint32_t lastWiFiCheckTick = 0;

if (!startSTAFlag) {

startSTAFlag = true;

startSTA();

Serial.printf("Heap size:%d\r\n", ESP.getFreeHeap());

}

//未连接1s重连

if ( WiFi.status() != WL_CONNECTED ) {

if (millis() - lastWiFiCheckTick > 1000) {

lastWiFiCheckTick = millis();

}

}

//连接成功建立

else {

if (taskStarted == false) {

taskStarted = true;

Serial.print("\r\nGet IP Address: ");

Serial.println(WiFi.localIP());

startTCPClient();

}

}

}

// 将获取到的时间转化为时、分,并存储起来

void set_time(String get){

ESP.wdtFeed();

传说中的“喂狗”函数,防止ESP8266复位

}

团队介绍:

成员 分工 贡献率
唐奥 创意策划、框架搭建 负责部分硬件搭建 55%
田晁旭 功能完善与调试 负责部分硬件搭建 45%

团队故事:

这个想法诞生于去年年末疫情解控放开,身边大部分人抗体检测呈阳性,并且还有许多学长学姐处于考研复习的关键期,转阳后心理负担重,产生的巨大的心理压力无处发泄;同时很多人属于第一次“阳”,对于如何复健毫无头绪。综合以上需求,我们构思了一个集提供新冠医疗建议、论坛形式交流倾诉、健康起居打卡检测于一体的小程序“阳康宝典”。

我们最初的重点在“提供新冠医疗建议”上,但随着国家科学有效的开放管控,全国疫情情况已经趋于平稳,维持在一个低峰状态,遂经过成员讨论,将小程序的核心功能定位在“用户论坛”和“健康起居打卡”功能上。

“用户论坛”的灵感来源于BBS具有的发帖、回帖等相关功能,用户在小程序注册后即可拥有发帖、回帖等权限,不过这只是一个雏形,多样化方面(比如有色字、加粗、下划线等基本功能,评论图片,论坛背景等美化工作仍需加强完善。

“健康起居打卡”以饮水习惯检测为例,通过ESP8266 Module WiFi模块实现器件(蜂鸣器、OLED显示屏等)与小程序的沟通互动,ESP8266模块连接互联网后自动同步当前时间,并在OLED显示屏上实时显示,用户端通过巴法云发送定时指令,ESP8266接受到信息后从中提取时间并启动定时提醒,提醒到点喝水。

刚开始实现这个功能的时候,采用的是统一发放的ESP8266-S01,发现并不能很好地实现预想功能,ESP8266-S01不能带动蜂鸣器报警,也不能驱动OLED显示时间,基于此我们迅速购买了ESP8266-NodeMCU,顺利解决了问题,继续推进整体的构建。

此后无数个日日夜夜里,我们在努力攻克不断产生的新问题,终于让整个构想小有雏形。

不知道经过了多少天,我们设想的核心功能——定时提醒打卡功能终于完成了,尽管功能有些简陋,但聊胜于无哈哈哈。

导论是一个绝佳的机会,让我们去尝试,去努力,去探求解决方法。我们看见了自身存在的不足,并努力补齐短板。在实现的过程中,不仅学到了有用的拓展知识,锻炼了自己debug的能力,而且培养了我们团队协作的精神,培养了我们坚持到底锲而不舍的精神。

B站视频

https://www.bilibili.com/video/BV1Jc411u71e/

GitHub代码

https://gitee.com/ling_da_jin/Yangkang_book

参考资料:

大数据显示:疫情三年,青少年焦虑、抑郁上升趋势惊人,我们能做什么?

新冠病毒感染者居家指引(第一版)

【物联网】基础篇 ESP8266-NodeMCU学习一

【开源】微信小程序控制ESP8266