Sakura__(0x01) 设备端(硬件端)
本部分源码放置于
3.2.1 机械结构
机械结构的主体由锁体、锁片、锁舌三部分组成,材料选择7系铝合金,由铣床和线切割机加工制成。微型直流电机轴上齿轮与锁片上的齿条啮合,电机旋转带动锁片移动,使锁舌固定或脱离锁体,达到开解锁的目的。
电机采用6mm空心杯电机,带行星齿轮减速箱,减速比达到700:1,使减速后的出轴转速为47 rpm,扭矩200g/cm,能输出足够的力带动锁片。
3.2.2 电路结构
设备端统一使用3.3V DC供电,霍尔传感器、蜂鸣器、电机驱动IC与ESP8266的GPIO引脚相连,霍尔传感器负责检测锁舌上的磁铁磁性以确定锁止状态,无源蜂鸣器负责在配网状态时发出提示音,电机驱动IC负责控制电机正反转从而实现开解锁。
设计选用A3144E开关霍尔传感器,当锁舌上的磁铁S面靠近传感器霍尔面时,输出低电平,相反的无感应时输出高电平。
MX1508是双路有刷直流马达驱动IC,该IC采用H 桥电路结构设计,低导通内阻MOSFET 功率开关管,内部集成续流二极管和带迟滞效应的过热保护电路 (TSD)。
INAx | INBx | OUTAx | OUTBx | 功能 |
---|---|---|---|---|
L | L | Z | Z | 待机 |
H | L | H | L | 正转 |
L | H | L | H | 反转 |
H | H | L | L | 刹车 |
3.2.3 程序说明
ESP8266的Arduino框架提供基于SPIFFS的文件系统,SPIFFS专为小型系统设计用于嵌入式目标的SPI闪存设备,而在框架所规划的Flash布局中,文件系统与程序存储在同一闪存芯片上,烧写程序并不会修改文件系统内容。而且框架还提供借助Flash模拟的EEPROM,位于SPIFFS之后的闪存扇区。7
在本设计中,使用SPIFFS存放设备编号、设备MQTT认证密码以及服务器地址(AuthInfo.json文件,以JSON格式存储),使用EEPROM存放WiFi连接信息。这样烧录新设备或设备信息、服务器信息发生变化时,只需使用ESP8266FS工具将AuthInfo.json文件写入SPIFFS,不影响固件程序的正常使用,能做到指令与数据的分离。
设备上电启动后,调用EEPROM并读取第一位是否为空以此来判断有无WiFi连接信息,若无WiFi连接信息则进入SmartConfig配网模式,若存在WiFi连接信息则进入连接模式。在连接模式中读取WiFi连接信息并尝试连接,如果30s后未成功连接再转入SmartConfig配网模式,配网模式30s未成功配网再转回连接模式,如此循环。在配网模式下,蜂鸣器发出提示音提醒用户使用微信AirKiss配网,并在成功配网后保存WiFi连接信息,延迟1s后重启单片机,以此来解决配网后DHCP拿不到IP地址的Bug。
网络连接完毕后,紧接着读取SPIFFS中的AuthInfo.json文件,设置MQTT协议连接的相关参数,MQTT协议连接依靠PubSubClient库实现[ ]。这样就完成了setup阶段。
部分代码展示
void smartConfig()
{
// 在STA模式下启动SmartConfig
WiFi.mode(WIFI_STA);
Serial.println("\r\nWait for Smartconfig");
WiFi.beginSmartConfig();
int i = 0;
while (WiFi.status() != WL_CONNECTED)
{
delay(1000);
Serial.print(".");
// 过程中播放提示音
tonePlay();
i+= 1;
if (i==20) { return; }
}
// SmartConfig成功 保存WiFi信息并重启
if (WiFi.smartConfigDone())
{
Serial.println("SmartConfig Success");
Serial.printf("SSID:%s\r\n", WiFi.SSID().c_str());
Serial.printf("PSW:%s\r\n", WiFi.psk().c_str());
Serial.println(WiFi.localIP());
strcpy(config.ssid, WiFi.SSID().c_str());
strcpy(config.psw, WiFi.psk().c_str());
saveConfig();
delay(1000);
ESP.restart();
}
}
bool beginConfig()
{
// STA模式 取出WiFi信息并尝试连接
WiFi.mode(WIFI_STA);
loadConfig();
Serial.printf("SSID:%s\r\n", config.ssid);
Serial.printf("PSW:%s\r\n", config.psw);
Serial.println("\r\WiFi begin");
WiFi.begin(config.ssid, config.psw);
int i = 0;
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
i += 1;
if (i==60) { return 0; }
}
return 1;
}
bool loadAuthInfo()
{
// 从SPIFFS中打开AuthInfo.json文件
if (!SPIFFS.begin())
{
Serial.println("Failed to mount file system");
return false;
}
File InfoFile = SPIFFS.open("/AuthInfo.json", "r");
if (!InfoFile) {
Serial.println("Failed to open config file");
return false;
}
size_t size = InfoFile.size();
if (size > 1024) {
Serial.println("Config file size is too large");
return false;
}
// 分配一个缓冲区 并将文件内容载入
std::unique_ptr<char[]> buf(new char[size]);
InfoFile.readBytes(buf.get(), size);
// StaticJsonBuffer 静态分配内存(in the stack) DynamicJsonBuffer 动态分配内存(on the heap)
// DynamicJsonBuffer jsonBuffer;
StaticJsonBuffer<200> jsonBuffer;
JsonObject& json = jsonBuffer.parseObject(buf.get());
if (!json.success()) {
Serial.println("Failed to parse config file");
return false;
}
// json解码 取出数据
mqtt_server = json["mqtt_server"];
String n = json["lockno"];
lockno = (char *)malloc(n.length() * sizeof(char));
strcpy(lockno, n.c_str());
String p = json["lpassword"];
lpassword = (char *)malloc(p.length() * sizeof(char));
strcpy(lpassword, p.c_str());
return true;
}
void setup()
{
// 打开串口通讯 用于调试
Serial.begin(115200);
Serial.println("Start module");
// 输出VCC电压 用于调试
Serial.print("System voltage(mV): ");
Serial.println(ESP.getVcc());
// 接口定义
// pinMode(ClearPin, INPUT_PULLUP);
pinMode(HallPin, INPUT_PULLUP);
pinMode(M_IN1, OUTPUT);
pinMode(M_IN2, OUTPUT);
// 进入连接网络循环
EEPROM.begin(1024);
// attachInterrupt(digitalPinToInterrupt(ClearPin), clearConfig , FALLING);
while(EEPROM.read(0) == 0)
{
smartConfig();
}
while(!beginConfig())
{
smartConfig();
}
Serial.println(WiFi.localIP());
// 载入SPIFFS存储的数据
if (!loadAuthInfo()) {
Serial.println("Failed to load config");
ESP.restart();
} else {
Serial.println("Config loaded");
}
//设置MQTT连接参数
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
}
程序进入loop阶段后与云端MQTT Broker建立连接并订阅 /lockno/call ,根据接收到call指令完成相应动作:执行开锁、报告状态、OTA固件更新。开关锁动作通过改变电机驱动IC(MX1508)对应引脚的电平进而使电机转动来实现,定时执行的readval函数负责检测霍尔传感器以报告锁止状态,并在锁舌插入后自动执行关锁动作。OTA(Over the Air)更新指不通过串行端口烧录而直接用WiFi在随时随地实现固件更新的过程[ ],在本设计中的思路是通过下达OTA固件更新指令在指定时间点完成全网设备统一固件更新。
部分代码展示
void callback(char* topic, byte* payload, unsigned int length) {
// 对接收到的命令判断处理
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
switch(payload[0])
{
case 'u':
UNLOCK();
break;
case 'e':
EXPORT();
break;
case 'o':
OTA();
break;
}
}
void reconnect() {
while (!client.connected())
{
Serial.print("Attempting MQTT connection...");
// MQTT连接凭据和遗嘱消息
if (client.connect(lockno,lockno,lpassword,buildTopic("statu"),0,1,"-2"))
{
// 上线订阅call并输出状态
Serial.println("connected");
client.subscribe(buildTopic("call"));
// client.subscribe(buildTopic("ping"));
// EXPORT();
readval();
}
else
{
// 若因网络问题无法上线 则重连WiFi
Serial.print("failed, rc=");
Serial.println(client.state());
if (client.state() == -2)
{
WiFi.reconnect();
Serial.println(WiFi.localIP());
}
Serial.println(" try again in 5 seconds");
delay(5000);
}
}
}
void UNLOCK()
{
// 执行解锁
Serial.println("UNLOCK");
digitalWrite(M_IN1,LOW);
digitalWrite(M_IN2,HIGH);
delay(250);
digitalWrite(M_IN1,LOW);
digitalWrite(M_IN2,LOW);
}
void EXPORT()
{
// 输出状态
Serial.println("EXPORT");
char* sc = (char *)malloc(1 * sizeof(char));
sprintf(sc, "%d", statu);
client.publish(buildTopic("statu"), sc, true);
}
void OTA()
{
// OTA更新
t_httpUpdate_return ret = ESPhttpUpdate.update("http://****/lock/firmware");
switch(ret) {
case HTTP_UPDATE_FAILED:
Serial.println("[update] Update failed.");
break;
case HTTP_UPDATE_OK:
Serial.println("[update] Update ok.");
break;
}
}
void readval()
{
// 采集霍尔传感器状态
int Hall = 0;
for (int i=0;i<5;i++) {
Hall += digitalRead(HallPin);
delay(10);
}
if (Hall == 5) {
// Serial.println("1*5,UNLOCK");
if (statu != 0) {
statu = 0;
EXPORT();
}
return ;
}
if (Hall == 0) {
// Serial.println("0*5,LOCK");
if (statu != 1) {
if (statu == 0) {
// 执行上锁
digitalWrite(M_IN1,HIGH);
digitalWrite(M_IN2,LOW);
delay(250);
digitalWrite(M_IN1,LOW);
digitalWrite(M_IN2,LOW);
}
statu = 1;
EXPORT();
}
return ;
}
}
void loop()
{
// 建立MQTT连接 处理传入消息
if (!client.connected()) {
reconnect();
}
client.loop();
// 定时执行readval
long now = millis();
if (now - lastMsg > 1000) {
lastMsg = now;
readval();
}
}