節約儲存空間方式收集HDC1080 溫濕度資料

Delores Cetleh
5 min readSep 7, 2020

--

這篇是為了解決上回要用 CubeCell 每10秒取得一組HDC1080資料,連續15分鐘! (共 90筆 資料 ,溫溼度解析度 8bit ,理論值 90 *2 Bytes(8bits)=180 Bytes)

硬體限制

  1. 採用CubeCell膠囊 HTCC-AC01HTCC-AC02
  2. 只能使用內建的RAM 作為暫存
  3. LoRa 每筆最大傳輸上限255 Bytes

量測紀錄範圍

  1. 溫度:- 75.0 ~ +125.0 (稍微調整一下溫度在可量測範圍)
  2. 濕度: 0.0 ~ 100.0

資料分析

  1. 記錄溫度至少要可以紀錄 “2000”的暫存器大小。 這樣就要11 BITs. (2 Bytes)
  2. 記錄濕度至少要可以紀錄 “1000”的暫存器大小。 這樣就要10 BITs. (2 Bytes)
  3. 結論:總共至少要4 Bytes 每組資料! 90筆資料就需要360 Bytes. 而目標是用255Bytes傳送LoRa訊號!

資料壓縮

由於資料是溫度與濕度訊息為連續資料不會大幅跳動, D桃除第一筆資料外打算只用1Byte記錄差異值,換句話說就可以用92 Bytes紀錄溫度或濕度: 2B+1B+1B+1B… = 2B+1B*90=92 Bytes。 92 Bytes + 92 Bytes =184 Bytes 就夠用了!

D桃自創-D式編碼

撰寫程式

D桃在試了好幾次後發現差值如果是0時,Arduino 轉換成ASCII=’nul’,這是被禁止的。 為了易讀性只好Shift 到 ASCII (77) = ‘M’ , 這樣只要變動值不要超過 13 應該都可以在26個字母裡當成字串顯示出來。

以下為 Cubecell 部分:

#include “LoRaWan_APP.h”
#include “Arduino.h”
#include “lora.h”
#include “_hdc1080.h”
#include <ArduinoJson.h>
#include “HDC1080.h”
#define timetillsleep 100 //醒 100 ms
#define timetillwakeup 9900 //睡 9900 ms
#ifndef LoraWan_RGB
#define LoraWan_RGB 0
#endif
#define RF_FREQUENCY 433000000 // Hz
#define TX_OUTPUT_POWER 14 // dBm
#define LORA_BANDWIDTH 0 // [0: 125 kHz,
// 1: 250 kHz,
// 2: 500 kHz,
// 3: Reserved]
#define LORA_SPREADING_FACTOR 7 // [SF7..SF12]
#define LORA_CODINGRATE 1 // [1: 4/5,
// 2: 4/6,
// 3: 4/7,
// 4: 4/8]
#define LORA_PREAMBLE_LENGTH 8 // Same for Tx and Rx
#define LORA_SYMBOL_TIMEOUT 0 // Symbols
#define LORA_FIX_LENGTH_PAYLOAD_ON false
#define LORA_IQ_INVERSION_ON false
#define RX_TIMEOUT_VALUE 1000
#define BUFFER_SIZE 300 // Define the payload size here
HDC1080 hdc1080;
String deviceID=”HDC1080_1";
int packageID=0;

char RH_diff[100];
char T_diff[100];
int BeginHumidity=0;
int BeginTemperature=0;
static TimerEvent_t sleep;
static TimerEvent_t wakeUp;
uint8_t lowpower=1;
uint8_t cntPoint=0;
uint8_t wakeuptimes=0;
int lastHumidity=0;
int lastTemperature=0;
int mycount=0;
uint8_t num=65;
char txpacket[BUFFER_SIZE];
static RadioEvents_t RadioEvents;
double txNumber;
int16_t rssi,rxSize;
void loraInit(){
txNumber=0;
rssi=0;
Radio.Init( &RadioEvents );
Radio.SetChannel( RF_FREQUENCY );
Radio.SetTxConfig( MODEM_LORA, TX_OUTPUT_POWER, 0, LORA_BANDWIDTH,
LORA_SPREADING_FACTOR, LORA_CODINGRATE,
LORA_PREAMBLE_LENGTH, LORA_FIX_LENGTH_PAYLOAD_ON,
true, 0, 0, LORA_IQ_INVERSION_ON, 3000 );
}

