目录
一、背景 1
二、系统设计 1
1、选择的协议:MQTT 3
2、硬件单元 3
3、软件单元 3
4、整个框图: 4
三、硬件设计 4
四、软件设计 12
1、 主要通讯协议之MQTT 12
2、主要功能之蜂鸣器播放天空之城(葫芦娃) 16
五、使用说明 18
主要功能:
手机APP上点击钢琴键1(do),2(re),3(mi),4(fa),5(sol),6(la),7(si),NodeMcu则控制蜂鸣器发出相应的音调,并且灯泡会在钢琴键按下的时候闪烁一下,可以根据给出的《两只老虎》的简谱来弹奏。
界面上下方已有曲目中,点击《葫芦娃》或者《天空之城》的按钮,蜂鸣器会发出一段完整的曲子,并且在Oled显示屏上显示这首曲子的英文名。
1、选择的协议:MQTT
Android端实现消息推送的协议有很多种,而MQTT是一个轻量级的消息发布/订阅协议,它是实现基于手机客户端的消息推送服务器的理想解决方案。
MQTT的优点:
客户机较小并且 MQTT 协议 高效地使用网络带宽,在这个意义上,其为轻量级。MQTT 协议支持可靠的传送和即发即弃的传输。在此协议中,消息传送与应用程序脱离。 脱离应用程序的程度取决于写入 MQTT 客户机和 MQTT 服务器的方式。脱离式传送能够将应用程序从任何服务器连接和等待消息中解脱出来。 交互模式与电子邮件相似,但在应用程序编程方面进行了优化。
协议具有许多不同的功能:
它是一种发布/预订协议。
除提供一对多消息分发外,发布/预订也脱离了应用程序。对于具有多个客户机的应用程序来说,这些功能非常有用。
它与消息内容没有任何关系。
它通过 TCP/IP 运行,TCP/IP 可以提供基本网络连接。
它针对消息传送提供三种服务质量:
“至多一次” 消息根据底层因特网协议网络尽最大努力进行传递。 可能会丢失消息。 例如,将此服务质量与通信环境传感器数据一起使用。 对于是否丢失个别读取或是否稍后立即发布新的读取并不重要。
“至少一次” 保证消息抵达,但可能会出现重复。
“刚好一次” 确保只收到一次消息。 例如,将此服务质量与记帐系统一起使用。 重复或丢失消息可能会导致不便或收取错误费用。
它是一种管理网络中消息流的经济方式。 例如,固定长度的标题仅 2 个字节长度,并且协议交换可最大程度地减少网络流量。
它具有一种“遗嘱”功能,该功能通知订户客户机从 MQTT 服务器异常断开连接。请参阅“最后的消息”发布。
因此,本文转载自http://www.biyezuopin.vip/onews.asp?id=15077我们使用MQTT作为本次大作业通信协议,基本实现过程在后面的部分会详细介绍。
MQTT服务器的搭建在后面部分详细介绍。
2、硬件单元
硬件单元由NodeMcu,蜂鸣器,Oled显示屏,灯泡组成。
NodeMcu为主控设备,其他模块接在NodeMcu上,由NodeMcu来控制其他模块。
各个模块的控制方法在后面的部分有详细介绍。
3、软件单元
可自动连上MQTT服务器的APP。
package com.example.leixiaorong_de_bigwork_finally.activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import com.example.leixiaorong_de_bigwork_finally.activity.Model.AlarmModel;
import com.example.leixiaorong_de_bigwork_finally.activity.data.MyAlarmDataBase;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
public class AlarmService extends Service {
private static final int ONE_DAY_TIME = 1000*60*60*24;
private static final int CONFIG_ONE = 0;
private static final int CONFIG_TWO = 1;
private static AlarmManager alarmManager;
private MyBinder myBinder = new MyBinder();
@Nullable
@Override
public IBinder onBind(Intent intent) {
return myBinder;
}
@Override
public void onCreate() {
Log.d("Service","启动服务");
super.onCreate();
}
@Override
public void onDestroy() {
Intent service = new Intent(this,AlarmService.class);
this.startService(service);
Log.d("Service","重启服务");
super.onDestroy();
}
public static class MyBinder extends Binder {
private String[] mTimeSplit;
private int mHour;
private int mMinute;
public void setSingleAlarm(
Context context, String time, int id) {
mTimeSplit = time.split(":");
mHour = Integer.parseInt(mTimeSplit[0]);
mMinute = Integer.parseInt(mTimeSplit[1]);
Calendar c = Calendar.getInstance();
c.set(c.get(Calendar.YEAR), c.get(Calendar.MONTH),
c.get(Calendar.DAY_OF_MONTH), mHour, mMinute, 0);
long mTimeInfo = c.getTimeInMillis();
long actualTime = mTimeInfo > System.currentTimeMillis()
? mTimeInfo : mTimeInfo + ONE_DAY_TIME;
alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
int flagDayOfWeek = 0;
alarmManager.set(AlarmManager.RTC_WAKEUP, actualTime, getIntent(CONFIG_ONE, context, id, flagDayOfWeek));
Log.d("Service", "单次闹钟设置完成");
}
public void setEverydayAlarm(Context context, String time, int id) {
mTimeSplit = time.split(":");
mHour = Integer.parseInt(mTimeSplit[0]);
mMinute = Integer.parseInt(mTimeSplit[1]);
Calendar c = Calendar.getInstance();
c.set(c.get(Calendar.YEAR), c.get(Calendar.MONTH),
c.get(Calendar.DAY_OF_MONTH), mHour, mMinute, 0);
long mTimeInfo = c.getTimeInMillis();
long actualTime = mTimeInfo > System.currentTimeMillis()
? mTimeInfo : mTimeInfo + ONE_DAY_TIME;
alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
int flagDayOfWeek = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, actualTime,
getIntent(CONFIG_ONE, context, id, flagDayOfWeek));
Log.d("Service", "每日闹钟,调用了setExact()");
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, actualTime,
getIntent(CONFIG_ONE, context, id, flagDayOfWeek));
Log.d("Service", "每日闹钟,调用了set()");
}
Log.d("Service","每日闹钟设置完成");
}
public void setDiyAlarm(Context context, String repeat, String time,
int id, String repeatCode) {
List<Integer> dayOfWeekList = loadDayOfWeek(repeat, repeatCode);
mTimeSplit = time.split(":");
mHour = Integer.parseInt(mTimeSplit[0]);
mMinute = Integer.parseInt(mTimeSplit[1]);
Calendar c = Calendar.getInstance();
c.set(c.get(Calendar.YEAR), c.get(Calendar.MONTH),
c.get(Calendar.DAY_OF_MONTH), mHour, mMinute, 0);
long mTimeInfo = c.getTimeInMillis();
long actualTime = mTimeInfo > System.currentTimeMillis()
? mTimeInfo : mTimeInfo + ONE_DAY_TIME;
int currentDayOfWeek = Calendar.getInstance().get(Calendar.DAY_OF_WEEK); //今天是星期几
Log.d("Service", "今天是" + currentDayOfWeek);
for (int j = 0; j < dayOfWeekList.size(); j++) {
int flagDayOfWeek = dayOfWeekList.get(j);
Log.d("Service", "第"+(j+1)+"次循环,这次的目标是: "+flagDayOfWeek);
if (currentDayOfWeek == flagDayOfWeek) {
setAlarm(actualTime, context, id, flagDayOfWeek);
Log.d("Service","已设置,目标与今天相同,是" + flagDayOfWeek);
} else if (currentDayOfWeek < flagDayOfWeek) {
int gapDay = flagDayOfWeek - currentDayOfWeek;
long realTime = actualTime + gapDay * ONE_DAY_TIME;
setAlarm(realTime, context, id, flagDayOfWeek);
Log.d("Service", "已设置,目标小于今天,是" + flagDayOfWeek);
} else if (currentDayOfWeek > flagDayOfWeek) {
int gapDay = 7 - currentDayOfWeek + flagDayOfWeek;
long realTime = gapDay * ONE_DAY_TIME + actualTime;
setAlarm(realTime, context, id, flagDayOfWeek);
Log.d("Service", "已设置,目标大于今天,是" + flagDayOfWeek);
}
}
dayOfWeekList.clear();
Log.d("Service","自定义闹钟设置完成!");
}
@NonNull
private List<Integer> loadDayOfWeek(String repeat, String repeatCode) {
List<Integer> dayOfWeekList = new ArrayList<>();
if (repeat.equals("周一至周五")) {
for (int i = 2; i < 7; i++) {
dayOfWeekList.add(i);
}
} else if (repeat.equals("周六周日")) {
dayOfWeekList.add(1);
dayOfWeekList.add(7);
} else {
if (repeatCode!=null){
String[] splitCode = repeatCode.split(",");
for (int i = 0; i < splitCode.length; i++) {
dayOfWeekList.add(Integer.parseInt(splitCode[i]));
Log.d("Service", "获取到自定义dayOfWeek");
}
}
}
return dayOfWeekList;
}
private void setAlarm(long realTime, Context context, int id, int flagDayOfWeek) {
alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, realTime,
getIntent(CONFIG_TWO, context, id, flagDayOfWeek));
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, realTime,
getIntent(CONFIG_TWO, context, id + flagDayOfWeek, flagDayOfWeek));
}
}
public void cancelAlarm(AlarmModel alarm, int id, Context context){
if (alarm==null){
Log.d("alarm null","null");
}
Log.d("id ", String.valueOf(id));
String repeat = alarm.getRepeatType();
AlarmManager alarmManager = (AlarmManager) context.getSystemService(ALARM_SERVICE);
if (repeat.equals("每天")||repeat.equals("只响一次")){
PendingIntent pi = PendingIntent.getBroadcast(context, id,
new Intent(context, MyAlarmReceiver.class), 0);
alarmManager.cancel(pi);
}else {
List<Integer> flagDayOfWeek = loadDayOfWeek(repeat,alarm.getRepeatCode());
for (int i = 0;i<flagDayOfWeek.size();i++){
PendingIntent pi = getIntent(CONFIG_TWO,context,id,flagDayOfWeek.get(i));
alarmManager.cancel(pi);
Log.d("Service","取消id是"+id+" 星期"+flagDayOfWeek+"的闹钟");
}
}
}
private PendingIntent getIntent(int config, Context context, int id, int flagDayOfWeek) {
Intent intent = new Intent(context, MyAlarmReceiver.class);
intent.putExtra(MyAlarmReceiver.ID_FLAG, Integer.toString(id));
switch (config) {
case CONFIG_ONE:
return PendingIntent.getBroadcast(context,
id+flagDayOfWeek, intent, PendingIntent.FLAG_CANCEL_CURRENT);
case CONFIG_TWO:
return PendingIntent.getBroadcast(context,
id+flagDayOfWeek, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
return null;
}
}
public static class RebootReceiver extends BroadcastReceiver {
private String mTime,mRepeat,mActive,mRepeatCode;
private int mID;
private AlarmService.MyBinder binder = new MyBinder();
@Override
public void onReceive(final Context context, Intent intent) {
if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")){
Log.d("RebootReceiver","接收到开机广播");
MyAlarmDataBase db = new MyAlarmDataBase(context);
List<AlarmModel> alarm = db.getAllAlarms();
for (AlarmModel am : alarm) {
mTime = am.getTime();
mRepeat = am.getRepeatType();
mID = am.getID();
mActive = am.getActive();
if (mRepeat.equals("自定义")){
mRepeatCode = am.getRepeatCode();
}
if (mActive.equals("true")){
switch (mRepeat) {
case "只响一次":
binder.setSingleAlarm(context, mTime, mID);
break;
case "每天":
binder.setEverydayAlarm(context, mTime, mID);
break;
default:
binder.setDiyAlarm(context, mRepeat, mTime, mID,mRepeatCode);
}
Log.v("RebootReceiver","完成重启后闹钟设置");
}
}
}
}
}
}