×

运动服建议站开源分享

消耗积分:0 | 格式:zip | 大小:0.58 MB | 2022-11-03

李俊

分享资料个

描述

使用这个 Activewear Suggestion 站,在晨跑时根据室内和室外热指数之间的差异来寻找合适的衣服变得更加容易。这是使用带有格罗夫防护罩和开放天气地图“当前天气数据”API 的粒子硼构建的。

我连接了一个 DHT 11 传感器来收集室内温度/相对湿度和一个 OLED 屏幕来显示从开放天气地图收集的室内和室外温度/相对湿度。最后,一个 RGB LED 会根据使用我们的室内热指数和室外热指数函数计算的室内和室外热指数之间的差异来改变颜色。

对于这个项目,我将使用 Particle Workbench,它在一个易于安装的软件包中提供了物联网开发所需的所有工具、库和扩展。

此外,通过单击此链接,您将被带到作为集成开发环境的 Particle Web IDE;它恰好在您的网络浏览器中运行。这将包含为您预加载的所有代码。您需要做的就是复制项目。

由于 Grove 系统的易用性,即使您刚刚开始,这也应该是一个简单易懂的项目。

目标:

该项目的目标是收集和比较室内和室外热量指数。热量指数结合了空气温度和相对湿度,因为“重要的不是热量,而是湿度”。这让我可以在早上快速决定是否应该在晨跑之前穿额外的运动服。

代码亮点

虽然复制代码和调用项目完成很容易,但有必要查看一些程序逻辑部分以了解发生了什么。

步骤1:

该项目的第一步是在 Grove FeatherWing 上添加硼并连接所有传感器。使用 Grove 不需要焊接。只需插入传感器,您就可以专注于编码和集成!

下面是 Grove FeatherWing 上的传感器连接图表:

连接:

  • Grove DHT 11 --> A0
  • Grove RGB LED --> UART
  • Grove OLED --> I2C_1
pYYBAGNiSd2AEO5iAAJf1r_t-1I421.png
Active Wear Station 连接
 

有关详细信息,请查看页面底部的示意图↓

第2步:

成功连接传感器后,现在我们将预先设置库的包含和变量定义。

在代码的开头,您将看到#include哪些用于在您的工作台中包含外部库。这使您可以访问大量标准 C 库(一组预制函数),以及专门为 Arduino 编写的库。同样,您将看到#define哪个是有用的 C++ 组件,它允许您为常量命名程序编译前的值。

这些声明将根据用于每个传感器的引脚配置指定在此“Activewear Suggestion Station”项目中使用的库。

#include <Adafruit_DHT.h>

#include <Grove_ChainableLED.h>

#include <Grove_OLED_128x64.h>

#include <Ubidots.h>

// Defining Temp Humd pin
#define DHTPIN A0 

// Defining DHT11
#define DHTTYPE DHT11 

// Defining Ubidots webhook name
const char *WEBHOOK_NAME = "Ubidots"; 

// Ubidots constant 
Ubidots ubidots("webhook", UBI_PARTICLE); 

// Temp Humi object
DHT dht(DHTPIN, DHTTYPE); 

// LED object with respective pins
ChainableLED leds (RX, TX, 1);

void updateDisplay (double inside, double outside);

// global outdoor temp variable
float tempOutdoor = -100; 

// global outdoor humditiy variable
float humidityOutdoor = -1; 

// Read Humidity Data
float humidity = dht.getHumidity();

// Read Temp Data
float temp = dht.getTempFarenheit();

第 3 步:

在设置#include#define声明之后,是时候在用于初始化我们的库和配置对象的部分中初始化和设置初始值了,setup()这些设置您稍后将在代码中使用。

void setup() {
  Serial.begin(9600);
  Serial.println("Active Wear Station"); 
  
// initialize DHT library
  dht.begin();

// initialize LED library
  leds.init();

  Wire.begin();

// initialize display library
  SeeedOled.init(); 

// Clearing display
  SeeedOled.clearDisplay();
  SeeedOled.setNormalDisplay();
  SeeedOled.setPageMode();

// Adding Active Wear Station at setup
  SeeedOled.setTextXY(2, 0);
  SeeedOled.putString("Active");
  SeeedOled.setTextXY(3, 0);
  SeeedOled.putString("Wear");
  SeeedOled.setTextXY(4, 0);
  SeeedOled.putString("Suggestion");
  SeeedOled.setTextXY(5, 0);
  SeeedOled.putString("Station");

最后,最后一部分setup()Particle.subscribe; 订阅允许云向许多设备发送消息。在这种情况下,是我们的setCurrentWeather webhook 将室外温度和湿度从 Open Weather Map API 事件发送到我们的 Boron。更多信息可以在这里找到

// Subscribing to GetWeatherForecast webhook
  Particle.subscribe(System.deviceID() + "/GetWeatherForecast/", setCurrentWeather, MY_DEVICES);
}

第4步:

了解主要代码loop():

在创建一个setup()初始化并设置初始值的函数之后,该loop()函数完全按照其名称所暗示的那样工作,并连续循环,允许您的程序进行更改和响应。这用于主动控制您的粒子设备。

对于这个项目,我们正在收集 2 个数据源:

1-测量室内温度和湿度

本项目采集的室内数据来自于附在的温湿度传感器A0,在代码开头定义为:

  • float temp = dht.getTempFarenheit();
  • float humidity = dht.getHumidity();

首先要做的是验证传感器数据是否有效。如果有任何无效数据,代码将使用Serial.println()输出,“Failed to read from DHT11 sensor!”。

// Check if any indoor data reads have failed
  if (isnan(temp) || isnan(humidity)){
    Serial.println("Failed to read from DHT sensor");
    return;
  }

2- 收集室外温度和湿度数据

接下来是创建发布周期变量并检查自上次发布以来的最后时间,以便有效地从 Open Weather Maps 使用我们的 API 调用。

millis()time 函数返回自 Particle 设备开始运行当前程序以来经过的毫秒数,并用 减去LastPublish这使我们能够确定是否Last publish >= publishPeriod大于或等于 15 分钟才能再次发布。

如果这是真的,If 语句将分配lastPublish = millis();天气预报数据并将其发布到粒子控制台,使用Particle.publish.

//  Publishing every 15 min
  const unsigned long publishPeriod = 15 * 60 * 1000;
// Last publish variable 
  static unsigned long lastPublish = 10000 - publishPeriod;

// Check time since last publish occurred and publish collected Webhook Outdoor weather forecast data
  if (millis() - lastPublish >= publishPeriod) {
    lastPublish = millis();
    Particle.publish("GetWeatherForecast", PRIVATE);
  }

Particle.publish; 允许您通过 Particle Device Cloud 发布事件,该事件将转发给所有注册的侦听器,例如回调、订阅的服务器发送事件流以及其他通过Particle.subscribe. 更多信息可以在这里找到

LED灯逻辑:

如果室内热指数“ inside ”低于室外热指数,则指示灯变为蓝色,表示:不需要额外的运动服。

如果室内热量指数“户外”大于室外热量指数,则指示灯变为红色,表示:需要额外的运动服

// LED light logic based on indoor vs outdoor heat indexes

// Indoor heat index lower than outdoor heat index turn blue
  if (inside < outside){
  leds.setColorRGB(0,0,0,255);
  }
  
// Indoor heat index greater than outdoor heat index turn red
  if (inside > outside){
    leds.setColorRGB(0,255,0,0);
  }

继上一步之后,当我们有更多数据时,我们会更新 OLED 显示屏,以在本地显示 Activewear Suggestion Station 上的热量指数。

// Updating OLED Display
updateDisplay (inside, outside);

最后,我们的最后loop()一部分是添加将发布到 Ubidots 的数据:

// Ubidots Variables publish into Ubidots dashboard
  ubidots.add("Indoor Temp", temp);
  ubidots.add("Indoor Humidity", humidity);
  ubidots.add("Outdoor Temp", tempOutdoor);
  ubidots.add("Outdoor Humidity", humidityOutdoor);
  ubidots.add("Indoor Heat Index",indoorHeatIndex(temp, humidity));
  ubidots.add("Outdoor Heat Index", outdoorHeatIndex(tempOutdoor, humidityOutdoor));

  bool bufferSent = false;
  bufferSent = ubidots.send(WEBHOOK_NAME, PUBLIC);

