initial checkin of code/etc
This commit is contained in:
parent
bd19351c5f
commit
cd7b496bc3
6 changed files with 741 additions and 0 deletions
18
CMakeLists.txt
Normal file
18
CMakeLists.txt
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
cmake_minimum_required(VERSION 3.12)
|
||||||
|
|
||||||
|
# Specify the project name
|
||||||
|
project(HortiTel)
|
||||||
|
|
||||||
|
# Pull in SDK (must be before project)
|
||||||
|
include(pico/pico_sdk_import.cmake)
|
||||||
|
include(pico/pico_extras_import_optional.cmake)
|
||||||
|
|
||||||
|
# Initialize the Pico SDK
|
||||||
|
pico_sdk_init()
|
||||||
|
|
||||||
|
# Point this to whever you have downloaded the Melopero source from:
|
||||||
|
# https://github.com/melopero/Melopero_Perpetuo_Lora/tree/main/C%2B%2B/src
|
||||||
|
add_subdirectory(../Pico/Melopero/Melopero_Perpetuo_Lora/C++/src/ build)
|
||||||
|
|
||||||
|
# Add our src directory
|
||||||
|
add_subdirectory(src)
|
||||||
59
pico/pico_extras_import_optional.cmake
Normal file
59
pico/pico_extras_import_optional.cmake
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
# This is a copy of <PICO_EXTRAS_PATH>/external/pico_extras_import.cmake
|
||||||
|
|
||||||
|
# This can be dropped into an external project to help locate pico-extras
|
||||||
|
# It should be include()ed prior to project()
|
||||||
|
|
||||||
|
if (DEFINED ENV{PICO_EXTRAS_PATH} AND (NOT PICO_EXTRAS_PATH))
|
||||||
|
set(PICO_EXTRAS_PATH $ENV{PICO_EXTRAS_PATH})
|
||||||
|
message("Using PICO_EXTRAS_PATH from environment ('${PICO_EXTRAS_PATH}')")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (DEFINED ENV{PICO_EXTRAS_FETCH_FROM_GIT} AND (NOT PICO_EXTRAS_FETCH_FROM_GIT))
|
||||||
|
set(PICO_EXTRAS_FETCH_FROM_GIT $ENV{PICO_EXTRAS_FETCH_FROM_GIT})
|
||||||
|
message("Using PICO_EXTRAS_FETCH_FROM_GIT from environment ('${PICO_EXTRAS_FETCH_FROM_GIT}')")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (DEFINED ENV{PICO_EXTRAS_FETCH_FROM_GIT_PATH} AND (NOT PICO_EXTRAS_FETCH_FROM_GIT_PATH))
|
||||||
|
set(PICO_EXTRAS_FETCH_FROM_GIT_PATH $ENV{PICO_EXTRAS_FETCH_FROM_GIT_PATH})
|
||||||
|
message("Using PICO_EXTRAS_FETCH_FROM_GIT_PATH from environment ('${PICO_EXTRAS_FETCH_FROM_GIT_PATH}')")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (NOT PICO_EXTRAS_PATH)
|
||||||
|
if (PICO_EXTRAS_FETCH_FROM_GIT)
|
||||||
|
include(FetchContent)
|
||||||
|
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
|
||||||
|
if (PICO_EXTRAS_FETCH_FROM_GIT_PATH)
|
||||||
|
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_EXTRAS_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
|
||||||
|
endif ()
|
||||||
|
FetchContent_Declare(
|
||||||
|
pico_extras
|
||||||
|
GIT_REPOSITORY https://github.com/raspberrypi/pico-extras
|
||||||
|
GIT_TAG master
|
||||||
|
)
|
||||||
|
if (NOT pico_extras)
|
||||||
|
message("Downloading Raspberry Pi Pico Extras")
|
||||||
|
FetchContent_Populate(pico_extras)
|
||||||
|
set(PICO_EXTRAS_PATH ${pico_extras_SOURCE_DIR})
|
||||||
|
endif ()
|
||||||
|
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
|
||||||
|
else ()
|
||||||
|
if (PICO_SDK_PATH AND EXISTS "${PICO_SDK_PATH}/../pico-extras")
|
||||||
|
set(PICO_EXTRAS_PATH ${PICO_SDK_PATH}/../pico-extras)
|
||||||
|
message("Defaulting PICO_EXTRAS_PATH as sibling of PICO_SDK_PATH: ${PICO_EXTRAS_PATH}")
|
||||||
|
endif()
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (PICO_EXTRAS_PATH)
|
||||||
|
set(PICO_EXTRAS_PATH "${PICO_EXTRAS_PATH}" CACHE PATH "Path to the PICO EXTRAS")
|
||||||
|
set(PICO_EXTRAS_FETCH_FROM_GIT "${PICO_EXTRAS_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of PICO EXTRAS from git if not otherwise locatable")
|
||||||
|
set(PICO_EXTRAS_FETCH_FROM_GIT_PATH "${PICO_EXTRAS_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download EXTRAS")
|
||||||
|
|
||||||
|
get_filename_component(PICO_EXTRAS_PATH "${PICO_EXTRAS_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
|
||||||
|
if (NOT EXISTS ${PICO_EXTRAS_PATH})
|
||||||
|
message(FATAL_ERROR "Directory '${PICO_EXTRAS_PATH}' not found")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(PICO_EXTRAS_PATH ${PICO_EXTRAS_PATH} CACHE PATH "Path to the PICO EXTRAS" FORCE)
|
||||||
|
add_subdirectory(${PICO_EXTRAS_PATH} pico_extras)
|
||||||
|
endif()
|
||||||
84
pico/pico_sdk_import.cmake
Normal file
84
pico/pico_sdk_import.cmake
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
|
||||||
|
|
||||||
|
# This can be dropped into an external project to help locate this SDK
|
||||||
|
# It should be include()ed prior to project()
|
||||||
|
|
||||||
|
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
|
||||||
|
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
|
||||||
|
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
|
||||||
|
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
|
||||||
|
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
|
||||||
|
message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
|
||||||
|
message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
|
||||||
|
|
||||||
|
if (NOT PICO_SDK_PATH)
|
||||||
|
if (PICO_SDK_FETCH_FROM_GIT)
|
||||||
|
include(FetchContent)
|
||||||
|
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
|
||||||
|
if (PICO_SDK_FETCH_FROM_GIT_PATH)
|
||||||
|
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
|
||||||
|
endif ()
|
||||||
|
# GIT_SUBMODULES_RECURSE was added in 3.17
|
||||||
|
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
|
||||||
|
FetchContent_Declare(
|
||||||
|
pico_sdk
|
||||||
|
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||||
|
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
|
||||||
|
GIT_SUBMODULES_RECURSE FALSE
|
||||||
|
)
|
||||||
|
else ()
|
||||||
|
FetchContent_Declare(
|
||||||
|
pico_sdk
|
||||||
|
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||||
|
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
|
||||||
|
)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (NOT pico_sdk)
|
||||||
|
message("Downloading Raspberry Pi Pico SDK")
|
||||||
|
FetchContent_Populate(pico_sdk)
|
||||||
|
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
|
||||||
|
endif ()
|
||||||
|
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
|
||||||
|
else ()
|
||||||
|
message(FATAL_ERROR
|
||||||
|
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
|
||||||
|
)
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
|
||||||
|
if (NOT EXISTS ${PICO_SDK_PATH})
|
||||||
|
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
|
||||||
|
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
|
||||||
|
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
|
||||||
|
|
||||||
|
include(${PICO_SDK_INIT_CMAKE_FILE})
|
||||||
24
src/CMakeLists.txt
Normal file
24
src/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
add_executable(receiver
|
||||||
|
receiver.cpp
|
||||||
|
)
|
||||||
|
target_link_libraries(receiver
|
||||||
|
MeloperoPerpetuo
|
||||||
|
pico_stdlib
|
||||||
|
hardware_adc
|
||||||
|
)
|
||||||
|
pico_enable_stdio_usb(receiver 1)
|
||||||
|
pico_enable_stdio_uart(receiver 0)
|
||||||
|
pico_add_extra_outputs(receiver)
|
||||||
|
|
||||||
|
|
||||||
|
add_executable(sender
|
||||||
|
sender.cpp
|
||||||
|
)
|
||||||
|
target_link_libraries(sender
|
||||||
|
MeloperoPerpetuo
|
||||||
|
pico_stdlib
|
||||||
|
hardware_adc
|
||||||
|
)
|
||||||
|
pico_enable_stdio_usb(sender 1)
|
||||||
|
pico_enable_stdio_uart(sender 0)
|
||||||
|
pico_add_extra_outputs(sender)
|
||||||
282
src/receiver.cpp
Normal file
282
src/receiver.cpp
Normal file
|
|
@ -0,0 +1,282 @@
|
||||||
|
/**
|
||||||
|
* Melopero Perpetou LoRa - Allotment Telemetry Receiver
|
||||||
|
*
|
||||||
|
* This code began life as the Melopero sample code the Perpetuo LoRa board.
|
||||||
|
* This can be found here: https://github.com/melopero/Melopero_Perpetuo_Lora
|
||||||
|
*
|
||||||
|
* As per their code I choose to continue the MIT licencing for this.
|
||||||
|
*
|
||||||
|
* This is the code for a LoRa reciever note that collects the data from
|
||||||
|
* one or many LoRa sender nodes which transmit sensor data from various
|
||||||
|
* points on an allotment or similar environment where you may want to
|
||||||
|
* record sensor data. The sensor data is output via the serial console
|
||||||
|
* so that a host computer can then do whatever is needed with it (i.e.
|
||||||
|
* send out to IP network via MQTT, or place into a database, etc.)
|
||||||
|
*
|
||||||
|
* Yvan Seth <allotment.sensors@seth.id.au>
|
||||||
|
* https://yvan.seth.id.au/tag/lora.html
|
||||||
|
*/
|
||||||
|
#include <cstdio>
|
||||||
|
#include <vector>
|
||||||
|
#include <numeric>
|
||||||
|
#include <cmath>
|
||||||
|
#include <algorithm>
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "hardware/gpio.h"
|
||||||
|
#include "hardware/adc.h"
|
||||||
|
#include "MeloperoPerpetuo.h"
|
||||||
|
|
||||||
|
static const int SAMPLES = 16;
|
||||||
|
|
||||||
|
float readADCVoltage( int adc ) {
|
||||||
|
std::vector<uint16_t> values;
|
||||||
|
|
||||||
|
// get ADC readings
|
||||||
|
adc_select_input( adc );
|
||||||
|
for ( int i = 0; i < SAMPLES; ++i ) {
|
||||||
|
uint16_t adc_val = adc_read();
|
||||||
|
values.push_back( adc_val );
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate standard deviation
|
||||||
|
float avg = std::accumulate( values.begin(), values.end(), (float)0.0 ) / values.size();
|
||||||
|
float sd = 0.0;
|
||||||
|
for ( int i = 0; i < values.size(); ++i ) {
|
||||||
|
sd += pow( values[i] - avg, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint16_t> filtered;
|
||||||
|
if ( 0 == sd ) {
|
||||||
|
filtered = values;
|
||||||
|
} else {
|
||||||
|
// eliminate outliers here
|
||||||
|
std::copy_if( values.begin(), values.end(), std::back_inserter(filtered), [=](uint16_t val) {
|
||||||
|
bool pass = abs( (float)val - avg ) < (3 * sd);
|
||||||
|
if ( ! pass ) {
|
||||||
|
printf( "rejected: %d\n", val );
|
||||||
|
}
|
||||||
|
return pass;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate the average of remaining values
|
||||||
|
float adc_avg = std::accumulate( filtered.begin(), filtered.end(), 0.0 ) / filtered.size();
|
||||||
|
|
||||||
|
// convert to pin voltage
|
||||||
|
float adc_volts = adc_avg * (3.3f / (1 << 12));
|
||||||
|
|
||||||
|
return adc_volts;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is the structure of the received data
|
||||||
|
// TODO: we should support the core real data being a variable-length array
|
||||||
|
// perhaps like: null-terminated-label, int-type-code, value-length-dep-on-type
|
||||||
|
// or just do what normal folks do these days and make it a text format
|
||||||
|
struct rxdata {
|
||||||
|
// the "header"
|
||||||
|
// see LoRaEMB on page 42: https://www.embit.eu/wp-content/uploads/2020/10/ebi-LoRa_rev1.0.1.pdf
|
||||||
|
uint16_t length;
|
||||||
|
uint16_t options;
|
||||||
|
uint8_t wtf; // possibly an extra byte in the received data here? what is it? padding?
|
||||||
|
int16_t rssi;
|
||||||
|
uint16_t src;
|
||||||
|
uint16_t dst; // see LoRaEMB on page 42: https://www.embit.eu/wp-content/uploads/2020/10/ebi-LoRa_rev1.0.1.pdf
|
||||||
|
|
||||||
|
// our data starts here
|
||||||
|
uint8_t charge_state; // the charge status as supplied by Melopero library - actual struct +3 bytes padding/alignment
|
||||||
|
float mcu_temp; // the internal RPi temperature sensor value
|
||||||
|
float vbat; // battery circuit voltage - charging voltage or battery
|
||||||
|
float vin; // supply voltage, i.e. USB, solar, or battery
|
||||||
|
|
||||||
|
// 1 byte checksum, simply the low byte of the sum of the previous bytes
|
||||||
|
// not documented in the above doc for some reason, I suspect it's not our problem to validate it
|
||||||
|
uint8_t checksum;
|
||||||
|
};
|
||||||
|
|
||||||
|
// FIXME: error detection - format, length, etc - this code is unsafe
|
||||||
|
uint8_t * deserialise_i16(uint8_t * buf, int16_t * val) {
|
||||||
|
uint16_t uval = 0;
|
||||||
|
uval |= ((uint16_t)buf[0]) << 8;
|
||||||
|
uval |= (uint16_t)buf[1];
|
||||||
|
*val = (int16_t)uval;
|
||||||
|
return buf + 2;
|
||||||
|
}
|
||||||
|
uint8_t * deserialise_u16(uint8_t * buf, uint16_t * val) {
|
||||||
|
*val = 0; // just to be sure
|
||||||
|
*val |= ((uint16_t)buf[0]) << 8;
|
||||||
|
*val |= (uint16_t)buf[1];
|
||||||
|
return buf + 2;
|
||||||
|
}
|
||||||
|
uint8_t * deserialise_u8(uint8_t * buf, uint8_t * val) {
|
||||||
|
*val = (uint8_t)buf[0];
|
||||||
|
return buf + 1;
|
||||||
|
}
|
||||||
|
// not cross-plaform, but my application doesn't need to be
|
||||||
|
uint8_t * deserialise_float(uint8_t * buf, float * val) {
|
||||||
|
*val = 0; // just to be sure
|
||||||
|
*((uint32_t*)val) |= ((uint32_t)buf[0]) << 24;
|
||||||
|
*((uint32_t*)val) |= ((uint32_t)buf[1]) << 16;
|
||||||
|
*((uint32_t*)val) |= ((uint32_t)buf[2]) << 8;
|
||||||
|
*((uint32_t*)val) |= (uint32_t)buf[3];
|
||||||
|
return buf + 4;
|
||||||
|
}
|
||||||
|
void deseralise_rxdata(uint8_t * buf, struct rxdata *rxd) {
|
||||||
|
buf = deserialise_u16(buf, &(rxd->length));
|
||||||
|
buf = deserialise_u16(buf, &(rxd->options));
|
||||||
|
buf = deserialise_u8(buf, &(rxd->wtf));
|
||||||
|
buf = deserialise_i16(buf, &(rxd->rssi));
|
||||||
|
buf = deserialise_u16(buf, &(rxd->src));
|
||||||
|
buf = deserialise_u16(buf, &(rxd->dst));
|
||||||
|
buf = deserialise_u8(buf, &(rxd->charge_state));
|
||||||
|
buf = deserialise_float(buf, &(rxd->mcu_temp));
|
||||||
|
buf = deserialise_float(buf, &(rxd->vbat));
|
||||||
|
buf = deserialise_float(buf, &(rxd->vin));
|
||||||
|
buf = deserialise_u8(buf, &(rxd->checksum));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main function
|
||||||
|
int main() {
|
||||||
|
stdio_init_all(); // Initialize all standard IO
|
||||||
|
|
||||||
|
MeloperoPerpetuo melopero;
|
||||||
|
|
||||||
|
melopero.init(); // Initialize the board and peripherals
|
||||||
|
|
||||||
|
melopero.led_init();
|
||||||
|
melopero.blink_led(3, 250);
|
||||||
|
|
||||||
|
melopero.sendCmd(0x01);
|
||||||
|
sleep_ms(500);
|
||||||
|
printf("response to deviceId\n");
|
||||||
|
melopero.printResponse();
|
||||||
|
|
||||||
|
// LoRaEMB operating mode configuration
|
||||||
|
melopero.stopNetwork();
|
||||||
|
sleep_ms(500);
|
||||||
|
printf("response to stopNetwork\n");
|
||||||
|
melopero.printResponse();
|
||||||
|
|
||||||
|
melopero.setNetworkPreferences(false, false, false);
|
||||||
|
sleep_ms(500);
|
||||||
|
printf("response to setNetworkPreferences\n");
|
||||||
|
melopero.printResponse();
|
||||||
|
|
||||||
|
melopero.setOutputPower(0x0a); // Example power level
|
||||||
|
sleep_ms(500);
|
||||||
|
printf("response to setOutputPower\n");
|
||||||
|
melopero.printResponse();
|
||||||
|
|
||||||
|
melopero.setOperatingChannel(1, SPREADING_FACTOR_7, BANDWIDTH_125, CODING_RATE_4_5); // Example channel
|
||||||
|
sleep_ms(500);
|
||||||
|
printf("response to setOperatingChannel\n");
|
||||||
|
melopero.printResponse();
|
||||||
|
|
||||||
|
melopero.setNetworkAddress(0x1235); // Example network address
|
||||||
|
sleep_ms(500);
|
||||||
|
printf("response to setNetworkAddress\n");
|
||||||
|
melopero.printResponse();
|
||||||
|
|
||||||
|
uint8_t network_id[] = {0x00, 0x01}; // Example network ID
|
||||||
|
melopero.setNetworkId(network_id, sizeof(network_id));
|
||||||
|
sleep_ms(500);
|
||||||
|
printf("response to setNetworkId\n");
|
||||||
|
melopero.printResponse();
|
||||||
|
|
||||||
|
melopero.setEnergySaveMode(ENERGY_SAVE_MODE_ALWAYS_ON);
|
||||||
|
sleep_ms(500);
|
||||||
|
printf("response to setEnergySaveModeRxAlways\n");
|
||||||
|
melopero.printResponse();
|
||||||
|
|
||||||
|
melopero.startNetwork();
|
||||||
|
sleep_ms(500);
|
||||||
|
printf("response to startNetwork\n");
|
||||||
|
melopero.printResponse();
|
||||||
|
|
||||||
|
adc_init();
|
||||||
|
adc_set_temp_sensor_enabled(true);
|
||||||
|
melopero.enablelWs2812(true);
|
||||||
|
while (1) {
|
||||||
|
|
||||||
|
printf("\n=============================================\n");
|
||||||
|
|
||||||
|
// print out the battery charging state, also set LED colour code
|
||||||
|
printf("Battery: %d (", melopero.getChargerStatus());
|
||||||
|
if (melopero.isCharging()) {
|
||||||
|
printf("charging)\n");
|
||||||
|
melopero.setWs2812Color(255, 255, 0, 0.1);
|
||||||
|
}
|
||||||
|
else if (melopero.isFullyCharged()) {
|
||||||
|
printf("charged)\n");
|
||||||
|
melopero.setWs2812Color(0, 255, 0, 0.05);
|
||||||
|
}
|
||||||
|
else if (melopero.hasRecoverableFault()) {
|
||||||
|
printf("fault: recoverable)\n");
|
||||||
|
melopero.setWs2812Color(0, 0, 255, 0.05);
|
||||||
|
}
|
||||||
|
else if (melopero.hasNonRecoverableFault()) {
|
||||||
|
printf("fault: non-recoverable)\n");
|
||||||
|
melopero.setWs2812Color(255, 255, 255, 0.25);
|
||||||
|
}
|
||||||
|
sleep_ms(500);
|
||||||
|
|
||||||
|
// check the temperature of the RP2350
|
||||||
|
float voltage = readADCVoltage( 4 );
|
||||||
|
float temp = 27.0 - ((voltage - 0.706) / 0.001721); // values from RP2350 documentation
|
||||||
|
printf( "RP2350 Temperature: %0.2f C\n", temp );
|
||||||
|
|
||||||
|
// read received LoRa data, if available
|
||||||
|
uint8_t rxbuff[sizeof(struct rxdata)]; // at least enough for our expected data
|
||||||
|
size_t rxbuff_ptr = 0;
|
||||||
|
if (melopero.checkRxFifo(500)) { // if there is data received...
|
||||||
|
// TODO: this really needs some sort of validation
|
||||||
|
do {
|
||||||
|
printf("rx: ");
|
||||||
|
for (size_t i = 0; i < melopero.responseLen; i++) {
|
||||||
|
if (rxbuff_ptr < sizeof(rxbuff)) {
|
||||||
|
rxbuff[rxbuff_ptr] = melopero.response[i];
|
||||||
|
printf("0x%02X ", rxbuff[rxbuff_ptr]);
|
||||||
|
} else {
|
||||||
|
// discarded excess data
|
||||||
|
printf("(0x%02X) ", melopero.response[i]);
|
||||||
|
}
|
||||||
|
rxbuff_ptr++;
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
} while (melopero.checkRxFifo(500)); // Keep checking the FIFO for new data
|
||||||
|
|
||||||
|
// NOTE: the length could be < or > actual buffer length
|
||||||
|
printf( "Received Data:\nlength=%d\ndata={", rxbuff_ptr );
|
||||||
|
for (size_t i = 0; i < sizeof(rxbuff); i++) {
|
||||||
|
printf("0x%02X ", rxbuff[i]);
|
||||||
|
}
|
||||||
|
printf("}\n");
|
||||||
|
|
||||||
|
struct rxdata rxd = {};
|
||||||
|
deseralise_rxdata( rxbuff, &rxd );
|
||||||
|
|
||||||
|
printf( "Received\n" );
|
||||||
|
printf( " Data Length: %u bytes\n", rxd.length );
|
||||||
|
printf( " Options: 0x%08X (bitfield)\n", rxd.options );
|
||||||
|
printf( " WTF: 0x%02X (undocumented field?)\n", rxd.wtf );
|
||||||
|
printf( " Signal Strength: %d dBm\n", rxd.rssi );
|
||||||
|
printf( " Source Addr: 0x%04X\n", rxd.src );
|
||||||
|
printf( " Dest Addr: 0x%04X (0xFFFF is broadcast)\n", rxd.dst );
|
||||||
|
printf( " Payload: \n" );
|
||||||
|
printf( " RP2350 Temperature: %0.2f C\n", rxd.mcu_temp );
|
||||||
|
printf( " Charge State: %u\n", rxd.charge_state );
|
||||||
|
printf( " Battery Voltage: %0.2fV\n", rxd.vbat );
|
||||||
|
printf( " Supply Voltage: %0.2fV\n", rxd.vin );
|
||||||
|
printf( " Checksum: %02X\n", rxd.checksum );
|
||||||
|
|
||||||
|
} else {
|
||||||
|
printf("nothing in the rx fifo\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// take a nap
|
||||||
|
sleep_ms(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// technically this is unreachable?
|
||||||
|
melopero.enablelWs2812(false);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
274
src/sender.cpp
Normal file
274
src/sender.cpp
Normal file
|
|
@ -0,0 +1,274 @@
|
||||||
|
#include <cstdio>
|
||||||
|
#include <vector>
|
||||||
|
#include <numeric>
|
||||||
|
#include <cmath>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <string>
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "hardware/gpio.h"
|
||||||
|
#include "hardware/adc.h"
|
||||||
|
#include "MeloperoPerpetuo.h"
|
||||||
|
|
||||||
|
static const int ADC_SAMPLE_COUNT = 16;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a given ADC value, returns a voltage value.
|
||||||
|
*
|
||||||
|
* Actually reads ACD_SAMPLE_COUNT values and returns an average after an
|
||||||
|
* outlier elimination filter.
|
||||||
|
*/
|
||||||
|
float readADCVoltage( int adc ) { std::vector<uint16_t> values;
|
||||||
|
|
||||||
|
// get ADC readings
|
||||||
|
adc_select_input( adc );
|
||||||
|
for ( int i = 0; i < ADC_SAMPLE_COUNT; ++i ) {
|
||||||
|
uint16_t adc_val = adc_read();
|
||||||
|
values.push_back( adc_val );
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate standard deviation
|
||||||
|
float avg = std::accumulate( values.begin(), values.end(), (float)0.0 ) / values.size();
|
||||||
|
float sd = 0.0;
|
||||||
|
for ( int i = 0; i < values.size(); ++i ) {
|
||||||
|
sd += pow( values[i] - avg, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint16_t> filtered;
|
||||||
|
if ( 0 == sd ) {
|
||||||
|
filtered = values;
|
||||||
|
} else {
|
||||||
|
// eliminate outliers here
|
||||||
|
std::copy_if( values.begin(), values.end(), std::back_inserter(filtered), [=](uint16_t val) {
|
||||||
|
bool pass = abs( (float)val - avg ) < (3 * sd);
|
||||||
|
if ( ! pass ) {
|
||||||
|
printf( "rejected: %d\n", val );
|
||||||
|
}
|
||||||
|
return pass;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate the average of remaining values
|
||||||
|
float adc_avg = std::accumulate( filtered.begin(), filtered.end(), 0.0 ) / filtered.size();
|
||||||
|
|
||||||
|
// convert to pin voltage
|
||||||
|
float adc_volts = adc_avg * (3.3f / (1 << 12));
|
||||||
|
|
||||||
|
return adc_volts;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is our data transfer/packet struct - note: it isn't platform/endian portable
|
||||||
|
struct txdata {
|
||||||
|
uint16_t options; // options as defined page 42: https://www.embit.eu/wp-content/uploads/2020/10/ebi-LoRa_rev1.0.1.pdf
|
||||||
|
uint16_t dest; // destination id, 0xFFFF for broadcast
|
||||||
|
uint8_t charge_state; // the charge status as supplied by Melopero library
|
||||||
|
float mcu_temp; // the internal RPi temperature sensor value
|
||||||
|
float vbat; // battery circuit voltage - charging voltage or battery
|
||||||
|
float vin; // supply voltage, i.e. USB, solar, or battery
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t* serialise_u8(uint8_t* buf, uint8_t val) {
|
||||||
|
buf[0] = val;
|
||||||
|
return buf + 1;
|
||||||
|
}
|
||||||
|
uint8_t* serialise_u16(uint8_t* buf, uint16_t val) {
|
||||||
|
buf[0] = val >> 8;
|
||||||
|
buf[1] = val;
|
||||||
|
return buf + 2;
|
||||||
|
}
|
||||||
|
uint8_t* serialise_u32(uint8_t* buf, uint32_t val) {
|
||||||
|
buf[0] = (val & 0xff000000) >> 24;
|
||||||
|
buf[1] = (val & 0x00ff0000) >> 16;
|
||||||
|
buf[2] = (val & 0x0000ff00) >> 8;
|
||||||
|
buf[3] = (val & 0x000000ff);
|
||||||
|
return buf + 4;
|
||||||
|
}
|
||||||
|
size_t serialise_txdata(struct txdata* txd, uint8_t* buf) {
|
||||||
|
uint8_t* bufptr = buf;
|
||||||
|
bufptr = serialise_u16(bufptr, txd->options);
|
||||||
|
bufptr = serialise_u16(bufptr, txd->dest);
|
||||||
|
bufptr = serialise_u8(bufptr, txd->charge_state);
|
||||||
|
float mcu_temp = txd->mcu_temp;
|
||||||
|
bufptr = serialise_u32(bufptr, *((uint32_t*)(&mcu_temp)));
|
||||||
|
float vbat = txd->vbat;
|
||||||
|
bufptr = serialise_u32(bufptr, *((uint32_t*)(&vbat)));
|
||||||
|
float vin = txd->vin;
|
||||||
|
bufptr = serialise_u32(bufptr, *((uint32_t*)(&vin)));
|
||||||
|
return (bufptr - buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main function
|
||||||
|
int main() {
|
||||||
|
stdio_init_all(); // Initialize all standard IO
|
||||||
|
|
||||||
|
MeloperoPerpetuo melopero;
|
||||||
|
melopero.init(); // Initialize the board and peripherals
|
||||||
|
|
||||||
|
melopero.led_init();
|
||||||
|
melopero.blink_led(2, 500);
|
||||||
|
|
||||||
|
melopero.sendCmd(0x01);
|
||||||
|
sleep_ms(500);
|
||||||
|
printf("response to deviceId\n");
|
||||||
|
melopero.printResponse();
|
||||||
|
|
||||||
|
// LoRaEMB operating mode configuration sequence:
|
||||||
|
// Stop Network
|
||||||
|
// Set Network preferences
|
||||||
|
// Set Output power
|
||||||
|
// Set Operating channel
|
||||||
|
// Set Network address
|
||||||
|
// Set Network ID
|
||||||
|
// Set Energy save mode (the default value is TX_ONLY, set RX_ALWAYS)
|
||||||
|
// Start Network
|
||||||
|
|
||||||
|
melopero.stopNetwork();
|
||||||
|
sleep_ms(500);
|
||||||
|
printf("response to stopNetwork\n");
|
||||||
|
melopero.printResponse();
|
||||||
|
|
||||||
|
melopero.setNetworkPreferences(false, false, false);
|
||||||
|
sleep_ms(500);
|
||||||
|
printf("response to setNetworkPreferences\n");
|
||||||
|
melopero.printResponse();
|
||||||
|
|
||||||
|
melopero.setOutputPower(0x0a); // Example power level
|
||||||
|
sleep_ms(500);
|
||||||
|
printf("response to setOutputPower\n");
|
||||||
|
melopero.printResponse();
|
||||||
|
|
||||||
|
melopero.setOperatingChannel(1, SPREADING_FACTOR_7, BANDWIDTH_125, CODING_RATE_4_5); // Example channel
|
||||||
|
sleep_ms(500);
|
||||||
|
printf("response to setOperatingChannel\n");
|
||||||
|
melopero.printResponse();
|
||||||
|
|
||||||
|
uint16_t network_address = 0x1234;
|
||||||
|
melopero.setNetworkAddress(network_address); // Example network address
|
||||||
|
sleep_ms(500);
|
||||||
|
printf("response to setNetworkAddress\n");
|
||||||
|
melopero.printResponse();
|
||||||
|
|
||||||
|
uint8_t network_id[] = {0x00, 0x01}; // Example network ID
|
||||||
|
melopero.setNetworkId(network_id, sizeof(network_id));
|
||||||
|
sleep_ms(500);
|
||||||
|
printf("response to setNetworkId\n");
|
||||||
|
melopero.printResponse();
|
||||||
|
|
||||||
|
melopero.setEnergySaveMode(ENERGY_SAVE_MODE_TX_ONLY);
|
||||||
|
sleep_ms(500);
|
||||||
|
printf("response to setEnergySaveModeRxAlways\n");
|
||||||
|
melopero.printResponse();
|
||||||
|
|
||||||
|
//melopero.setEnergySaveModeRxAlways();
|
||||||
|
melopero.startNetwork();
|
||||||
|
sleep_ms(500);
|
||||||
|
printf("response to startNetwork\n");
|
||||||
|
melopero.printResponse();
|
||||||
|
|
||||||
|
|
||||||
|
// and GO!
|
||||||
|
adc_init();
|
||||||
|
adc_gpio_init(26);
|
||||||
|
adc_gpio_init(27);
|
||||||
|
adc_set_temp_sensor_enabled(true);
|
||||||
|
while (1) {
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////
|
||||||
|
// do a litte dance
|
||||||
|
|
||||||
|
// simple LED on
|
||||||
|
gpio_put(23, 1);
|
||||||
|
|
||||||
|
// cycle the RGB LEDS - just for fun
|
||||||
|
melopero.enablelWs2812(true);
|
||||||
|
melopero.setWs2812Color(255, 0, 0, 0.2);
|
||||||
|
sleep_ms(500);
|
||||||
|
melopero.setWs2812Color(0, 255, 0, 0.2);
|
||||||
|
sleep_ms(500);
|
||||||
|
melopero.setWs2812Color(0, 0, 255, 0.2);
|
||||||
|
sleep_ms(500);
|
||||||
|
melopero.setWs2812Color(0, 0, 0, 0);
|
||||||
|
melopero.enablelWs2812(false);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////
|
||||||
|
// read sensor values
|
||||||
|
printf("\n============================================\n");
|
||||||
|
|
||||||
|
struct txdata txd = {};
|
||||||
|
txd.options = 0;
|
||||||
|
txd.dest = 0xFFFF;
|
||||||
|
|
||||||
|
// print out the battery charging state
|
||||||
|
txd.charge_state = melopero.getChargerStatus();
|
||||||
|
printf("Battery: %d (", txd.charge_state);
|
||||||
|
if (melopero.isCharging()) {
|
||||||
|
printf("charging)\n");
|
||||||
|
melopero.setWs2812Color(255, 255, 0, 0.1);
|
||||||
|
}
|
||||||
|
else if (melopero.isFullyCharged()) {
|
||||||
|
printf("charged)\n");
|
||||||
|
melopero.setWs2812Color(0, 255, 0, 0.05);
|
||||||
|
}
|
||||||
|
else if (melopero.hasRecoverableFault()) {
|
||||||
|
printf("fault: recoverable)\n");
|
||||||
|
melopero.setWs2812Color(0, 0, 255, 0.05);
|
||||||
|
}
|
||||||
|
else if (melopero.hasNonRecoverableFault()) {
|
||||||
|
printf("fault: non-recoverable)\n");
|
||||||
|
melopero.setWs2812Color(255, 255, 255, 0.25);
|
||||||
|
}
|
||||||
|
sleep_ms(500);
|
||||||
|
melopero.setWs2812Color(0, 0, 0, 0);
|
||||||
|
|
||||||
|
// flip the enable on the VSEN on/off each iteration, this is just
|
||||||
|
// testing it can turn an LED on/off if you like, or enable/disable
|
||||||
|
// other external hardware (thus saving power, only power on sensors
|
||||||
|
// to read data every 5 minutes, say...)
|
||||||
|
int vsen = gpio_get( 0 );
|
||||||
|
printf( "vsen: %d\n", vsen );
|
||||||
|
if ( vsen ) {
|
||||||
|
printf("vsen off\n");
|
||||||
|
gpio_put( 0, 0 );
|
||||||
|
} else {
|
||||||
|
printf("vsen on\n");
|
||||||
|
gpio_put( 0, 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the temperature of the RP2350
|
||||||
|
float voltage = readADCVoltage( 4 );
|
||||||
|
txd.mcu_temp = 27.0 - ((voltage - 0.706) / 0.001721);
|
||||||
|
printf( "RP2350 Temperature: %0.2f C\n", txd.mcu_temp );
|
||||||
|
|
||||||
|
// read voltage on ADC0 (battery voltage sense)
|
||||||
|
float adc0_v = readADCVoltage( 0 );
|
||||||
|
txd.vbat = (adc0_v * (98600 + 149100)) / 149100; // measured values of volage divider
|
||||||
|
printf( "Battery Voltage: %0.2fV\n", txd.vbat );
|
||||||
|
|
||||||
|
// read voltage on ACD1 (supply voltage sense)
|
||||||
|
float adc1_v = readADCVoltage( 1 );
|
||||||
|
txd.vin = (adc1_v * (98600 + 14890)) / 14890; // measured values of volage divider
|
||||||
|
printf( "Supply Voltage: %0.2fV\n", txd.vin );
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////
|
||||||
|
// send some data
|
||||||
|
uint8_t sendbuf[sizeof(txdata)]; // at least big enough...
|
||||||
|
size_t data_length = serialise_txdata(&txd, sendbuf);
|
||||||
|
printf( "Sending Data:\n length=%d\n data={", data_length );
|
||||||
|
for (size_t i = 0; i < data_length; i++) {
|
||||||
|
printf("0x%02X ", sendbuf[i]);
|
||||||
|
}
|
||||||
|
printf("}\n");
|
||||||
|
melopero.transmitData(sendbuf, data_length);
|
||||||
|
|
||||||
|
sleep_ms(500); // seems a bit kludgy?
|
||||||
|
printf("response to transmitData\n");
|
||||||
|
melopero.printResponse();
|
||||||
|
|
||||||
|
// simple LED off
|
||||||
|
gpio_put(23, 0);
|
||||||
|
|
||||||
|
// snoozZzZzZzZzzze
|
||||||
|
sleep_ms(5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue