diff --git a/README.md b/README.md
index ba2f747..b898a7c 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1,56 @@
-# parasite
-A low power soil moisture sensor based on the nRF52840.
+# b-parasite
+
+
+
-# TODO
-* Figure out how to calibrate the ADC when running from different voltages, as the battery discharges
-* Experiment with different TX power / battery tradeoff. Since we'll be deepsleeping most of the time, maybe we can get away with a lot of TX power.
-* Implement deep sleep
-* Measure current in deep sleep
-* Measure current in operation (ADC + BLE adversiting)
-* Test with a range of 2-5V input. Disconnect from USB and monitor using a serial post. Use the bench power supply to power Vcc with variable voltage. *DO NOT CONNECT VCC FROM THE USB-TO-SERIAL BOARD!**
-* Test with a coin cell
-* Implement ADC for battery monitoring
-* Figure out a way for people to configure the device with a custom name. Idea: BLE service (this is what my [hacked xiaomi temp sensor](https://github.com/atc1441/ATC_MiThermometer) does)
-* Figure out how OTA works (if at all) over BLE
-* Design new board using the nrf52840 (E73-C) instead of esp32
+b-parasite is an open source Bluetooth Low Energy (BLE) soil moisture and ambient temperature/humidity sensor.
-# Done
-* Implement battery level monitoring
-* Implement BLE advertising with moisture
-* Implement ADC for the parasitic capacitor; check out air/water range (using protoboard)
-* Simple PWM square wave generator
-* Hook square wave generator to the protoboard sensor circuit
-* Make the protoboard sensor work
\ No newline at end of file
+# Features
+* Soil moisture sensor. I wrote about how capacitive soil moisture sensors works on [this Twitter thread](https://twitter.com/rbaron_/status/1367182806368071685), based on [this great post](https://wemakethings.net/2012/09/26/capacitance_measurement/) on wemakethings.net
+* Air temperature and humity sensor using a [Sensirion's SHTC3](https://www.sensirion.com/en/environmental-sensors/humidity-sensors/digital-humidity-sensor-shtc3-our-new-standard-for-consumer-electronics/)
+* Powered by a common CR2032 coin cell, with a battery life of possibly over a year - see "Battery Life" below
+* Open hardware and open source design
+
+# Repository Organization
+* [code/b-parasite/](./code/b-parasite/) - firmware code based on the [nRF5 SDK](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fstruct_sdk%2Fstruct%2Fsdk_nrf5_latest.html&cp=7_1)
+* [kicad/](./kicad/) - KiCad schematic, layout and fabrication files for the printed circuit board (PCB)
+* [data/](data/) - data for testing and sensor calibration
+* [bridge/](bridge/) - an [ESPHome](https://github.com/esphome/esphome)-based BLE-MQTT bridge
+
+
+# How It Works
+
+
+
+
+b-parasite works by periodically measuring the soil moisture, air temperature/humidity and broadcasting those values via Bluetooth Low Energy (BLE) advertisement packets. After doing so, the board goes into a sleep mode until it's time for another measurement. The sleep interval is configurable - I often use 10 minutes between readings, which is a good compromise between fresh data and saving battery.
+
+At this point, b-parasite's job is done. We have many possibilities of how to capture its BLE advertisement packet and what to do with the data. What works okay for me is having a BLE-MQTT bridge that listens for these BLE broadcasts, decodes them and ships the sensor values through MQTT messages. The MQTT broker is them responsible for relaying the sensor data to interested parties. This is the topology shown in the diagram above.
+
+A popular choice for a BLE-MQTT bridge is the [ESPHome](https://github.com/esphome/esphome) project, which runs on our beloved [ESP32](https://www.espressif.com/en/products/socs/esp32) boards. I forked ESPHome into [rbaron/esphome](https://github.com/rbaron/esphome) and added support for the `b_parasite` platform. An example project using this fork is defined in this repo, under [bridge/](bridge/) (check out [README.md](bridge/README.md) there for more info).
+
+# Battery Life
+**tl;dr:** By taking readings 10 minutes apart, the battery should last for over a year.
+
+The main parameters involved in estimating the battery life are:
+* Current consumption (both in operation and during sleep)
+* Duty cycle (how much time it spends in operation vs. sleeping)
+* Battery capacity - this is roughly 230 mAh for CR2032 cells
+
+In the following screenshot, I measured the voltage of a 10 Ohm series resistor during the on-cycle, for a 8dBm transmitting power (the voltage is negative, so it is upside down):
+
+
+
+
+
+The short high peaks correspond to when the radio is active, sending broadcasting packets. The average current consumption during this active time is roughly 9mA. Let's round it to 10mA. During off time, I measure a current of less than 3uA.
+
+With these parameters in hand, I put together [this spreadsheet](https://docs.google.com/spreadsheets/d/157JQiX20bGkTrlbvWbWRrs_WViL3MgVZffSCWRR7uAI/edit#gid=0) in which you can estimate the battery life. For example, for an active time of one second and sleep time of ten minutes, we see a runtime of 488.10 days.
+
+
+
+
+
+# License
+The hardware and associated design files are released under the [Creative Commons CC BY-SA 4.0 license](https://creativecommons.org/licenses/by-sa/4.0/).
+The code is released under the [MIT license](https://opensource.org/licenses/MIT).
\ No newline at end of file
diff --git a/hub/.gitignore b/bridge/.gitignore
similarity index 100%
rename from hub/.gitignore
rename to bridge/.gitignore
diff --git a/bridge/README.md b/bridge/README.md
new file mode 100644
index 0000000..8825e9f
--- /dev/null
+++ b/bridge/README.md
@@ -0,0 +1,21 @@
+# What's a Bridge (or a Hub)?
+b-parasite periodically broadcasts its sensors readings via bluetooth low energy (BLE). Usually, a bridge (a separate device) is used for capturing those broadcasts, decoding them, and forwarding them somewhere else (like MQTT or directly to a database).
+
+# An ESPHome-based Example Bridge
+This directory contains a hub implementation based on the [ESPHome project](https://github.com/esphome/esphome/), which runs on the popular [ESP32](https://www.espressif.com/en/products/socs/esp32) microcontrollers.
+
+The `parahub.yaml` file defines an ESPHome project using the `b_parasite` platform. This platform has not yet been merged in the official ESPHome repository, so if you want to use this example bridge, you'll need to use the [rbaron/esphome](https://github.com/rbaron/esphome) fork.
+
+## Secrets
+`parahub.yaml` contains some directives such as `!secret mqtt_password`, which instructs it to read secrets from a `secrets.yaml` file.
+
+This file is usually not commited to version control, so you'll need to populate your own. Here's an example:
+
+```yaml
+# Example secrets.yaml
+wifi_ssid: WIFI_NETWORK_NAME
+wifi_password: WIFI_PASSWORD
+mqtt_broker: MQTT_BROKER
+mqtt_password: MQTT_PASSWORD
+ota_password: OTA_PASSWORD
+``
\ No newline at end of file
diff --git a/hub/parahub.yaml b/bridge/parahub.yaml
similarity index 90%
rename from hub/parahub.yaml
rename to bridge/parahub.yaml
index 94b1e0a..cbf24fb 100644
--- a/hub/parahub.yaml
+++ b/bridge/parahub.yaml
@@ -9,8 +9,8 @@ wifi:
power_save_mode: none
mqtt:
- broker: CHANGE_ME
- username: CHANGE_ME
+ broker: !secret mqtt_broker
+ username: !secret mqtt_user
password: !secret mqtt_password
esp32_ble_tracker:
diff --git a/code/alternative/parasite/.clang-format b/code/alternative/parasite/.clang-format
deleted file mode 100644
index 2593ef5..0000000
--- a/code/alternative/parasite/.clang-format
+++ /dev/null
@@ -1 +0,0 @@
-BasedOnStyle: Google
\ No newline at end of file
diff --git a/code/alternative/parasite/.gitignore b/code/alternative/parasite/.gitignore
deleted file mode 100644
index 89cc49c..0000000
--- a/code/alternative/parasite/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-.pio
-.vscode/.browse.c_cpp.db*
-.vscode/c_cpp_properties.json
-.vscode/launch.json
-.vscode/ipch
diff --git a/code/alternative/parasite/Makefile b/code/alternative/parasite/Makefile
deleted file mode 100644
index 61a5f4d..0000000
--- a/code/alternative/parasite/Makefile
+++ /dev/null
@@ -1,8 +0,0 @@
-
-.PHONY: lint
-lint:
- find ./ -type f \( -iname \*.h -o -iname \*.cpp \) | xargs clang-format -n -Werror
-
-.PHONY: fix
-fix:
- find ./ -type f \( -iname \*.h -o -iname \*.cpp \) | xargs clang-format -i
\ No newline at end of file
diff --git a/code/alternative/parasite/boards/e73-tbb.json b/code/alternative/parasite/boards/e73-tbb.json
deleted file mode 100644
index b87b095..0000000
--- a/code/alternative/parasite/boards/e73-tbb.json
+++ /dev/null
@@ -1,61 +0,0 @@
-{
- "build": {
- "arduino":{
- "ldscript": "nrf52832_s132_v6.ld"
- },
- "core": "nRF5",
- "cpu": "cortex-m4",
- "extra_flags": "-DNRF52832_XXAA -DNRF52",
- "f_cpu": "64000000L",
- "hwids": [
- [
- "0x1a86",
- "0x7523"
- ]
- ],
- "usb_product": "E73-TBB nRF52832 development board",
- "mcu": "nrf52832",
- "variant": "Generic",
- "bsp": {
- "name": "Ebyte"
- },
- "softdevice": {
- "sd_flags": "-DS132",
- "sd_name": "s132",
- "sd_version": "6.1.1",
- "sd_fwid": "0x00B7"
- },
- "zephyr": {
- "variant": "E73-TBB"
- }
- },
- "connectivity": [
- "bluetooth"
- ],
- "debug": {
- "jlink_device": "nRF52832_xxAA",
- "svd_path": "nrf52.svd"
- },
- "frameworks": [
- "arduino",
- "zephyr"
- ],
- "name": "E73-TBB nRF52832 development board",
- "upload": {
- "maximum_ram_size": 65536,
- "maximum_size": 524288,
- "require_upload_port": true,
- "speed": 115200,
- "protocol": "nrfutil",
- "protocols": [
- "jlink",
- "nrfjprog",
- "nrfutil",
- "stlink",
- "cmsis-dap",
- "blackmagic"
- ]
- },
- "url": "https://www.ebyte.com/en/product-view-news.aspx?id=889",
- "vendor": "Ebyte"
-}
\ No newline at end of file
diff --git a/code/alternative/parasite/include/README b/code/alternative/parasite/include/README
deleted file mode 100644
index 194dcd4..0000000
--- a/code/alternative/parasite/include/README
+++ /dev/null
@@ -1,39 +0,0 @@
-
-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
diff --git a/code/alternative/parasite/lib/README b/code/alternative/parasite/lib/README
deleted file mode 100644
index 6debab1..0000000
--- a/code/alternative/parasite/lib/README
+++ /dev/null
@@ -1,46 +0,0 @@
-
-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
-#include
-
-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
diff --git a/code/alternative/parasite/lib/parasite/parasite/adc.cpp b/code/alternative/parasite/lib/parasite/parasite/adc.cpp
deleted file mode 100644
index ec6bda3..0000000
--- a/code/alternative/parasite/lib/parasite/parasite/adc.cpp
+++ /dev/null
@@ -1,86 +0,0 @@
-#include "parasite/adc.h"
-
-#include
-
-#include
-
-namespace parasite {
-namespace {
-// Resultion in bits. ADC read values will be mapped to [0, 2^reference].
-constexpr int kResolution = 10;
-
-/*
- * Battery monitoring
- */
-// Use the internal 0.6 reference with a gain of 1/2.
-// This lets us read values in the range [0, 1.2V].
-constexpr eAnalogReference kBattADCReference = AR_INTERNAL_1_2;
-constexpr double kMaxVoltage = 1.2;
-// How many samples will be averaged out.
-constexpr uint32_t kOversampling = 32;
-// Voltage divider.
-constexpr double kBattDividerR1 = 1470;
-constexpr double kBattDividerR2 = 470;
-constexpr double kBattDividerFactor =
- (kBattDividerR1 + kBattDividerR2) / kBattDividerR2;
-
-constexpr double kDiodeDrop = 0.6;
-
-// We need the sensor to behave well as the battery discharge. I plan on
-// using a CR2032 coin cell, whose voltage ranges from roughly 3.0 (charged)
-// to 2.0V (discharged). This is the input range for Vdd, so all of our
-// analog-to-digital (ADC) measurements are somewhat relative to this varying
-// voltage. The soil sensor itself is at heart an RC circuit: it charges to
-// 0.63Vcc within a time constant (RC). More importantly, within a cycle, the
-// final charge is proportional to Vcc. If we sample this with our ADC, and use
-// a sampling mode that is relative to Vcc (AR_VDD4 here), the final voltage
-// should "cancel out", keeping the sampled value constant as we vary Vcc. In
-// other words, in theory, the raw value returned from the ADC should remain
-// constant as I keep the sensor in the same state and vary the input voltage.
-// In practice, I'm seeing a small drift in the raw sampled values. This could
-// be due to some capacitance or lower transistor response times in the fast
-// discharge circuit.
-// I captured some (Vcc, raw adc sample) pairs and did a linear regression to
-// correct for these values when the sensor is out in the air and in the water.
-// This should mitigate the drifting issue enough.
-// In reality, I don't think leaving this correction out would be a big deal. I
-// expect the battery to discharge over a period of a few months, and I'd expect
-// plants to be watered with the period of days/couple of weeks. This drift
-// wouldn't be noticeable in that time scale, but since we can do better, why
-// not?
-// Some assumptions for these hardcoded values:
-// - Sampling resolution of 10 bits!
-// - Input (vdd) is in the range 2.0 - 3.0V
-double GetRawValAir(double vdd) { return vdd * 94.29 + 308.95; }
-double GetRawValWater(double vdd) { return vdd * 36.02 - 57.1; }
-
-} // namespace
-
-double BatteryMonitor::Read() {
- analogOversampling(kOversampling);
- analogReference(kBattADCReference);
- int batt_val = analogRead(pin_);
- double v_in = kMaxVoltage * batt_val / (1 << kResolution);
- return kBattDividerFactor * v_in;
-}
-
-soil_reading_t SoilMonitor::Read(double vdd) {
- analogOversampling(kOversampling);
- // Set up the analog reference to be VDD. This allows us to cancel out
- // the effect of the battery discharge across time, since the RC circuit
- // also depends linearly on VDD.
- analogReference(AR_VDD4);
- int raw = analogRead(pin_);
- double v_out_corr = (vdd * raw) / 1023 + 0.6;
- int raw_corr = v_out_corr / vdd * 1023;
-
- double air = GetRawValAir(vdd);
- double water = GetRawValWater(vdd);
- double percentage = static_cast(raw - air) / (water - air);
- // Serial.printf(
- // "vdd: %f, raw: %d, raw_corr: %d, v_out_corr: %f, percentage: %f\n", vdd,
- // raw, raw_corr, v_out_corr, 100 * percentage);
- return {raw, std::max(0.0, std::min(1.0, percentage))};
-}
-
-} // namespace parasite
\ No newline at end of file
diff --git a/code/alternative/parasite/lib/parasite/parasite/adc.h b/code/alternative/parasite/lib/parasite/parasite/adc.h
deleted file mode 100644
index 1ba5de7..0000000
--- a/code/alternative/parasite/lib/parasite/parasite/adc.h
+++ /dev/null
@@ -1,31 +0,0 @@
-#ifndef _PARASITE_ADC_H_
-#define _PARASITE_ADC_H_
-
-namespace parasite {
-
-class BatteryMonitor {
- public:
- explicit BatteryMonitor(int pin) : pin_(pin) {}
- double Read();
-
- private:
- const int pin_;
-};
-
-struct soil_reading_t {
- int raw;
- double parcent;
-};
-
-class SoilMonitor {
- public:
- explicit SoilMonitor(int pin) : pin_(pin) {}
-
- soil_reading_t Read(double vdd);
-
- private:
- // Pin the analog signal is connected to.
- const int pin_;
-};
-} // namespace parasite
-#endif // _PARASITE_ADC_H_
\ No newline at end of file
diff --git a/code/alternative/parasite/lib/parasite/parasite/ble.cpp b/code/alternative/parasite/lib/parasite/parasite/ble.cpp
deleted file mode 100644
index b184167..0000000
--- a/code/alternative/parasite/lib/parasite/parasite/ble.cpp
+++ /dev/null
@@ -1,46 +0,0 @@
-#include "ble.h"
-
-#include
-
-namespace parasite {
-namespace {}
-
-void BLEAdvertiser::SetData(BLEAdvertisementData data) {
- data_ = std::move(data);
- if (is_running_) {
- Bluefruit.Advertising.stop();
- }
- Bluefruit.Advertising.clearData();
- Bluefruit.Advertising.setData(data_.GetRawData(), data_.GetDataLen());
- if (is_running_) {
- Bluefruit.Advertising.start(0);
- }
-}
-
-void BLEAdvertiser::Init() {
- ble_gap_addr_t addr;
- addr.addr_id_peer = 1;
- addr.addr_type = BLE_GAP_ADDR_TYPE_PUBLIC;
- std::copy(std::begin(mac_addr_), std::end(mac_addr_), std::begin(addr.addr));
- Bluefruit.begin(1, 1);
- Bluefruit.setName("Parasite");
- Bluefruit.setAddr(&addr);
- is_initialized_ = true;
-}
-
-void BLEAdvertiser::Start() {
- if (!is_initialized_) {
- Init();
- }
- Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
- Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
- Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
- is_running_ = true;
-}
-
-void BLEAdvertiser::Stop() {
- Bluefruit.Advertising.stop();
- is_running_ = false;
-}
-
-} // namespace parasite
\ No newline at end of file
diff --git a/code/alternative/parasite/lib/parasite/parasite/ble.h b/code/alternative/parasite/lib/parasite/parasite/ble.h
deleted file mode 100644
index 5722e08..0000000
--- a/code/alternative/parasite/lib/parasite/parasite/ble.h
+++ /dev/null
@@ -1,30 +0,0 @@
-#ifndef _PARASITE_BLE_H_
-#define _PARASITE_BLE_H_
-
-#include
-#include
-
-#include "parasite/ble_advertisement_data.h"
-
-namespace parasite {
-
-using MACAddr = std::array;
-
-class BLEAdvertiser {
- public:
- explicit BLEAdvertiser(MACAddr mac_addr) : mac_addr_(std::move(mac_addr)) {}
- void SetData(BLEAdvertisementData data);
- void Start();
- void Stop();
- bool IsRunning() const { return is_running_; };
-
- private:
- bool is_initialized_;
- bool is_running_;
- void Init();
- BLEAdvertisementData data_;
- MACAddr mac_addr_;
-};
-
-} // namespace parasite
-#endif // _PARASITE_BLE_H_
\ No newline at end of file
diff --git a/code/alternative/parasite/lib/parasite/parasite/ble_advertisement_data.cpp b/code/alternative/parasite/lib/parasite/parasite/ble_advertisement_data.cpp
deleted file mode 100644
index 6ad8829..0000000
--- a/code/alternative/parasite/lib/parasite/parasite/ble_advertisement_data.cpp
+++ /dev/null
@@ -1,27 +0,0 @@
-#include "ble_advertisement_data.h"
-
-#include
-
-namespace parasite {
-
-void BLEAdvertisementData::SetSoilMoistureRaw(int soil_moisture_raw) {
- uint16_t packed_value = soil_moisture_raw;
- data_[kRawSoilMoistureOffset] = packed_value >> 8;
- data_[kRawSoilMoistureOffset + 1] = packed_value & 0xff;
-}
-
-void BLEAdvertisementData::SetSoilMoisturePercent(
- double soil_moisture_percent) {
- // TODO: 1.0 * MAX is too close to overflow for comfort?
- uint16_t packed_value = ((1 << 16) - 1) * soil_moisture_percent;
- data_[kPercentSoilMoistureOffset] = packed_value >> 8;
- data_[kPercentSoilMoistureOffset + 1] = packed_value & 0xff;
-}
-
-void BLEAdvertisementData::SetBatteryVoltage(double battery_voltage) {
- uint16_t packed_value = 1000 * battery_voltage;
- data_[kBatteryVoltageOffset] = packed_value >> 8;
- data_[kBatteryVoltageOffset + 1] = packed_value & 0xff;
-}
-
-} // namespace parasite
\ No newline at end of file
diff --git a/code/alternative/parasite/lib/parasite/parasite/ble_advertisement_data.h b/code/alternative/parasite/lib/parasite/parasite/ble_advertisement_data.h
deleted file mode 100644
index c024614..0000000
--- a/code/alternative/parasite/lib/parasite/parasite/ble_advertisement_data.h
+++ /dev/null
@@ -1,51 +0,0 @@
-#ifndef _PARASITE_BLE_ADVERTISEMENT_DATA_H_
-#define _PARASITE_BLE_ADVERTISEMENT_DATA_H_
-
-// #include
-#include
-
-#include
-
-namespace parasite {
-
-constexpr size_t kAdvertisementDataLen = 21;
-constexpr size_t kRawSoilMoistureOffset = 14;
-constexpr size_t kPercentSoilMoistureOffset = kRawSoilMoistureOffset + 2;
-constexpr size_t kBatteryVoltageOffset = kRawSoilMoistureOffset + 4;
-
-class BLEAdvertisementData {
- public:
- void SetSoilMoistureRaw(int soil_moisture_raw);
- void SetSoilMoisturePercent(double soil_moisture_percent);
- void SetBatteryVoltage(double battery_voltage);
- const uint8_t* GetRawData() const { return data_; }
- size_t GetDataLen() const { return kAdvertisementDataLen; }
-
- private:
- uint8_t data_[kAdvertisementDataLen] = {
- 9, // Length of name + data type.
- BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME,
- 'P',
- 'a',
- 'r',
- 'a',
- 's',
- 'i',
- 't',
- 'e',
- 10, // Length of the service data + data type.
- BLE_GAP_AD_TYPE_SERVICE_DATA,
- 0x1a,
- 0x18, // Environment sensor service UUID (0x181a).
- 0x00,
- 0x00, // Raw soil humidity (2 bytes).
- 0x00,
- 0x00, // Percentage soil humidity (2 bytes).
- 0x00,
- 0x00, // Battery voltage (2 bytes representing millivolts).
- 0x00,
- };
-};
-
-} // namespace parasite
-#endif // _PARASITE_BLE_ADVERTISEMENT_DATA_H_
\ No newline at end of file
diff --git a/code/alternative/parasite/lib/parasite/parasite/pwm.cpp b/code/alternative/parasite/lib/parasite/parasite/pwm.cpp
deleted file mode 100644
index deb6bf4..0000000
--- a/code/alternative/parasite/lib/parasite/parasite/pwm.cpp
+++ /dev/null
@@ -1,39 +0,0 @@
-#include "pwm.h"
-
-#include
-#include
-#include
-
-namespace parasite {
-
-namespace {
-// nRF52 PWM specs/guide:
-// https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.nrf52832.ps.v1.1%2Fpwm.html
-// Adafruit's HardwarePWM wapper:
-// https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/a86fd9f36c88db96f676cead1e836377a37c7b05/cores/nRF5/HardwarePWM.cpp
-
-// No scaling. The PWM counter will increase with a frequency of 16MHz.
-constexpr unsigned long kPWMFrequencyPrescale = PWM_PRESCALER_PRESCALER_DIV_1;
-} // namespace
-
-SquareWaveGenerator::SquareWaveGenerator(double frequency, int pin_number)
- : frequency_(frequency), pin_number_(pin_number) {
- // In conjunction with the PWM clock frequency, max_count defines the
- // frequency of the square wave.
- const int max_count = 16e6 / frequency;
-
- // Since we want a duty cycle of 0.5, we flip the PVM output when the counter
- // reaches max_count / 2;
- const int flip_at_count = max_count / 2;
-
- HwPWM0.addPin(pin_number);
- HwPWM0.setClockDiv(kPWMFrequencyPrescale);
- HwPWM0.setMaxValue(max_count);
- HwPWM0.writePin(pin_number, flip_at_count);
-}
-
-void SquareWaveGenerator::Start() { HwPWM0.begin(); }
-
-void SquareWaveGenerator::Stop() { HwPWM0.stop(); }
-
-} // namespace parasite
\ No newline at end of file
diff --git a/code/alternative/parasite/lib/parasite/parasite/pwm.h b/code/alternative/parasite/lib/parasite/parasite/pwm.h
deleted file mode 100644
index 264a8d5..0000000
--- a/code/alternative/parasite/lib/parasite/parasite/pwm.h
+++ /dev/null
@@ -1,25 +0,0 @@
-#ifndef _PARASITE_PWM_H_
-#define _PARASITE_PWM_H_
-
-namespace parasite {
-
-// This is a simple, single-channel PWM-based square wave generator.
-// This only ever works for a single pin. If you call this function
-// twice with different pin numbers, nothing good will come out of it.
-// I am particularly proud of how well I resisted making this generic
-// and reusable.
-void SetupSquareWave(double frequency, int pin_number);
-
-class SquareWaveGenerator {
- public:
- SquareWaveGenerator(double frequency, int pin_number);
- void Start();
- void Stop();
-
- private:
- double frequency_;
- int pin_number_;
-};
-
-} // namespace parasite
-#endif // _PARASITE_PWM_H_
\ No newline at end of file
diff --git a/code/alternative/parasite/platformio.ini b/code/alternative/parasite/platformio.ini
deleted file mode 100644
index 7766280..0000000
--- a/code/alternative/parasite/platformio.ini
+++ /dev/null
@@ -1,33 +0,0 @@
-; 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:e73-tbb]
-; platform = nordicnrf52
-; board = adafruit_feather_nrf52832
-; framework = arduino
-; upload_port = /dev/cu.usbserial-14330
-; monitor_port = /dev/cu.usbserial-14330
-; ; build_flags = -DNRF52 -DS132 -DNRF51_S132 -DNRF5
-
-; ; Let's keep it simple for now and just use adafruit_feather_nrf52832.
-; ; board = e73-tbb
-; ; board_build.ldscript = ./ldscripts/nrf52832_s132_v6.ld
-
-[env:nrf52840]
-platform = nordicnrf52
-board = adafruit_feather_nrf52840
-framework = arduino
-upload_port = /dev/cu.usbmodem14101
-monitor_port = /dev/cu.usbmodem14101
-; build_flags = -DUSE_LFSYNT
-debug_tool = jlink
-; build_flags = -DUSE_TINYUSB -DNRF52 -DS140 -DNRF5
-; build_flags = -DCFG_DEBUG=0
\ No newline at end of file
diff --git a/code/alternative/parasite/src/main.cpp b/code/alternative/parasite/src/main.cpp
deleted file mode 100644
index a634ffb..0000000
--- a/code/alternative/parasite/src/main.cpp
+++ /dev/null
@@ -1,111 +0,0 @@
-// #define NRF_LOG_BACKEND_UART_ENABLED 0
-// #define NRF_LOG_ENABLED 0
-
-#include
-
-#include
-
-#include "parasite/adc.h"
-#include "parasite/ble.h"
-#include "parasite/ble_advertisement_data.h"
-#include "parasite/pwm.h"
-
-// variants/feather_nrf52840_express/variant.cpp
-constexpr int kLED1Pin = 17; // P0.28
-constexpr int kLED2Pin = 18; // P0.02
-constexpr int kPWMPin = 33; // 0.09
-constexpr int kSoilAnalogPin = 21; // P0.31, AIN7
-constexpr int kBattAnalogPin = 15; // P0.05, AIN3
-constexpr int kBattMonitorEnablePin = 19; // P0.03, AIN1
-constexpr int kDischargeEnablePin = 16; // P0.30;
-constexpr double kPWMFrequency = 500000;
-
-// parasite::SquareWaveGenerator square_wave_generator(kPWMFrequency, kPWMPin);
-
-const parasite::MACAddr kMACAddr = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
-parasite::BLEAdvertiser advertiser(kMACAddr);
-
-parasite::BatteryMonitor batt_monitor(kBattAnalogPin);
-parasite::SoilMonitor soil_monitor(kSoilAnalogPin);
-
-SoftwareTimer timer;
-
-void updateAdvertisingData(parasite::BLEAdvertiser* advertiser,
- const parasite::soil_reading_t& soil_reading,
- double battery_voltage) {
- parasite::BLEAdvertisementData data;
-
- data.SetSoilMoistureRaw(soil_reading.raw);
- data.SetSoilMoisturePercent(soil_reading.parcent);
- data.SetBatteryVoltage(battery_voltage);
-
- advertiser->SetData(data);
-
- if (!advertiser->IsRunning()) {
- advertiser->Start();
- }
-}
-
-/*
- * * WARNING *
- * To get this callback to work, I had to increase the freeRTOS timer stack in
- * ~/.platformio/packages/framework-arduinoadafruitnrf52/cores/nRF5/freertos/config/FreeRTOSConfig.h
- * #define configTIMER_TASK_STACK_DEPTH (1024)
- */
-void timer_cb(TimerHandle_t timer_handle) {
- parasite::SquareWaveGenerator square_wave_generator(kPWMFrequency, kPWMPin);
-
- // digitalToggle(kLED1Pin);
-
- digitalWrite(kBattMonitorEnablePin, HIGH);
- delay(10);
- double battery_voltage = batt_monitor.Read();
- digitalWrite(kBattMonitorEnablePin, LOW);
- Serial.printf("Batt voltage: %f\n", battery_voltage);
-
- digitalWrite(kDischargeEnablePin, HIGH);
- square_wave_generator.Start();
- delay(10);
- parasite::soil_reading_t soil_reading = soil_monitor.Read(battery_voltage);
- square_wave_generator.Stop();
- digitalWrite(kDischargeEnablePin, LOW);
- Serial.printf("Moisture val: %d, %f%%\n", soil_reading.raw,
- 100 * soil_reading.parcent);
-
- updateAdvertisingData(&advertiser, soil_reading, battery_voltage);
-
- // Keep adversiting for 1 second.
- delay(500);
- advertiser.Stop();
-}
-
-void setup() {
- // Serial.begin(9600);
- // // pinMode(kLED1Pin, OUTPUT);
- // pinMode(kDischargeEnablePin, OUTPUT);
- // pinMode(kBattMonitorEnablePin, OUTPUT);
-
- // digitalWrite(kDischargeEnablePin, HIGH);
- // parasite::SquareWaveGenerator square_wave_generator(kPWMFrequency,
- // kPWMPin); square_wave_generator.Start();
-
- // timer.begin(1000, timer_cb, /*timerID=*/nullptr, /*repeating=*/true);
- // timer.start();
-
- // Suspend the loop task. Under the hood this is a freeRTOS task set up
- // by the Adafruit_nNRF52_Arduino package.
- // suspendLoop();
- // NRF_UART0->TASKS_STOPTX = 1;
- // NRF_UART0->TASKS_STOPRX = 1;
- // NRF_UART0->ENABLE = 0;
-
- // NRF_SPI0->ENABLE = 0;
- // __disable_irq()
- // sd_power_system_off();
- // NRF_POWER->SYSTEMOFF = POWER_SYSTEMOFF_SYSTEMOFF_Enter;
-}
-
-void loop() {
- Serial.printf("Ok");
- delay(1000);
-}
\ No newline at end of file
diff --git a/code/alternative/parasite/test/README b/code/alternative/parasite/test/README
deleted file mode 100644
index b94d089..0000000
--- a/code/alternative/parasite/test/README
+++ /dev/null
@@ -1,11 +0,0 @@
-
-This directory is intended for PlatformIO Unit Testing 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/page/plus/unit-testing.html
diff --git a/code/b-parasite/README.md b/code/b-parasite/README.md
new file mode 100644
index 0000000..8367bae
--- /dev/null
+++ b/code/b-parasite/README.md
@@ -0,0 +1,25 @@
+# Overview
+
+This is the b-parasite formware based on Nordic's [nRF5 SDK](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fstruct_sdk%2Fstruct%2Fsdk_nrf5_latest.html&cp=7_1).
+
+It uses Nordic's SoftDevice, which should additionally be flashed to the chip before running our firmware.
+
+I use a [JLink probe](https://www.segger.com/products/debug-probes/j-link/) for flashing and debugging.
+
+# Configuration
+The b-parasite specific configuration, such as active/sleep time and transmitting power are defined in [config/prst_config.h](./config/prst_config.h).
+
+# Flashing SoftDevice and Firmware
+```bash
+# Flash softdevice
+$ SDK_ROOT=~/dev/nrf52/sdk/nRF5_SDK_17.0.2_d674dde make flash_softdevice
+# Compile annd flash our firmware
+$ SDK_ROOT=~/dev/nrf52/sdk/nRF5_SDK_17.0.2_d674dde make flash
+```
+
+# Debugging
+Calls to `NRF_LOG` will be readable on the console using `JLinkRTTLogger`. This is the handy one-liner I use for pulling log messages:
+
+```bash
+$ echo "\n\n\n\n0\n/dev/stdout" | JLinkRTTLogger | sed 's/^.*app: //'
+```
\ No newline at end of file
diff --git a/code/b-parasite/config/prst_config.h b/code/b-parasite/config/prst_config.h
index 8821b31..4aca9aa 100644
--- a/code/b-parasite/config/prst_config.h
+++ b/code/b-parasite/config/prst_config.h
@@ -20,7 +20,7 @@
#define PRST_BLE_DEBUG 0
#define PRST_BLE_PROTOCOL_VERSION 1
#define PRST_BLE_MAC_ADDR_LSB1 0x00
-#define PRST_BLE_MAC_ADDR_LSB0 0x02
+#define PRST_BLE_MAC_ADDR_LSB0 0x07
#define PRST_BLE_ADV_NAME "prst"
// Total time spend advertising.
#define PRST_BLE_ADV_TIME_IN_MS 1000
diff --git a/hub/README.md b/hub/README.md
deleted file mode 100644
index c531c24..0000000
--- a/hub/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-The hub is an [ESPHome fork](https://github.com/rbaron/esphome/).
-This is the central device that listens to parasite BLE broadcasts and pipe them to MQTT.
diff --git a/img/excalidraw/diagram.excalidraw b/img/excalidraw/diagram.excalidraw
new file mode 100644
index 0000000..9738f40
--- /dev/null
+++ b/img/excalidraw/diagram.excalidraw
@@ -0,0 +1,1823 @@
+{
+ "type": "excalidraw",
+ "version": 2,
+ "source": "https://excalidraw.com",
+ "elements": [
+ {
+ "type": "rectangle",
+ "version": 312,
+ "versionNonce": 684547894,
+ "isDeleted": false,
+ "id": "wOM-TAHLMcwqpKHCbT5WQ",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 418.82126333286277,
+ "y": 10.851487989549383,
+ "strokeColor": "#5c940d00",
+ "backgroundColor": "#40c057",
+ "width": 42.26315724887501,
+ "height": 122.07805128752327,
+ "seed": 1617415378,
+ "groupIds": [
+ "p3Hzqef3t_oUXYygsPA9S"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [
+ "0Bbe93ekS0PflrvHmHeg6"
+ ]
+ },
+ {
+ "type": "diamond",
+ "version": 457,
+ "versionNonce": 480499498,
+ "isDeleted": false,
+ "id": "_oW-k8hBfnwIh2n7-qn28",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 418.631480597388,
+ "y": 97.20845771954362,
+ "strokeColor": "#00000000",
+ "backgroundColor": "#40c057",
+ "width": 41.68794222643415,
+ "height": 68.30660448875595,
+ "seed": 1420773006,
+ "groupIds": [
+ "p3Hzqef3t_oUXYygsPA9S"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElementIds": []
+ },
+ {
+ "type": "rectangle",
+ "version": 354,
+ "versionNonce": 1367869558,
+ "isDeleted": false,
+ "id": "yCIHZHMnW1Cbi9qhq3c2l",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 425.7105159987692,
+ "y": 15.07888778186313,
+ "strokeColor": "#5c940d00",
+ "backgroundColor": "#ced4da",
+ "width": 27.777829940730673,
+ "height": 38.19084419261929,
+ "seed": 706890322,
+ "groupIds": [
+ "p3Hzqef3t_oUXYygsPA9S"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElementIds": []
+ },
+ {
+ "type": "rectangle",
+ "version": 245,
+ "versionNonce": 1899192810,
+ "isDeleted": false,
+ "id": "tmWvYPpnp-_ijof9YkMx7",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 674.5454376730273,
+ "y": 145.38689392882497,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 99.61742054332387,
+ "height": 80.73908025568178,
+ "seed": 1498894418,
+ "groupIds": [
+ "iMEMBM9ETuRMmcVuyjb5q"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [
+ "0Bbe93ekS0PflrvHmHeg6",
+ "sSe_u6JguAwo2IjyTcmtf",
+ "fRE6LgSeHkUczs_esvaVK",
+ "ZBrCv5ckYDCUvXCJPLPTM"
+ ]
+ },
+ {
+ "type": "text",
+ "version": 195,
+ "versionNonce": 2006682038,
+ "isDeleted": false,
+ "id": "oUH0LGQEdKOh8RvwIhbr3",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 693.3541479446892,
+ "y": 173.25643405666588,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 62,
+ "height": 25,
+ "seed": 1501785170,
+ "groupIds": [
+ "iMEMBM9ETuRMmcVuyjb5q"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [],
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Bridge",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "middle"
+ },
+ {
+ "type": "arrow",
+ "version": 541,
+ "versionNonce": 151229610,
+ "isDeleted": false,
+ "id": "0Bbe93ekS0PflrvHmHeg6",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 472.86776592726926,
+ "y": 89.77686891778612,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 190.1376437805592,
+ "height": 93.11853697737601,
+ "seed": 1399056530,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [],
+ "startBinding": {
+ "elementId": "wOM-TAHLMcwqpKHCbT5WQ",
+ "focus": 0.02474516113281168,
+ "gap": 11.783345345531473
+ },
+ "endBinding": {
+ "elementId": "tmWvYPpnp-_ijof9YkMx7",
+ "focus": -0.41974699609185345,
+ "gap": 11.540027965198817
+ },
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 190.1376437805592,
+ 93.11853697737601
+ ]
+ ],
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow"
+ },
+ {
+ "type": "rectangle",
+ "version": 395,
+ "versionNonce": 707584758,
+ "isDeleted": false,
+ "id": "0LIbxMOC6ColzL-8abXE2",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 415.2542726553188,
+ "y": 217.5512893478588,
+ "strokeColor": "#5c940d00",
+ "backgroundColor": "#40c057",
+ "width": 42.26315724887501,
+ "height": 122.07805128752327,
+ "seed": 36529934,
+ "groupIds": [
+ "tt8TJi9GtJ7crdPOJdSId"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [
+ "sSe_u6JguAwo2IjyTcmtf"
+ ]
+ },
+ {
+ "type": "diamond",
+ "version": 543,
+ "versionNonce": 96431978,
+ "isDeleted": false,
+ "id": "GcMO1TyI9c28aiwvbRZko",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 415.06448991984405,
+ "y": 303.9082590778531,
+ "strokeColor": "#00000000",
+ "backgroundColor": "#40c057",
+ "width": 41.68794222643415,
+ "height": 68.30660448875595,
+ "seed": 252369106,
+ "groupIds": [
+ "tt8TJi9GtJ7crdPOJdSId"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElementIds": []
+ },
+ {
+ "type": "rectangle",
+ "version": 440,
+ "versionNonce": 1270944822,
+ "isDeleted": false,
+ "id": "gHXS2gMUfaGzBSxZDgx_8",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 422.1435253212252,
+ "y": 221.77868914017262,
+ "strokeColor": "#5c940d00",
+ "backgroundColor": "#ced4da",
+ "width": 27.777829940730673,
+ "height": 38.19084419261929,
+ "seed": 1581864270,
+ "groupIds": [
+ "tt8TJi9GtJ7crdPOJdSId"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElementIds": []
+ },
+ {
+ "type": "arrow",
+ "version": 440,
+ "versionNonce": 464646698,
+ "isDeleted": false,
+ "id": "sSe_u6JguAwo2IjyTcmtf",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 466.4930695089648,
+ "y": 292.4974606986219,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 199.469327059659,
+ "height": 100.64064478645665,
+ "seed": 1213076494,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [],
+ "startBinding": {
+ "elementId": "0LIbxMOC6ColzL-8abXE2",
+ "focus": 0.40581838827781747,
+ "gap": 8.975639604771004
+ },
+ "endBinding": {
+ "elementId": "tmWvYPpnp-_ijof9YkMx7",
+ "focus": 0.35665133375594604,
+ "gap": 8.583041104403435
+ },
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 199.469327059659,
+ -100.64064478645665
+ ]
+ ],
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow"
+ },
+ {
+ "type": "rectangle",
+ "version": 395,
+ "versionNonce": 641539446,
+ "isDeleted": false,
+ "id": "JMcoORPvDFzDR5KdgzVYc",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "dotted",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 460.6299769130841,
+ "y": 58.99837186739026,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 221.69611150568173,
+ "height": 246.53115012428975,
+ "seed": 780430738,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElementIds": []
+ },
+ {
+ "type": "text",
+ "version": 221,
+ "versionNonce": 1036393706,
+ "isDeleted": false,
+ "id": "FJCkBT3Rm8KTJOdpeG8PU",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "dotted",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 494.1253881790643,
+ "y": 1.2336734920351802,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 141,
+ "height": 50,
+ "seed": 2115066258,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [],
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Bluetooth Low\nEnergy (BLE)",
+ "baseline": 43,
+ "textAlign": "left",
+ "verticalAlign": "top"
+ },
+ {
+ "type": "line",
+ "version": 691,
+ "versionNonce": 2129002166,
+ "isDeleted": false,
+ "id": "KWmra4rj-q0Y77F5QZxEc",
+ "fillStyle": "cross-hatch",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1106.3373136383211,
+ "y": 42.97837101474643,
+ "strokeColor": "#000000",
+ "backgroundColor": "#12b886",
+ "width": 0,
+ "height": 94.32205860137395,
+ "seed": 816883218,
+ "groupIds": [
+ "JQBZmDFAhSoC_gBscHaZ9"
+ ],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "startBinding": null,
+ "endBinding": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 94.32205860137395
+ ]
+ ],
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null
+ },
+ {
+ "type": "line",
+ "version": 700,
+ "versionNonce": 1233579946,
+ "isDeleted": false,
+ "id": "zIyMxYMvu-0g0NMcxM5wc",
+ "fillStyle": "cross-hatch",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1105.524483534404,
+ "y": 138.27258431829262,
+ "strokeColor": "#000000",
+ "backgroundColor": "#12b886",
+ "width": 195.38140710284574,
+ "height": 0,
+ "seed": 1822555150,
+ "groupIds": [
+ "JQBZmDFAhSoC_gBscHaZ9"
+ ],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "startBinding": null,
+ "endBinding": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 195.38140710284574,
+ 0
+ ]
+ ],
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null
+ },
+ {
+ "type": "ellipse",
+ "version": 503,
+ "versionNonce": 710981622,
+ "isDeleted": false,
+ "id": "LM99pbPwmAjABzHNKAikD",
+ "fillStyle": "cross-hatch",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1112.2016511148652,
+ "y": 90.64348058066156,
+ "strokeColor": "#000000",
+ "backgroundColor": "#15aabf",
+ "width": 4.491526600065473,
+ "height": 4.491526600065473,
+ "seed": 392383054,
+ "groupIds": [
+ "JQBZmDFAhSoC_gBscHaZ9"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [
+ "ccBFKpwBlu_cKPnT6sIW3",
+ "uyIPQJH5LeK3gUwAZ_Puh"
+ ]
+ },
+ {
+ "type": "ellipse",
+ "version": 495,
+ "versionNonce": 857696874,
+ "isDeleted": false,
+ "id": "Brp3o6Vsr6zWjQeZKdSE5",
+ "fillStyle": "cross-hatch",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1125.676230915063,
+ "y": 97.38077048075985,
+ "strokeColor": "#000000",
+ "backgroundColor": "#15aabf",
+ "width": 4.491526600065473,
+ "height": 4.491526600065473,
+ "seed": 2064107922,
+ "groupIds": [
+ "JQBZmDFAhSoC_gBscHaZ9"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [
+ "ccBFKpwBlu_cKPnT6sIW3"
+ ]
+ },
+ {
+ "type": "ellipse",
+ "version": 495,
+ "versionNonce": 548555062,
+ "isDeleted": false,
+ "id": "P39Aj5dIhNYlzEIZPxyB5",
+ "fillStyle": "cross-hatch",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1139.150810715259,
+ "y": 104.11806038085763,
+ "strokeColor": "#000000",
+ "backgroundColor": "#15aabf",
+ "width": 4.491526600065473,
+ "height": 4.491526600065473,
+ "seed": 1902170254,
+ "groupIds": [
+ "JQBZmDFAhSoC_gBscHaZ9"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [
+ "ccBFKpwBlu_cKPnT6sIW3"
+ ]
+ },
+ {
+ "type": "ellipse",
+ "version": 494,
+ "versionNonce": 828017962,
+ "isDeleted": false,
+ "id": "8HQBDCBOxxRCQHvSk9NSl",
+ "fillStyle": "cross-hatch",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1152.6253905154551,
+ "y": 104.11806038085763,
+ "strokeColor": "#000000",
+ "backgroundColor": "#15aabf",
+ "width": 4.491526600065473,
+ "height": 4.491526600065473,
+ "seed": 1849476946,
+ "groupIds": [
+ "JQBZmDFAhSoC_gBscHaZ9"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [
+ "ccBFKpwBlu_cKPnT6sIW3"
+ ]
+ },
+ {
+ "type": "ellipse",
+ "version": 497,
+ "versionNonce": 289387126,
+ "isDeleted": false,
+ "id": "v_n8ka0bzv2Z2lqgfuE5Y",
+ "fillStyle": "cross-hatch",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1159.3626804155535,
+ "y": 90.64348058066156,
+ "strokeColor": "#000000",
+ "backgroundColor": "#15aabf",
+ "width": 4.491526600065473,
+ "height": 4.491526600065473,
+ "seed": 911445710,
+ "groupIds": [
+ "JQBZmDFAhSoC_gBscHaZ9"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [
+ "ccBFKpwBlu_cKPnT6sIW3"
+ ]
+ },
+ {
+ "type": "ellipse",
+ "version": 493,
+ "versionNonce": 1772312554,
+ "isDeleted": false,
+ "id": "6hPniRQlwWldf88smHFTo",
+ "fillStyle": "cross-hatch",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1172.8372602157492,
+ "y": 77.16890078046544,
+ "strokeColor": "#000000",
+ "backgroundColor": "#15aabf",
+ "width": 4.491526600065473,
+ "height": 4.491526600065473,
+ "seed": 898370834,
+ "groupIds": [
+ "JQBZmDFAhSoC_gBscHaZ9"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [
+ "ccBFKpwBlu_cKPnT6sIW3"
+ ]
+ },
+ {
+ "type": "ellipse",
+ "version": 493,
+ "versionNonce": 1993208758,
+ "isDeleted": false,
+ "id": "w9OgHXSe3IWjFCu133B04",
+ "fillStyle": "cross-hatch",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1186.3118400159453,
+ "y": 70.43161088036726,
+ "strokeColor": "#000000",
+ "backgroundColor": "#15aabf",
+ "width": 4.491526600065473,
+ "height": 4.491526600065473,
+ "seed": 1121223950,
+ "groupIds": [
+ "JQBZmDFAhSoC_gBscHaZ9"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [
+ "ccBFKpwBlu_cKPnT6sIW3"
+ ]
+ },
+ {
+ "type": "ellipse",
+ "version": 497,
+ "versionNonce": 470076074,
+ "isDeleted": false,
+ "id": "qpHubUAO5Yck4lYcYBI8W",
+ "fillStyle": "cross-hatch",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1193.0491299160435,
+ "y": 50.21974118007266,
+ "strokeColor": "#000000",
+ "backgroundColor": "#15aabf",
+ "width": 4.491526600065473,
+ "height": 4.491526600065473,
+ "seed": 1718393554,
+ "groupIds": [
+ "JQBZmDFAhSoC_gBscHaZ9"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [
+ "ccBFKpwBlu_cKPnT6sIW3"
+ ]
+ },
+ {
+ "type": "ellipse",
+ "version": 492,
+ "versionNonce": 729943286,
+ "isDeleted": false,
+ "id": "Z5RTqsCihvAJSIxnKDWDh",
+ "fillStyle": "cross-hatch",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1206.5237097162394,
+ "y": 56.95703108017101,
+ "strokeColor": "#000000",
+ "backgroundColor": "#15aabf",
+ "width": 4.491526600065473,
+ "height": 4.491526600065473,
+ "seed": 2019341134,
+ "groupIds": [
+ "JQBZmDFAhSoC_gBscHaZ9"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [
+ "ccBFKpwBlu_cKPnT6sIW3"
+ ]
+ },
+ {
+ "type": "ellipse",
+ "version": 499,
+ "versionNonce": 1328145770,
+ "isDeleted": false,
+ "id": "3T6kPYxByT-HGYYQDeiyU",
+ "fillStyle": "cross-hatch",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1219.9982895164367,
+ "y": 77.16890078046544,
+ "strokeColor": "#000000",
+ "backgroundColor": "#15aabf",
+ "width": 4.491526600065473,
+ "height": 4.491526600065473,
+ "seed": 1333096594,
+ "groupIds": [
+ "JQBZmDFAhSoC_gBscHaZ9"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [
+ "ccBFKpwBlu_cKPnT6sIW3"
+ ]
+ },
+ {
+ "type": "ellipse",
+ "version": 498,
+ "versionNonce": 2095119926,
+ "isDeleted": false,
+ "id": "xvXxEx432Wc-Pv3mv15dM",
+ "fillStyle": "cross-hatch",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1233.4728693166328,
+ "y": 70.43161088036726,
+ "strokeColor": "#000000",
+ "backgroundColor": "#15aabf",
+ "width": 4.491526600065473,
+ "height": 4.491526600065473,
+ "seed": 558176654,
+ "groupIds": [
+ "JQBZmDFAhSoC_gBscHaZ9"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [
+ "ccBFKpwBlu_cKPnT6sIW3"
+ ]
+ },
+ {
+ "type": "ellipse",
+ "version": 495,
+ "versionNonce": 2086512682,
+ "isDeleted": false,
+ "id": "amyHCfO6TwVv-rMo1STQP",
+ "fillStyle": "cross-hatch",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1240.2101592167303,
+ "y": 56.95703108017101,
+ "strokeColor": "#000000",
+ "backgroundColor": "#15aabf",
+ "width": 4.491526600065473,
+ "height": 4.491526600065473,
+ "seed": 1415417426,
+ "groupIds": [
+ "JQBZmDFAhSoC_gBscHaZ9"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [
+ "ccBFKpwBlu_cKPnT6sIW3"
+ ]
+ },
+ {
+ "type": "ellipse",
+ "version": 493,
+ "versionNonce": 1070581622,
+ "isDeleted": false,
+ "id": "zDrraP8_TxXx2uVsHcNIa",
+ "fillStyle": "cross-hatch",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1253.6847390169266,
+ "y": 70.43161088036726,
+ "strokeColor": "#000000",
+ "backgroundColor": "#15aabf",
+ "width": 4.491526600065473,
+ "height": 4.491526600065473,
+ "seed": 1422309326,
+ "groupIds": [
+ "JQBZmDFAhSoC_gBscHaZ9"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [
+ "ccBFKpwBlu_cKPnT6sIW3"
+ ]
+ },
+ {
+ "type": "ellipse",
+ "version": 496,
+ "versionNonce": 998970090,
+ "isDeleted": false,
+ "id": "foRnTb6MAqswc_eaBcSDP",
+ "fillStyle": "cross-hatch",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1267.1593188171225,
+ "y": 63.69432098026921,
+ "strokeColor": "#000000",
+ "backgroundColor": "#15aabf",
+ "width": 4.491526600065473,
+ "height": 4.491526600065473,
+ "seed": 1241248786,
+ "groupIds": [
+ "JQBZmDFAhSoC_gBscHaZ9"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [
+ "ccBFKpwBlu_cKPnT6sIW3"
+ ]
+ },
+ {
+ "type": "ellipse",
+ "version": 494,
+ "versionNonce": 1687382198,
+ "isDeleted": false,
+ "id": "FBr-n-yCOOjrbFZqFgjxg",
+ "fillStyle": "cross-hatch",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1280.6338986173198,
+ "y": 50.21974118007266,
+ "strokeColor": "#000000",
+ "backgroundColor": "#15aabf",
+ "width": 4.491526600065473,
+ "height": 4.491526600065473,
+ "seed": 1526369806,
+ "groupIds": [
+ "JQBZmDFAhSoC_gBscHaZ9"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [
+ "ccBFKpwBlu_cKPnT6sIW3"
+ ]
+ },
+ {
+ "type": "line",
+ "version": 841,
+ "versionNonce": 1866278314,
+ "isDeleted": false,
+ "id": "G0CqHcZGBUDgD7usFJihP",
+ "fillStyle": "cross-hatch",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1116.717578006715,
+ "y": 177.66266117630005,
+ "strokeColor": "#0b7285",
+ "backgroundColor": "#15aabf",
+ "width": 166.74792502742872,
+ "height": 54.834053909132045,
+ "seed": 1380426194,
+ "groupIds": [
+ "JQBZmDFAhSoC_gBscHaZ9"
+ ],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "startBinding": null,
+ "endBinding": null,
+ "points": [
+ [
+ 0,
+ -83.62875535215913
+ ],
+ [
+ 12.35169815017993,
+ -77.82720016040798
+ ],
+ [
+ 25.639131008706805,
+ -71.27705720197928
+ ],
+ [
+ 39.11371080890303,
+ -71.46420414364857
+ ],
+ [
+ 45.85100070900118,
+ -85.31307782718368
+ ],
+ [
+ 59.51272745086683,
+ -98.97480456904943
+ ],
+ [
+ 72.9873072510631,
+ -105.15065364413918
+ ],
+ [
+ 79.3503032678225,
+ -126.11111111111131
+ ],
+ [
+ 92.45058918467998,
+ -118.81238038600489
+ ],
+ [
+ 106.11231592654556,
+ -98.03906986070237
+ ],
+ [
+ 119.96118961008064,
+ -105.33780058580867
+ ],
+ [
+ 126.51133256850939,
+ -118.81238038600489
+ ],
+ [
+ 140.17305931037498,
+ -104.96350670246997
+ ],
+ [
+ 153.83478605224082,
+ -111.51364966089865
+ ],
+ [
+ 166.74792502742872,
+ -124.80108251942546
+ ]
+ ],
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null
+ },
+ {
+ "type": "text",
+ "version": 206,
+ "versionNonce": 181461494,
+ "isDeleted": false,
+ "id": "RWqawdyp3leLwOKNZ-iOU",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "dotted",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0.46688202509651155,
+ "x": 515.5019965020822,
+ "y": 104.09602956828192,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 116,
+ "height": 25,
+ "seed": 808593870,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [],
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Broadcasts",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top"
+ },
+ {
+ "type": "text",
+ "version": 321,
+ "versionNonce": 1889460330,
+ "isDeleted": false,
+ "id": "_4lYYh2jDPrXnCS7uecV5",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "dotted",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 5.833439126035513,
+ "x": 487.440456030427,
+ "y": 215.27225940098288,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 116,
+ "height": 25,
+ "seed": 814621838,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [],
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Broadcasts",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top"
+ },
+ {
+ "type": "arrow",
+ "version": 1083,
+ "versionNonce": 361575222,
+ "isDeleted": false,
+ "id": "uyIPQJH5LeK3gUwAZ_Puh",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1035.3962051739086,
+ "y": 142.04442207355027,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 62.75129968974352,
+ "height": 43.27864776360924,
+ "seed": 1139602126,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [],
+ "startBinding": {
+ "elementId": "gb0fyq_8y3JU2Q4VM4zhA",
+ "focus": -0.027144464292857252,
+ "gap": 6.59922111607176
+ },
+ "endBinding": {
+ "elementId": "LM99pbPwmAjABzHNKAikD",
+ "focus": 1.9666821956543166,
+ "gap": 15.081113546781326
+ },
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 62.75129968974352,
+ -43.27864776360924
+ ]
+ ],
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow"
+ },
+ {
+ "type": "text",
+ "version": 277,
+ "versionNonce": 997933866,
+ "isDeleted": false,
+ "id": "flLS6pfuc7kQcz2xyExhE",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1145.4845578789934,
+ "y": 6.388086888697103,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 121,
+ "height": 25,
+ "seed": 831764494,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [],
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Dashboards",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top"
+ },
+ {
+ "type": "arrow",
+ "version": 1238,
+ "versionNonce": 1059937398,
+ "isDeleted": false,
+ "id": "VEEeMo-f-xKXLLwJruUzN",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1029.817460120521,
+ "y": 172.9077869576372,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 102.13073190264868,
+ "height": 60.10673962778938,
+ "seed": 357989454,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [],
+ "startBinding": {
+ "elementId": "gb0fyq_8y3JU2Q4VM4zhA",
+ "focus": -0.5727925248587693,
+ "gap": 1
+ },
+ "endBinding": {
+ "elementId": "M0JZhpgvuAHEviL4CyrV4",
+ "focus": -0.3559735274582948,
+ "gap": 8.009588068181756
+ },
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 102.13073190264868,
+ 60.10673962778938
+ ]
+ ],
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow"
+ },
+ {
+ "type": "rectangle",
+ "version": 350,
+ "versionNonce": 440225258,
+ "isDeleted": false,
+ "id": "M0JZhpgvuAHEviL4CyrV4",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1139.9577800913514,
+ "y": 211.26205483969142,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 96.7060990767045,
+ "height": 66.06833718039775,
+ "seed": 135796302,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [
+ "VEEeMo-f-xKXLLwJruUzN"
+ ]
+ },
+ {
+ "type": "draw",
+ "version": 321,
+ "versionNonce": 762731958,
+ "isDeleted": false,
+ "id": "TswycczKDbWWWYovp1yrt",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1142.1993239478854,
+ "y": 213.56346863585617,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 91.74227627840901,
+ "height": 33.64263361150563,
+ "seed": 192183570,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [],
+ "startBinding": null,
+ "endBinding": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 48.992476029829504,
+ 29.031871448863626
+ ],
+ [
+ 91.74227627840901,
+ -4.610762162642004
+ ]
+ ],
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null
+ },
+ {
+ "type": "text",
+ "version": 315,
+ "versionNonce": 1374683306,
+ "isDeleted": false,
+ "id": "e6w_ruGf4sZTHZFvzFXjX",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1124.6597065828287,
+ "y": 176.01932357193573,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 118,
+ "height": 25,
+ "seed": 1966972882,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [],
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Email alerts",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top"
+ },
+ {
+ "type": "arrow",
+ "version": 759,
+ "versionNonce": 93140726,
+ "isDeleted": false,
+ "id": "KSqswVSYD_78t7qmbkMtJ",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1035.2730433193344,
+ "y": 215.99634385804688,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 87.52523035454465,
+ "height": 164.698790467172,
+ "seed": 297106770,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [],
+ "startBinding": {
+ "elementId": "gb0fyq_8y3JU2Q4VM4zhA",
+ "focus": -0.5429782793755688,
+ "gap": 6.455583198813031
+ },
+ "endBinding": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 87.52523035454465,
+ 164.698790467172
+ ]
+ ],
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow"
+ },
+ {
+ "type": "rectangle",
+ "version": 686,
+ "versionNonce": 2090365302,
+ "isDeleted": false,
+ "id": "U-6TLgFXn-Sg0w7A6RVCv",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "dotted",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 769.6500739788311,
+ "y": 66.02776584377523,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 164.7852228338068,
+ "height": 241.7388527610085,
+ "seed": 249950134,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElementIds": []
+ },
+ {
+ "type": "text",
+ "version": 395,
+ "versionNonce": 209324266,
+ "isDeleted": false,
+ "id": "r-2p9jtokKr2b3Q4l2X0R",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "dotted",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 773.0731419387031,
+ "y": 33.486822351232604,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 132,
+ "height": 25,
+ "seed": 578066538,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [],
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "WiFi/Internet",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top"
+ },
+ {
+ "type": "rectangle",
+ "version": 563,
+ "versionNonce": 381080246,
+ "isDeleted": false,
+ "id": "gb0fyq_8y3JU2Q4VM4zhA",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 929.2000395771973,
+ "y": 142.56387646167298,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 99.61742054332387,
+ "height": 80.73908025568178,
+ "seed": 710300394,
+ "groupIds": [
+ "UamYFq4ppxUxy97FWAiqE"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [
+ "0Bbe93ekS0PflrvHmHeg6",
+ "sSe_u6JguAwo2IjyTcmtf",
+ "fRE6LgSeHkUczs_esvaVK",
+ "VEEeMo-f-xKXLLwJruUzN",
+ "uyIPQJH5LeK3gUwAZ_Puh",
+ "KSqswVSYD_78t7qmbkMtJ",
+ "ZBrCv5ckYDCUvXCJPLPTM"
+ ]
+ },
+ {
+ "type": "text",
+ "version": 522,
+ "versionNonce": 1927109546,
+ "isDeleted": false,
+ "id": "7OAhcsf0QbumiPFcDk2-o",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 945.5087498488593,
+ "y": 157.93341658951385,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 67,
+ "height": 50,
+ "seed": 186394806,
+ "groupIds": [
+ "UamYFq4ppxUxy97FWAiqE"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [],
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "MQTT\nBroker",
+ "baseline": 43,
+ "textAlign": "center",
+ "verticalAlign": "middle"
+ },
+ {
+ "type": "line",
+ "version": 4155,
+ "versionNonce": 955714550,
+ "isDeleted": false,
+ "id": "U4dUbAeh1VRC2mwmWtOb5",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1140.0449754456638,
+ "y": 388.080463840647,
+ "strokeColor": "#0a11d3",
+ "backgroundColor": "#228be6",
+ "width": 88.21658171083376,
+ "height": 113.8575037534261,
+ "seed": 82828982,
+ "groupIds": [
+ "ZKBrme1TmS-BgybKc47Dj"
+ ],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "startBinding": null,
+ "endBinding": null,
+ "points": [
+ [
+ -0.22814350714115691,
+ -43.414939319563715
+ ],
+ [
+ 0.06274947619197979,
+ 42.63794490105306
+ ],
+ [
+ -0.21453039840335475,
+ 52.43469208825097
+ ],
+ [
+ 4.315205554872581,
+ 56.66774540453215
+ ],
+ [
+ 20.089784992984285,
+ 60.25027917349701
+ ],
+ [
+ 46.7532926683984,
+ 61.365826671969444
+ ],
+ [
+ 72.22851104292477,
+ 59.584691681394986
+ ],
+ [
+ 85.76368213524371,
+ 55.325139565662596
+ ],
+ [
+ 87.67263486434864,
+ 51.7342924478499
+ ],
+ [
+ 87.94074036468018,
+ 43.84700272879395
+ ],
+ [
+ 87.73030872197806,
+ -36.195582644606276
+ ],
+ [
+ 87.2559282533682,
+ -43.758132174307036
+ ],
+ [
+ 81.5915337527493,
+ -47.984890854524416
+ ],
+ [
+ 69.66352776578219,
+ -50.4328058257654
+ ],
+ [
+ 42.481213744224995,
+ -52.49167708145666
+ ],
+ [
+ 20.68789182864576,
+ -51.26396751574663
+ ],
+ [
+ 3.5475921483286084,
+ -47.099726468136254
+ ],
+ [
+ -0.2758413461535838,
+ -43.46664538034193
+ ],
+ [
+ -0.22814350714115691,
+ -43.414939319563715
+ ]
+ ],
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null
+ },
+ {
+ "type": "line",
+ "version": 1888,
+ "versionNonce": 1053316714,
+ "isDeleted": false,
+ "id": "cBhgTx6TChUJVN2aWM_vu",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1140.5041935280199,
+ "y": 412.1095128226318,
+ "strokeColor": "#0a11d3",
+ "backgroundColor": "transparent",
+ "width": 88.30808627974527,
+ "height": 9.797916664247975,
+ "seed": 1770721194,
+ "groupIds": [
+ "ZKBrme1TmS-BgybKc47Dj"
+ ],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "startBinding": null,
+ "endBinding": null,
+ "points": [
+ [
+ 0,
+ -2.1538602707609424
+ ],
+ [
+ 2.326538897826852,
+ 1.751753055375216
+ ],
+ [
+ 12.359939318521995,
+ 5.028526743934819
+ ],
+ [
+ 25.710950037209347,
+ 7.012921076245119
+ ],
+ [
+ 46.6269757640547,
+ 7.193749997581346
+ ],
+ [
+ 71.03526003420632,
+ 5.930375670950649
+ ],
+ [
+ 85.2899738827162,
+ 1.3342483900732343
+ ],
+ [
+ 88.30808627974527,
+ -2.6041666666666288
+ ]
+ ],
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null
+ },
+ {
+ "type": "line",
+ "version": 1975,
+ "versionNonce": 828251446,
+ "isDeleted": false,
+ "id": "atmtDI0kqJOAyHOXJsWap",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1139.4104520159583,
+ "y": 379.0353582605601,
+ "strokeColor": "#0a11d3",
+ "backgroundColor": "transparent",
+ "width": 88.30808627974527,
+ "height": 9.797916664247975,
+ "seed": 338597878,
+ "groupIds": [
+ "ZKBrme1TmS-BgybKc47Dj"
+ ],
+ "strokeSharpness": "round",
+ "boundElementIds": [],
+ "startBinding": null,
+ "endBinding": null,
+ "points": [
+ [
+ 0,
+ -2.1538602707609424
+ ],
+ [
+ 2.326538897826852,
+ 1.751753055375216
+ ],
+ [
+ 12.359939318521995,
+ 5.028526743934819
+ ],
+ [
+ 25.710950037209347,
+ 7.012921076245119
+ ],
+ [
+ 46.6269757640547,
+ 7.193749997581346
+ ],
+ [
+ 71.03526003420632,
+ 5.930375670950649
+ ],
+ [
+ 85.2899738827162,
+ 1.3342483900732343
+ ],
+ [
+ 88.30808627974527,
+ -2.6041666666666288
+ ]
+ ],
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null
+ },
+ {
+ "type": "ellipse",
+ "version": 4992,
+ "versionNonce": 822475050,
+ "isDeleted": false,
+ "id": "LEsrWKYb83znr4OLAMiT6",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1138.2896293297965,
+ "y": 336.6408282939866,
+ "strokeColor": "#0a11d3",
+ "backgroundColor": "#fff",
+ "width": 87.65074610854188,
+ "height": 17.72670397681366,
+ "seed": 2107607658,
+ "groupIds": [
+ "ZKBrme1TmS-BgybKc47Dj"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [
+ "bxuMGTzXLn7H-uBCptINx"
+ ]
+ },
+ {
+ "type": "ellipse",
+ "version": 362,
+ "versionNonce": 1162937974,
+ "isDeleted": false,
+ "id": "pyKtcbEKbUkrPRCLtFwlT",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1209.7993616303772,
+ "y": 361.2166311711202,
+ "strokeColor": "#0a11d3",
+ "backgroundColor": "#fff",
+ "width": 12.846057046979809,
+ "height": 13.941904362416096,
+ "seed": 1438918966,
+ "groupIds": [
+ "ZKBrme1TmS-BgybKc47Dj"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElementIds": []
+ },
+ {
+ "type": "ellipse",
+ "version": 411,
+ "versionNonce": 956384234,
+ "isDeleted": false,
+ "id": "mICkyd3IOSvgCeeHY0ttU",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1209.7993616303772,
+ "y": 391.81908503018064,
+ "strokeColor": "#0a11d3",
+ "backgroundColor": "#fff",
+ "width": 12.846057046979809,
+ "height": 13.941904362416096,
+ "seed": 1993780522,
+ "groupIds": [
+ "ZKBrme1TmS-BgybKc47Dj"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElementIds": []
+ },
+ {
+ "type": "ellipse",
+ "version": 465,
+ "versionNonce": 1167378358,
+ "isDeleted": false,
+ "id": "3kIptIO4rEK_Qx6fgMg_6",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1209.7993616303772,
+ "y": 425.0798862046772,
+ "strokeColor": "#0a11d3",
+ "backgroundColor": "#fff",
+ "width": 12.846057046979809,
+ "height": 13.941904362416096,
+ "seed": 152408694,
+ "groupIds": [
+ "ZKBrme1TmS-BgybKc47Dj"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElementIds": []
+ },
+ {
+ "type": "text",
+ "version": 367,
+ "versionNonce": 549795498,
+ "isDeleted": false,
+ "id": "d7ZRhnqNSc1YdZmD9cQOa",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1130.748068696516,
+ "y": 301.5875303590451,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 113,
+ "height": 25,
+ "seed": 803234218,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElementIds": [],
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Databases",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top"
+ },
+ {
+ "id": "ZBrCv5ckYDCUvXCJPLPTM",
+ "type": "arrow",
+ "x": 777.9233006301095,
+ "y": 187.78486093023616,
+ "width": 146.09844970703136,
+ "height": 1.4185292445675657,
+ "angle": 0,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "seed": 1679774058,
+ "version": 207,
+ "versionNonce": 575561974,
+ "isDeleted": false,
+ "boundElementIds": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 146.09844970703136,
+ -1.4185292445675657
+ ]
+ ],
+ "lastCommittedPoint": null,
+ "startBinding": {
+ "elementId": "tmWvYPpnp-_ijof9YkMx7",
+ "focus": 0.06234764646250402,
+ "gap": 3.7604424137583123
+ },
+ "endBinding": {
+ "elementId": "gb0fyq_8y3JU2Q4VM4zhA",
+ "focus": -0.07096206142482626,
+ "gap": 5.178289240056586
+ },
+ "startArrowhead": null,
+ "endArrowhead": "arrow"
+ },
+ {
+ "id": "hPVMvYiJgjNpoQp3MViqV",
+ "type": "text",
+ "x": 805.766928559797,
+ "y": 116.11005233170135,
+ "width": 91,
+ "height": 50,
+ "angle": 0,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "seed": 1769446378,
+ "version": 85,
+ "versionNonce": 956092778,
+ "isDeleted": false,
+ "boundElementIds": null,
+ "text": "MQTT\nmessages",
+ "fontSize": 20,
+ "fontFamily": 1,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "baseline": 43
+ }
+ ],
+ "appState": {
+ "gridSize": null,
+ "viewBackgroundColor": "#ffffff"
+ }
+}
\ No newline at end of file
diff --git a/img/excalidraw/diagram.png b/img/excalidraw/diagram.png
new file mode 100644
index 0000000..e5fd81b
Binary files /dev/null and b/img/excalidraw/diagram.png differ
diff --git a/img/excalidraw/diagram.svg b/img/excalidraw/diagram.svg
new file mode 100644
index 0000000..7d974c7
--- /dev/null
+++ b/img/excalidraw/diagram.svg
@@ -0,0 +1,16 @@
+
\ No newline at end of file
diff --git a/img/orig/img1.jpg b/img/orig/img1.jpg
index 607eeb8..7197bc0 100755
Binary files a/img/orig/img1.jpg and b/img/orig/img1.jpg differ
diff --git a/img/orig/img3.jpg b/img/orig/img3.jpg
new file mode 100755
index 0000000..eeba98c
Binary files /dev/null and b/img/orig/img3.jpg differ
diff --git a/img/resized/img1.jpg b/img/resized/img1.jpg
index bece7f6..22ff8e0 100755
Binary files a/img/resized/img1.jpg and b/img/resized/img1.jpg differ
diff --git a/img/resized/img3.jpg b/img/resized/img3.jpg
new file mode 100755
index 0000000..fd1c3a1
Binary files /dev/null and b/img/resized/img3.jpg differ
diff --git a/resources.md b/resources.md
deleted file mode 100644
index 03f8830..0000000
--- a/resources.md
+++ /dev/null
@@ -1,431 +0,0 @@
-# My development board
-* [E73-TBB(52832)](https://www.ebyte.com/en/product-view-news.aspx?id=889)
-* [Manual PDF](file:///Users/rbaron/Downloads/E73-TBX_UserManual_EN_v1.0(1).pdf)
-* 512KB flash
-* 64KB RAM
-
-* Using the [Generic](https://github.com/sandeepmistry/arduino-nRF5/blob/master/boards.txt#L93) board variant from arduino-nRF5. This is kinda similar to the [Adafruit feather nrf52832 board definition](https://github.com/platformio/platform-nordicnrf52/blob/develop/boards/adafruit_feather_nrf52832.json), but seems to use sandeepmistry:openocd instead of nrfutil and uses a different linker as well.
-
-# Articles
-* Great article about using Rust and Apache Mynewt, but also covers J-Link, ST-Link, openocd, unlocking the nrf52. [Link on medium](https://medium.com/@ly.lee/coding-nrf52-with-rust-and-apache-mynewt-on-visual-studio-code-9521bcba6004)
-*
-# nrf command line utilities
-* [nrfjprog](https://www.nordicsemi.com/Software-and-tools/Development-Tools/nRF-Command-Line-Tools) Used for programming hex files using the J-Link programmer.
-* [nrfutil](https://github.com/NordicSemiconductor/pc-nrfutil) Used for [DFU](https://en.wikipedia.org/wiki/USB#Device_Firmware_Upgrade) - device firmware update - via the USB port. No J-Link. This is what Adafruit uses for uploading code via USB. This is "higher level" and requires a pre-existing bootloader in the nrf52 chip that understands DFU. The [Adafruit_nRF52_Bootloader](https://github.com/adafruit/Adafruit_nRF52_Bootloader) is such a bootloader, which we can burn using nrfjprog + J-Link once. Adafruit has its own version, [`adafruit-nrfutil`](https://github.com/adafruit/Adafruit_nRF52_nrfutil).
-
-# SWD vs. Bootloader vs. DFU
-* How `nrfutil` is used in the platformio nordicnrf52 package: [link](https://github.com/platformio/platform-nordicnrf52/blob/develop/builder/main.py#L319)
-* Seems like we can use DFU if we install a DFU-enabled bootloader like the [Adafruit_nRF52_Bootloader](https://github.com/adafruit/Adafruit_nRF52_Bootloader)
-* Using the adafruit nrf52 bootloader with the E73-TBB [link](https://ssihla.wordpress.com/2019/07/23/using-adafruits-bluefruit-nrf52-feather-bootloader-on-a-cheap-chineese-board/)
-
-## Burning the Adafruit bootloader
-1. `$ brew cask install nordic-nrf-command-line-tools` -> Didn't work. I had to manually install the pkgs from [nordic](https://www.nordicsemi.com/Software-and-tools/Development-Tools/nRF-Command-Line-Tools/Download)
-1. `pip install intelhex`
-1. `PATH=$PATH:/Users/rbaron/.platformio/packages/framework-arduinoadafruitnrf52/tools/adafruit-nrfutil/macos make BOARD=feather_nrf52840_express all`
-
-## Burning our custom bootloader
-The error I was making was using a non-softdevice bootloader. The following works:
-```bash
-# Compile
-$ PATH=$PATH:/Users/rbaron/.platformio/packages/framework-arduinoadafruitnrf52/tools/adafruit-nrfutil/macos make BOARD=e73_tbb all
-
-# Flash (make sure you use the _s132_ hex file)
-$ JAVA_HOME=/Users/rbaron/homebrew/Cellar/openjdk/15.0.1 nrfjprog --program _build/build-e73_tbb/e73_tbb_bootloader-0.4.0-2-g4ba802d_s132_6.1.1.hex --sectoranduicrerase -f nrf52 --reset
-```
-
-Now we can drop the JLink and use the USB serial for uploading/monitoring.
-
-## Uploading with nrfutil
-```
-$ ~/.platformio/packages/framework-arduinoadafruitnrf52/tools/adafruit-nrfutil/macos/adafruit-nrfutil dfu genpkg --dev-type 0x0052 --sd-req 0x00B7 --application .pio/build/e73-tbb/firmware.hex dfu-pkg.zip
-Zip created at dfu-pkg.zip
-
-$ ~/.platformio/packages/framework-arduinoadafruitnrf52/tools/adafruit-nrfutil/macos/adafruit-nrfutil dfu serial --package dfu-pkg.zip -p /dev/cu.usbserial-14330 -b 115200
-Upgrading target on /dev/cu.usbserial-14330 with DFU package /Users/rbaron/dev/parasite/code/parasite/dfu-pkg.zip. Flow control is disabled, Dual bank, Touch disabled
-########################################
-########################################
-########################################
-########################################
-########################################
-#######################
-Activating new firmware
-Device programmed.
-```
-
-# Linking
-In [platformio-core/tools/pioplatform.py](https://github.com/platformio/platformio-core/blob/9c20ab81cb68f1ffb7a8cac22ce95c4c797643ec/platformio/builder/tools/pioplatform.py#L130):
-```Python
-if "build.ldscript" in board_config:
- env.Replace(LDSCRIPT_PATH=board_config.get("build.ldscript"))
-```
-In [platformio-core/platformio.py](https://github.com/platformio/platformio-core/blob/9c20ab81cb68f1ffb7a8cac22ce95c4c797643ec/platformio/builder/tools/platformio.py#L65):
-```Python
-# append into the beginning a main LD script
-if env.get("LDSCRIPT_PATH") and not any("-Wl,-T" in f for f in env["LINKFLAGS"]):
- env.Prepend(LINKFLAGS=["-T", env.subst("$LDSCRIPT_PATH")])
-```
-The `-T` flag expects a path to a linker script, as per [`ld` docs](https://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_chapter/ld_3.html).
-
-Frameworks, such as the `nordicnrf52`, contain [boards definitions](https://github.com/platformio/platform-nordicnrf52/blob/develop/boards/adafruit_feather_nrf52832.json#L4) that specify the `ldscript`.
-
-1. For the [adafruit_feather_nrf52832](https://github.com/platformio/platform-nordicnrf52/blob/develop/boards/adafruit_feather_nrf52832.json#L4) board, we have:
-
-```
-"arduino":{
- "ldscript": "nrf52832_s132_v6.ld"
-},
-```
-Which is defined [here](https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/master/cores/nRF5/linker/nrf52832_s132_v6.ld).
-
-2. For the [nrf52_dk](https://github.com/platformio/platform-nordicnrf52/blob/develop/boards/nrf52_dk.json#L4), we have:
-```
-"arduino":{
- "ldscript": "nrf52_xxaa.ld"
-},
-```
-Which is the most common linker script, and is part of the nordic official SDK, it seems, available [here](https://github.com/NordicSemiconductor/nrfx/blob/master/mdk/nrf52_xxaa.ld). For reference, check out the linker for the [Adafruit nRF52 Bootloader](https://github.com/adafruit/Adafruit_nRF52_Bootloader/blob/master/linker/nrf52.ld). Here you can see the [memory map](https://learn.adafruit.com/bluefruit-nrf52-feather-learning-guide/hathach-memory-map) of the nRF52 Feather.
-
-## Same platform, different framework
-Both the Feather nRF52832 and the Generic boards above point to the same platform, the [nordicnrf52](). Inside this platorm, there's a switch for selecting which framework to use, based on the board name, [here](https://github.com/platformio/platform-nordicnrf52/blob/develop/platform.py#L38):
-```Python
-if self.board_config(board).get("build.bsp.name",
- "nrf5") == "adafruit":
- self.frameworks["arduino"][
- "package"] = "framework-arduinoadafruitnrf52"
-```
-
-## What does each line in the linker mean?
-* [The most commented linker script in the world](https://github.com/theacodes/Winterbloom_Castor_and_Pollux/blob/main/firmware/scripts/samd21g18a.ld)
-
-### Memory layout
-The [SoftDevice S132 spec](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fsds_s112%2FSDS%2Fs1xx%2Fmem_usage%2Fmem_resource_map_usage.html&anchor=mem_resource_map_usage) specifies the layout as the soft device begins at addr 0x0 and the application code comes after it, at the address `APP_CODE_BASE`.
-
-# PWM
-* [HardwarePWM.cpp](https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/a86fd9f36c88db96f676cead1e836377a37c7b05/cores/nRF5/HardwarePWM.cpp#L112) by Adafruit is a good reference. [Servo.c](https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/4d703b6f38262775863a16a603c12aa43d249f04/libraries/Servo/src/nrf52/Servo.cpp#L154) uses it.
-* [nrf52832 pwm spec](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.nrf52832.ps.v1.1%2Fpwm.html)
-* [nrf5 SDK PWM reference](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.sdk5.v11.0.0%2Fgroup__nrf__pwm__hal.html) HAL => Hardware Abstraction Layer?
-
-# Analog to digital converter (ADC)
-* In nrf52-land, the ADC is called [SSADC](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.sdk5.v15.0.0%2Fgroup__nrf__adc.html)
-* Example in the sdk: in `examples/peripheral/saadc/main.c`
-* [Docs from Adafruit](https://learn.adafruit.com/bluefruit-nrf52-feather-learning-guide/nrf52-adc). It uses Arduino's `analogRead`.
-
-## Resoultion
-The default resolution is 10 bits (1024 values),
-
-## Reference value
- The default reference is an internally supplied 3.6V. It seems like we woulld only be able to read values up to 3.6V. This might be enough if we're using a CR2032 coin cell, but might not if we use a LiPo (4.2V fully charged).
-
-**Question**: what happens if Vcc < 3.6?
-
-There is a possibility of using Vcc as a reference. Then I imagine 0 -> 0V; 1024 -> Vcc. This might be exactly what we want:
-In a PWM positive pulse, our RC_parasitic will rise to 0.63Vcc in R*C_para time. If we use Vcc as a reference for the ADC, I think we "cancel out" the Vcc factor.
-
-* How adafruit uses the Vcc reference: [link](https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/master/cores/nRF5/wiring_analog_nRF52.c#L76)
-* How arduino-nRF5 uses the Vcc refenrece: [link](https://github.com/sandeepmistry/arduino-nRF5/blob/master/cores/nRF5/wiring_analog_nRF52.c#L98)
-
-So in Arduino land, using AR_VDD4 does all we need: use nrf's SAADC_CH_CONFIG_REFSEL_VDD1_4 (Vcc/4 ref) and a gain of 4 (SAADC_CH_CONFIG_GAIN_Gain1_4).
-While connected via USB, with:
-```
-analogReference(AR_VDD4);
-int sens_val = analogRead(kSensAnalogPin);
-```
-I'm getting ~680 when in the air; ~65 while holding the sensor. The default resolution is 10 bits, so 1024 -> 3.3V. On the scope, I'm reading the sensor output value to be 2.16V. This matches the value I'm reading: 1024/3.3 * 2.15 = 667. Nice.
-
-The Vcc voltage seems to have an impact though (with nrf52840):
-Vcc = 3.5 => Moisture Air: 668; Hand: 65
-Vcc = 3.0 => Moisture Air: 640; Hand: 53
-Vcc = 2.5 => Moisture Air: 605; Hand: 32
-Vcc = 2.0 => Moisture Air: 547; Hand: 23
-
-Possible issues:
-* Fast discharging circuit is not fully open with lower voltage?
-* Weird capacitance stored in the transitor messes up the quick switching?
-* Time it takes to overcome the 0.6V diode drop in the output circuit?
-
-Possible fixes:
-* Measure and correct it via a software interpolation
-
-For now I'm implementing the software fix that seems to work well in practice. The measurements and regression coefficients are in [this spreadsheet](https://docs.google.com/spreadsheets/d/1F6rtkCX7626tzZff0NANZVknawb_U4uS38zjyy6ZPEE/edit#gid=1442651809).
-
-# Battery
-* [CR2032 datasheet](https://data.energizer.com/pdfs/cr2032.pdf)
- * 235 mAh (from 3.0V to 2.0V)
- * Typical discharge current: 0.19 mA -> 1200 hours
-
-# Battery monitoring
-* Good post on how to measure lipo batteries: [link](https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/measuring-lithium-battery-voltage-with-nrf52#:~:text=To%20reduce%20the%20leakage%20current,of%2040%20us%2C%20see%20here.)
-We have a choice of using different references when using the analog-to-digital converter. For measuing the peak of the RC charging circuit, it makes sense to use VCC as the reference, as the rise in the RC is proportional to VCC.
-For battery monitoring, we need an absolute reference. Luckily, we can use the internal reference of 0.6V. To increase the range of values, we can combine this with a gain parameter. This is what the arduino-nrf5 does in [wiring_analog_nRF52.c](https://github.com/sandeepmistry/arduino-nRF5/blob/master/cores/nRF5/wiring_analog_nRF52.c#L92).
-With a gain of 1/2, we could read the absolute range of [0, 1.2V]. Since we're interested in the max value of roughly 4.2 (fully charged LiPo, or alternatively 3.0V for a CR2032 coin cell), we can use a voltage divider with R1 = 1470 kOhm and R2 = 470kOhm. This would give us a range of [0, ~5V].
-This seems to be working okay, but I need to investigate if making it stiffer (lower R1 and R2) improves the accuracy. With higher resistor values, we minimize the quiescent current, but increase the source impedance. Even hooking up the oscilloscope changes the reading value.
-
-There is a way to measure Vcc without any external circuitry. It uses the NRF_SAADC_INPUT_VDD. See [this post](https://devzone.nordicsemi.com/f/nordic-q-a/24159/what-is-the-pin-for-the-battery-voltage-on-nrf52pca1040-dk) on devzone.
-
-## Ideas for improvement:
-* Decrease the impedance of the voltage divider, but somehow use a mcu-controlled switch so we don't pay the current price when the MCU is sleeping (which is most of the time). [This stackexchange answer](https://electronics.stackexchange.com/a/64491) mentions a similar approach
-* Use even larger resistor values and attach a capacitor across R2, as suggested by [this answer](https://www.eevblog.com/forum/projects/battery-monitoring-voltage-divider/msg2524116/#msg2524116). We can reach nanoamps of current, which is negligible in this design. Question: does the capacitor self leak? If so, we'd need to be constantly pumping charges into it. How significant is this effect? If we do oversampling, the capacitor probably won't charge in time for multiple fast measurements. But the capacitor might act like a filter itself, negating the need for oversampling.
- * [This post on jeelabs.org](https://jeelabs.org/wp-content/uploads/2013/05/16/measuring-the-battery-without-draining-it/) also does some experiments with a capacitor across R2.
-
-# BLE
-* BLE examples for platformio with nordicnrf52 platform: [link](https://github.com/platformio/platform-nordicnrf52/tree/master/examples)
-* [nrf5 BLE examples](https://devzone.nordicsemi.com/nordic/short-range-guides/b/bluetooth-low-energy/posts/ble-advertising-a-beginners-tutorial) are way more complicated than I need for now
-* How the alternative firmware for the xiaomi temp sensor works: [link](https://github.com/atc1441/ATC_MiThermometer)
- * The MCU is a [Telink TLSR8251](http://wiki.telink-semi.cn/doc/ds/DS_TLSR8251-E_Datasheet%20for%20Telink%20BLE+IEEE802.15.4%20Multi-Standard%20Wireless%20SoC%20TLSR8251.pdf)
- * Deep sleep current is 1-2uA
- * It sends an _advertisement packet_ every 1 minute with the MAC, temperature, humidity and battery level
- * [main](https://github.com/atc1441/ATC_MiThermometer/blob/master/ATC_Thermometer/main.c)
- * [main_loop](https://github.com/atc1441/ATC_MiThermometer/blob/916cef7db24977ec187e68ab6e718b7b7a4988e6/ATC_Thermometer/app.c#L76)
- * [advertisement_data](https://github.com/atc1441/ATC_MiThermometer/blob/master/ATC_Thermometer/ble.c#L39) definition
- * [set_adv_data](https://github.com/atc1441/ATC_MiThermometer/blob/master/ATC_Thermometer/ble.c#L178) - where the temp, humidity and batt levels are encoded into the advertisement data
- * They don't use manufacturer data. They use a custom UUID service (0x181a).
- * Using the manufacturer data requires a SIG membership for getting the manufacturer ID (first two bytes in the manufacturer data)
- * Using the service data also requires applying for a 16-bit (2 byte) ID from SIG ([link](https://www.bluetooth.com/specifications/assigned-numbers/))
- * Aha! I think we can use the 0x181a service UUID (the same one the alternative xiaomi firmware uses). This service is reserved for "Environmental Sensors". Check out the offical assigned numbers list [here](https://btprodspecificationrefs.blob.core.windows.net/assigned-values/16-bit%20UUID%20Numbers%20Document.pdf).
- * We can use the SERVICE_DATA (int 16) in the BLE advertisement packet to pack service data for the UUID 0x181a.
-* How the xiaomi BLE temp sensor works in ESPHome: [link](https://github.com/esphome/esphome/blob/5c86f332b269fd3e4bffcbdf3359a021419effdd/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp)
-* [How advertisement works YouTube video](https://www.youtube.com/watch?v=CJcLabp42b4) by nordic
- * In your case:
- * GAP role: broadcaster
- * Advertisement type: legacy ADV_NONCONN_IND (non-connectable broadcast)
- * Advertising data starts at 22:17
- * The payload might contain 37 bytes in the classic/legacy protocol:
- * addr (6 bytes) (either public or random)
- * Public has privacy concerns
- * Random can be static or private (resolvable or non-resolvable)
- * data (31 bytes)
- * The 31 bytes of advertisement data can be split into structures
- * Each structure has a length (1 byte), a type (n bytes) and data (remaining bytes)
- * Examples of structures are service UUID, local name, manufacturer specific data
- * Manufacturer specific data is usually the way to transmit data in the advertisement packet
- * Requires a company id. Check out the [list](https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers). Adafruit has the 0x0822.
-* [BLEPeripheral](https://github.com/sandeepmistry/arduino-BLEPeripheral) seems to be a popular library choice
- * [setManufacturerData](https://github.com/sandeepmistry/arduino-BLEPeripheral/blob/161a4163f565be3cd5b62bbc59f0c2b522d82b02/src/BLEPeripheral.h#L72) is probably what we want
- * It seems to be highly geared towards nrf51 SOCs, although it mentions support for some nrf52 ones
-* [Adafruit_nRF52_Arduino](https://github.com/adafruit/Adafruit_nRF52_Arduino)
- * [BLEAdvertising guide](https://learn.adafruit.com/bluefruit-nrf52-feather-learning-guide/bleadvertising)
- * [BLEAdvertising.h](https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/master/libraries/Bluefruit52Lib/src/BLEAdvertising.h#L87)
-* Can we update the manufaturer advertising data dynamically?
- * [Hint on devzone.nordicsemi.com](https://devzone.nordicsemi.com/f/nordic-q-a/11217/adc-values-in-advertising-data-dynamically-changing)
-* What MAC address to use?
- * Public - might have some security implications, since BLE can be used for tracking
- * Random - safer, since its random, but it "breaks" our MAC-addr based Hub config
-
-## Packing values into the advertising packet
-### Soil humidity
-If there's room, I'd like to pack the raw analog-to-digital value and a percentage.
-* Values for the ADC are 10 bit (0-1023). So we could use 10 bits for this. For simplicity, I'll keep this byte-aligned and use 16 bits (2 bytes) for this. It means
- I'll have to scale [0, 2^10) to [0-2^16) (and back in the hub).
-* Percentage values are floating points, but we don't have nearly as much precision for justifying 32/64 bits. Even using integers in [0-100] would be fine. I'll splurge and use two bytes here as well. It means I'll have to scale [0, 1.0] to [0, 2^16) and back in the hub.
-
-# Central BLE (hub)
-The "central" BLE will be responsible for listening to parasite's BLE broadcasts and parsing its manufacturer's data.
-One idea is to use ESPHome for this, for example like the xiaomi sensor does:
-* [Xiaomi component's parse_device](https://github.com/esphome/esphome/blob/dev/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp#L19)
-* [esp32_ble_tracker calls all registered parse_devices()](https://github.com/esphome/esphome/blob/dev/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp#L68)
-* [Setting up the development environment for ESPHome](https://esphome.io/guides/contributing.html#setting-up-development-environment)
-
-Question: parasite will advertise a _lot_ of packets in short bursts. How is this data "deduplicated"? I imagine ESPHome won't parse all the packets and forward them to MQTT.
-
-# OTA
-
-# Measuring current consumption
-* Good [issue](https://github.com/atc1441/ATC_MiThermometer/issues/134) on the xiaomi sensor tracker
-
-# Data persistence
-* Adafruit has a internal filesystem implementation. See [example](https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/master/libraries/InternalFileSytem/examples/Internal_ReadWrite/Internal_ReadWrite.ino). Here's the [library implementation](https://github.com/adafruit/Adafruit_nRF52_Arduino/tree/master/libraries/InternalFileSytem/src/flash)
-* Adafruit's [LittleFS](https://github.com/adafruit/Adafruit_nRF52_Arduino/tree/master/libraries/Adafruit_LittleFS)
-* Examples of using [fstorage](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.sdk5.v15.0.0%2Flib_fstorage.html) from nordic: [link](https://github.com/NordicPlayground/nRF5-flash-storage-examples).
-* Example from the SDK: `flashwrite` in examples\peripheral\flashwrite
-* [sd_flash_write](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.s132.api.v3.0.0%2Fgroup___n_r_f___s_o_c___f_u_n_c_t_i_o_n_s.html)
-
-Questions:
-* What addresses are safe to write to? We need to avoid:
- * Bootloader
- * SoftDevice
- * Application
- * More sections?
-
-# Deep sleep
-* [Adafruit_nRF52_Arduino](https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/master/cores/nRF5/wiring.h#L34) has a waitForEvent function, which internally calls [sd_app_evt_wait](https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/master/cores/nRF5/wiring.c#L101).
-* System ON/OFF. System ON mode is a power saving mode in which the RTCounters are active, so they can emit events for waking up the CPU. System off has no RTC active.
-* the RTC COMPARE event can be used to wake up. See the examples/peripheral/rtc.
-* [app_timer on nordicsemi](https://devzone.nordicsemi.com/f/nordic-q-a/46031/need-a-30-second-interrupt---using-rtc)
-* [Adafruit_nRF52_Arduino issue](https://github.com/adafruit/Adafruit_nRF52_Arduino/issues/165)
- * `delay()` calls waitForEvent?
- * [suspendLoop](https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/master/libraries/Bluefruit52Lib/examples/Peripheral/beacon/beacon.ino#L60) can probably save energy when no deep sleep is used.
- * [More on power consumption](https://github.com/adafruit/Adafruit_nRF52_Arduino/issues/165#issuecomment-407288522). where @hathach mentions that the `delay()` actually uses [`waitForEvent`](https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/4d703b6f38262775863a16a603c12aa43d249f04/cores/nRF5/wiring.c#L84), which itself calls
-* [Loopless / low power examples](https://github.com/adafruit/Adafruit_nRF52_Arduino/pull/262) pull request on Adafruit_nRF52_Arduino
- * They use the SoftwareTimer, which
-
-* The example in examples/peripheral/rtc is straight-forward: initialize RTC and set up the TICK and COMPARE0 event. The TICK event is triggered on every clock cycle. The COMPARE0 only when the counter reaches a specified value.
- * It uses the nrf_drv_rtc.h header, which doesn't seem to be included in the Arduino core implementaton
- * There's a similar one in Arduino called [nrfx/hal/nrf_rtc.h](https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/4d703b6f38262775863a16a603c12aa43d249f04/cores/nRF5/nordic/nrfx/hal/nrf_rtc.h). It seems to use the same call to `nrf_rtc_cc_set` under the hood.
- * [nrfx](https://github.com/NordicSemiconductor/nrfx) is "a standalone set of drivers for peripherals present in Nordic Semiconductor's SoCs".
-
-* The [RTC driver](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.sdk5.v15.0.0%2Fgroup__nrfx.html) has some interesting functions:
- * nrfx_rtc_cc_set
- * nrfx_rtc_tick_disable
-* Interrupt handlers are registered in the NVIC (nested vectored interrupt controller)
-
-*[wolfssl](https://fossies.org/linux/wolfssl/wolfcrypt/src/port/arm/cryptoCell.c) uses the functions I'm thinking of using.
-
-* Reminders:
- * RTC0 and RTC1 might be in use. Try using RTC2
- * Initialize the low frequency clock LFCLKSRC
- * Disable the TICK interrupt
- * Set up an IRQ (callback) [example from nordic forum](https://devzone.nordicsemi.com/f/nordic-q-a/52923/rtc-interrupt-handler-not-working)
- * Set up the COMPARE[0] event/interrupt
- * Disable LFCLKSTARTED interrupt? [link from nordic forum](https://devzone.nordicsemi.com/f/nordic-q-a/70091/early-wake-up-from-power-clock-irq-when-using-rtc-to-wake-up)
- * FPU (floating point interrupts) also might trigger the interrupt. Important thread from [nordic](https://devzone.nordicsemi.com/f/nordic-q-a/23242/single-float-division-causing-7x-higher-current-draw)
- * Doesn't work with C++?? [devzone](https://devzone.nordicsemi.com/f/nordic-q-a/13465/nvic-registered-interrupt-doesn-t-work)
-
-
-* Good tips on how to save energy (for nrf51, but the same ideas apply) [link](https://devzone.nordicsemi.com/f/nordic-q-a/1657/how-to-minimize-current-consumption-for-ble-application-on-nrf51822#5187)
-
-## Deep Sleep with SoftwareTimer
-This is a FreeRTOS scheduled task. It seems to work, but by default it wasn't. I had to increase the FreeRTOS timer stack size, as hinted in [this GitHub issue](https://github.com/adafruit/Adafruit_nRF52_Arduino/issues/188)], in ~/.platformio/packages/framework-arduinoadafruitnrf52/cores/nRF5/freertos/config/FreeRTOSConfig.h.
-
-* Issues
- * nrfx_clock_init is not linked by default in platformio. Is there a more conventional way of using this?
- * freeRTOS on Adafruit_nRF52_Arduino uses the RTC. [link](https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/4d703b6f38262775863a16a603c12aa43d249f04/cores/nRF5/freertos/config/FreeRTOSConfig.h)
-# Arduino specifics
-* How `main` works by calling `setup` and `loop`. [Link for Adafruit](https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/4d703b6f38262775863a16a603c12aa43d249f04/cores/nRF5/main.cpp#L74), [link for arduino-nrf5](https://github.com/sandeepmistry/arduino-nRF5/blob/master/cores/nRF5/main.cpp#L27). Or: why dropping the `setup` and `loop` doesn't work directly, as they do with ESP32.
-
-## FreeRTOS
-* [Low Power Support or tickless idle](https://www.freertos.org/low-power-tickless-rtos.html)
-* [Adafruit use of freeRTOS for scheduling the loop() function in Arduino](https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/4d703b6f38262775863a16a603c12aa43d249f04/cores/nRF5/main.cpp#L94)
-* [Adafruit's SoftwareTimer](https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/4d703b6f38262775863a16a603c12aa43d249f04/cores/nRF5/utility/SoftwareTimer.cpp) also uses freeRTOS tasks
-* How/where is freeRTOS implemented for nrf52?
- * [Adafruit_nRF52_Arduino/cores/nRF5/freertos/](https://github.com/adafruit/Adafruit_nRF52_Arduino/tree/4d703b6f38262775863a16a603c12aa43d249f04/cores/nRF5/freertos)
-* [FreeRTOS support in nordic SDK](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.sdk5.v15.0.0%2Fnrf_freertos_example.html)
- * examples/blinky_freertos
- * examples/blinky_rtc_freertos
-
-# Nordic specifics
-* HAL vs. Drivers
- * [Great answer on nordic forum](https://devzone.nordicsemi.com/f/nordic-q-a/5964/nrf_dvr_xxx-vs-nrf_xxx)
- * HAL are lower-level. They provide some functions for accessing registers. Drivers are higher level, and usually use the HAL functions. Drivers can be used with or without SoftDevices.
-
-
-# nRF52840
-I've started using a standalone nrf52840 module (e73-2g4m08s1c), since I want a barebones module to test power consumption. This chip is very similar to the nrf52832, except it has built-in usb support!
-
-## Adafruit bootloader
-The bootloader is different. It has one extra feature: double resetting puts the chip into DFU/CDC mode. I actually verified that it shows up as a mass storage device on my mac!
-
-## Questions
-* Booting in DFU/CDC creates a new serial device in /dev/cu.usbmodem*. What about when the application is running? How does Serial.print() work? How can I read those?
- * framework-arduinoadafruitnrf52/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_USBD_CDC.h likely has the answer and it uses TinyUSB.
-* Reset button seems to be "configurable" to use pin 0.18. Do I need to pre-configure it or will resetting work with Adafruit's bootloader for putting it into flash mode by quickly resetting it twice?
- * In the [ItsyBitsy schematic](https://cdn-learn.adafruit.com/assets/assets/000/087/158/original/adafruit_products_schem.png?1579387035), it seems like the reset switch simply pulls P0.18 to gnd.
- *
-## Power input
- * When connected to USB, VBUS is 5V
- * The datasheet mentions 1.7V - 5.5V supply voltage
- * But there are two supply pins (page 79):
- * VDD handles 1.7 - 3.6V
- * VDDH handles 2.5 - 5.5V
- * So, when connecting to USB, do I need to bridge VBUS and VDDH high?
- * (page 65): "As a consequence, VBUS and either VDDH or VDD supplies are required for USB peripheral operation"
-
-
-## Bluetooth
-I got basic sketches working on the barebones e73c breakout, except the ones running bluetooth.
-I was able to deviry that the program hands exactly when it reaches the Bluefruit.begin() function.
-Googling suggests that it's missing a low frequency oscillating crystal.
-
-Details on oscillator on page 84.
-
-### How to enable the internal synthetic one?
-In [arduino-nRF5](https://github.com/sandeepmistry/arduino-nRF5/blob/master/boards.txt#L49) there is a menu in which we can select:
-- Crystal (-DUSE_LFXO)
-- RC (-DUSE_LFRC)
-- Synthetic (-DUSE_LFSYNT)
-
-Changing the following on framework-arduinoadafruitnrf52/variants/feather_nrf52840_express/variant.h made it work:
-```C
-// #define USE_LFXO // Board uses 32khz crystal for LF
-#define USE_LFRC // Board uses RC for LF
-```
-
-# Using the raw nrf5 SDK
-- SparkFun article on how to setup vscode for development [link](https://learn.sparkfun.com/tutorials/nrf52840-advanced-development-with-the-nrf5-sdk/setting-up-a-vs-code-environment)
-
-## Dev boards
-- [PCA10040](https://docs.zephyrproject.org/1.9.0/boards/arm/nrf52_pca10040/doc/nrf52_pca10040.html) uses an nrf52832
-- [PCA10056](https://docs.zephyrproject.org/1.14.1/boards/arm/nrf52840_pca10056/doc/index.html) uses an nrf52840. This is what I want.
-
-## Timers, RTC
-- [devzone thread](https://devzone.nordicsemi.com/f/nordic-q-a/12731/clocks-timers-rtc-why):
- - Timers use the HFCLK; better resolution, more power usage
- - RTC uses the low frequency source LFCLK; less accurate, less power usage
-I want RTC, since I don't need to be super precise and I want to save as much battery as possible.
-
-## Power down
-- [powerdown examples](https://github.com/NordicPlayground/nrf51-powerdown-examples) for nrf51 and nrf52
-- How to measure current with the dev kit using an oscilloscope. [Docs by nordic](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nrf52832_dk%2FUG%2Fnrf52_DK%2Fhw_meas_current.html)
-- Good thread on devzone about current measurement [link](https://devzone.nordicsemi.com/nordic/short-range-guides/b/hardware-and-layout/posts/current-measurement-guide-measuring-current-with-n)
-- Another thread on how to minimize current consumption on [devzone](https://devzone.nordicsemi.com/f/nordic-q-a/1657/how-to-minimize-current-consumption-for-ble-application-on-nrf51822#5187)
-
-There is something very weird going on. The current usage of my module varies wildly with the input voltage (Vcc). Something is off:
-- Vcc = 2.5 => 15uA
-- Vcc = 3.3V => 1ma
-- Vcc = 3.6 => > 2ma
-
-Very weird. What I did:
-- Tried the same sketch with a barebones nrf52832 module (E73-2G4M04S1B). It seems to behave as expected: around 2uA current in the input range 2V-3.6V;
-- Soldered _another_ nrf52840 to another breakout board. Now I'm seeing ~4uA in the input range 2V-3.6V;
-
-This suggestes there was something wrong with my previous nrf52840 module. Maybe I abused it too much or shorted something while soldering (but I wasn't able to find it with a contuniuity test). Also the system on might be because the nrf52840 has more RAM to keep powered on. We can selectively disable some, I think.
-
-Anyway, I'm considering this solved.
-
-A side note: the E73-2G4M04S1B module uses the "old" nrf52832 module, but it shows lower current _and_ it has a 32kHz crystal built in, which the E73-..C does not. I think the old module might be a better choice for us. The only downside is that it's slightly larger, but shouldn't matter at all.
-
-So the latest data is:
-- System ON sleep: ~4uA
-- System OFF sleep: 0.3uA (pretty cool!)
-
-## BLE
-I had to change the LFCLK source in these params:
-#define NRFX_CLOCK_CONFIG_LF_SRC 0
-#define NRF_SDH_CLOCK_LF_SRC 0
-#define CLOCK_CONFIG_LF_CAL_ENABLED 1
-#define NRF_SDH_CLOCK_LF_ACCURACY 1
-
-## Debugging
-- Basics of debugging with SEGGER embedded studio [video](https://www.youtube.com/watch?v=uP8RYgYGRvI)
-
-## Error handling
-- Nice post on devzone [link](https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/an-introduction-to-error-handling-in-nrf5-projects)
-
-## RC vs. Crystal
-- Good post with pros/cros on [devzone](https://devzone.nordicsemi.com/f/nordic-q-a/19/what-s-the-benefit-of-having-an-external-32-khz-crystal)
-Botom line:
-- RC is "cheaper" (doesn't require crystal of course)
-- RC is omre power hungry
- - Needs to be recalibrated often (requires turning on the HFCLK of 16MHz)
- - Accuracy is worse, so requires bigger windows for transmitting
-- All in all it should increase of 8-10uA on average
-- [github.com/joric/nrfmicro/wiki/Crystal](https://github.com/joric/nrfmicro/wiki/Crystal)
-
-
-Question: can we pay this cost only when the BLE is transmitting?
-Answer: I don't think so, since the RTC depends on the LFCLK. In theory we could let go of the calibration during system on sleep and let it drift by however many seconds it does. It won't matter if it drifts for a dozen seconds on a 5 minutes sleep. But it starts to get tricky to implement and measure. The best solution is to just stick a crystal on the board.
-
-## DEC capacitors
-The nrf52832 SoC requires a few decoupling caps on its DEC* pins. User NeverDie took some good pics of the module with the cap removed and it seems like the capacitors are already there: [link](https://forum.mysensors.org/topic/6961/nrf5-action/311?lang=en-US)
-
-# Electronics
-## Temperature sensor
-Comparison table form Sensirion: [link](https://www.sensirion.com/en/environmental-sensors/humidity-sensors/)
-* SHT31 is the one I bought for testing
-* SHTC3
- * ~ $1.5 each
- * Google JLC support for assembly [link](https://jlcpcb.com/parts/componentSearch?searchTxt=sht30)
- * 0.3uA in sleep
- * KiCad footprint on [GitHub](https://kicad.github.io/symbols/Sensor_Humidity)
-
-# Antenna
-* Guide for PCB design on [devzone](https://devzone.nordicsemi.com/nordic/short-range-guides/b/hardware-and-layout/posts/general-pcb-design-guidelines-for-nrf52)