void sendLora(){
int i=0;
packageID++;
if (packageID>10){
packageID=0;
}
while (i<80){ // resend 80 times
Radio.Send( (uint8_t *)txpacket, strlen(txpacket) ); //send the package out
delay(80); // delay 20 ms
i++;
}// total take 80 * 20 ms for send 1 message= 1600ms
}
void initialHDC1080(){
hdc1080.end();
delay(20);
hdc1080.begin(0x40);
delay(20);
hdc1080.setResolution(HDC1080_RESOLUTION_8BIT, HDC1080_RESOLUTION_8BIT);
BeginHumidity=(int)(hdc1080.readHumidity()*10);
BeginTemperature=(int)(hdc1080.readTemperature()*10);
lastHumidity=BeginHumidity;
lastTemperature=BeginTemperature;
Serial.printf(“initialData finish,BeginHumidity: %d ,BeginTemperature: %d.\r\n”,BeginHumidity,BeginTemperature);
}
void setDiffData_HDC1080() {
hdc1080.end();
delay(20);
hdc1080.begin(0x40);
delay(20);
hdc1080.setResolution(HDC1080_RESOLUTION_8BIT, HDC1080_RESOLUTION_8BIT);
delay(20);
char last_diff_Humidity=lastHumidity-(int)(hdc1080.readHumidity()*10)+77;//shit 到 ASCII 77 (‘M’)
lastHumidity=(int)(hdc1080.readHumidity()*10);
RH_diff[cntPoint]= last_diff_Humidity;
char last_diff_Temperature=lastTemperature-(int)(hdc1080.readTemperature()*10)+77;//shit 到 ASCII 77 (‘M’) 為中心點
lastTemperature=(int)(hdc1080.readTemperature()*10);
T_diff[cntPoint]= (char) last_diff_Temperature;
cntPoint++;
hdc1080.end();
}
void onSleep()
{
Serial.printf(“Going into lowpower mode, %d ms later wake up.\r\n”,timetillwakeup);
lowpower=1;
//timetillwakeup ms later wake up;
TimerSetValue( &wakeUp, timetillwakeup );
TimerStart( &wakeUp );
}
void onWakeUp()
{
int _timetillsleep=timetillsleep;
digitalWrite(Vext,LOW);

if (mycount > 10){
String temp;
num++;
if (num>85){num=65;}
temp=”{\”dID\”:\””+deviceID+”\”,”;
strcat(txpacket,temp.c_str());

temp=”\”pID\”:”+(String)packageID+”,”;
strcat(txpacket,temp.c_str());

temp=”\”bRH\”:”+(String)BeginHumidity+”,”;
strcat(txpacket,temp.c_str());

temp=”\”bT\”:”+(String)BeginTemperature+”,”;
strcat(txpacket,temp.c_str());

temp=”\”dRH\”:\””+(String)RH_diff+”\”,”;
strcat(txpacket,temp.c_str());

temp=”\”dT\”:\””+(String)T_diff+”\”}”;
strcat(txpacket,temp.c_str());

Serial.println(“txpacket:”);
Serial.println(txpacket);
delay(20);
sendLora();
memset(txpacket, 0, sizeof txpacket);
mycount=0;
cntPoint=0;
initialHDC1080();
_timetillsleep=2000;
}
setDiffData_HDC1080();
delay(20);
mycount++;

Serial.printf(“Woke up, %d ms later into lowpower mode.\r\n”,_timetillsleep);
//timetillsleep ms later into lowpower mode;
TimerSetValue( &sleep, _timetillsleep );
TimerStart( &sleep );
lowpower=0;
digitalWrite(Vext,HIGH);
}
void setup() {
boardInitMcu( );
Serial.begin(115200);
//vext on
pinMode(Vext,OUTPUT);
digitalWrite(Vext,LOW);
loraInit();
initialHDC1080();
Radio.Sleep( );
TimerInit( &sleep, onSleep );
TimerInit( &wakeUp, onWakeUp );
onSleep();
}

void loop()
{
if(lowpower){
//note that lowPowerHandler() runs six times before the mcu goes into lowpower mode;
lowPowerHandler();
}
}

以下為 WB32 (Heltec WiFi Lora 32)部分:

#include “heltec.h”
#include “cert.h”
#include <ArduinoJson.h>
#define BAND 433E6 //you can set band here directly,e.g. 868E6,915E6
String rssi = “RSSI — “;
String packSize = “ — “;
String packet ;
int lastpID=255;
long lastPackageSUM=0;
int count=0;
int T=0;
int RH=0;
bool sendtoAWSNow=false;
void cloudSetUp(void)
{
// Set WiFi to station mode and disconnect from an AP if it was previously connected
WiFi.disconnect(true);
delay(1000);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid,pass);
WiFi.setAutoConnect(true);
delay(100);
byte count = 0;
while(WiFi.status() != WL_CONNECTED)
{
delay(500);
Heltec.display -> drawString(0, 0, “Connecting…”);
Heltec.display -> display();
delay(500);
Heltec.display -> clear();
if(WiFi.status() == WL_CONNECTED)
{
Heltec.display -> drawString(0, 0, “Connecting…OK.”);
Serial.println(“wifi ok”);
connectToAWS();
wifiOk=true;
Heltec.display -> display();
delay(500);
}
else
{
Heltec.display -> clear();
Heltec.display -> drawString(0, 0, “Connecting…Failed”);
Heltec.display -> display();
Serial.print(“no wifi”);
}
}
Heltec.display -> drawString(0, 10, “WIFI Setup done”);
Heltec.display -> display();
delay(500);
}

