first commit

This commit is contained in:
Alexander Gabriel 2023-11-03 00:48:34 +01:00
commit 618ff4dcef
13 changed files with 499 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
src/TTN-configuration.h

4
.vscode/arduino.json vendored Normal file
View File

@ -0,0 +1,4 @@
{
"configuration": "CPUFreq=240,UploadSpeed=921600,DebugLevel=none,LORAWAN_REGION=0,LoRaWanDebugLevel=0,LORAWAN_DEVEUI=0,LORAWAN_PREAMBLE_LENGTH=0",
"board": "Heltec-esp32:esp32:wifi_lora_32_V2"
}

10
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,10 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}

39
include/README Normal file
View File

@ -0,0 +1,39 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

46
lib/README Normal file
View File

@ -0,0 +1,46 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

25
platformio.ini Normal file
View File

@ -0,0 +1,25 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:heltec_wifi_lora_32_V2]
platform = espressif32
board = heltec_wifi_lora_32_V2
framework = arduino
lib_deps =
adafruit/Adafruit NeoPixel@^1.11.0
https://github.com/olikraus/U8g2_Arduino
https://github.com/ricaun/SimpleLMIC
boschsensortec/BSEC Software Library@1.6.1480
mcci-catena/MCCI LoRaWAN LMIC library@^4.1.1
sabas1080/CayenneLPP@^1.1.0
monitor_speed = 115200
build_flags =
-D hal_init=LMICHAL_init
-D CFG_eu868=1

View File

@ -0,0 +1,31 @@
const char *devAddr = "";
const char *nwkSKey = "";
const char *appSKey = "";
//config for Arduino Due, GPS connected to Serial2... Don't ask...
const lmic_pinmap lmic_pins = {
.nss = 10,
.rxtx = LMIC_UNUSED_PIN,
.rst = LMIC_UNUSED_PIN,
.dio = {2, 3, LMIC_UNUSED_PIN},
};
#define GPSSerial Serial2
//Config for TTGO T-Beam T22 v1.1
const lmic_pinmap lmic_pins = {
.nss = 18,
.rxtx = LMIC_UNUSED_PIN,
.rst = 23,
.dio = {26, 33, 32},
};
#define GPSSerial Serial1
//#define GPS_RX_PIN 34
//#define GPS_TX_PIN 12
//Heltec Lora 32 v2
const lmic_pinmap lmic_pins = {
.nss = 18,
.rxtx = LMIC_UNUSED_PIN,
.rst = 14,
.dio = {26, 34, 35},
};

2
src/cp_lmic_project_config.sh Executable file
View File

@ -0,0 +1,2 @@
cp src/lmic_project_config.h ".pio/libdeps/heltec_wifi_lora_32_V2/MCCI LoRaWAN LMIC library/project_config"

57
src/decoder.js Normal file
View File

@ -0,0 +1,57 @@
function decodeFloat32(bytes) {
var sign = (bytes & 0x80000000) ? -1 : 1;
var exponent = ((bytes >> 23) & 0xFF) - 127;
var significand = (bytes & ~(-1 << 23));
if (exponent == 128)
return sign * ((significand) ? Number.NaN : Number.POSITIVE_INFINITY);
if (exponent == -127) {
if (significand == 0) return sign * 0.0;
exponent = -126;
significand /= (1 << 22);
} else significand = (significand | (1 << 23)) / (1 << 23);
return sign * significand * Math.pow(2, exponent);
}
function decodeInt16(bytes) {
if ((bytes & 1 << 15) > 0) { // value is negative (16bit 2's complement)
bytes = ((~bytes) & 0xffff) + 1; // invert 16bits & add 1 => now positive value
bytes = bytes * -1;
}
return bytes;
}
function int16_LE(bytes, idx) {
bytes = bytes.slice(idx || 0);
return bytes[0] << 0 | bytes[1] << 8;
}
function int32_LE(bytes, idx) {
bytes = bytes.slice(idx || 0);
return bytes[0] << 0 | bytes[1] << 8 | bytes[2] << 16 | bytes[3] << 24;
}
function Decoder(bytes, port) {
// Decode an uplink message from a buffer
// (array) of bytes to an object of fields.
var decoded = {
co2: decodeFloat32(int32_LE(bytes, 0)),
};
return decoded;
}
function decodeUplink(input) {
bytes = (input.bytes[0] << 8 | input.bytes[1] << 16 | input.bytes[2] << 8 | input.bytes[3]);
return {
data: {
co2: bytes/100
},
warnings: [],
errors: []
};
}