和粒子控制台:

// Particle publish temperatures, humidities and heat indexes into console 
  Particle.publish("Indoor Temp",String (temp));
  Particle.publish("Indoor Humidity", String (humidity));
  Particle.publish("Outdoor Temp",String (tempOutdoor));
  Particle.publish("Outdoor Humidity", String (humidity));
  Particle.publish("Indoor Heat Index",String(indoorHeatIndex(temp, humidity)));
  Particle.publish("Outdoor Heat Index",String(outdoorHeatIndex(tempOutdoor, humidityOutdoor)));

我们的 Webhook 的亮点:

Webhook 是一种简单而灵活的方式,可以将数据从您的 Particle 设备发送到 Internet 上的其他应用程序和服务。

Webhook 弥合了物理世界和数字世界之间的鸿沟,帮助您将数据放在需要的地方。更多信息在这里

在这个项目中,我们使用了两个 webhook 集成:

1-第一个 webhook 是我们的GetWeatherForecast :它从openweathermap.org中提取数据,并允许我们按城市名称访问当前天气数据。

在本节中,我们收集室外温度和室外湿度。

{
    "event": "GetWeatherForecast",
    "responseTopic": "{{PARTICLE_DEVICE_ID}}/{{PARTICLE_EVENT_NAME}}",
    "url": "https://api.openweathermap.org/data/2.5/weather",
    "requestType": "GET",
    "noDefaults": true,
    "rejectUnauthorized": true,
    "responseTemplate": "{\"temp\":{{{main.temp}}},\"hum\":{{{main.humidity}}}}",
    "query": {
        "q": "Boston",
        "units": "imperial",
        "appid": "Your Open Weather Map API KEY"
    }
}

在创建GetWeatherForecast webhook 并开始收集数据后,我们使用这个 JSON 解析器“ setCurrentWeather ”函数来收集数据,因为收集的所有数据都是 JSON 格式,不能用于逻辑和显示。

// Json Parser for data collected from Open Weather API
void setCurrentWeather(const char *event, const char *data) {
    Log.info("subscriptionHandler %s", data);
    JSONValue outerObj = JSONValue::parseCopy(data);
    JSONObjectIterator iter(outerObj);
    while (iter.next()) {
        if (iter.name() == "temp") {
            tempOutdoor = iter.value().toDouble();
        }
        if (iter.name() == "hum") {
            humidityOutdoor = iter.value().toDouble();
        }
    }}

2-第二个 webhook 是Ubidots :这是我们使用 Ubidots 图形、图表和表格可视化我们的粒子设备收集的数据的地方。

要了解更多如何在此处集成您的设备,请参阅使用 Particle Webhooks 将您的 Particle 设备连接到 Ubidots的指南

{
    "event": "Ubidots",
    "url": "https://industrial.api.ubidots.com/api/v1.6/devices/{{{PARTICLE_DEVICE_ID}}}",
    "requestType": "POST",
    "noDefaults": false,
    "rejectUnauthorized": true,
    "headers": {
        "X-Auth-Token": "YOUR_UBIDOTS_TOKEN_HERE",
        "Content-Type": "application/json"
    },
    "body": "{{{PARTICLE_EVENT_VALUE}}}"
}

最后 :

收集室内温度/湿度和室外温度/湿度后。我们创建了两个函数来计算室内热量指数和室外热量指数:下面是室内热量指数方程:

// Indoor heat index equation
double indoorHeatIndex (float temp, float humidity) {
    const double c1 = -42.379;
    const double c2 = 2.04901523;
    const double c3 = 10.14333127;
    const double c4 = -.22475541;
    const double c5 = -0.00683783;
    const double c6 = -0.05481717;
    const double c7 = 0.00122874;
    const double c8 = 0.00085282;
    const double c9 = -0.00000199;

    double heatIndex = c1 + (c2 * temp) +
                           (c3 * humidity) +
                           (c4 * temp*humidity) + 
                           (c5 * (temp*temp)) +
                           (c6 * (humidity * humidity)) +
                           (c7 * (temp * temp) * humidity) + 
                           (c8 * temp * (humidity * humidity)) +
                           (c9 * (temp * temp) * (humidity * humidity));

  return heatIndex;
}

