×

Nerd无线电子宠物开源设计

消耗积分:2 | 格式:zip | 大小:0.15 MB | 2022-11-22

分享资料个

描述

如果你有一只物联网宠物,它会吃什么?WiFi SSID,当然!

Nerd 是一种无线电子宠物,它通过收集 WiFi SSID 以及一些休息和阳光来生存。

为了让它茁壮成长,你必须平衡离线和在线模式与光明和黑暗,以确保它有一个适当的日常吃-睡-书呆子程序。

如果没有 WiFi 太久,它将使用其内置的压电扬声器以摩尔斯电码发送 SOS。离线时间越长,蜂鸣声越多。

简而言之

书呆子将每半小时醒来一次以扫描周围的网络。如果它检测到新网络,它将存储它们并在低功耗模式下重新进入睡眠状态(以节省电池寿命)。否则它会用蜂鸣器发出噪音来抱怨,直到你喂它或把它放在黑暗中。

它还会通过连接到您的 wifi 网络了解何时在家。在家时,Nerd 将能够连接到互联网并获取当前时间和日期。

如果超过两天不喂,它会急剧死亡,发出很大的噪音。

成分

  • RGB LED
  • 光电晶体管
  • 蜂鸣器
  • 电池
  • 220欧姆电阻

学习目标

  • 管理完整的 WiFi 功能
  • 在闪存中存储数据
  • 管理时间和实时时钟
  • 管理低功耗模式

想知道更多?

本教程是让您熟悉 MKR1000 和 IoT 的一系列实验的一部分。所有实验都可以使用 MKR IoT Bundle 中包含的组件构建。

  • 书呆子

设立董事会

为了实现所有功能,我们将使用以下库:

  • WiFi101 //连接到互联网并扫描网络
  • FlashStorage // 保存值,以便它们不会在每次重新启动时被擦除
  • RTCZero // 管理时间触发事件
  • ArduinoLowPower //节省电池电量
  • WiFiUdp // 从互联网上获取时间和日期

您可以按照本指南中的说明从库管理器下载它们。

扫描 WiFi 网络

书呆子渴望网络!

扫描网络非常简单, 只需上传此示例草图或转到> 示例 > WiFi101 > ScanNetworksAdvanced以获得更多扩展版本。

#include  
#include  
void setup() { 
 //Initialize serial and wait for port to open: 
 Serial.begin(9600); 
 while (!Serial) { 
   ; // wait for serial port to connect. Needed for native USB port only 
 } 
 // scan for existing networks: 
 Serial.println(); 
 Serial.println("Scanning available networks..."); 
 listNetworks(); 
} 
void loop() { 
 delay(10000); 
 // scan for existing networks: 
 Serial.println("Scanning available networks..."); 
 listNetworks(); 
} 
void listNetworks() { 
 // scan for nearby networks: 
 Serial.println("** Scan Networks **"); 
 int numSsid = WiFi.scanNetworks(); 
 if (numSsid == -1) 
 { 
   Serial.println("Couldn't get a WiFi connection"); 
   while (true); 
 } 
 // print the list of networks seen: 
 Serial.print("number of available networks: "); 
 Serial.println(numSsid); 
 // print the network number and name for each network found: 
 for (int thisNet = 0; thisNet < numSsid; thisNet++) { 
   Serial.print(thisNet + 1); 
   Serial.print(" SSID: "); 
   Serial.println(WiFi.SSID(thisNet)); 
   Serial.flush(); 
 } 
 Serial.println(); 
} 

在闪存中存储值

当然,您不希望书呆子每次没电时就死掉!

为了避免这种行为,我们将在闪存中保存一些变量(作为食物量),以便即使在电路板关闭并再次打开后也可以检索它们。

您可以使用以下方法了解基本功能:

示例 > FlashStorage > FlashStoreAndRetrieve

我们将保存网络的名称,以便书呆子只吃一次,我们还将使用保存这些 SSID 的数组来计算它在白天吃的食物量。

保存在闪存中的值将在电路板重置后保留,但不会在新草图的上传后保留。每次上传新草图时,闪存也会被清空。

此草图将扫描网络并将 SSID 保存在闪存中。