10
src/lmic_project_config.h Normal file
View File

@ -0,0 +1,10 @@
// project-specific definitions
#define CFG_eu868 1
//#define CFG_us915 1
//#define CFG_au915 1
//#define CFG_as923 1
// #define LMIC_COUNTRY_CODE LMIC_COUNTRY_CODE_JP /* for as923-JP; also define CFG_as923 */
//#define CFG_kr920 1
//#define CFG_in866 1
#define CFG_sx1276_radio 1
//#define LMIC_USE_INTERRUPTS

133
src/main.cpp Normal file
View File

@ -0,0 +1,133 @@
#include <Arduino.h>
#include "bsec.h"
#define USE_DISPLAY
#ifdef USE_DISPLAY
#include <U8g2lib.h>
#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif
#include "CayenneLPP.h"
CayenneLPP lpp(160);
#include <Adafruit_NeoPixel.h>
#define LED_PIN 23
#define LED_COUNT 1
#define LEDALWAYSON
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ 15, /* data=*/ 4, /* reset=*/ 16);
#endif
#include <SimpleLMIC.h>
#include "TTN-configuration.h"
SimpleLMIC ttn;
int TTNDebugLoopInterval = 1000;
uint32_t TTNDebugLoopMillis = millis() - TTNDebugLoopInterval;
int TTNSendLoopInterval = 60*1000;
uint32_t TTNSendLoopMillis = millis() - TTNSendLoopInterval;
int DisplayUpdateLoopInterval = 1000;
uint32_t DisplayUpdateLoopMillis = millis() - DisplayUpdateLoopInterval;
int NeoPixelUpdateLoopInterval = 1000;
uint32_t NeoPixelUpdateLoopMillis = millis() - NeoPixelUpdateLoopInterval;
// Create an object of the class Bsec
Bsec iaqSensor;
String output;
#include "main.h"
// Entry point for the example
void setup(void)
{
Serial.begin(115200);
Wire.begin();
iaqSensor.begin(BME680_I2C_ADDR_PRIMARY, Wire);
output = "\nBSEC library version " + String(iaqSensor.version.major) + "." + String(iaqSensor.version.minor) + "." + String(iaqSensor.version.major_bugfix) + "." + String(iaqSensor.version.minor_bugfix);
Serial.println(output);
//checkIaqSensorStatus();
bsec_virtual_sensor_t sensorList[10] = {
BSEC_OUTPUT_RAW_TEMPERATURE,
BSEC_OUTPUT_RAW_PRESSURE,
BSEC_OUTPUT_RAW_HUMIDITY,
BSEC_OUTPUT_RAW_GAS,
BSEC_OUTPUT_IAQ,
BSEC_OUTPUT_STATIC_IAQ,
BSEC_OUTPUT_CO2_EQUIVALENT,
BSEC_OUTPUT_BREATH_VOC_EQUIVALENT,
BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE,
BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY,
};
iaqSensor.updateSubscription(sensorList, 10, BSEC_SAMPLE_RATE_LP);
checkIaqSensorStatus();
#ifdef USE_DISPLAY
setupDisplay();
#endif
// Print the header
output = "Timestamp [ms], raw temperature [°C], pressure [hPa], raw relative humidity [%], gas [Ohm], IAQ, IAQ accuracy, temperature [°C], relative humidity [%], Static IAQ, CO2 equivalent, breath VOC equivalent";
Serial.println(output);
strip.begin();
strip.show();
strip.setBrightness(50);
ttn.begin();
ttn.setSubBand(2);
ttn.onMessage(message);
ttn.personalize(devAddr, nwkSKey, appSKey);
}
// Function that is looped forever
void loop(void)
{
ttn.loop();
unsigned long time_trigger = millis();
if (iaqSensor.run()) { // If new data is available
output = String(time_trigger);
output += ", " + String(iaqSensor.rawTemperature);
output += ", " + String(iaqSensor.pressure);
output += ", " + String(iaqSensor.rawHumidity);
output += ", " + String(iaqSensor.gasResistance);
output += ", " + String(iaqSensor.iaq);
output += ", " + String(iaqSensor.iaqAccuracy);
output += ", " + String(iaqSensor.temperature);
output += ", " + String(iaqSensor.humidity);
output += ", " + String(iaqSensor.staticIaq);
output += ", " + String(iaqSensor.co2Equivalent);
output += ", " + String(iaqSensor.breathVocEquivalent);
Serial.println(output);
} else {
checkIaqSensorStatus();
}
#ifdef USE_DISPLAY
updateDisplay();
#endif
updateNeoPixel();
if (!ttn.isBusy() && ttn.isLink() && millis() - TTNSendLoopMillis > TTNSendLoopInterval)
{
TTNSendLoopMillis = millis();
Serial.println("Not Busy!");
Serial.println(iaqSensor.co2Equivalent);
Serial.println(sizeof(iaqSensor.co2Equivalent));
lpp.addTemperature(1, iaqSensor.co2Equivalent);
ttn.write(lpp.getBuffer(), lpp.getSize());
ttn.send();
}
}