下面是室外热指数方程:

// Outdoor heat index equation
double outdoorHeatIndex (float tempOutdoor, float humidityOutdoor) {
    const double c1 = -42.379;
    const double c2 = 2.04901523;
    const double c3 = 10.14333127;
    const double c4 = -.22475541;
    const double c5 = -0.00683783;
    const double c6 = -0.05481717;
    const double c7 = 0.00122874;
    const double c8 = 0.00085282;
    const double c9 = -0.00000199;

    double outHeatIndex = c1 + (c2 * tempOutdoor) +
                           (c3 * humidityOutdoor) +
                           (c4 * tempOutdoor*humidityOutdoor) + 
                           (c5 * (tempOutdoor*tempOutdoor)) +
                           (c6 * (humidityOutdoor * humidityOutdoor)) +
                           (c7 * (tempOutdoor * tempOutdoor) * humidityOutdoor) + 
                           (c8 * tempOutdoor * (humidityOutdoor * humidityOutdoor)) +
                           (c9 * (tempOutdoor * tempOutdoor) * (humidityOutdoor * humidityOutdoor));

  return outHeatIndex;
}

如果您正确按照说明进行操作,您现在应该拥有一个带有本地显示器的有效运动服建议站。

多亏了Particle.publish你也可以在室内看到;温度、湿度和热量指数。除此之外,您还可以看到户外;通过转到粒子控制台并在我的设备选项卡上选择设备或检查事件选项卡(您将能够在其中一次查看所有设备的发布)远程温度、湿度和热量指数。

pYYBAGNiSeGAFNDsAAOTKiYevHI411.png
推入粒子控制台的数据截图
 

如何找到 Activewear Suggestion Station 的用途:1- Ubidots Dashboard 是一个有用的可视化工具;您可以创建一个免费帐户来使用Ubidots STEM构建、开发、测试和学习

我创建了一个服装分类仪表板,通过实时更新指标来帮助我做出早上的决定,显示与室内和室外热量指数相关的数据。

该仪表板的主要目的是提供热量指数差异的综合快照,以及我应该在晨跑时穿什么类型的服装。

poYBAGNiSeSAFczVAAIxYxEAjUE975.png
Ubidots 服装分类仪表板
 

2- 在站本身上使用本地显示器 + RGB LED 将有助于:

  • 根据 RGB Light 逻辑确定哪个热指数更高:

Blue --> good to go

Red --> Wear additional active wear

  • 以华氏度显示的室内和室外热指数的可视化显示
 
 
 
pYYBAGNiSgWAPY1mABQiwfg1cgY401.jpg
 
1 / 3Station 上的蓝色 RGB 指示灯
 

** 请记住:该站不突出降水量,因此请穿着相应的衣服以保持安全和温暖。

未来的工作

  • 对于接下来的步骤,我将为这个项目创建一个第 2 部分,其中包括:一个 Google Firebase webhook,用于使用 Firebase 的实时数据库开始实时存储数据。

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

评论(0)
发评论

下载排行榜

全部0条评论

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

'+ '

'+ '