#include  
#include  
#include  
#define MAGIC_NUMBER 0x7423  // arbitrary number to double check the saved SSID 
#define MaxNet 30  // max amount of network to be saved  
int PosToBeSaved = 0; // Variable used to navigate the array of networks 
int daily_amount_of_food = 12; // The amount of food per day needed to survive 
// Struct of variable to be saved in flash memory 
typedef struct { 
 int magic; 
 boolean valid[MaxNet]; 
 char SSIDs[MaxNet][100]; 
} Networks; 
FlashStorage(my_flash_store, Networks); 
Networks values; 
void setup() { 
 Serial.begin(115200); 
 while(!Serial); // wait until the Serial montior has be opened 
 delay(2000); 
 values = my_flash_store.read(); // Read values from flash memory 
 if (values.magic == MAGIC_NUMBER) { // If token is correct print saved networks 
   Serial.println("saved data:"); 
   Serial.println(""); 
   for (int a = 0; a < MaxNet; a++) { 
     if (values.valid[a]) { 
       Serial.println(values.SSIDs[a]); 
     } else { 
       PosToBeSaved = a; 
     } 
   } 
 } 
} 
void loop() { 
 // Temporarly save the number of networks 
 int networks_already_saved = PosToBeSaved; 
 getNetwork(); 
 if (PosToBeSaved >= daily_amount_of_food) { 
   Serial.println("Enough food for today"); 
 } 
 delay(5000); 
} 
// Feed the Nerd with networks's SSID 
void getNetwork() { 
 // scan for nearby networks: 
 Serial.println("\n*Scan Networks*\n"); 
 int numSsid = WiFi.scanNetworks(); 
 delay(1000); 
 if (numSsid == -1) 
 { 
   Serial.println("There are no WiFi networks here.."); 
 } else { 
   Serial.print("number of available networks: "); 
   Serial.println(numSsid); 
   // print the network number and name for each network found: 
   for (int thisNet = 0; thisNet < numSsid; thisNet++) { 
     Serial.print("SSID: "); 
     Serial.println(WiFi.SSID(thisNet)); 
     delay(500); 
     char* net = WiFi.SSID(thisNet); 
     bool canBeSaved = true; 
     // check if the network has already been saved 
     for (int a = 0; a < PosToBeSaved ; a++) { 
       if (values.valid[a]) { 
         if (strncmp(net, values.SSIDs[a], 100) == 0 || strnlen(net, 100) == 0) { 
           Serial.println("Not saved"); 
           canBeSaved = false; 
         } 
       } 
     } 
     // Store ssid name 
     if (canBeSaved && PosToBeSaved < MaxNet) { 
       if (strlen(net) + 1 < 100 && strlen(net) > 0) { // check if the SSID name fits 100 bytes 
         memset(values.SSIDs[PosToBeSaved], 0, sizeof(values.SSIDs[PosToBeSaved])); // set all characters to zero 
         memcpy(values.SSIDs[PosToBeSaved], net, strlen(net) + 1); // copy "net" to values.SSDs[thisNet] 
         values.valid[PosToBeSaved] = true; 
         values.magic = MAGIC_NUMBER; 
         my_flash_store.write(values); 
         Serial.println(String(values.SSIDs[PosToBeSaved]) + " saved in position " + String(PosToBeSaved)); 
         PosToBeSaved ++; 
       } 
       else { 
         Serial.println(" network skipped"); 
       } 
     } 
   } 
 } 
} 

请注意我们如何定义可以保存的最大网络数量,以避免内存空间问题:

#define MaxNet 30  
int PosToBeSaved = 0; 

已用于导航保存网络名称的数组并测量已吃的食物量。

管理时间

我们可以将实时时钟 (RTC) 的功能与 WiFi 相结合,以获取当前时间和日期,然后设置板的内部时钟。通过这种方式,我们可以在很长一段时间内触发基于时间的事件,而无需使用millis() 当您必须将毫秒转换为天时可能会很棘手的函数。

我们将从网络时间协议(NTP) 时间服务器获取时间,然后使用它设置 RTC。请注意,收到的时间采用纪元格式,即自 1970 年 1 月 1 日以来的秒数。

您可以从example > WiFi101 > WiFiUdpNtpClient运行基本草图

在下面的草图中,我们将事物放在一起:扫描网络功能和与时间相关的功能。

  • bool atHome = false; 用于触发 WiFi 连接,因此请求服务器获取时间。
  • check_home() 用于扫描所有可用网络并查看其中一个是否为家庭 WiFi 网络。
  • connect_WiFi() 然后调用,它将板连接到 WiFi,触发对服务器的请求并打印当前时间。
  • rtc.setEpoch(epoch + GMT); 用于以纪元格式以当前时间启动 RTC,修改 GMT 变量以将时间调整为您当前的时区。