125
src/main.h Normal file
View File

@ -0,0 +1,125 @@
void errLeds(void)
{
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
delay(100);
digitalWrite(LED_BUILTIN, LOW);
delay(100);
}
void printTTNDebug() {
Serial.print("ttn.isBusy(): ");
Serial.print(ttn.isBusy());
Serial.print(" ttn.isLink(): ");
Serial.print(ttn.isLink());
Serial.println();
}
void message(uint8_t *payload, size_t size, uint8_t port)
{
Serial.println("Received " + String(size) + " bytes on port " + String(port));
switch (port) {
case 1:
break;
}
}
void updateNeoPixel(){
if(millis() - NeoPixelUpdateLoopMillis > NeoPixelUpdateLoopInterval) {
NeoPixelUpdateLoopMillis = millis();
double warningValue = 800;
double criticalValue = 1500;
double steps = 2000/strip.numPixels();
float co2 = iaqSensor.co2Equivalent;
for(int i=0; i<strip.numPixels(); i++) {
#ifndef LEDALWAYSON
if(co2 >= steps*(i+1)) {
#endif
if(co2 >= criticalValue) strip.setPixelColor(i, 255, 0, 0);
else if(co2 >= warningValue) strip.setPixelColor(i, 255, 165, 0);
else if(co2 < warningValue) strip.setPixelColor(i, 0, 255, 0);
#ifndef LEDALWAYSON
}
else {
strip.setPixelColor(i, 0, 0, 0);
}
#endif
strip.show();
}
}
}
// Helper function definitions
void checkIaqSensorStatus(void)
{
if (iaqSensor.status != BSEC_OK) {
if (iaqSensor.status < BSEC_OK) {
output = "BSEC error code : " + String(iaqSensor.status);
Serial.println(output);
for (;;)
errLeds(); /* Halt in case of failure */
} else {
output = "BSEC warning code : " + String(iaqSensor.status);
Serial.println(output);
}
}
if (iaqSensor.bme680Status != BME680_OK) {
if (iaqSensor.bme680Status < BME680_OK) {
output = "BME680 error code : " + String(iaqSensor.bme680Status);
Serial.println(output);
for (;;)
errLeds(); /* Halt in case of failure */
} else {
output = "BME680 warning code : " + String(iaqSensor.bme680Status);
Serial.println(output);
}
}
}
#ifdef USE_DISPLAY
void updateDisplay() {
if(millis() - DisplayUpdateLoopMillis > DisplayUpdateLoopInterval) {
DisplayUpdateLoopMillis = millis();
char displayText[256];
u8g2.clearBuffer();
char tempString[10];
dtostrf(iaqSensor.temperature, 4, 2, tempString);
sprintf(displayText, "Temp: %s", tempString);
u8g2.drawStr(0,10,displayText);
char humidString[10];
dtostrf(iaqSensor.humidity, 4, 2, humidString);
sprintf(displayText, "Humidity: %s", humidString);
u8g2.drawStr(0,20,displayText);
char co2String[10];
dtostrf(iaqSensor.co2Equivalent, 4, 2, co2String);
sprintf(displayText, "CO2-Equivalent: %s", "");
u8g2.drawStr(0,30,displayText);
sprintf(displayText, "%20s", co2String);
u8g2.drawStr(0,40,displayText);
sprintf(displayText, "ttn.isBusy(): %d", ttn.isBusy());
u8g2.drawStr(0,50,displayText);
sprintf(displayText, "ttn.isLink(): %d", ttn.isLink());
u8g2.drawStr(0,60,displayText);
u8g2.sendBuffer();
}
}
void setupDisplay() {
u8g2.begin();
u8g2.setFont(u8g2_font_courB08_tr); // choose a suitable font
updateDisplay();
}
#endif

11
test/README Normal file
View File

@ -0,0 +1,11 @@
This directory is intended for PlatformIO Test Runner and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html