'+ ''+ '
'+ ''+ ''+ '
'+ ''+ '' ); $.get('/article/vipdownload/aid/'+webid,function(data){ if(data.code ==5){ $(pop_this).attr('href',"/login/index.html"); return false } if(data.code == 2){ //跳转到VIP升级页面 window.location.href="//m.obk20.com/vip/index?aid=" + webid return false } //是会员 if (data.code > 0) { $('body').append(htmlSetNormalDownload); var getWidth=$("#poplayer").width(); $("#poplayer").css("margin-left","-"+getWidth/2+"px"); $('#tips').html(data.msg) $('.download_confirm').click(function(){ $('#dialog').remove(); }) } else { var down_url = $('#vipdownload').attr('data-url'); isBindAnalysisForm(pop_this, down_url, 1) } }); }); //是否开通VIP $.get('/article/vipdownload/aid/'+webid,function(data){ if(data.code == 2 || data.code ==5){ //跳转到VIP升级页面 $('#vipdownload>span').text("开通VIP 免费下载") return false }else{ // 待续费 if(data.code == 3) { vipExpiredInfo.ifVipExpired = true vipExpiredInfo.vipExpiredDate = data.data.endoftime } $('#vipdownload .icon-vip-tips').remove() $('#vipdownload>span').text("VIP免积分下载") } }); }).on("click",".download_cancel",function(){ $('#dialog').remove(); }) var setWeixinShare={};//定义默认的微信分享信息,页面如果要自定义分享,直接更改此变量即可 if(window.navigator.userAgent.toLowerCase().match(/MicroMessenger/i) == 'micromessenger'){ var d={ title:'运动服建议站开源分享',//标题 desc:$('[name=description]').attr("content"), //描述 imgUrl:'https://'+location.host+'/static/images/ele-logo.png',// 分享图标,默认是logo link:'',//链接 type:'',// 分享类型,music、video或link,不填默认为link dataUrl:'',//如果type是music或video,则要提供数据链接,默认为空 success:'', // 用户确认分享后执行的回调函数 cancel:''// 用户取消分享后执行的回调函数 } setWeixinShare=$.extend(d,setWeixinShare); $.ajax({ url:"//www.obk20.com/app/wechat/index.php?s=Home/ShareConfig/index", data:"share_url="+encodeURIComponent(location.href)+"&format=jsonp&domain=m", type:'get', dataType:'jsonp', success:function(res){ if(res.status!="successed"){ return false; } $.getScript('https://res.wx.qq.com/open/js/jweixin-1.0.0.js',function(result,status){ if(status!="success"){ return false; } var getWxCfg=res.data; wx.config({ //debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 appId:getWxCfg.appId, // 必填,公众号的唯一标识 timestamp:getWxCfg.timestamp, // 必填,生成签名的时间戳 nonceStr:getWxCfg.nonceStr, // 必填,生成签名的随机串 signature:getWxCfg.signature,// 必填,签名,见附录1 jsApiList:['onMenuShareTimeline','onMenuShareAppMessage','onMenuShareQQ','onMenuShareWeibo','onMenuShareQZone'] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2 }); wx.ready(function(){ //获取“分享到朋友圈”按钮点击状态及自定义分享内容接口 wx.onMenuShareTimeline({ title: setWeixinShare.title, // 分享标题 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); //获取“分享给朋友”按钮点击状态及自定义分享内容接口 wx.onMenuShareAppMessage({ title: setWeixinShare.title, // 分享标题 desc: setWeixinShare.desc, // 分享描述 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 type: setWeixinShare.type, // 分享类型,music、video或link,不填默认为link dataUrl: setWeixinShare.dataUrl, // 如果type是music或video,则要提供数据链接,默认为空 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); //获取“分享到QQ”按钮点击状态及自定义分享内容接口 wx.onMenuShareQQ({ title: setWeixinShare.title, // 分享标题 desc: setWeixinShare.desc, // 分享描述 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); //获取“分享到腾讯微博”按钮点击状态及自定义分享内容接口 wx.onMenuShareWeibo({ title: setWeixinShare.title, // 分享标题 desc: setWeixinShare.desc, // 分享描述 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); //获取“分享到QQ空间”按钮点击状态及自定义分享内容接口 wx.onMenuShareQZone({ title: setWeixinShare.title, // 分享标题 desc: setWeixinShare.desc, // 分享描述 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); }); }); } }); } function openX_ad(posterid, htmlid, width, height) { if ($(htmlid).length > 0) { var randomnumber = Math.random(); var now_url = encodeURIComponent(window.location.href); var ga = document.createElement('iframe'); ga.src = 'https://www1.elecfans.com/www/delivery/myafr.php?target=_blank&cb=' + randomnumber + '&zoneid=' + posterid+'&prefer='+now_url; ga.width = width; ga.height = height; ga.frameBorder = 0; ga.scrolling = 'no'; var s = $(htmlid).append(ga); } } openX_ad(828, '#berry-300', 300, 250);