#include  
#include  
#include  
#include  
#include  
WiFiUDP udp; 
WiFiUDP Udp; 
RTCZero rtc; 
#define MAGIC_NUMBER 0x7423  // arbitrary number to double check the saved SSID 
#define MaxNet 30  // max amount of network to be saved  
const char* home_ssid = SECRET_SSID;    //  your network SSID (name) 
const char* password = SECRET_PSWD;  // your network password 
int PosToBeSaved = 0; // Variable used to navigate the array of networks 
int daily_amount_of_food = 12; // The amount of food per day needed to survive 
bool atHome = false; 
// Struct of variable to be saved in flash memory 
typedef struct {   
 int magic; 
 boolean valid[MaxNet]; 
 char SSIDs[MaxNet][100]; 
 int alive_days; 
 int last_time_feeded; 
} Networks; 
FlashStorage(my_flash_store, Networks); 
Networks values; 
void setup() { 
 Serial.begin(115200); 
 delay(2000); 
 rtc.begin(); // enable real time clock functionalities 
 values = my_flash_store.read(); // Read values from flash memory 
 if (values.magic == MAGIC_NUMBER) { // If token is correct print saved networks 
   Serial.println("saved data:"); 
   Serial.println(""); 
   for (int a = 0; a < MaxNet; a++) { 
     if (values.valid[a]) { 
       Serial.println(values.SSIDs[a]); 
     } else { 
       PosToBeSaved = a; 
     } 
   } 
 } 
} 
void loop() { 
   if(!atHome) check_home(); 
   // Temporarly save the number of networks 
   int networks_already_saved = PosToBeSaved;    
   getNetwork(); 
if (PosToBeSaved >= daily_amount_of_food) {  
  Serial.println("Enough food for today");  
}  
} 
void check_home() { 
 int numSsid = WiFi.scanNetworks(); 
 if (numSsid != -1) { 
   for (int thisNet = 0; thisNet < numSsid; thisNet++) { 
     delay(100); 
     if (strncmp(WiFi.SSID(thisNet), home_ssid, 100) == 0) { 
       Serial.println("Yay, I'm home \n"); 
       atHome = true; 
       connect_WiFi(); 
     } 
   } 
 } 
} 
void connect_WiFi() { 
 if (WiFi.status() != WL_CONNECTED) { 
   while (WiFi.begin(home_ssid, password) != WL_CONNECTED) { 
     delay(500); 
   } 
   Serial.println("WiFi connected \n"); 
   GetCurrentTime(); 
   printTime(); 
 } 
} 
// Feed the Nerd with networks's SSID 
void getNetwork() { 
 // scan for nearby networks: 
 Serial.println("*Scan Networks*"); 
 int numSsid = WiFi.scanNetworks(); 
 delay(1000); 
 if (numSsid == -1) 
 { 
   Serial.println("There are no WiFi networks here.."); 
 } else { 
   Serial.print("number of available networks: "); 
   Serial.println(numSsid); 
   // print the network number and name for each network found: 
   for (int thisNet = 0; thisNet < numSsid; thisNet++) { 
     Serial.print("SSID: "); 
     Serial.println(WiFi.SSID(thisNet)); 
     delay(500); 
     char* net = WiFi.SSID(thisNet); 
     bool canBeSaved = true; 
     // check if the network has already been saved 
     for (int a = 0; a < PosToBeSaved ; a++) {  
       if (values.valid[a]) { 
         if (strncmp(net, values.SSIDs[a], 100) == 0 || strnlen(net, 100) == 0) {  
           Serial.println("Not saved"); 
           canBeSaved = false; 
         } 
       } 
     } 
     // Store ssid name 
     if (canBeSaved && PosToBeSaved < MaxNet) {  
       if (strlen(net) + 1 < 100 && strlen(net) > 0) { // check if the SSID name fits 100 bytes 
         memset(values.SSIDs[PosToBeSaved], 0, sizeof(values.SSIDs[PosToBeSaved])); // set all characters to zero 
         memcpy(values.SSIDs[PosToBeSaved], net, strlen(net) + 1); // copy "net" to values.SSDs[thisNet] 
         values.valid[PosToBeSaved] = true; 
         values.last_time_feeded = rtc.getEpoch(); 
         values.magic = MAGIC_NUMBER; 
         my_flash_store.write(values); 
         Serial.println(String(values.SSIDs[PosToBeSaved]) + " saved in position " + String(PosToBeSaved)); 
         PosToBeSaved ++; 
       } 
       else { 
         Serial.println(" network skipped"); 
       } 
     } 
   } 
 } 
} 
/************************************************* 
 Start an UDP connection to get the time in unix, 
 then set the real time clock (rtc) 
************************************************/ 
unsigned int localPort = 2390;      // local port to listen for UDP packets 
IPAddress timeServer(129, 6, 15, 28); // time.nist.gov NTP server 
const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message 
byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets 
const int GMT = 1 * 60 * 60; //change this to adapt it to your time zone   hours*minutes*seconds 
unsigned long epoch; 
void GetCurrentTime() { 
 int numberOfTries = 0, maxTries = 6; 
 do { 
   epoch = readLinuxEpochUsingNTP(); 
   numberOfTries++; 
 } 
 while ((epoch == 0) || (numberOfTries > maxTries)); 
 if (numberOfTries > maxTries) { 
   Serial.print("NTP unreachable!!"); 
   while (1); 
 } 
 else { 
   Serial.print("Epoch received: "); 
   Serial.println(epoch); 
   rtc.setEpoch(epoch + GMT); 
   Serial.println(); 
 } 
} 
unsigned long readLinuxEpochUsingNTP() 
{ 
 Udp.begin(localPort); 
 sendNTPpacket(timeServer); // send an NTP packet to a time server 
 // wait to see if a reply is available 
 delay(1000); 
 if ( Udp.parsePacket() ) { 
   Serial.println("NTP time received"); 
   // We've received a packet, read the data from it 
   Udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer 
   //the timestamp starts at byte 40 of the received packet and is four bytes, 
   // or two words, long. First, esxtract the two words: 
   unsigned long highWord = word(packetBuffer[40], packetBuffer[41]); 
   unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]); 
   // combine the four bytes (two words) into a long integer 
   // this is NTP time (seconds since Jan 1 1900): 
   unsigned long secsSince1900 = highWord << 16 | lowWord; 
   // now convert NTP time into everyday time: 
   // Unix time starts on Jan 1 1970. In seconds, that's 2208988800: 
   const unsigned long seventyYears = 2208988800UL; 
   // subtract seventy years: 
   Udp.stop(); 
   return (secsSince1900 - seventyYears); 
 } 
 else { 
   Udp.stop(); 
   return 0; 
 } 
} 
// send an NTP request to the time server at the given address 
unsigned long sendNTPpacket(IPAddress & address) 
{ 
 // set all bytes in the buffer to 0 
 memset(packetBuffer, 0, NTP_PACKET_SIZE); 
 // Initialize values needed to form NTP request 
 // (see URL above for details on the packets) 
 packetBuffer[0] = 0b11100011;   // LI, Version, Mode 
 packetBuffer[1] = 0;     // Stratum, or type of clock 
 packetBuffer[2] = 6;     // Polling Interval 
 packetBuffer[3] = 0xEC;  // Peer Clock Precision 
 // 8 bytes of zero for Root Delay & Root Dispersion 
 packetBuffer[12]  = 49; 
 packetBuffer[13]  = 0x4E; 
 packetBuffer[14]  = 49; 
 packetBuffer[15]  = 52; 
 // all NTP fields have been given values, now 
 // you can send a packet requesting a timestamp: 
 Udp.beginPacket(address, 123); //NTP requests are to port 123 
 Udp.write(packetBuffer, NTP_PACKET_SIZE); 
 Udp.endPacket(); 
} 
void printTime() { 
 // Print date... 
 Serial.print(rtc.getDay()); 
 Serial.print(" / "); 
 Serial.print(rtc.getMonth()); 
 Serial.print(" / "); 
 Serial.print(rtc.getYear()); 
 Serial.print("\t"); 
 // ...and time 
 print2digits(rtc.getHours()); 
 Serial.print(": "); 
 print2digits(rtc.getMinutes()); 
 Serial.print(": "); 
 print2digits(rtc.getSeconds()); 
 Serial.println(""); 
} 
void print2digits(int number) { 
 if (number < 10) { 
   Serial.print("0"); 
 } 
 Serial.print(number);

实现当前时间允许我们使用像这样的简单函数来管理 Nerd 的生死:

 if(rtc.getEpoch() - values.last_time_fed >= 86400*2){ 
 // complain and eventually die :(
 } 

其中86400 是一天中的秒数, values.last_time_fed 是以纪元格式存储的时间值,其中 Nerd 上次被喂食并以纪元格式rtc.getEpoch() 返回当前时间

低功耗模式

我们可以使用低功耗模式让我们的电路板进入睡眠状态。这意味着它将禁用其大部分功能(包括 WiFi)以节省电池电量。

由于我们希望 Nerd 定期醒来,我们可以轻松设置一个计时器:

#include "ArduinoLowPower.h" 
int sleeping_time = 5000; // 5 seconds 
bool awake = true; 
void setup() { 
 Serial.begin(9600); 
 LowPower.attachInterruptWakeup(RTC_ALARM_WAKEUP, WakeUp, CHANGE); 
 pinMode(LED_BUILTIN, OUTPUT); 
} 
void loop() { 
 if (awake) { 
   digitalWrite(LED_BUILTIN, HIGH); 
   delay(500); 
   digitalWrite(LED_BUILTIN, LOW); 
   delay(500); 
 } 
 awake = false; 
 Serial.println("going to sleep"); 
 LowPower.sleep(sleeping_time); 
} 
void WakeUp() { 
 Serial.println("awake"); 
 awake = true; 
} 

请注意,该WakeUp() 函数附加到中断,这意味着它不能包含任何包含延迟的代码。但是我们可以设置布尔变量来触发循环中的事件。


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

评论(0)
发评论

下载排行榜

全部0条评论

快来发表一下你的评论吧 !