void connectToAWS()
{
// Configure WiFiClientSecure to use the AWS certificates we generated
net.setCACert(AWS_CERT_CA);
net.setCertificate(AWS_CERT_CRT);
net.setPrivateKey(AWS_CERT_PRIVATE);
// Connect to the MQTT broker on the AWS endpoint we defined earlier
client.begin(AWS_IOT_ENDPOINT, 8883, net);
// Try to connect to AWS and count how many times we retried.
int retries = 0;
Serial.println(“Connecting to AWS IOT”);
while (!client.connect(DEVICE_NAME) && retries < AWS_MAX_RECONNECT_TRIES) {
Serial.print(“.”);
delay(100);
retries++;
}
// Make sure that we did indeed successfully connect to the MQTT broker
// If not we just end the function and wait for the next loop.
if(!client.connected()){
Serial.println(“AWS Timeout!”);
Heltec.display -> drawString(0, 20, “AWS Timeout!”);
return;
}
// If we land here, we have successfully connected to AWS!
// And we can subscribe to topics and send messages.
Serial.println(“AWS Connected!”);
awsConnectOk=true;
delay(500);
Heltec.display -> drawString(0, 20, “AWS connected”);
}

void LoRaData(){
Heltec.display->clear();
Heltec.display->setTextAlignment(TEXT_ALIGN_LEFT);
Heltec.display->setFont(ArialMT_Plain_10);
Heltec.display->drawString(0 , 15 , “Received “+ packSize + “ bytes”);
Heltec.display->drawString(0 , 25 , “Temperature “+ (String)(T/10)+”.”+ (String)(T%10)+ “ deg C”);
Heltec.display->drawString(0 , 35 , “Humidity “+ (String)(RH/10)+”.”+ (String)(T%10) + “ percent”);
Heltec.display->drawString(0, 0, rssi);
Heltec.display->display();
}

void readReceiveData(String receivePacket){
Serial.println(receivePacket);

StaticJsonDocument<1000> doc;
char json[] =
“{\”sensor\”:\”gps\”,\”time\”:1351824120,\”data\”:[48.756080,2.302038]}”;
// Deserialize the JSON document
DeserializationError error = deserializeJson(doc, receivePacket);

// Test if parsing succeeds.
if (error) {
Serial.print(F(“deserializeJson() failed: “));
Serial.println(error.c_str());
return;
}
const char* dID = doc[“dID”];
int bRH = doc[“bRH”];
int bT = doc[“bT”];
String dRH = doc[“dRH”];
String dT = doc[“dT”];

int pID = doc[“pID”];
if (lastpID!=pID){
// Print values.
Serial.println(dID);
Serial.println(“RH:”);
Serial.println(bRH);
for (int i=0;i<dRH.length();i++){
Serial.println(bRH+(uint8_t) dRH[i]-77);
}
Serial.println(“T:”);
Serial.println(bT);
for (int i=0;i<dT.length();i++){
Serial.println(bT+(uint8_t) dT[i]-77);
}
T=bT;
RH=bRH;
LoRaData();
if (awsConnectOk){
sendtoAWSNow=true;
}
}
lastpID=pID;
}

void cbk(int packetSize) {
packet =””;
packSize = String(packetSize,DEC);
String packageID=”A”;
long packageSUM=0;
for (int i = 0; i < packetSize; i++) {
char temp= (char) LoRa.read();
packet += temp;
if (i==0){
packageID=packet[0];
}
packageSUM=uint8_t (temp);
}
readReceiveData(packet);
}
void setup() {
//WIFI Kit series V1 not support Vext control
Heltec.begin(true /*DisplayEnable Enable*/, true /*Heltec.Heltec.Heltec.LoRa Disable*/, true /*Serial Enable*/, true /*PABOOST Enable*/, BAND /*long BAND*/);
// put in standby mode
LoRa.setSignalBandwidth(125E3);
LoRa.setSpreadingFactor(7);
LoRa.setCodingRate4(1);
LoRa.setPreambleLength(8);
Heltec.display->init();
Heltec.display->flipScreenVertically();
Heltec.display->setFont(ArialMT_Plain_10);
delay(1500);
Heltec.display->clear();
Heltec.display->drawString(0, 0, “Heltec.LoRa Initial success!”);
Heltec.display->drawString(0, 10, “Wait for incoming data…”);
Heltec.display->display();
cloudSetUp();
delay(1000);
//LoRa.onReceive(cbk);
LoRa.receive();
}
void loop() {
if (sendtoAWSNow){
Serial.println(“AWS_IOT_TOPIC”);
Serial.println(AWS_IOT_TOPIC);
Serial.println(“receivePacket”);
Serial.println(packet);
client.publish(AWS_IOT_TOPIC,packet);
sendtoAWSNow=false;
}
int packetSize = LoRa.parsePacket();
if (packetSize) {
cbk(packetSize);
}
delay(10);
}

完工測試

WB32 的 console

完工!

後記

D桃後來要做IOT 網頁 Dashboard 後端工程時本來想採用這個程式,後來發現JSON格式還蠻浪費空間的!最後改用Heltec AC0x 乒乓球遊戲Heltec WB32 乒乓球遊戲的方法來設計取樣上傳到HeltecWB32。

--

--