Compare commits

...
Sign in to create a new pull request.

273 commits
bthome ... main

Author SHA1 Message Date
rbaron
5214f904f1
Merge pull request #214 from ryanrolds/rolds/msec_sleep
Adjusting sleep durations to be milliseconds. Needed for FCC 15b testing.
2024-09-06 09:08:33 +02:00
Ryan R. Olds
20693143ae Fixing build failure 2024-09-05 13:27:16 -07:00
Ryan R. Olds
2e5f89b228 Adjusting code 2024-09-03 16:25:14 -07:00
Ryan R. Olds
2dc7144de1 Switching sleep to to msec. Will aid FCC 15b testing 2024-09-03 16:23:28 -07:00
rbaron
5f671b0170
Merge pull request #207 from seime/debug_log_photo
Log photo adc value, not soil adc
2024-06-04 19:02:55 +02:00
Arne Seime
21dd14d7f3 Remove obsolete logging of soil voltage (always 0) 2024-06-04 16:55:30 +02:00
rbaron
f8c28cebd6
Merge pull request #208 from mrred2k/main
Add link to new case from Printables to README.md
2024-06-04 09:19:08 +02:00
rbaron
8ef2b5b748
Update README.md
- Parasite -> b-parasite
- Link directly to English website instead of german
2024-06-04 09:08:42 +02:00
mrred2k
4e0f3911bd Added a new case 2024-06-03 21:30:17 +02:00
Arne Seime
20d98d02a0 Log photo adc value, not soil adc 2024-06-03 10:21:51 +02:00
rbaron
ccdbaaf142
Merge pull request #194 from mrred2k/main
Include Printables link to 3D printable holder in README.md
2024-04-30 08:02:30 +02:00
mrred2k
57cf5aa0e5 Fixed Typos, deleted model 2024-04-29 21:18:27 +02:00
rbaron
e7e4b77eef
Merge pull request #188 from jrhbcn/main
Report battery percentage in BLE BTHOME_V2 protocol
2024-04-20 13:49:11 +02:00
jrhbcn
dc02480da9
Update encoding.c to fix formatting errors 2024-04-20 13:41:03 +02:00
jrhbcn
4796bfab5f
Update encoding.c
Changes in BTHOME_V2 protocol:
- Reduce resolution of humidity and moisture to 1 byte
- Add battery percentage
2024-04-20 13:10:36 +02:00
rbaron
e62521d2ae
Merge pull request #177 from ScottG489/patch-1
Small typo fix in BLE sample README
2024-03-07 19:15:37 +01:00
rbaron
16c4d23c47
Merge pull request #182 from oleo65/change-d1-component-part-no
Change d1 component part no
2024-03-04 19:07:17 +01:00
Ole Odendahl
15920b1ae4
Reflected changes to D1 in kicad files. 2024-02-25 16:03:26 +01:00
Ole Odendahl
d26eab5825
Updated BOM to changed inventory of JLCPCB.
Changed D1 to equivalent component.
2024-02-25 15:57:46 +01:00
rbaron
c7aaf5b388
Merge pull request #180 from jhbruhn/patch-1 2024-02-14 23:05:36 +01:00
Jan-Henrik Bruhn
905978e8c9
Only read positive voltages in adc.c
The ADC sometimes responds with negative voltages read on the ADC input due to noise. This can lead to very high illuminance readings at night (4095 << 4 to be exact due to two's complement :) ).
This commit caps ADC readings at 0.
2024-02-14 11:59:14 +01:00
Scott Giminiani
4c0fe9b4c6
Small typo fix in BLE sample README 2024-01-07 09:20:30 -08:00
rbaron
dd373ea235
Merge pull request #176 from victorhooi/patch-1
Fix broken link for ALS-PT19 datasheet
2024-01-01 22:53:51 +01:00
Victor Hooi
25a5941777
Fix broken link for ALS-PT19 datasheet 2024-01-01 23:02:42 +11:00
rbaron
37ea3a1062
Merge pull request #165 from rbaron/nrf2.5
Update to nRF Connect SDK 2.5
2023-11-08 08:33:01 +01:00
rbaron
d3ddd089bd
Merge pull request #166 from rbaron/nrf52833-flashfix
Fix nRF52833 flash partitions
2023-11-08 08:32:27 +01:00
rbaron
28c2809b04 Fix nRF52833 flash partitions
The nRF52833 has only 512 kB of flash. The .dts file was incorrect, as a
product of having it forked from the nRF52840 .dts files, which has 1 MB
of flash.

The new, correct partitions are closely related to the
[nrf52833dk](ec52722861/boards/arm/nrf52833dk_nrf52833/nrf52833dk_nrf52833.dts (L215)).
2023-11-07 21:32:09 +01:00
rbaron
ad3cd4194f Remove PM configs from soil_read_loop 2023-11-07 21:18:20 +01:00
rbaron
171a1eb4a0 Uses newwer FIXED_PARTITION_ID for fs_mount_t
... This is what new samples use, but there should be no functional
difference.
2023-11-07 20:57:54 +01:00
rbaron
5199ceaa50 Update to nRF Connect SDK 2.5 2023-11-07 18:31:32 +01:00
rbaron
7de5115705
Merge pull request #163 from rbaron/nrf-2.4.2
Bump nrf-connect sdk version to 2.4
2023-10-21 17:34:02 +02:00
rbaron
d5ed701c45 Simplify actions -- no artifacts 2023-10-21 17:26:55 +02:00
rbaron
141071a414 Refactor github actions
Reason: https://github.com/NordicPlayground/nrf-docker/pull/75
2023-10-21 17:21:39 +02:00
rbaron
35618227d5 Also bump in devcontainer.json 2023-10-21 10:30:36 +02:00
rbaron
77c204397e Bump nrf-connect sdk version to 2.4.2
Manually tested with both ble and zigbee samples.
2023-10-21 10:28:18 +02:00
mrred2k
5d2b648b22 Added the link to the holder
Added the 3d Printed holder from printables. Also available in "case"
2023-08-29 11:30:49 +02:00
mrred2k
da1642514a Created a holder for 10 b-parasides
Created a simple 3d printable holder for dealing with a bigger number of b-parasides.
2023-08-29 11:22:59 +02:00
rbaron
85ca1a67bc
Merge pull request #153 from rbaron/ble-bthomev2-def
[ble sample] Sets BTHome V2 as the default encoding
2023-07-23 18:45:15 +02:00
rbaron
e68c113513 [ble sample] Sets BTHome V2 as the default encoding
This change has been planned for a while, and BTHome v2 + Home Assistant
is currently the most convenient, hassle-free and maintained way to
interface with the BLE sample currently.
2023-07-23 18:39:01 +02:00
rbaron
e0481994b2
Merge pull request #152 from rbaron/ble-assert
[ble] __ASSERT initialization & sensor reading
2023-07-23 18:35:15 +02:00
rbaron
9ea5c7b4d0 Make init & loop fns static 2023-07-23 18:30:23 +02:00
rbaron
1a5acd02a5 [ble] __ASSERT initialization & sensor reading
After we tracked down a rare, annoying bug for the zigbee sample in #150,
this PR does something similar for the ble sample. If there's an error
either initializing the board of reading the sensors, __ASSERT will
reboot the system, instead of letting it hang forever.
2023-07-23 18:10:28 +02:00
rbaron
8fb45f7165
Merge pull request #150 from rbaron/zb-wdt
[zigbee sample] Fix rare infinite loop bug & introduce watchdog
2023-07-23 17:51:21 +02:00
rbaron
9fdd15626a Better error handling for SHTC3 reading 2023-07-15 11:07:35 +02:00
rbaron
c70eb600a3 [zigbee sample] Introduce watchdog for sensor task 2023-07-15 09:45:34 +02:00
rbaron
95ec660cb6
Merge pull request #146 from rbaron/zb-debug++
Fix (minor?) issues with the ZigBee sample
2023-07-15 09:25:18 +02:00
rbaron
d84a6422fe
Merge pull request #148 from Judman/main
Slightly Modified High Airflow Case
2023-07-03 08:05:06 +02:00
Judman
8c21fe73d5
Replace Current High Airflow Cases 2023-07-02 11:33:26 -05:00
Judman
0a73159ce9
Delete b_parasite_case_high_airflow_v2.stl 2023-07-02 11:29:08 -05:00
Judman
105016e24c
Slightly Modified High Airflow Case
Removed the circular supports the extended all the way to the build plate and left small bumps at the top of the case. Replaced with slightly wider and flatter supports that fit within the case constraints. Also slightly enlarged the PCB groove. I found it way too tight and ended up ripping off a batter holder and pads during removal.
2023-07-01 22:41:25 -05:00
rbaron
bc3d834b20 Make it mandatory to select a factory reset method
... in order to avoid unhappy surprises
2023-07-01 08:26:21 +02:00
rbaron
37aff683ad LOG_INF -> LOG_DBG 2023-07-01 08:04:17 +02:00
rbaron
c86f04e406 Nuclear reset option if recurring sensor task cannot be rescheduled
Famous last words: "this should never happen"
2023-07-01 08:04:17 +02:00
rbaron
7f42dd0c00 Add missing prst_led_off after stopping LED flashing timer 2023-07-01 08:04:17 +02:00
rbaron
46feb542cc More fixes to the ZigBee sample
- 🚨 Missing `break` statements in `zboss_signal_handler`
- Makes `zboss_signal_handler` return fast. According to the
  [docs](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.thread_zigbee.v2.0.0%2Fgroup__zb__comm__signals.html):
  "Signal processing should not do long operations synchronously". I
  removed calls to `prst_flash_led` (essentially a huge sync delay) and `debug_counters_increment`,
  which were blocking. I'll try migrating the debug_counters to
  non-blocking separately
- Main sensor reading task is started for the first time upon
  the `ZB_ZDO_SIGNAL_SKIP_STARTUP` signal. This is what the
  [weather_station sample](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/applications/zigbee_weather_station/README.html) does. It should have no functional difference, but there's [some special handling for alarms started from the ZigBee task](0469247dc4/subsys/zigbee/osif/zb_nrf_platform.c (L426))
2023-07-01 08:04:17 +02:00
rbaron
62a99cc716 Reintroduced accidentally deleted comment 2023-07-01 08:04:17 +02:00
rbaron
90f99bd5b0 Fixes (minor?) issues with the ZigBee sample
1. zigbee_configure_sleepy_behavior must be called before zigbee_enable - [source
docs](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/libraries/zigbee/zigbee_app_utils.html#c.zigbee_configure_sleepy_behavior)

2. zb_zdo_pim_set_long_poll_interval must be called after the network is joined - [source docs](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/zboss/3.5.2.0/group__zdo__poll__control.html#ga3aae8929b30e71c872f937771b89c768).
On top of that, the argument for the call was incorrect - it expects ms directly.

3. Removed the unecessary CONFIG_PM, seems like there's no impact in power consumption. Could it cause some weirdness?

4. Implemented CONFIG_PRST_ZB_FACTORY_RESET_DISABLED=y to fully disable factory resetting. May aid debugging.

5. Implemented multi/single channel support. Helps with battery life when scanning networks
2023-07-01 08:04:15 +02:00
rbaron
5882312a77 [zb] Debugging changes:
- Disables double reset
- Fully reset with zb_reset() on reset watchdog instead of
  user_input_indicate()
- Debug counters before/after sensors read
2023-07-01 08:03:42 +02:00
rbaron
a4a02257f9
Merge pull request #147 from rbaron/zb-sw1-factory-reset
[ZigBee sample] Implements factory reset via SW1 button (hardware v2.0.0+)
2023-07-01 07:55:45 +02:00
rbaron
f9f122b818 Formatting 2023-06-27 21:34:49 +02:00
rbaron
d5891f4a6f [ZigBee sample] Implements factory reset via SW1 button (hardware v2.0.0+) 2023-06-27 21:28:56 +02:00
rbaron
56c5fa7ba0
Merge pull request #133 from rbaron/zb_debug_counters
[ZigBee Sample] Introduce debug counters
2023-06-27 19:08:53 +02:00
rbaron
f6b4600044
Merge pull request #140 from rbaron/config_board_revision_code
Introduce CONFIG_BOARD_REVISION_CODE
2023-06-15 18:36:22 +02:00
rbaron
afa53f0e94 Introduce CONFIG_BOARD_REVISION_CODE
Beside having a `CONFIG_BOARD_REVISION` Kconfig string that represents
our semantic versioning for board revisions, the ZigBee sample requires
an integer representation of it. Instead of conditioning the integer
version on the string version only for that sample and having to touch
the ZigBee sample whenever we add a revision, I decided to have a
board-wide config, so that both string and int versions live close to
each other.

Fixes #138.
2023-06-15 07:45:55 +02:00
rbaron
426195184d
Merge pull request #136 from rbaron/code-of-conduct
Create CODE_OF_CONDUCT.md
2023-06-09 18:58:59 +02:00
rbaron
c9df52fd5f
Create CODE_OF_CONDUCT.md
As endearing as it is to get unactionable hate messages, I would rather we focus our efforts on having fun and fostering a small, great, positive, welcoming community, as it's has been so far.

This code of conduct is from https://www.contributor-covenant.org/, based on Mozilla's. I hope we never have to enforce it, and it sucks that we even have to think about these things. But at least this makes it a little more explicit about what's unacceptable behavior.
2023-06-09 09:27:46 +02:00
rbaron
930239e385 Cleanup + double_reset counter 2023-06-07 08:26:05 +02:00
rbaron
015ce94bbd Cleanup 2023-06-07 08:10:23 +02:00
rbaron
987c4f52c0 Removes async debug counters API - not needed 2023-06-07 08:08:34 +02:00
rbaron
509350457f [ZigBee sample] Introduce debug counters
In the effort to increse reliability of the Zigbee sample, #126 and #130
introduced handling for possibly rare, hard-to-debug events that may be
occurring. While these changes seem to help in practive, we don't know
exactly how often, if ever, they are triggered.

This PR introduces debug_counters in hope to add some visilibty in these
hard-to-debug, rare conditions.

Calling `prst_debug_counters_increment("some_counter")` will increment a
value stored in flash. These values are then dumped via logging when the
device boots. Moving forward we can also consider exposing these values
via a ZigBee cluster.
2023-06-07 08:08:34 +02:00
rbaron
bcc5a853d0
Merge pull request #130 from oleo65/zigbee-schedule-steering-restart
Zigbee schedule steering restart
2023-06-07 08:07:44 +02:00
Ole Odendahl
3aa33cdd89
Fixed api of restart watchdog. 2023-06-02 11:39:52 +02:00
Ole Odendahl
ce748c4759
Added restart watchdog timeout to Kconfig. 2023-06-02 11:31:58 +02:00
Ole Odendahl
5cede82dc5
Changing api of restart handler to only expose needed functions.
This encapsulates the low level timer logic from the caller.
2023-06-02 11:16:49 +02:00
Ole Odendahl
9285a6bc41
Added comment to describe nordic funny function name. 2023-06-02 11:15:46 +02:00
Ole Odendahl
6f10dc9e19
Fixed clang format. 2023-06-02 11:07:47 +02:00
Ole Odendahl
c52d5f7a48
Added steering restart handler to signal handler steering branch.
Made the device flash less frequently to save power on unexpected connection drops.
2023-06-02 11:07:47 +02:00
Ole Odendahl
727138415e
Added restart handler module. 2023-06-02 11:07:46 +02:00
Ole Odendahl
69c0ed3694
Separated two signals for better debugging and possibly different handling. 2023-06-02 11:07:46 +02:00
Ole Odendahl
ba786eb298
Moving steering successful flashing to success if branch. 2023-06-02 11:00:17 +02:00
rbaron
6580cf40a2
Merge pull request #126 from MJDSys/fix_KRKNWK-12017
Implement workaround for errata KRKNWK-12017
2023-05-31 21:21:08 +02:00
Matthew Dawson
54b1c48638
Implement workaround for errata KRKNWK-12017
Nordic has published an errata for the nRF Connect SDK for versions
>1.8.0 where a Zigbee End Device can end up getting stuck if the
parent device does not acknowledge the "Device Announcement packet".

They have a suggested workaround to implement in the SDK, which has
been adapted for the custom signal handler used here.

This is an effort to solve issues where my parasites would occasionally
drop off my network and require a reboot.  After 24Hrs, I've not yet
had a device disappear but it has taken >weeks before a device would
fail.  Unforunately it's hard to debug the board as the chips are in a
low power state when this occurs.
2023-05-10 10:45:30 -04:00
rbaron
e56a7748b0
Update kitspace.yaml
#125
2023-05-08 08:58:08 +02:00
rbaron
117b257336
Update README.md
Fix wiki link
2023-05-03 19:56:07 +02:00
rbaron
70d716520d
Update cases options in README.md 2023-04-19 21:41:53 +02:00
rbaron
414427df5b
Merge pull request #121 from mneuhaus/patch-1
Add Link to another Case to the Readme
2023-04-19 21:32:18 +02:00
Marc Neuhaus
fbf8e09f0d
Add Link to another Case to the Readme 2023-04-19 09:23:28 +02:00
rbaron
0ca44f737b
Merge pull request #120 from rbaron/ble-nonconn
[ble] Make advertising non-connectable
2023-04-16 14:26:41 +02:00
rbaron
7ac6d98b3b [ble] Make advertising non-connectable
I've been debugging a Shelly [bluetooth
proxy](https://www.home-assistant.io/integrations/shelly/#bluetooth-support)
feature, which didn't like b-parasite's advertising. It swallowed it before proxying it to HA.

I compared to both ATC and the legacy b-parasite advertising
(pre-nrf-connect) and found it to be related to the
non-connectable/non-scannable settings. Until this PR, we were
mistankenly settings the advertising to scannable, although with no
scannable data. The Zephyr code that sets this parameter based on the
scannable data is [here](c0fcd35531/subsys/bluetooth/host/adv.c (L860)).

My bet is that some scanners like ESPHome are okay with this, but Shelly
is not. Either way, I believe this PR makes the advertisements more
compliant.
2023-04-16 12:28:23 +02:00
rbaron
9ff5180cd7
Merge pull request #117 from rbaron/hardware-v2
Version 2.0.0
2023-03-30 19:30:06 +02:00
rbaron
fdf4609131 Update README.md with photo of v2.0.0 board 2023-03-30 19:20:33 +02:00
rbaron
00d11f50a7 Remove hardware-v2 from workflow triggers 2023-03-30 07:57:37 +02:00
rbaron
caec746c26 Mentions input sample in README.md 2023-03-30 07:57:07 +02:00
rbaron
cb40173b6b Remove debug info 2023-03-29 21:46:41 +02:00
rbaron
ebc26fcccd Cleanups 2023-03-29 21:42:41 +02:00
rbaron
dbded329a4 Build input sample as part of the CI 2023-03-29 21:30:16 +02:00
rbaron
e82c1b6bc1 Implements button handling
- DTS bindings
- Debouncing logic
- New `input` for testing and interrupt power profiling
2023-03-29 21:28:24 +02:00
rbaron
2c052cd376
Merge pull request #116 from oleo65/zigbee-identify-callback
Added zigbee identify callback
2023-03-27 19:36:59 +02:00
Ole Odendahl
20d6c893bf
Added callback for handling identify button action as declared in identify cluster. 2023-03-27 12:20:03 +02:00
rbaron
02b3970ffc Shorten job names 2023-03-19 16:45:55 +01:00
rbaron
6c618d946c Fixes workflow matrix variables and debug 2023-03-19 16:37:33 +01:00
rbaron
ead6fd434d Refactor github actions into parallel jobs 2023-03-19 16:26:12 +01:00
rbaron
bf9f4e1b5b Get GitHub to build some samples for 2.0.0 2023-03-19 15:26:58 +01:00
rbaron
a6995d1ce8 Update date on pcbnew 2023-03-19 15:06:39 +01:00
rbaron
c7128ef163 Generate fabrication files 2023-03-19 15:06:39 +01:00
rbaron
5eef25f86b Updates SW1 location and pin
Previous, SW1 was too close to RST, making it difficult to press one
without pressing another. After changing its position, it was more
convenient to route SW1 to pin 10 on U1 - That's P0.30/AIN6 both on
nRF52840 (C) and nRF52833 (E) modules.
2023-03-19 15:06:39 +01:00
rbaron
19e0354113 Fixes Q1 in PCB layout. I think 2023-03-19 15:06:39 +01:00
rbaron
5cc40a41f6 Set calibration coefficients for 2.0.0 2023-03-19 15:06:39 +01:00
rbaron
7f652ce75d BIG YIKES - Fix wrong symbol/footprint for MMBT3906 (Q1)
I previously used a basic 2N3906 and then changed its footprint to
SOT-23. This led to the wrong pin assignment.
This commit fixes it by using the correct symbol and footprint for it.

Note that the PCB layout still needs fixing.
2023-03-19 15:06:39 +01:00
rbaron
b34bc60a0a Actually use the devicetree calibration coeffs 2023-03-19 15:06:39 +01:00
rbaron
2f7a25e266 Add devicetree calibration coeffs to nrf52833 board 2023-03-19 15:06:39 +01:00
rbaron
e24530b7c3 Fix devicetree-coeffs bug 2023-03-19 15:06:39 +01:00
rbaron
1983f31af9 Store calibration polynomial in devicetree
This will enable us to transparently use different calibration for
different board revisions. The 2.0.0 revision changes the sensing
circuitry and will need its own coeffs.

In this commit, both models are run in parallel for debugging.
2023-03-19 15:06:39 +01:00
rbaron
dbf288138e Add 2.0.0 board revision & CONFIG_BOARD_REVISION Kconfig 2023-03-19 15:06:39 +01:00
rbaron
53f806a8e5 Fix stale silkscreen 1.2.0 -> 2.0.0 2023-03-19 15:06:39 +01:00
rbaron
cfc5491849 Plotted gerbers 2023-03-19 15:06:39 +01:00
rbaron
8dceb6c7cc Plot PDF & generate BOM 2023-03-19 15:06:39 +01:00
rbaron
a79c1579f3 Fix drawing 2023-03-19 15:06:39 +01:00
rbaron
e71eaf9823 Routed 0.10 to a new test pad 2023-03-19 15:06:39 +01:00
rbaron
df6e327947 Slightly better routing 2023-03-19 15:06:39 +01:00
rbaron
b3ae950ee0 Adds sketch of RC circuit in silkscreen 2023-03-19 15:06:39 +01:00
rbaron
1a2dcbb70b Initial routing complete 2023-03-19 15:06:39 +01:00
rbaron
03a281fada New PWM testpoint and added LCSC part # for the E73 module 2023-03-19 15:06:39 +01:00
rbaron
123149d3d2 Some initial hardware changes for the 2.0.0 revision
- Simplified the fast discharge circuit to use a single PNP BJT instead
of two NPN BJTs
- Swapped nRF52 module's footprint for one with larger pads
- Added a user button tied to P0.12 (same as Button 2 from [nrf52840_dk](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nrf52840_dk%2FUG%2Fdk%2Fhw_buttons_leds.html))
- Added a reset switch

These are only schematic changes - PCB layout is stale and needs to be
recreated.
2023-03-19 15:06:39 +01:00
rbaron
d5a548a110
Merge pull request #110 from lonewolf896/main
Modified the high airflow case
2023-03-11 07:28:47 +01:00
Garrett Elkins
1ffc876ad0
Add files via upload 2023-03-10 21:17:13 -08:00
Garrett Elkins
34ddee5ea3
Delete b-parasite-high-airflow.stl 2023-03-10 21:17:00 -08:00
Garrett Elkins
3959abbf03
Delete b_parasite_case_high_airflow.stl 2023-03-10 21:16:52 -08:00
Garrett Elkins
2bff7d8741
Delete b-parasite-high-airflow.3mf 2023-03-10 21:16:18 -08:00
Garrett Elkins
abe8845c4b
Delete b_parasite_case_high_airflow.3mf 2023-03-10 21:16:04 -08:00
Garrett Elkins
40a8676e59
updated high airflow model
modified the high airflow model to increase the print reliability
2023-03-10 21:15:26 -08:00
rbaron
5e315ba9cd [ble] Update connection interval docs & power profile
Closes #98
2023-02-12 16:13:55 +01:00
rbaron
7be5fe1669 [ble] Bumps default advertising interval to [30, 40] ms
This makes the BLE sample use the same parameters as the legacy
firmware, and immproves the range.
2023-02-12 16:02:51 +01:00
rbaron
d292190d31
Merge pull request #103 from MJDSys/fix_battery_information 2023-02-02 08:14:47 +01:00
Matthew Dawson
d3bcc13749
Fix battery quantity/size reporting
The API for reporting information requires a pointer to the data value,
and cannot deal with a constant directly.  The current quantity/size
returned by the device are random values from memory.

Instead move these values into the prst_batt_attrs_t struct and
set their values in prst_zb_attrs_init.
2023-02-02 00:43:13 -05:00
rbaron
4b277bd62f
Merge pull request #12 from kitspace-forks/main 2023-01-31 07:54:05 +01:00
Kaspar Emanuel
ee708db400 Add gerbers field to kitspace.yaml 2023-01-30 22:42:11 +00:00
Kaspar Emanuel
05e25d166a Add kitspace.yaml 2023-01-30 22:42:03 +00:00
rbaron
997bfb7518
Merge pull request #102 from rbaron/ble-adv-int
Make advertisement intervals configurable
2023-01-28 11:52:00 +01:00
rbaron
28db7224a6 Make advertisement intervals configurable
We were previously relying on the default values (100ms, 150ms). This
PR makes it easier to test and configure parameters for different
tradeoffs between range / power consumption (#98).
2023-01-28 11:44:17 +01:00
rbaron
41ba531fba
Merge pull request #101 from MJDSys/zigbee_2.2_sdk 2023-01-23 11:12:55 +01:00
Matthew Dawson
424b471eb8
Update containers to use SDK 2.2.0
The zigbee sample no longer compiles with SDK 2.1, so update the used
docker image to 2.2.0.
2023-01-22 19:03:34 -05:00
Matthew Dawson
af47848b80
Update Zigbee example to work with SDK 2.2.0
The new SDK requires 802.15.4 to be enabled in the board DTS file.

Also disable IPv6 explicitly to avoid compile errors related to its
network stack, since it is not used currently.

Closes #94
2023-01-22 02:51:24 -05:00
rbaron
119055b97a
Merge pull request #95 from derrohrbach/issue/missing-counter
Fix: Counter was missing
2023-01-14 11:31:59 +01:00
Marvin Rohrbach
ee38d4561a Fix: Counter was missing 2023-01-12 08:12:18 +00:00
rbaron
1fbd971c4f
Merge pull request #91 from rbaron/zigbee-double-reset
[zb] Implement a double-reset factory resetting method
2022-12-27 19:35:36 +01:00
rbaron
5d25499ae1 [zb] Implement double-reset factory resetting method 2022-12-27 19:22:01 +01:00
rbaron
8105bfcd7b Ensures sensor-reading callback is always rescheduled 2022-12-26 11:06:26 +01:00
rbaron
98433502af Default PRST_ZB_SLEEP_DURATION_SEC to 60s 2022-12-24 18:37:00 +01:00
rbaron
14399dffb4 Fix artifact uploading in GitHub build-and-upload action 2022-12-24 17:22:15 +01:00
rbaron
54792ce35f
Merge pull request #81 from rbaron/zigbee-sample
Introduce an experimental/educactional/exploratory Zigbee firmware sample
2022-12-24 10:29:38 +01:00
rbaron
c5a129a39a Poor man's templating for PRST_ZB_BUILD_DATE=__TODAY__ in GitHub actions 2022-12-24 10:19:10 +01:00
rbaron
3d68e5c689 Better docs 2022-12-24 10:06:35 +01:00
rbaron
a34b1f8721
Merge pull request #89 from rbaron/nrf52833-adc-fix
Fix ADC config for nRF52833
2022-12-24 09:48:12 +01:00
rbaron
c76b8dc289 Fix ADC config for nRF52833 2022-12-21 18:40:48 +01:00
rbaron
fe63fda2f1 Setup Kconfig for prstlib 2022-12-19 18:34:03 +01:00
rbaron
c567408861 Fix prst_zb_attrs.c format 2022-12-19 09:33:18 +01:00
rbaron
06627ff85d
Merge pull request #84 from oleo65/zigbee
Zigbee improvements
2022-12-19 09:27:43 +01:00
Ole Odendahl
54382e3356
Removed log level config from board definition.
Moved prst lib log level config to application layer with prst lib default fallback behaviour.
2022-12-18 21:23:10 +01:00
Ole Odendahl
0f859c77cd
Slightly improved power cluster with more values. 2022-12-18 17:27:43 +01:00
Ole Odendahl
580f425b01
Added hardware version config value. 2022-12-18 17:15:58 +01:00
Ole Odendahl
ac8677f3e8
Renamed basic cluster attributes to comply with library defaults. 2022-12-18 17:12:25 +01:00
Ole Odendahl
0933c4a2a6
Fixed default sleep duration in Kconfig to 600s.
Removed specific Kconfig values from prj*.conf files.
2022-12-18 16:57:50 +01:00
Ole Odendahl
412188502b
Changed name of config value for prstlib default log level. 2022-12-18 16:55:32 +01:00
Ole Odendahl
891a63a1b3
Added separate debug config to better separate flags during development. 2022-12-16 13:15:51 +01:00
Ole Odendahl
61729aa81a
Made more basic attributes compile time configurable. 2022-12-16 12:16:42 +01:00
Ole Odendahl
3b91d7525c
Improved basic attribute list to include more properties. 2022-12-16 12:15:36 +01:00
Ole Odendahl
0a3d711787
Fixed bug in cluster list not connecting power cluster. 2022-12-16 12:10:08 +01:00
Ole Odendahl
03b45062c0
Fixing sensor logging millivolts. 2022-12-16 11:49:37 +01:00
Ole Odendahl
96e1e85179
Making zigbee main log level configurable. 2022-12-16 11:21:52 +01:00
Ole Odendahl
228b6f2f16
Making core log level configurable via Kconfig entry. 2022-12-16 11:21:27 +01:00
rbaron
5da4ad836f Update description 2022-12-10 16:52:35 +01:00
rbaron
37237a4301 More accurate power profile 2022-12-10 15:48:02 +01:00
rbaron
17b487f362 Add docs 2022-12-10 15:45:22 +01:00
rbaron
3a6561bbf4 Make the LED flash while pairing 2022-12-10 15:14:01 +01:00
rbaron
7b4e64cec7 Implemented a pairing-reset detection strategy
This is a "tricky" workaround due to the lack of physical buttons:

We can distinguish between the two "power up" modes:

- Power on: when the device receives power for the first time. Example:
swapping batteries, prroviding external power. This _DOES NOT_ reset the
pairing info.

- Reset pin: when the device is manually reset (by shorting the RESET
pin to GND), or when a new firmware is flashed. This _RESETS_ the
pairing info.
2022-12-10 12:37:29 +01:00
rbaron
8eb54106d5 Add illuminance cluster & reporting 2022-12-10 10:47:16 +01:00
rbaron
3c2094db2d Add GitHub action 2022-12-09 20:01:46 +01:00
rbaron
fbf1a35099 Moves configs to Kconfig 2022-12-09 19:38:56 +01:00
rbaron
cb6ed73691 Add battery percentage computation 2022-12-09 19:02:55 +01:00
rbaron
676b525bd9 Reading and sending real soil moisture works! 2022-12-07 22:22:41 +01:00
rbaron
01bb9da2a4 Cleanups 2022-12-07 21:43:30 +01:00
rbaron
f7373cc6da Custom Soil Moisture cluster works! :)
This commits adds a Soil Moisture cluster according to the one that
exists in https://github.com/Koenkk/zigbee-herdsman/blob/master/src/zcl/definition/cluster.ts#L2570.
The cluster Id is 1032 (I didn't find it in the official specs).
2022-12-07 20:10:52 +01:00
rbaron
15b1d9d2a8 Batt, temp and humi show up (with reporting!) on HA with zigbee2mqtt :)) 2022-12-07 20:10:52 +01:00
rbaron
9b1efb7f82 Quick and dirty commenting out all LEDs 2022-12-07 20:10:52 +01:00
rbaron
d857448df8 Added a humidity cluster 2022-12-07 20:10:52 +01:00
rbaron
55808ed7e1 Links prstlib and restructures project to match other samples 2022-12-07 20:10:52 +01:00
rbaron
d8322c9162 Modified the zigbee template with added temperature cluster
According to
https://developer.nordicsemi.com/nRF_Connect_SDK/doc/2.1.2/nrf/ug_zigbee_adding_clusters.html.
2022-12-07 20:10:52 +01:00
rbaron
f090b25a2c Got manufacturer data to show as "b-parasite" in zigbee2mqtt 2022-12-07 20:10:52 +01:00
rbaron
f05d1f8e3b Zigbee sample works with nRF52840dk + zigbee2mqtt 2022-12-07 20:10:51 +01:00
rbaron
af3a9add74 Fix README links 2022-12-07 20:04:33 +01:00
rbaron
fdf41575a7
Merge pull request #76 from rbaron/nrf-connect
Migrate code to the nRF Connect SDK
2022-12-07 20:01:32 +01:00
rbaron
3bf90451db Remove legacy nrf-sdk code/b-parasite 2022-12-07 19:28:20 +01:00
rbaron
192cca127c Update README.md 2022-12-07 19:19:43 +01:00
rbaron
002500dcd6 Adds samples/ble/README.md 2022-12-07 18:57:27 +01:00
rbaron
a8c7d3c1cd <zephyr/zephyr.h> -> <zephyr/kernel.h> in prstlib 2022-12-06 19:18:37 +01:00
rbaron
f7806d4c7e Really fixing BTHome v2 illuminance encoding this time 2022-12-04 10:32:20 +01:00
rbaron
cab55d9b8c <zephyr/zephyr.h> -> <zephyr/kernel.h> everywhere 2022-12-04 10:16:00 +01:00
rbaron
381bbb7ceb Fix illuminance encoding in ble, BTHome v2 2022-12-04 10:13:01 +01:00
rbaron
cc62786d16 Updates GitHub actions to build for different board revisions 2022-12-03 14:11:22 +01:00
rbaron
8bfd75de78 Fix BTHome v2 encoding - sort entries by Object IDs 2022-12-03 13:49:17 +01:00
rbaron
bc08c8284a Updates Zephyr imports to use the <zephyr/...> prefix
See https://docs.zephyrproject.org/latest/releases/release-notes-3.2.html#api-changes
2022-12-03 11:26:03 +01:00
rbaron
7c0b83e94e Add blackmagicprobe.board.cmake to board.cmake for both variants 2022-12-03 11:05:24 +01:00
rbaron
6969d74a8c Fixes GitHub actions to use board revisions 2022-12-03 11:04:20 +01:00
rbaron
f476e69229 Adds board revisions for nRF52833 variant 2022-12-03 11:03:55 +01:00
rbaron
dc88916823 Add board 1.0.0, 1.1.0, 1.2.0 board revisions for nRF52840 variant 2022-12-03 10:46:48 +01:00
rbaron
a93171b9bf Adds support for user-supplied BT address
Also cleans up BLE configs
2022-12-01 19:48:36 +01:00
rbaron
b483af4126 Fixes actions 2022-11-30 21:13:19 +01:00
rbaron
ad45efb6d9 Cleanup actions 2022-11-30 21:06:08 +01:00
rbaron
b731006dbb Adds bthomv1 build for nRF52833 to GitHub workflow 2022-11-30 19:28:48 +01:00
rbaron
a9a478f68f Add a nRF52840 build to GitHub workflow 2022-11-30 19:22:13 +01:00
rbaron
0bda7de7cf Add b-parasite with nRF52833 board definition 2022-11-30 19:18:25 +01:00
rbaron
fe09c0b2f1 Update github actions to generate more samples 2022-11-30 18:33:06 +01:00
rbaron
9c62b12446 Update default Kconfig in samples/ble 2022-11-29 22:52:08 +01:00
rbaron
dbaf44ecc1 Cleanup comments 2022-11-29 20:25:15 +01:00
rbaron
3b104cdf24 Implements BTHome v1 and v2 encodings for ble sample 2022-11-29 20:05:41 +01:00
rbaron
4a6e3dcec3 Update github action & workflow to build nrf-connect samples 2022-11-28 19:44:41 +01:00
rbaron
fc3a0652aa Add blinky sample 2022-11-28 19:38:32 +01:00
rbaron
46a8285f59 Reuse macros.h in nrf-connect/samples/soil_read_loop/src/main.c 2022-11-27 23:21:50 +01:00
rbaron
89d2999bd8 Deletes old sample 2022-11-27 22:36:32 +01:00
rbaron
cc4c47927b Moves soil_read_loop to samples/ 2022-11-27 22:36:06 +01:00
rbaron
00a7114dd1 Extracts boards/ and dts/ to prstlib 2022-11-27 18:56:47 +01:00
rbaron
5ceccfafcc Extracts prstlib
Moves ble sample to code/nrf-connect/samples
2022-11-27 18:37:42 +01:00
rbaron
df7700cdd7 Recalibrated. BLE sample seems to work e2e @ 2.7uA sleep :) 2022-11-25 23:42:40 +01:00
rbaron
a9d551f33c Adds a calibration sample 2022-11-25 23:20:03 +01:00
rbaron
5510fd963c BLE sample kinda works. Pending calibration 2022-11-25 22:30:42 +01:00
rbaron
339e5bebe9 Fixs PWM current consumption - SYSTEM ON sleep @ ~2.7uA 2022-11-22 22:21:16 +01:00
rbaron
6705c11867 Cleanup 2022-11-18 01:16:44 +01:00
rbaron
d18e7ab2d6 Kconfigure BLE service data length 2022-11-18 01:14:58 +01:00
rbaron
c5a5fcaf5f Adds simple Kconfig 2022-11-18 00:50:10 +01:00
rbaron
a65b00a439 Cleanup 2022-11-17 19:56:02 +01:00
rbaron
9333913571 Encoding of b-parasite protocol v2 works I guess 2022-11-17 19:51:35 +01:00
rbaron
8c7a2c35de Update shtc3 API 2022-11-16 19:49:09 +01:00
rbaron
597542649a Moved BLE stuff to src/ble/ 2022-11-16 19:28:40 +01:00
rbaron
3749d689ba Very minimal, working BLE advertising 2022-11-16 00:00:16 +01:00
rbaron
8cfb87ef32 Configs button 2022-11-14 22:37:03 +01:00
rbaron
ffeca36c27 Configures LED 2022-11-14 22:22:08 +01:00
rbaron
c15120178f Enables RTT console for bparasite_nrf52840 2022-11-14 19:13:04 +01:00
rbaron
a639343b6e Code commpiles for custom bparasite_nrf52840 board 2022-11-14 19:01:39 +01:00
rbaron
427f9279de Revert "Uses a single PNP BJT in the fast discharge circuit"
This reverts commit fb7dff8bfe.

Fixes #72
2022-11-14 08:48:02 +01:00
rbaron
2779fe9af6 Started adding a b-parasite board. Not working yet 2022-11-14 08:47:14 +01:00
rbaron
3d6b4eaef1 Tame log levels 2022-11-13 17:28:38 +01:00
rbaron
59f2aad3db Some hacky macros for error handling 2022-11-13 17:26:50 +01:00
rbaron
107936770e Better error handling in adc.c 2022-11-13 16:50:34 +01:00
rbaron
6b1f52925e Moved adc stuff to adc.c 2022-11-13 16:18:30 +01:00
rbaron
522452b465 ADC works 2022-11-13 14:37:55 +01:00
rbaron
9c9492567f PWM kinda works 2022-11-13 11:20:31 +01:00
rbaron
bae06952fb Initial commit
SHTC3 comm via i2c works
2022-11-12 12:46:15 +01:00
rbaron
baec1c2a80 Adds missing base.stl to experimental/rotisserie
Fixes #63
2022-11-08 19:19:08 +01:00
rbaron
fb7dff8bfe Uses a single PNP BJT in the fast discharge circuit 2022-11-06 18:20:47 +01:00
rbaron
54c6c09ff0
Merge pull request #68 from oleo65/python-flash-script
Introduces Python helper script for flashing with OpenOCD (pyocd)
2022-10-23 19:02:58 +02:00
Ole Odendahl
149e15480d
Fixed typo in readme and changed meaning of the sentence to better reflect the actual situation. 2022-10-23 19:00:57 +02:00
Ole Odendahl
d8dfcc69a9
Final cosmetic changes in flashing code to improve output and variable naming. 2022-10-23 18:55:46 +02:00
Ole Odendahl
e45bb43b84
Clarified mac address code so that it will be clearer what it contains (static device mac). 2022-10-23 18:51:56 +02:00
Ole Odendahl
100f4046fa
Added basic readme for flashing based on issue #67. 2022-10-23 18:10:08 +02:00
Ole Odendahl
53e52f3267
Moving the flashing files to different subfolder for better discoverability. 2022-10-23 18:09:43 +02:00
Ole Odendahl
a25ec5ca58
Simplified the cli of flashing scripts. 2022-10-23 18:00:11 +02:00
Ole Odendahl
8202761646
Added requirements file for python environment setup. 2022-10-23 17:36:28 +02:00
Ole Odendahl
39f839b794
Changed use of memory addresses in mac address reader to hex representation. 2022-10-23 17:32:57 +02:00
Ole Odendahl
1174bcab67
Added script for facilitating flashing procedures with pyocd. 2022-10-23 11:48:24 +02:00
Ole Odendahl
f4df95e6a0
Added mac converter class for easy reading of mac address from microcontroller. 2022-10-23 11:38:12 +02:00
rbaron
785c8d7874
Merge pull request #65 from hallsny/main
high airflow case design stl and 3mf files
2022-10-10 23:09:06 +02:00
hallsny
e1ec8fd5a8
high airflow case design stl and 3mf files 2022-10-10 13:16:10 -07:00
rbaron
220ff6ec61
Merge pull request #64 from rbaron/rotisserie
Open sources the rotisserie / spinning drying jig
2022-09-29 23:25:58 +02:00
rbaron
0c7222d199 Open sources the rotisserie / spinning drying jig
Fixes #63
2022-09-29 23:21:32 +02:00
rbaron
9c2c3d7c3a
Merge pull request #62 from sairon/cheaper-lcsc-r10
replace R10 LCSC part with equivalent without extra fee
2022-09-29 22:56:19 +02:00
Jan Čermák
f44e91f865
replace R10 LCSC part with equivalent without extra fee 2022-09-29 20:28:56 +02:00
rbaron
b0cbba00c4
Merge pull request #57 from Hedda/patch-1
nrf2480 / nrf2833  -> nRF52840 / nRF52833
2022-09-09 17:00:59 +02:00
Hedda
c339538913
Makefile comment nrf2480 -> nRF52840
Makefile comment nrf2480 -> nRF52840
2022-09-09 16:53:01 +02:00
Hedda
314a7f2fe3
README.md nrf2480 -> nRF52840
README.md nrf2480 -> nRF52840
2022-09-09 16:51:33 +02:00
rbaron
7ebfe7e399
Update "Battery Life" section in README.md
Fixes #48.
2022-09-09 16:25:48 +02:00
rbaron
82eabe778a Fixes BTHome encoding 2022-08-30 21:02:51 +02:00
rbaron
5c1ea4a164
Merge pull request #53 from rbaron/bthome
Adds support for the BTHome BLE protocol
2022-08-29 21:35:27 +02:00
195 changed files with 183657 additions and 32312 deletions

View file

@ -0,0 +1,13 @@
{
"name": "nrf-connect:v2.4",
"image": "nordicplayground/nrfconnect-sdk:v2.5-branch",
"features": {
},
"customizations": {
"vscode": {
"extensions": [
"nordic-semiconductor.nrf-connect"
]
}
}
}

View file

@ -1,13 +0,0 @@
FROM debian:bullseye-slim
RUN apt-get update && \
apt-get -y install wget tar unzip make clang-format gcc-arm-none-eabi
RUN cd /opt && \
wget https://www.nordicsemi.com/-/media/Software-and-other-downloads/SDKs/nRF5/Binaries/nRF5SDK1702d674dde.zip -O nRF5_SDK.zip && \
unzip nRF5_SDK.zip && \
mv nRF5_SDK_17.0.2_d674dde nRF5_SDK
COPY build.sh /build.sh
ENTRYPOINT ["/build.sh"]

View file

@ -1,5 +1,27 @@
name: 'Build'
description: 'Builds b-parasite firmware'
name: "Build"
description: "Builds a nrf-connect sample for b-parasite"
inputs:
sample-dir:
description: "Sample directory to build"
required: true
board:
description: "Board definition to use"
default: bparasite_nrf52840
revision:
description: "Board revision use"
default: "2.0.0"
cmake-extra:
description: "Extra CMake arguments"
default: ""
runs:
using: 'docker'
image: 'Dockerfile'
using: "composite"
steps:
- run: |
docker run --rm -v ${GITHUB_WORKSPACE}:/repo \
nordicplayground/nrfconnect-sdk:v2.5-branch \
west build \
--build-dir /repo/${{ inputs.sample-dir }}/build \
--pristine \
--board ${{ inputs.board }}@${{ inputs.revision }} \
/repo/${{ inputs.sample-dir }} -- ${{ inputs.cmake-extra }}
shell: bash

View file

@ -1,10 +0,0 @@
#!/bin/bash
set -eux -o pipefail
export SDK_ROOT=/opt/nRF5_SDK
export GNU_INSTALL_ROOT=/usr/bin/
cd "$GITHUB_WORKSPACE/code/b-parasite"
make clean
make lint
make

View file

@ -6,12 +6,106 @@ on:
pull_request:
branches:
- main
jobs:
build:
lint:
runs-on: ubuntu-latest
name: Checks format & builds b-parasite's firmware
name: Check code format
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Check clang-format
uses: jidicula/clang-format-action@v4.9.0
with:
check-path: "code/nrf-connect"
exclude-regex: '\/build\/'
build-blinky:
runs-on: ubuntu-latest
name: Build blinky
needs:
- lint
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build blinky
uses: ./.github/actions/build
with:
sample-dir: code/nrf-connect/samples/blinky
board: bparasite_nrf52840
revision: 2.0.0
output-bin: blinky_nrf52840.hex
build-input:
runs-on: ubuntu-latest
name: Build input
needs:
- lint
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build input
uses: ./.github/actions/build
with:
sample-dir: code/nrf-connect/samples/input
board: bparasite_nrf52840
revision: 2.0.0
output-bin: input_nrf52840.hex
build-soil-read-loop:
runs-on: ubuntu-latest
name: Build soil_read_loop
needs:
- lint
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build soil_read_loop
uses: ./.github/actions/build
with:
sample-dir: code/nrf-connect/samples/soil_read_loop
board: bparasite_nrf52840
revision: 2.0.0
output-bin: soil_read_loop_nrf52840.hex
build-ble:
strategy:
matrix:
soc: [nrf52840, nrf52833]
revision: [1.1.0, 1.2.0, 2.0.0]
runs-on: ubuntu-latest
name: Build ble ${{ matrix.soc }}@${{ matrix.revision }}
needs:
- lint
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build
uses: ./.github/actions/build
with:
sample-dir: code/nrf-connect/samples/ble
board: bparasite_${{ matrix.soc }}
revision: ${{ matrix.revision }}
cmake-extra: -DCONFIG_PRST_BLE_ENCODING_BTHOME_V2=y -DCONFIG_PRST_SLEEP_DURATION_MSEC=1000 -DCONFIG_PRSTLIB_LOG_LEVEL_DBG=y
output-bin: ble_${{ matrix.soc }}_${{ matrix.revision }}_debug.hex
build-zigbee:
strategy:
matrix:
soc: [nrf52840, nrf52833]
revision: [1.2.0, 2.0.0]
runs-on: ubuntu-latest
name: Build zigbee ${{ matrix.soc }}@${{ matrix.revision }}
needs:
- lint
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build
uses: ./.github/actions/build
with:
sample-dir: code/nrf-connect/samples/zigbee
board: bparasite_${{ matrix.soc }}
revision: ${{ matrix.revision }}
cmake-extra: -DCONFIG_PRSTLIB_LOG_LEVEL_DBG=y
output-bin: zigbee_${{ matrix.soc }}_${{ matrix.revision }}_debug.hex

128
CODE_OF_CONDUCT.md Normal file
View file

@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
rbaron.net/contact.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View file

@ -2,81 +2,59 @@
# b-parasite
<p align="center">
<img src="img/resized/img1.jpg" width="512px" border="0" alt="PCB front and back photo" />
<img src="img/resized/b-parasite-2.0.0.jpg" width="512px" border="0" alt="PCB front and back photo" />
</p>
b-parasite is an open source Bluetooth Low Energy (BLE) soil moisture and ambient temperature/humidity/light sensor.
b-parasite is an open source soil moisture and ambient temperature/humidity/light sensor.
# 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
* Capacitive Soil moisture sensor - see [this blog post](https://rbaron.net/blog/2021/04/05/How-capacitive-soil-moisture-sensors-work.html), [this Twitter thread](https://twitter.com/rbaron_/status/1367182806368071685), and [this post](https://wemakethings.net/2012/09/26/capacitance_measurement/) for nice resources on how they work
* Air temperature and humidity 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/)
* Light sensor using an [ALS-PT19](https://everlighteurope.com/ambient-light-sensors/7/ALSPT19315CL177TR8.html) phototransistor
* Powered by a common CR2032 coin cell, with a battery life of possibly over a year - see "Battery Life" below
* Light sensor using an [ALS-PT19](https://en.everlight.com/wp-content/plugins/ItemRelationship/product_files/pdf/ALS-PT19-315C-L177-TR8_V8.pdf) phototransistor
* Powered by a common CR2032 coin cell, potentially for over two years
* Support for [nRF52840](https://www.nordicsemi.com/products/nrf52840) and [nRF52833](https://www.nordicsemi.com/products/nrf52833) modules
* Open hardware and open source design
# Wiki Pages
* [Hardware Versions](https://github.com/rbaron/b-parasite/wiki/Hardware-Versions)
* [How to order: PCB fabrication and SMT assembly](https://github.com/rbaron/b-parasite/wiki/How-to-order:-PCB-fabrication-and-SMT-assembly)
* [How to Program](https://github.com/rbaron/b-parasite/wiki/How-to-Program)
# Software
This repository also hosts a few different firmware samples for b-parasite.
|Sample|Description|Extra Documentation|
|---|---|---|
|[samples/ble](./code/nrf-connect/samples/ble)|This is the most battle-tested and useful firmware. It periodically reads all sensors and broadcast them via Bluetooth Low Energy (BLE). It works with [Home Assistant](https://www.home-assistant.io/) + [BTHome](https://bthome.io/) out of the box. |[Docs](./code/nrf-connect/samples/ble/README.md)|
|[samples/zigbee](./code/nrf-connect/samples/zigbee)| An experimental/educational/exploratory basic Zigbee sample built on [nRF Connect + ZBOSS](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/ug_zigbee.html). It integrates with [Home Assistant](https://www.home-assistant.io/) via [ZHA](https://www.home-assistant.io/integrations/zha) or [Zigbee2MQTT](https://www.zigbee2mqtt.io/). |[Docs](./code/nrf-connect/samples/zigbee/README.md)|
|[samples/blinky](./code/nrf-connect/samples/blinky)| The classic "Hello, world" |-|
|[samples/soil_read_loop](./code/nrf-connect/samples/soil_read_loop)| Reads the soil moisture sensor on a loop. Useful for experimenting and calibrating the sensor. |-|
|[samples/input](./code/nrf-connect/samples/input)| Handles button presses. Useful for power profiling GPIO interrupts and testing debouncing for push switches on [boards that have them](https://github.com/rbaron/b-parasite/wiki/Hardware-Versions). |-|
# Documentation
Information about how to order, assemble, build the samples, protect the sensor and flash the firmware is on [the Wiki](https://github.com/rbaron/b-parasite/wiki).
# 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)
* [code/nrf-connect/](./code/nrf-connect/) - Common library and samples, built with Nordic's [nRF Connect SDK](https://www.nordicsemi.com/Products/Development-software/nrf-connect-sdk).
* [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
* [case/](case/) - a 3D printable case
# How It Works
<p align="center">
<img src="img/excalidraw/diagram.png" border="0" alt="Diagram containing two b-parasites, a bridge & an MQTT broker" />
</p>
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. A common pattern 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 then responsible for relaying the sensor data to interested parties. This is the topology shown in the diagram above.
## Integrations
### ESPHome
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. b-parasite is officially supported and documentation for using it can be found in [the b-parasite ESPHome docs](https://esphome.io/components/sensor/b_parasite.html). An example of using this platform is also available in this repo, under [bridge/](bridge/) (check out [README.md](bridge/README.md) there for more info).
ESPHome is a battle-tested project with a vibrant community, and is currently the most mature b-parasite bridge. ESP32 are also cheap, so you can sprinkle a few of them around the house to cover a wide range, and even share the same ESP32 with other sensors.
### Home Assistant
b-parasite is supported by the [ble_monitor](https://github.com/custom-components/ble_monitor) Home Assistant custom component - please refer to the [docs](https://custom-components.github.io/ble_monitor/by_brand#rbaron). This custom component gets Home Assistant to automatically discover nearby b-parasites based on their advertisement data.
### Linux/Raspberry Pi & macOS
Another possibility is running [parasite-scanner](https://github.com/rbaron/parasite-scanner). It is a purpose-built bridge for b-parasites, and runs on Linux and macOS.
This is the quickest way to collect and visualize data from b-parasites, and I personally use it a lot for testing and debugging.
## Protocol and Data Encoding
Sensor data is transmitted via BLE advertisement broadcasts. [Here](./code/b-parasite/README.md) you can find a byte-by-byte description of the data as it is encoded inside the advertisement packet.
# 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):
<p align="center">
<img src="img/scope/8dbm-across-10ohm.png" border="0" alt="A screenshot of an oscilloscope used to measure current consumption" />
</p>
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.
<p align="center">
<img src="img/resized/img2.jpg" border="0" alt="b-parasite stuck into a small plant vase" />
</p>
# Case
A 3D printable case model can be found in [case/](case/).
![Render of the 3D printable case](./img/case/screenshot.png)
<p align="center">
<img src="img/case/screenshot.png" alt="Render of the original 3D printable case" />
</p>
We have three different 3D-printable cases:
1. Original snap-on case - [case/Top.stl](./case/Top.stl), [case/Bottom.stl](./case/Bottom.stl)
2. High airflow - [case/b_parasite_case_high_airflow.stl](./case/b_parasite_case_high_airflow.stl)
3. Mushroom-style - available on [Printables](https://www.printables.com/model/456571-mushroomcap-for-b-parasite-soil-moisture-sensor)
4. b-parasite Hat - available on [Printables](https://www.printables.com/model/901220-waterproof-case-for-b-parasite-soil-moisture-air-s)
# Accessories
Designs and hardware to help you, when building your own:
1. Desk holder for b-parasites [Printables](https://www.printables.com/de/model/566974-b-parasite-holder)
# 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/).

Binary file not shown.

Binary file not shown.

4
code/.clang-format Normal file
View file

@ -0,0 +1,4 @@
---
BasedOnStyle: Google
ColumnLimit: 0

View file

@ -1 +0,0 @@
_build

View file

@ -1,337 +0,0 @@
# Supported platforms:
# * E73_2G4M08S1C (nrf2480, default)
# * E73_2G4M08S1E (nrf2833)
# note: the missing whitespace after the = is on purpose here
PLATFORM ?=E73_2G4M08S1C
ifeq ($(PLATFORM),E73_2G4M08S1E)
SRC_FILES := \
$(SDK_ROOT)/modules/nrfx/mdk/gcc_startup_nrf52833.S \
$(SDK_ROOT)/modules/nrfx/mdk/system_nrf52833.c
TARGETS := nrf52833_xxaa
CFLAGS += -DNRF52833_XXAA
ASMFLAGS += -DNRF52833_XXAA
LINKER_SCRIPT := b_parasite_gcc_nrf52833.ld
HEAP_SIZE := 2048
STACK_SIZE := 2048
else ifeq ($(PLATFORM),E73_2G4M08S1C)
SRC_FILES := \
$(SDK_ROOT)/modules/nrfx/mdk/gcc_startup_nrf52840.S \
$(SDK_ROOT)/modules/nrfx/mdk/system_nrf52840.c
TARGETS := nrf52840_xxaa
CFLAGS += -DNRF52840_XXAA
ASMFLAGS += -DNRF52840_XXAA
LINKER_SCRIPT := b_parasite_gcc_nrf52840.ld
HEAP_SIZE := 8192
STACK_SIZE := 8192
endif
PROJECT_NAME := ble_app_beacon_pca10056_s140
OUTPUT_DIRECTORY := _build
# Set SDK_ROOT via env vars.
# SDK_ROOT := ../../../.././..
PROJ_DIR := ./src
# Source files common to all targets
SRC_FILES += \
$(SDK_ROOT)/components/libraries/log/src/nrf_log_backend_rtt.c \
$(SDK_ROOT)/components/libraries/log/src/nrf_log_backend_serial.c \
$(SDK_ROOT)/components/libraries/log/src/nrf_log_backend_uart.c \
$(SDK_ROOT)/components/libraries/log/src/nrf_log_default_backends.c \
$(SDK_ROOT)/components/libraries/log/src/nrf_log_frontend.c \
$(SDK_ROOT)/components/libraries/log/src/nrf_log_str_formatter.c \
$(SDK_ROOT)/components/libraries/button/app_button.c \
$(SDK_ROOT)/components/libraries/util/app_error.c \
$(SDK_ROOT)/components/libraries/util/app_error_handler_gcc.c \
$(SDK_ROOT)/components/libraries/util/app_error_weak.c \
$(SDK_ROOT)/components/libraries/scheduler/app_scheduler.c \
$(SDK_ROOT)/components/libraries/timer/app_timer2.c \
$(SDK_ROOT)/components/libraries/util/app_util_platform.c \
$(SDK_ROOT)/components/libraries/timer/drv_rtc.c \
$(SDK_ROOT)/components/libraries/hardfault/hardfault_implementation.c \
$(SDK_ROOT)/components/libraries/util/nrf_assert.c \
$(SDK_ROOT)/components/libraries/atomic_fifo/nrf_atfifo.c \
$(SDK_ROOT)/components/libraries/atomic/nrf_atomic.c \
$(SDK_ROOT)/components/libraries/balloc/nrf_balloc.c \
$(SDK_ROOT)/external/fprintf/nrf_fprintf.c \
$(SDK_ROOT)/external/fprintf/nrf_fprintf_format.c \
$(SDK_ROOT)/components/libraries/memobj/nrf_memobj.c \
$(SDK_ROOT)/components/libraries/pwr_mgmt/nrf_pwr_mgmt.c \
$(SDK_ROOT)/components/libraries/ringbuf/nrf_ringbuf.c \
$(SDK_ROOT)/components/libraries/experimental_section_vars/nrf_section_iter.c \
$(SDK_ROOT)/components/libraries/sortlist/nrf_sortlist.c \
$(SDK_ROOT)/components/libraries/strerror/nrf_strerror.c \
$(SDK_ROOT)/integration/nrfx/legacy/nrf_drv_clock.c \
$(SDK_ROOT)/integration/nrfx/legacy/nrf_drv_twi.c \
$(SDK_ROOT)/modules/nrfx/soc/nrfx_atomic.c \
$(SDK_ROOT)/modules/nrfx/drivers/src/nrfx_clock.c \
$(SDK_ROOT)/modules/nrfx/drivers/src/nrfx_gpiote.c \
$(SDK_ROOT)/modules/nrfx/drivers/src/nrfx_rtc.c \
$(SDK_ROOT)/modules/nrfx/drivers/src/nrfx_saadc.c \
$(SDK_ROOT)/modules/nrfx/drivers/src/nrfx_twi.c \
$(SDK_ROOT)/modules/nrfx/drivers/src/prs/nrfx_prs.c \
$(SDK_ROOT)/modules/nrfx/drivers/src/nrfx_pwm.c \
$(PROJ_DIR)/main.c \
$(SDK_ROOT)/external/segger_rtt/SEGGER_RTT.c \
$(SDK_ROOT)/external/segger_rtt/SEGGER_RTT_Syscalls_GCC.c \
$(SDK_ROOT)/external/segger_rtt/SEGGER_RTT_printf.c \
$(SDK_ROOT)/components/ble/common/ble_advdata.c \
$(SDK_ROOT)/components/ble/common/ble_srv_common.c \
$(SDK_ROOT)/external/utf_converter/utf.c \
$(SDK_ROOT)/components/softdevice/common/nrf_sdh.c \
$(SDK_ROOT)/components/softdevice/common/nrf_sdh_ble.c \
$(SDK_ROOT)/components/softdevice/common/nrf_sdh_soc.c \
$(PROJ_DIR)/prst/adc.c \
$(PROJ_DIR)/prst/ble.c \
$(PROJ_DIR)/prst/pwm.c \
$(PROJ_DIR)/prst/rtc.c \
$(PROJ_DIR)/prst/shtc3.c \
# Include folders common to all targets
INC_FOLDERS += \
$(SDK_ROOT)/components/nfc/ndef/generic/message \
$(SDK_ROOT)/components/nfc/t2t_lib \
$(SDK_ROOT)/components/nfc/t4t_parser/hl_detection_procedure \
$(SDK_ROOT)/components/ble/ble_services/ble_ancs_c \
$(SDK_ROOT)/components/ble/ble_services/ble_ias_c \
$(SDK_ROOT)/components/libraries/pwm \
$(SDK_ROOT)/components/libraries/usbd/class/cdc/acm \
$(SDK_ROOT)/components/libraries/usbd/class/hid/generic \
$(SDK_ROOT)/components/libraries/usbd/class/msc \
$(SDK_ROOT)/components/libraries/usbd/class/hid \
$(SDK_ROOT)/modules/nrfx/hal \
$(SDK_ROOT)/components/nfc/ndef/conn_hand_parser/le_oob_rec_parser \
$(SDK_ROOT)/components/libraries/log \
$(SDK_ROOT)/components/ble/ble_services/ble_gls \
$(SDK_ROOT)/components/libraries/fstorage \
$(SDK_ROOT)/components/nfc/ndef/text \
$(SDK_ROOT)/components/libraries/mutex \
$(SDK_ROOT)/components/libraries/gpiote \
$(SDK_ROOT)/components/libraries/bootloader/ble_dfu \
$(SDK_ROOT)/components/nfc/ndef/connection_handover/common \
$(SDK_ROOT)/components/nfc/ndef/generic/record \
$(SDK_ROOT)/components/ble/ble_advertising \
$(SDK_ROOT)/external/utf_converter \
$(SDK_ROOT)/components/ble/ble_services/ble_bas_c \
$(SDK_ROOT)/modules/nrfx/drivers/include \
$(SDK_ROOT)/components/libraries/experimental_task_manager \
$(SDK_ROOT)/components/ble/ble_services/ble_hrs_c \
$(SDK_ROOT)/components/softdevice/s140/headers/nrf52 \
$(SDK_ROOT)/components/nfc/ndef/connection_handover/le_oob_rec \
$(SDK_ROOT)/components/libraries/queue \
$(SDK_ROOT)/components/libraries/pwr_mgmt \
$(SDK_ROOT)/components/ble/ble_dtm \
$(SDK_ROOT)/components/toolchain/cmsis/include \
$(SDK_ROOT)/components/ble/ble_services/ble_rscs_c \
$(SDK_ROOT)/components/ble/common \
$(SDK_ROOT)/components/ble/ble_services/ble_lls \
$(SDK_ROOT)/components/nfc/platform \
$(SDK_ROOT)/components/nfc/ndef/connection_handover/ac_rec \
$(SDK_ROOT)/components/ble/ble_services/ble_bas \
$(SDK_ROOT)/components/libraries/mpu \
$(SDK_ROOT)/components/libraries/experimental_section_vars \
$(SDK_ROOT)/components/ble/ble_services/ble_ans_c \
$(SDK_ROOT)/components/libraries/slip \
$(SDK_ROOT)/components/libraries/delay \
$(SDK_ROOT)/components/libraries/csense_drv \
$(SDK_ROOT)/components/libraries/memobj \
$(SDK_ROOT)/components/ble/ble_services/ble_nus_c \
$(SDK_ROOT)/components/softdevice/common \
$(SDK_ROOT)/components/ble/ble_services/ble_ias \
$(SDK_ROOT)/components/libraries/usbd/class/hid/mouse \
$(SDK_ROOT)/components/libraries/low_power_pwm \
$(SDK_ROOT)/components/nfc/ndef/conn_hand_parser/ble_oob_advdata_parser \
$(SDK_ROOT)/components/ble/ble_services/ble_dfu \
$(SDK_ROOT)/external/fprintf \
$(SDK_ROOT)/components/libraries/svc \
$(SDK_ROOT)/components/libraries/atomic \
$(SDK_ROOT)/components \
$(SDK_ROOT)/components/libraries/scheduler \
$(SDK_ROOT)/components/libraries/cli \
$(SDK_ROOT)/components/ble/ble_services/ble_lbs \
$(SDK_ROOT)/components/ble/ble_services/ble_hts \
$(SDK_ROOT)/components/libraries/crc16 \
$(SDK_ROOT)/components/nfc/t4t_parser/apdu \
$(SDK_ROOT)/components/libraries/util \
./config \
$(PROJ_DIR) \
$(SDK_ROOT)/components/libraries/usbd/class/cdc \
$(SDK_ROOT)/components/libraries/csense \
$(SDK_ROOT)/components/libraries/balloc \
$(SDK_ROOT)/components/libraries/ecc \
$(SDK_ROOT)/components/libraries/hardfault \
$(SDK_ROOT)/components/ble/ble_services/ble_cscs \
$(SDK_ROOT)/components/libraries/hci \
$(SDK_ROOT)/components/libraries/timer \
$(SDK_ROOT)/components/softdevice/s140/headers \
$(SDK_ROOT)/integration/nrfx \
$(SDK_ROOT)/components/nfc/t4t_parser/tlv \
$(SDK_ROOT)/components/libraries/sortlist \
$(SDK_ROOT)/components/libraries/spi_mngr \
$(SDK_ROOT)/components/libraries/led_softblink \
$(SDK_ROOT)/components/nfc/ndef/conn_hand_parser \
$(SDK_ROOT)/components/libraries/sdcard \
$(SDK_ROOT)/components/nfc/ndef/parser/record \
$(SDK_ROOT)/modules/nrfx/mdk \
$(SDK_ROOT)/components/ble/ble_services/ble_cts_c \
$(SDK_ROOT)/components/ble/ble_services/ble_nus \
$(SDK_ROOT)/components/libraries/twi_mngr \
$(SDK_ROOT)/components/ble/ble_services/ble_hids \
$(SDK_ROOT)/components/libraries/strerror \
$(SDK_ROOT)/components/libraries/crc32 \
$(SDK_ROOT)/components/nfc/ndef/connection_handover/ble_oob_advdata \
$(SDK_ROOT)/components/nfc/t2t_parser \
$(SDK_ROOT)/components/nfc/ndef/connection_handover/ble_pair_msg \
$(SDK_ROOT)/components/libraries/usbd/class/audio \
$(SDK_ROOT)/components/nfc/t4t_lib \
$(SDK_ROOT)/components/ble/peer_manager \
$(SDK_ROOT)/components/libraries/mem_manager \
$(SDK_ROOT)/components/libraries/ringbuf \
$(SDK_ROOT)/components/ble/ble_services/ble_tps \
$(SDK_ROOT)/components/nfc/ndef/parser/message \
$(SDK_ROOT)/components/ble/ble_services/ble_dis \
$(SDK_ROOT)/components/nfc/ndef/uri \
$(SDK_ROOT)/components/nfc/t4t_parser/cc_file \
$(SDK_ROOT)/components/ble/nrf_ble_qwr \
$(SDK_ROOT)/components/libraries/gfx \
$(SDK_ROOT)/components/libraries/button \
$(SDK_ROOT)/modules/nrfx \
$(SDK_ROOT)/components/libraries/twi_sensor \
$(SDK_ROOT)/integration/nrfx/legacy \
$(SDK_ROOT)/components/libraries/usbd/class/hid/kbd \
$(SDK_ROOT)/components/nfc/ndef/connection_handover/ep_oob_rec \
$(SDK_ROOT)/external/segger_rtt \
$(SDK_ROOT)/components/libraries/atomic_fifo \
$(SDK_ROOT)/components/ble/ble_services/ble_lbs_c \
$(SDK_ROOT)/components/nfc/ndef/connection_handover/ble_pair_lib \
$(SDK_ROOT)/components/libraries/crypto \
$(SDK_ROOT)/components/ble/ble_racp \
$(SDK_ROOT)/components/libraries/fds \
$(SDK_ROOT)/components/nfc/ndef/launchapp \
$(SDK_ROOT)/components/ble/ble_services/ble_hrs \
$(SDK_ROOT)/components/ble/ble_services/ble_rscs \
$(SDK_ROOT)/components/nfc/ndef/connection_handover/hs_rec \
$(SDK_ROOT)/components/libraries/usbd \
$(SDK_ROOT)/components/nfc/ndef/conn_hand_parser/ac_rec_parser \
$(SDK_ROOT)/components/libraries/stack_guard \
$(SDK_ROOT)/components/libraries/log/src \
# Libraries common to all targets
LIB_FILES += \
# Optimization flags
OPT = -O3 -g3
# Uncomment the line below to enable link time optimization
#OPT += -flto
# C flags common to all targets
CFLAGS += $(OPT)
CFLAGS += -DAPP_TIMER_V2
CFLAGS += -DAPP_TIMER_V2_RTC1_ENABLED
CFLAGS += -DCONFIG_GPIO_AS_PINRESET
CFLAGS += -DFLOAT_ABI_HARD
CFLAGS += -DNRF_SD_BLE_API_VERSION=7
CFLAGS += -DS140
CFLAGS += -DSOFTDEVICE_PRESENT
CFLAGS += -DDEBUG
# Setting -DDEBUG prints info via JLkinkRTTLogger, but hides the stack trace
# when debugging with VSCode's Cortex Debug extension.
# CFLAGS += -DDEBUG
CFLAGS += -mcpu=cortex-m4
CFLAGS += -mthumb -mabi=aapcs
CFLAGS += -Wall -Werror -Wno-maybe-uninitialized
CFLAGS += -mfloat-abi=hard -mfpu=fpv4-sp-d16
# keep every function in a separate section, this allows linker to discard unused ones
CFLAGS += -ffunction-sections -fdata-sections -fno-strict-aliasing
CFLAGS += -fno-builtin -fshort-enums
# C++ flags common to all targets
CXXFLAGS += $(OPT)
# Assembler flags common to all targets
ASMFLAGS += -g3
ASMFLAGS += -mcpu=cortex-m4
ASMFLAGS += -mthumb -mabi=aapcs
ASMFLAGS += -mfloat-abi=hard -mfpu=fpv4-sp-d16
ASMFLAGS += -DAPP_TIMER_V2
ASMFLAGS += -DAPP_TIMER_V2_RTC1_ENABLED
ASMFLAGS += -DCONFIG_GPIO_AS_PINRESET
ASMFLAGS += -DFLOAT_ABI_HARD
ASMFLAGS += -DNRF_SD_BLE_API_VERSION=7
ASMFLAGS += -DS140
ASMFLAGS += -DSOFTDEVICE_PRESENT
# Linker flags
LDFLAGS += $(OPT)
LDFLAGS += -mthumb -mabi=aapcs -L$(SDK_ROOT)/modules/nrfx/mdk -T$(LINKER_SCRIPT)
LDFLAGS += -mcpu=cortex-m4
LDFLAGS += -mfloat-abi=hard -mfpu=fpv4-sp-d16
# let linker dump unused sections
LDFLAGS += -Wl,--gc-sections
# use newlib in nano version
LDFLAGS += --specs=nano.specs
$(TARGETS): CFLAGS += -D__HEAP_SIZE=$(HEAP_SIZE)
$(TARGETS): CFLAGS += -D__STACK_SIZE=$(STACK_SIZE)
$(TARGETS): ASMFLAGS += -D__HEAP_SIZE=$(HEAP_SIZE)
$(TARGETS): ASMFLAGS += -D__STACK_SIZE=$(STACK_SIZE)
# Add standard libraries at the very end of the linker input, after all objects
# that may need symbols provided by these libraries.
LIB_FILES += -lc -lnosys -lm
.PHONY: default help
# Default target - first one defined
default: $(TARGETS)
# Print all targets that can be built
help:
@echo following targets are available:
@echo $(TARGETS)
@echo flash_softdevice
@echo sdk_config - starting external tool for editing sdk_config.h
@echo flash - flashing binary
TEMPLATE_PATH := $(SDK_ROOT)/components/toolchain/gcc
include $(TEMPLATE_PATH)/Makefile.common
$(foreach target, $(TARGETS), $(call define_target, $(target)))
.PHONY: flash flash_softdevice erase
# Flash the program
flash: default
@echo Flashing: $(OUTPUT_DIRECTORY)/$(TARGETS).hex
nrfjprog -f nrf52 --program $(OUTPUT_DIRECTORY)/$(TARGETS).hex --sectorerase
nrfjprog -f nrf52 --reset
# Flash softdevice
flash_softdevice:
@echo Flashing: s140_nrf52_7.2.0_softdevice.hex
nrfjprog -f nrf52 --program $(SDK_ROOT)/components/softdevice/s140/hex/s140_nrf52_7.2.0_softdevice.hex --sectorerase
nrfjprog -f nrf52 --reset
erase:
nrfjprog -f nrf52 --eraseall
SDK_CONFIG_FILE := ../config/sdk_config.h
CMSIS_CONFIG_TOOL := $(SDK_ROOT)/external_tools/cmsisconfig/CMSIS_Configuration_Wizard.jar
sdk_config:
java -jar $(CMSIS_CONFIG_TOOL) $(SDK_CONFIG_FILE)
.PHONY: flash_loop
flash_loop:
while [ 1 ]; do make flash && break; done
.PHONY: lint
lint:
find src -iname *.h -o -iname *.c | xargs clang-format -n -Werror
.PHONY: fix
lint-fix:
find src -iname *.h -o -iname *.c | xargs clang-format -i

View file

@ -1,72 +0,0 @@
# Overview
This is the b-parasite firmware 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 and 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: //'
```
# Bluetooth Low Energy Advertisement Data Encoding
Sensor data is encoded in the BLE advertisement packet as Service Data for the [Environmental Sensing Service profile](https://www.bluetooth.com/specifications/assigned-numbers/environmental-sensing-service-characteristics/) (UUID 0x181a).
Sensor data is encoded in unsigned 16 bits (2 bytes), and whenever multiple
bytes are used to represent a single value, the encoding is big-endian.
| Byte index | Description |
|------------|-------------------------------------------------------------------|
| 0 | Protocol version (4 bits) + reserved (3 bits) + `has_lux`* (1 bit)|
| 1 | Reserved (4 bits) + increasing, wrap-around counter (4 bits) |
| 2-3 | Battery voltage in millivolts |
| 4-5 | Temp in 1000 * Celsius (protocol v1) or 100 * Celsius (v2) |
| 6-7 | Relative air humidity, scaled from 0 (0%) to 0xffff (100%) |
| 8-9 | Soil moisture, scaled from from 0 (0%) to 0xffff (100%) |
| 10-15 | b-parasite's own MAC address |
| 16-17* | Ambient light in lux |
\* If the `has_lux` bit is set, bytes 16-17 shall contain the ambient light in lux.
If the `has_lux` bit is not set, bytes 16-17 may not exist or may contain
meaningless data. The reasons for this behavior are:
1. b-parasite version 1.0.x has no light sensor and its advertisement data may
have only 16 bytes if its using an older firmware. In this case, `has_lux` shall
never be set;
2. b-parasite version 1.1.x has space for an optional LDR. Users can configure
whether or not they have added the LDR by setting the `PRST_HAS_LDR` to 1 in
prst_config.h.
# Supported Modules
This code supports two E73 modules:
* E73-2G4M08S1C (nrf2480, default)
* E73-2G4M08S1E (nrf2833)
To choose for which one you want to compile, just pass PLATFORM as an env variable to make, and set it to the platform you want to use. For example, to compile for E73-2G4M08S1E:
```bash
SDK_ROOT=<...> PLATFORM=E73_2G4M08S1E make
```
and vice-versa for E73-2G4M08S1C, although that platform will be chosen as default anyways:
```bash
SDK_ROOT=<...> PLATFORM=E73_2G4M08S1C make
```

View file

@ -1,130 +0,0 @@
/* Linker script to configure memory regions. */
SEARCH_DIR(.)
GROUP(-lgcc -lc -lnosys)
MEMORY
{
FLASH (rx) : ORIGIN = 0x27000, LENGTH = 0x59000
RAM (rwx) : ORIGIN = 0x20002300, LENGTH = 0x1dd00
}
SECTIONS
{
}
SECTIONS
{
. = ALIGN(4);
.mem_section_dummy_ram :
{
}
.cli_sorted_cmd_ptrs :
{
PROVIDE(__start_cli_sorted_cmd_ptrs = .);
KEEP(*(.cli_sorted_cmd_ptrs))
PROVIDE(__stop_cli_sorted_cmd_ptrs = .);
} > RAM
.fs_data :
{
PROVIDE(__start_fs_data = .);
KEEP(*(.fs_data))
PROVIDE(__stop_fs_data = .);
} > RAM
.log_dynamic_data :
{
PROVIDE(__start_log_dynamic_data = .);
KEEP(*(SORT(.log_dynamic_data*)))
PROVIDE(__stop_log_dynamic_data = .);
} > RAM
.log_filter_data :
{
PROVIDE(__start_log_filter_data = .);
KEEP(*(SORT(.log_filter_data*)))
PROVIDE(__stop_log_filter_data = .);
} > RAM
} INSERT AFTER .data;
SECTIONS
{
.mem_section_dummy_rom :
{
}
.sdh_ble_observers :
{
PROVIDE(__start_sdh_ble_observers = .);
KEEP(*(SORT(.sdh_ble_observers*)))
PROVIDE(__stop_sdh_ble_observers = .);
} > FLASH
.sdh_soc_observers :
{
PROVIDE(__start_sdh_soc_observers = .);
KEEP(*(SORT(.sdh_soc_observers*)))
PROVIDE(__stop_sdh_soc_observers = .);
} > FLASH
.sdh_req_observers :
{
PROVIDE(__start_sdh_req_observers = .);
KEEP(*(SORT(.sdh_req_observers*)))
PROVIDE(__stop_sdh_req_observers = .);
} > FLASH
.sdh_state_observers :
{
PROVIDE(__start_sdh_state_observers = .);
KEEP(*(SORT(.sdh_state_observers*)))
PROVIDE(__stop_sdh_state_observers = .);
} > FLASH
.sdh_stack_observers :
{
PROVIDE(__start_sdh_stack_observers = .);
KEEP(*(SORT(.sdh_stack_observers*)))
PROVIDE(__stop_sdh_stack_observers = .);
} > FLASH
.nrf_queue :
{
PROVIDE(__start_nrf_queue = .);
KEEP(*(.nrf_queue))
PROVIDE(__stop_nrf_queue = .);
} > FLASH
.nrf_balloc :
{
PROVIDE(__start_nrf_balloc = .);
KEEP(*(.nrf_balloc))
PROVIDE(__stop_nrf_balloc = .);
} > FLASH
.cli_command :
{
PROVIDE(__start_cli_command = .);
KEEP(*(.cli_command))
PROVIDE(__stop_cli_command = .);
} > FLASH
.crypto_data :
{
PROVIDE(__start_crypto_data = .);
KEEP(*(SORT(.crypto_data*)))
PROVIDE(__stop_crypto_data = .);
} > FLASH
.pwr_mgmt_data :
{
PROVIDE(__start_pwr_mgmt_data = .);
KEEP(*(SORT(.pwr_mgmt_data*)))
PROVIDE(__stop_pwr_mgmt_data = .);
} > FLASH
.log_const_data :
{
PROVIDE(__start_log_const_data = .);
KEEP(*(SORT(.log_const_data*)))
PROVIDE(__stop_log_const_data = .);
} > FLASH
.log_backends :
{
PROVIDE(__start_log_backends = .);
KEEP(*(SORT(.log_backends*)))
PROVIDE(__stop_log_backends = .);
} > FLASH
} INSERT AFTER .text
INCLUDE "nrf_common.ld"

View file

@ -1,130 +0,0 @@
/* Linker script to configure memory regions. */
SEARCH_DIR(.)
GROUP(-lgcc -lc -lnosys)
MEMORY
{
FLASH (rx) : ORIGIN = 0x27000, LENGTH = 0xd9000
RAM (rwx) : ORIGIN = 0x20002300, LENGTH = 0x3dd00
}
SECTIONS
{
}
SECTIONS
{
. = ALIGN(4);
.mem_section_dummy_ram :
{
}
.cli_sorted_cmd_ptrs :
{
PROVIDE(__start_cli_sorted_cmd_ptrs = .);
KEEP(*(.cli_sorted_cmd_ptrs))
PROVIDE(__stop_cli_sorted_cmd_ptrs = .);
} > RAM
.fs_data :
{
PROVIDE(__start_fs_data = .);
KEEP(*(.fs_data))
PROVIDE(__stop_fs_data = .);
} > RAM
.log_dynamic_data :
{
PROVIDE(__start_log_dynamic_data = .);
KEEP(*(SORT(.log_dynamic_data*)))
PROVIDE(__stop_log_dynamic_data = .);
} > RAM
.log_filter_data :
{
PROVIDE(__start_log_filter_data = .);
KEEP(*(SORT(.log_filter_data*)))
PROVIDE(__stop_log_filter_data = .);
} > RAM
} INSERT AFTER .data;
SECTIONS
{
.mem_section_dummy_rom :
{
}
.sdh_soc_observers :
{
PROVIDE(__start_sdh_soc_observers = .);
KEEP(*(SORT(.sdh_soc_observers*)))
PROVIDE(__stop_sdh_soc_observers = .);
} > FLASH
.sdh_ble_observers :
{
PROVIDE(__start_sdh_ble_observers = .);
KEEP(*(SORT(.sdh_ble_observers*)))
PROVIDE(__stop_sdh_ble_observers = .);
} > FLASH
.pwr_mgmt_data :
{
PROVIDE(__start_pwr_mgmt_data = .);
KEEP(*(SORT(.pwr_mgmt_data*)))
PROVIDE(__stop_pwr_mgmt_data = .);
} > FLASH
.sdh_req_observers :
{
PROVIDE(__start_sdh_req_observers = .);
KEEP(*(SORT(.sdh_req_observers*)))
PROVIDE(__stop_sdh_req_observers = .);
} > FLASH
.sdh_state_observers :
{
PROVIDE(__start_sdh_state_observers = .);
KEEP(*(SORT(.sdh_state_observers*)))
PROVIDE(__stop_sdh_state_observers = .);
} > FLASH
.sdh_stack_observers :
{
PROVIDE(__start_sdh_stack_observers = .);
KEEP(*(SORT(.sdh_stack_observers*)))
PROVIDE(__stop_sdh_stack_observers = .);
} > FLASH
.nrf_queue :
{
PROVIDE(__start_nrf_queue = .);
KEEP(*(.nrf_queue))
PROVIDE(__stop_nrf_queue = .);
} > FLASH
.nrf_balloc :
{
PROVIDE(__start_nrf_balloc = .);
KEEP(*(.nrf_balloc))
PROVIDE(__stop_nrf_balloc = .);
} > FLASH
.cli_command :
{
PROVIDE(__start_cli_command = .);
KEEP(*(.cli_command))
PROVIDE(__stop_cli_command = .);
} > FLASH
.crypto_data :
{
PROVIDE(__start_crypto_data = .);
KEEP(*(SORT(.crypto_data*)))
PROVIDE(__stop_crypto_data = .);
} > FLASH
.log_const_data :
{
PROVIDE(__start_log_const_data = .);
KEEP(*(SORT(.log_const_data*)))
PROVIDE(__stop_log_const_data = .);
} > FLASH
.log_backends :
{
PROVIDE(__start_log_backends = .);
KEEP(*(SORT(.log_backends*)))
PROVIDE(__stop_log_backends = .);
} > FLASH
} INSERT AFTER .text
INCLUDE "nrf_common.ld"

View file

@ -1,102 +0,0 @@
#ifndef _PRST_CONFIG_H_
#define _PRST_CONFIG_H_
#include "nrf_gpio.h"
// Some configurations are version-specific. Uncomment the line corresponding
// the the version you're programming. The version can be found on the
// b-parasite board.
// #define PRST_VERSION_1_0_X
// #define PRST_VERSION_1_1_X
#define PRST_VERSION_1_2_X
// Built-in LED.
// Wether or not to turn the LED on/off during the wake-up cycle. Impacts
// battery life.
#define PRST_BLINK_LED 0
#define PRST_LED_PIN NRF_GPIO_PIN_MAP(0, 28)
// Deep sleep.
#define PRST_DEEP_SLEEP_IN_SECONDS 300
// Analog to digital converter (ADC).
// Prints out ADC debug info, such as the values read for battery and soil
// moisture.
#define PRST_ADC_BATT_DEBUG 0
#define PRST_ADC_SOIL_DEBUG 0
// BLE.
// Prints out BLE debug info, such as the final encoded advertisement packet.
#define PRST_BLE_DEBUG 0
// Supported BLE protocols.
// Default, custom BLE protocol.
#define PRST_BLE_PROTOCOL_BPARASITE_V2 0x01
// BTHome BLE protocol - https://bthome.io.
#define PRST_BLE_PROTOCOL_BTHOME 0x02
// Chosen BLE protocol.
#define PRST_BLE_PROTOCOL PRST_BLE_PROTOCOL_BPARASITE_V2
// There are two options for configuring the MAC address of b-parasites:
// 1. Comment out the PRST_BLE_MAC_ADDR to use a random static MAC address that
// is preprogrammed in each nRF52 chip.
// 2. Manually specify the MAC address you want below. In this scenario, the
// following constraints must be met to ensure valid random static MAC
// addresses:
// a. Two most significant bits are set to 1;
// b. The remaining bits should not _all_ be set to 0;
// c. The remaining bits should not _all_ be set to 1;
#define PRST_BLE_MAC_ADDR "f0:ca:f0:ca:01:01"
#define PRST_BLE_ADV_NAME "prst"
// Total time spend advertising.
#define PRST_BLE_ADV_TIME_IN_S 1
// Interval between advertising packets.
// From the specs, this value has to be greater or equal 20ms.
#define PRST_BLE_ADV_INTERVAL_IN_MS 30
// Possible values are ..., -8, -4, 0, 4, 8.
#define PRST_BLE_ADV_TX_POWER 8
// Experimental support for "long range" BLE, introduced in Bluetooth 5. It uses
// a different type of physical layer - the Coded PHY. Receivers should also
// scan using Coded PHY in order to find this device when operating in this
// mode.
#define PRST_BLE_EXPERIMENTAL_LONG_RANGE 0
// PWM.
#define PRST_PWM_PIN NRF_GPIO_PIN_MAP(0, 5)
#ifdef NRF52833_XXAA
#define PRST_FAST_DISCH_PIN NRF_GPIO_PIN_MAP(0, 25)
#else
#define PRST_FAST_DISCH_PIN NRF_GPIO_PIN_MAP(1, 10)
#endif
// SHT3C temp/humidity sensor.
#define PRST_SHT3C_DEBUG 0
// Version-specific configuration.
#if defined(PRST_VERSION_1_1_X)
// The photoresistor (LDR) is optional in this revision. If set to 1, the LDR's
// ADC channel will be sampled and its data will be encoded in the BLE
// advertisement packet.
#define PRST_HAS_LDR 1
// Light sensor pins.
#define PRST_PHOTO_V_PIN NRF_GPIO_PIN_MAP(0, 29)
#define PRST_PHOTO_OUT_PIN NRF_GPIO_PIN_MAP(0, 2)
// Whether to produce debug messages for the LDR
#define PRST_ADC_PHOTO_DEBUG 0
#elif defined(PRST_VERSION_1_2_X)
#define PRST_HAS_PHOTOTRANSISTOR 1
#define PRST_PHOTO_V_PIN NRF_GPIO_PIN_MAP(0, 29)
#define PRST_PHOTO_OUT_PIN NRF_GPIO_PIN_MAP(0, 2)
#define PRST_ADC_PHOTO_DEBUG 0
#endif // End of version-specific configuration.
#endif // _PRST_CONFIG_H_

File diff suppressed because it is too large Load diff

View file

@ -1,144 +0,0 @@
#include <stdbool.h>
#include <stdint.h>
#include "nrf_delay.h"
#include "nrf_gpio.h"
#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"
#include "nrf_pwr_mgmt.h"
#include "prst/adc.h"
#include "prst/ble.h"
#include "prst/data.h"
#include "prst/pwm.h"
#include "prst/rtc.h"
#include "prst/shtc3.h"
#include "prst_config.h"
// A small wrap-around counter for deduplicating BLE packets on the receiver.
static uint8_t run_counter = 0;
typedef enum {
SLEEPING,
ADVERTISING,
} State;
static State state = SLEEPING;
static void log_init(void) {
APP_ERROR_CHECK(NRF_LOG_INIT(NULL));
NRF_LOG_DEFAULT_BACKENDS_INIT();
NRF_LOG_INFO("Log inited.");
}
static void gpio_init(void) {
nrf_gpio_cfg_output(PRST_LED_PIN);
nrf_gpio_cfg_output(PRST_FAST_DISCH_PIN);
#if PRST_HAS_LDR || PRST_HAS_PHOTOTRANSISTOR
nrf_gpio_cfg_output(PRST_PHOTO_V_PIN);
#endif
NRF_LOG_INFO("GPIO pins inited.");
}
static void power_management_init(void) {
APP_ERROR_CHECK(nrf_pwr_mgmt_init());
NRF_LOG_INFO("GPIO pins inited.");
}
// This FPU exception mask trick is recommended for avoiding unwanted
// interupts from the floating point unit. This would be pretty bad,
// since it would wake us up from deep sleep for nothing.
#define FPU_EXCEPTION_MASK 0x0000009F
static void power_manage(void) {
__set_FPSCR(__get_FPSCR() & ~(FPU_EXCEPTION_MASK));
(void)__get_FPSCR();
NVIC_ClearPendingIRQ(FPU_IRQn);
nrf_pwr_mgmt_run();
}
// This is the RTC callback in which we do all of our work as quickly as
// possible:
// - Measure the soil moisture;
// - Measure the air temperature and humidity;
// - Encode the measurements into the BLE advertisement packet;
// - Turn on BLE advertising for a while;
// - Turn everything off and return back to sleep.
static void rtc_callback() {
#if PRST_BLINK_LED
nrf_gpio_pin_set(PRST_LED_PIN);
#endif
if (state == SLEEPING) {
prst_shtc3_read_t temp_humi = prst_shtc3_read();
nrf_gpio_pin_set(PRST_FAST_DISCH_PIN);
prst_pwm_init();
prst_pwm_start();
prst_adc_batt_read_t batt_read = prst_adc_batt_read();
prst_adc_soil_moisture_t soil_read = prst_adc_soil_read(batt_read.voltage);
prst_pwm_stop();
nrf_gpio_pin_clear(PRST_FAST_DISCH_PIN);
uint16_t lux = 0;
#if PRST_HAS_LDR || PRST_HAS_PHOTOTRANSISTOR
nrf_gpio_pin_set(PRST_PHOTO_V_PIN);
nrf_delay_ms(50);
prst_adc_photo_sensor_t photo_read = prst_adc_photo_read(batt_read.voltage);
lux = photo_read.brightness;
nrf_gpio_pin_clear(PRST_PHOTO_V_PIN);
#endif
prst_sensor_data_t sensors = {
.batt_mv = batt_read.millivolts,
.temp_c = temp_humi.temp_celsius,
.humi = temp_humi.humidity,
.soil_moisture = soil_read.relative,
.lux = lux,
.run_counter = run_counter,
};
prst_ble_update_adv_data(&sensors);
state = ADVERTISING;
prst_adv_start();
prst_rtc_set_timer(PRST_BLE_ADV_TIME_IN_S);
run_counter++;
} else if (state == ADVERTISING) {
prst_adv_stop();
state = SLEEPING;
prst_rtc_set_timer(PRST_DEEP_SLEEP_IN_SECONDS);
}
#if PRST_BLINK_LED
nrf_gpio_pin_clear(PRST_LED_PIN);
#endif
NRF_LOG_FLUSH();
}
int main(void) {
log_init();
gpio_init();
power_management_init();
prst_ble_init();
prst_adc_init();
prst_shtc3_init();
// Quick LED flash.
nrf_gpio_pin_set(PRST_LED_PIN);
nrf_delay_ms(200);
nrf_gpio_pin_clear(PRST_LED_PIN);
// Set up RTC. It will call our custom callback at a regular interval, defined
// by PRST_DEEP_SLEEP_IN_SECONDS.
prst_rtc_set_callback(rtc_callback);
prst_rtc_init();
// In addition to scheduling it, let's immediatelly call it - it makes
// debugging less tedious.
rtc_callback();
// Here we go into a low energy mode. The datasheet calls this mode "System
// ON", and in my tests it consumes around 2.7uA.
for (;;) {
power_manage();
}
}

View file

@ -1 +0,0 @@
BasedOnStyle: Google

View file

@ -1,182 +0,0 @@
#include "prst/adc.h"
#include <app_error.h>
#include <math.h>
#include <nrf_drv_saadc.h>
#include <nrf_log.h>
#include <nrf_saadc.h>
#include <stdint.h>
#include "prst_config.h"
#define PRST_ADC_RESOLUTION 10
#define PRST_ADC_BATT_INPUT NRF_SAADC_INPUT_VDD
#define PRST_ADC_BATT_CHANNEL 0
#define PRST_ADC_SOIL_INPUT NRF_SAADC_INPUT_AIN1
#define PRST_ADC_SOIL_CHANNEL 1
#define PRST_ADC_PHOTO_INPUT NRF_SAADC_INPUT_AIN0
#define PRST_ADC_PHOTO_CHANNEL 2
static nrf_saadc_value_t sample_adc_channel(uint8_t channel) {
nrf_saadc_value_t result;
// *WARNING* this function is blocking, which is ot ideal but okay, but it
// *does not work* when oversampling is set! I had to manually disable
// SAADC_CONFIG_OVERSAMPLE in sdk_config.h.
APP_ERROR_CHECK(nrf_drv_saadc_sample_convert(channel, &result));
return result;
}
// Caps the argument to the [0.0, 1.0] range.
static inline double cap_percentage(double value) {
return value > 1.0 ? 1.0 : (value < 0.0 ? 0.0 : value);
}
// Unused, since we'll call the SAADC synchronously for now.
void saadc_callback(nrf_drv_saadc_evt_t const* p_event) {
if (p_event->type == NRF_DRV_SAADC_EVT_DONE) {
ret_code_t err_code;
uint16_t size = p_event->data.done.size;
err_code = nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, size);
APP_ERROR_CHECK(err_code);
int i;
NRF_LOG_INFO("[adc] ADC event!");
for (i = 0; i < size; i++) {
NRF_LOG_INFO("[adc] %d", p_event->data.done.p_buffer[i]);
}
}
}
void prst_adc_init() {
nrf_saadc_channel_config_t batt_channel_config =
NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(PRST_ADC_BATT_INPUT);
APP_ERROR_CHECK(nrf_drv_saadc_init(NULL, saadc_callback));
APP_ERROR_CHECK(
nrf_drv_saadc_channel_init(PRST_ADC_BATT_CHANNEL, &batt_channel_config));
nrf_saadc_channel_config_t soil_channel_config =
NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(PRST_ADC_SOIL_INPUT);
soil_channel_config.reference = NRF_SAADC_REFERENCE_VDD4;
APP_ERROR_CHECK(
nrf_drv_saadc_channel_init(PRST_ADC_SOIL_CHANNEL, &soil_channel_config));
nrf_saadc_channel_config_t photo_channel_config =
NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(PRST_ADC_PHOTO_INPUT);
APP_ERROR_CHECK(nrf_drv_saadc_channel_init(PRST_ADC_PHOTO_CHANNEL,
&photo_channel_config));
}
prst_adc_batt_read_t prst_adc_batt_read() {
nrf_saadc_value_t result = sample_adc_channel(PRST_ADC_BATT_CHANNEL);
prst_adc_batt_read_t ret;
ret.raw = (uint16_t)result;
ret.voltage = (3.6 * result) / (1 << PRST_ADC_RESOLUTION);
ret.millivolts = ret.voltage * 1000;
#if PRST_ADC_BATT_DEBUG
NRF_LOG_INFO(
"[adc] Read battery voltage: %d (raw); %d mV; " NRF_LOG_FLOAT_MARKER " V",
ret.raw, ret.millivolts, NRF_LOG_FLOAT(ret.voltage));
#endif
return ret;
}
// If you got this far and really want to see how the sausage is made,
// this function estimates the soil moisture percent based on the raw
// ADC value as returned from the saadc. It assumes 10 bits resolution.
// Ideally, we're taking the ADC sample relative to the VDD voltage, so
// this input value should be stable across the range of input voltages.
// In practice, when varying the input voltage, this value is drifting
// enough to be annoying. To account for this drift, I collected ADC readings
// while varying the input voltage from 2V to 3V (CR2032 voltage range) and
// fitted two second degree polynomials over them - one for the sensor
// out in the air (representing a dry soil) and one while holding the
// sensor in my hand (representing a wet soil).
// This raw data is available at the data/ dir at the root of this repository.
static inline double get_soil_moisture_percent(
double battery_voltage, nrf_saadc_value_t raw_adc_output) {
const double x = battery_voltage;
const double dry = -12.9 * x * x + 111 * x + 228;
const double wet = -5.71 * x * x + 60.2 * x + 126;
#if PRST_ADC_SOIL_DEBUG
NRF_LOG_INFO("[adc] batt: " NRF_LOG_FLOAT_MARKER, NRF_LOG_FLOAT(x));
NRF_LOG_INFO("[adc] dry: " NRF_LOG_FLOAT_MARKER " wet: " NRF_LOG_FLOAT_MARKER,
NRF_LOG_FLOAT(dry), NRF_LOG_FLOAT(wet));
#endif
return (raw_adc_output - dry) / (wet - dry);
}
prst_adc_soil_moisture_t prst_adc_soil_read(double battery_voltage) {
nrf_saadc_value_t raw_adc_output = sample_adc_channel(PRST_ADC_SOIL_CHANNEL);
const double percentage =
get_soil_moisture_percent(battery_voltage, raw_adc_output);
prst_adc_soil_moisture_t ret;
ret.raw = raw_adc_output;
ret.percentage = percentage;
ret.relative = cap_percentage(percentage) * UINT16_MAX;
#if PRST_ADC_SOIL_DEBUG
NRF_LOG_INFO("[adc] Read soil moisture: %d (raw); " NRF_LOG_FLOAT_MARKER
" %% (percentage); %u (relative)",
ret.raw, NRF_LOG_FLOAT(percentage * 100), ret.relative);
#endif
return ret;
}
prst_adc_photo_sensor_t prst_adc_photo_read(double battery_voltage) {
nrf_saadc_value_t raw_photo_output =
MAX(0, sample_adc_channel(PRST_ADC_PHOTO_CHANNEL));
prst_adc_photo_sensor_t ret;
ret.raw = raw_photo_output;
ret.voltage = (3.6 * raw_photo_output) / (1 << PRST_ADC_RESOLUTION);
#if PRST_HAS_LDR
// The photo resistor forms a voltage divider with a 10 kOhm resistor.
// The voltage here is measured in the middle of the voltage divider.
// Vcc ---- (R_photo) ---|--- (10k) ---- GND
// Vout
// So we can estimate R_photo = R * (Vcc - Vout) / Vout
const float photo_resistance =
1e4f * (battery_voltage - ret.voltage) / ret.voltage;
// The relationship between the LDR resistance and the lux level is
// logarithmic. We need to solve a logarithmic equation to find the lux
// level, given the LDR resistance we just measured.
// These values work for the GL5528 LDR and were borrowed from
// https://github.com/QuentinCG/Arduino-Light-Dependent-Resistor-Library.
const float mult_value = 32017200.0f;
const float pow_value = 1.5832f;
ret.brightness =
MAX(0, MIN(mult_value / powf(photo_resistance, pow_value), UINT16_MAX));
#elif PRST_HAS_PHOTOTRANSISTOR
// The ALS-PT19 phototransistor is a device in which the current flow between
// its two terminals is controlled by how much light there is in the ambient.
// We measure that current by calculating the voltage across a resistor that
// is connected in series with the phototransistor.
const float phototransistor_resistor = 470.0f;
const float current_sun = 3.59e-3f;
// Assuming 10000 lux for the saturation test. Calibration with a proper light
// meter would be better.
const float lux_sun = 10000.0f;
const float current = ret.voltage / phototransistor_resistor;
ret.brightness = MAX(0, MIN(lux_sun * current / current_sun, UINT16_MAX));
#if PRST_ADC_PHOTO_DEBUG
NRF_LOG_INFO("[adc] Phototransistor current: " NRF_LOG_FLOAT_MARKER " uA",
NRF_LOG_FLOAT(1000000 * current));
#endif // PRST_ADC_PHOTO_DEBUG
#endif // PRST_HAS_PHOTOTRANSISTOR
#if PRST_ADC_PHOTO_DEBUG
NRF_LOG_INFO("[adc] Read brightness level: " NRF_LOG_FLOAT_MARKER
" mV %d (raw); %d (lux)",
NRF_LOG_FLOAT(1000 * ret.voltage), ret.raw, ret.brightness);
#endif
return ret;
}

View file

@ -1,34 +0,0 @@
#ifndef _PRST_ADC_H_
#define _PRST_ADC_H_
#include <stdint.h>
typedef struct prst_adc_batt_val {
int16_t raw;
uint16_t millivolts;
double voltage;
} prst_adc_batt_read_t;
typedef struct prst_adc_soil_moisture {
int16_t raw;
// A value from 0 (completely dry) to 2^10 (completely wet).
uint16_t relative;
double percentage;
} prst_adc_soil_moisture_t;
typedef struct prst_adc_photo_sensor {
int16_t raw;
double voltage;
// Value in lux.
uint16_t brightness;
} prst_adc_photo_sensor_t;
void prst_adc_init();
prst_adc_batt_read_t prst_adc_batt_read();
prst_adc_soil_moisture_t prst_adc_soil_read(double battery_voltage);
prst_adc_photo_sensor_t prst_adc_photo_read(double battery_voltage);
#endif // _PRST_ADC_H_

View file

@ -1,290 +0,0 @@
#include "prst/ble.h"
#include <ble_advdata.h>
#include <ble_gap.h>
#include <nordic_common.h>
#include <nrf_delay.h>
#include <nrf_log.h>
#include <nrf_sdh.h>
#include <nrf_sdh_ble.h>
#include "prst_config.h"
// The connection to configure. We only have the one.
#define PRST_CONN_CFG_TAG 1
#define NON_CONNECTABLE_ADV_INTERVAL \
MSEC_TO_UNITS(PRST_BLE_ADV_INTERVAL_IN_MS, UNIT_0_625_MS)
// Sensor data payload that will go into the advertisement message.
// We have a maximum of 20 bytes to play with here.
// Sensor data is encoded in unsigned 16 bits (2 bytes), and whenever multiple
// bytes are used to represent a single value, the encoding is big-endian:
/*
| Byte index | Description |
|------------|-----------------------------------------------------------------|
| 0 | Protocol version (4 bits) + reserved (3 bits) + has_lux* (1 bit)|
| 1 | Reserved (4 bits) + increasing, wrap-around counter (4 bits) |
| 2-3 | Battery voltage in millivolts |
| 4-5 | Temp in 1000 * Celsius (protocol v1) or 100 * Celsius (v2) |
| 6-7 | Relative air humidity, scaled from 0 (0%) to 0xffff (100%) |
| 8-9 | Soil moisture, scaled from from 0 (0%) to 0xffff (100%) |
| 10-15 | b-parasite's own MAC address |
| 16-17* | Ambient light in lux |
* If the has_lux bit is set, bytes 16-17 shall contain the ambient light in lux.
If the has_lux bit is not set, bytes 16-17 may not exist or may contain
meaningless data. The reasons for this behavior are:
1. b-parasite version 1.0.0 has no light sensor and its advertisement data may
have only 16 bytes if its using an older firmware. In this case, has_lux shall
never be set;
2. b-parasite version 1.1.0 has space for an optional LDR. Users can configure
whether or not they have added the LDR by setting the PRST_HAS_LDR to 1 in
prst_config.h.
*/
#if PRST_BLE_PROTOCOL == PRST_BLE_PROTOCOL_BPARASITE_V2
#define SERVICE_UUID 0x181a
#define SERVICE_DATA_LEN 18
#elif PRST_BLE_PROTOCOL == PRST_BLE_PROTOCOL_BTHOME
#define SERVICE_UUID 0x181c
#define SERVICE_DATA_LEN 16
#else
#error "PRST_BLE_PROTOCOL is not properly configured"
#endif
static uint8_t service_data[SERVICE_DATA_LEN];
// Stores the encoded advertisement data. As per BLE spec, 31 bytes max.
static uint8_t encoded_adv_data_[BLE_GAP_ADV_SET_DATA_SIZE_MAX];
// Structure holding high level advertisement data and contains a pointer to
// the actual encoded advertised bytes.
static ble_gap_adv_data_t gap_adv_data_ = {
.adv_data = {.p_data = encoded_adv_data_,
.len = BLE_GAP_ADV_SET_DATA_SIZE_MAX},
.scan_rsp_data = {.p_data = NULL, .len = 0}};
// We'll put our sensor data inside an advertisement service.
static ble_advdata_service_data_t advdata_service_data_ = {
.service_uuid = SERVICE_UUID,
.data = {
.p_data = service_data,
.size = SERVICE_DATA_LEN,
}};
// Holds the service data to be broadcasted. The contents of this struct
// will be encoded into gap_adv_data.
// Warning: do not update this while advertising.
static ble_advdata_t adv_data_ = {
.name_type = BLE_ADVDATA_FULL_NAME,
#if PRST_BLE_EXPERIMENTAL_LONG_RANGE
.flags = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE,
#else
.flags = BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED,
#endif
.p_service_data_array = &advdata_service_data_,
.service_data_count = 1,
};
// NRF supports multiple advertisement sets. This initialization is a request
// for configuring a new one.
static uint8_t adv_handle_ = BLE_GAP_ADV_SET_HANDLE_NOT_SET;
// Advertisement parameters.
static ble_gap_adv_params_t adv_params_;
// Stores the MAC address & type.
static ble_gap_addr_t gap_addr_ = {.addr_type =
BLE_GAP_ADDR_TYPE_RANDOM_STATIC};
static void init_advertisement_data() {
// We'll just broadcast our data, so we disallow connections and scan
// requests.
#if PRST_BLE_EXPERIMENTAL_LONG_RANGE
adv_params_.properties.type =
BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED;
adv_params_.primary_phy = BLE_GAP_PHY_CODED;
adv_params_.secondary_phy = BLE_GAP_PHY_CODED;
#else
adv_params_.properties.type =
BLE_GAP_ADV_TYPE_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED;
#endif
adv_params_.p_peer_addr = NULL;
adv_params_.filter_policy = BLE_GAP_ADV_FP_ANY;
adv_params_.interval = NON_CONNECTABLE_ADV_INTERVAL;
adv_params_.duration = 0; // Never time out.
ble_gap_conn_sec_mode_t sec_mode;
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode);
sd_ble_gap_device_name_set(&sec_mode, (const uint8_t*)PRST_BLE_ADV_NAME,
strlen(PRST_BLE_ADV_NAME));
uint32_t err_code =
sd_ble_gap_adv_set_configure(&adv_handle_, &gap_adv_data_, &adv_params_);
APP_ERROR_CHECK(err_code);
}
void prst_ble_init() {
uint32_t err_code;
// Enable SoftDevice request.
err_code = nrf_sdh_enable_request();
APP_ERROR_CHECK(err_code);
// Set the default config and get the starting RAM address.
uint32_t ram_start = 0;
err_code = nrf_sdh_ble_default_cfg_set(PRST_CONN_CFG_TAG, &ram_start);
APP_ERROR_CHECK(err_code);
// Enable SoftDevice.
err_code = nrf_sdh_ble_enable(&ram_start);
APP_ERROR_CHECK(err_code);
#ifdef PRST_BLE_MAC_ADDR
// Parses configured MAC address from PRST_BLE_MAC_ADDR.
int mac_bytes[6];
sscanf(PRST_BLE_MAC_ADDR, "%x:%x:%x:%x:%x:%x", &mac_bytes[0], &mac_bytes[1],
&mac_bytes[2], &mac_bytes[3], &mac_bytes[4], &mac_bytes[5]);
for (int i = 0; i < 6; i++) {
gap_addr_.addr[5 - i] = (uint8_t)mac_bytes[i];
}
APP_ERROR_CHECK(sd_ble_gap_addr_set(&gap_addr_));
#endif
APP_ERROR_CHECK(sd_ble_gap_addr_get(&gap_addr_));
NRF_LOG_INFO("[ble] MAC address: %02x:%02x:%02x:%02x:%02x:%02x\n",
gap_addr_.addr[5], gap_addr_.addr[4], gap_addr_.addr[3],
gap_addr_.addr[2], gap_addr_.addr[1], gap_addr_.addr[0]);
init_advertisement_data();
}
#if PRST_BLE_PROTOCOL == PRST_BLE_PROTOCOL_BPARASITE_V2
static void set_service_data_bparasite_protocol(
const prst_sensor_data_t* sensors) {
// Four bits for the protocol version.
service_data[0] |= (2 << 4) & 0xf0;
// Bit 0 of byte 0 specifies whether or not ambient light data exists in the
// payload.
#if PRST_HAS_LDR || PRST_HAS_PHOTOTRANSISTOR
service_data[0] |= 1;
#endif
// 4 bits for a small wrap-around counter for deduplicating messages on the
// receiver.
service_data[1] = sensors->run_counter & 0x0f;
service_data[2] = sensors->batt_mv >> 8;
service_data[3] = sensors->batt_mv & 0xff;
int16_t temp_centicelsius = 100 * sensors->temp_c;
service_data[4] = temp_centicelsius >> 8;
service_data[5] = temp_centicelsius & 0xff;
service_data[6] = sensors->humi >> 8;
service_data[7] = sensors->humi & 0xff;
service_data[8] = sensors->soil_moisture >> 8;
service_data[9] = sensors->soil_moisture & 0xff;
// Bytes 10-15 (inclusive) contain the whole MAC address in big-endian.
for (int i = 0; i < 6; i++) {
service_data[10 + i] = gap_addr_.addr[5 - i];
}
#if PRST_HAS_LDR || PRST_HAS_PHOTOTRANSISTOR
service_data[16] = sensors->lux >> 8;
service_data[17] = sensors->lux & 0xff;
#endif
}
#endif // PRST_BLE_PROTOCOL == PRST_BLE_PROTOCOL_BPARASITE_V2
#if PRST_BLE_PROTOCOL == PRST_BLE_PROTOCOL_BTHOME
static void set_service_data_bthome_protocol(
const prst_sensor_data_t* sensors) {
// See values in https://bthome.io/.
// 1. Soil moisture.
// uint16_t.
service_data[0] = (0b000 << 5) | 2;
// Type of measurement - Moisture.
service_data[1] = 0x14;
// Value. Factor of 0.01, so we need to multiply our the value in 100% by
// 1/0.01 = 100.
uint16_t soil_val = (10000 * sensors->soil_moisture) / UINT16_MAX;
service_data[2] = soil_val & 0xff;
service_data[3] = soil_val >> 8;
// 2. Temp.
// int16_t.
service_data[4] = (0b001 << 5) | 2;
// Type of measurement - temperature.
service_data[5] = 0x02;
// Value. Factor 0.01.
int16_t temp_val = 100 * sensors->temp_c;
service_data[6] = temp_val & 0xff;
service_data[7] = temp_val >> 8;
// 3. Humidity
// uint16_t.
service_data[8] = (0b000 << 5) | 2;
// Type - humidity.
service_data[9] = 0x03;
// Value. Factor 0.01, over 100%.
uint16_t humi_val = (10000 * sensors->humi) / UINT16_MAX;
service_data[10] = humi_val & 0xff;
service_data[11] = humi_val >> 8;
// 4. Battery voltage.
// uint16_t.
service_data[12] = (0b000 << 5) | 2;
// Type - voltage.
service_data[13] = 0x0c;
// Value. Factor of 0.001.
uint16_t batt_val = sensors->batt_mv;
service_data[14] = batt_val & 0xff;
service_data[15] = batt_val >> 8;
}
#endif // PRST_BLE_PROTOCOL == PRST_BLE_PROTOCOL_BTHOME
void prst_ble_update_adv_data(const prst_sensor_data_t* sensors) {
#if PRST_BLE_PROTOCOL == PRST_BLE_PROTOCOL_BPARASITE_V2
set_service_data_bparasite_protocol(sensors);
#elif PRST_BLE_PROTOCOL == PRST_BLE_PROTOCOL_BTHOME
set_service_data_bthome_protocol(sensors);
#endif
// Encodes adv_data_ into .gap_adv_data_.
uint32_t err_code = ble_advdata_encode(
&adv_data_, gap_adv_data_.adv_data.p_data, &gap_adv_data_.adv_data.len);
APP_ERROR_CHECK(err_code);
#if PRST_BLE_DEBUG
NRF_LOG_INFO("[ble] Encoded BLE adv packet:");
for (int i = 0; i < sizeof(encoded_adv_data_); i++) {
NRF_LOG_INFO("[ble] byte %02d: 0x%02x", i, encoded_adv_data_[i]);
nrf_delay_ms(50);
}
#endif
}
void prst_adv_start() {
APP_ERROR_CHECK(sd_ble_gap_adv_start(adv_handle_, PRST_CONN_CFG_TAG));
APP_ERROR_CHECK(sd_ble_gap_tx_power_set(BLE_GAP_TX_POWER_ROLE_ADV,
adv_handle_, PRST_BLE_ADV_TX_POWER));
#if PRST_BLE_DEBUG
NRF_LOG_INFO("[ble] Advertising started.\n");
#endif
}
void prst_adv_stop() {
ret_code_t err_code;
err_code = sd_ble_gap_adv_stop(adv_handle_);
APP_ERROR_CHECK(err_code);
#if PRST_BLE_DEBUG
NRF_LOG_INFO("[ble] Advertising stopped.\n");
#endif
}

View file

@ -1,18 +0,0 @@
#ifndef _PRST_BLE_H_
#define _PRST_BLE_H_
#include <app_error.h>
#include <stdint.h>
#include "prst/data.h"
// Initializes SoftDevice.
void prst_ble_init();
void prst_adv_start();
void prst_adv_stop();
void prst_ble_update_adv_data(const prst_sensor_data_t* sensors);
#endif // _PRST_BLE_H_

View file

@ -1,15 +0,0 @@
#ifndef _PRST_DATA_H_
#define _PRST_DATA_H_
#include <stdint.h>
typedef struct {
uint16_t batt_mv;
float temp_c;
uint16_t humi;
uint16_t soil_moisture;
uint16_t lux;
uint8_t run_counter;
} prst_sensor_data_t;
#endif // _PRST_DATA_H_

View file

@ -1,68 +0,0 @@
#include "prst/pwm.h"
#include <app_error.h>
#include <nordic_common.h>
#include <nrf_drv_pwm.h>
#include <nrf_log.h>
#include <nrf_pwm.h>
#include "prst_config.h"
// Each step in the counter will take 1/16e6 s.
#define PRST_PWM_BASE_FREQ NRF_PWM_CLK_16MHz
// We will count up to 16. It will take 1us at 16MHz.
// With the NRF_PWM_MODE_UP_AND_DOWN count mode, we assume 1us is half the
// output PWM period (total 2us => 500MHz frequency). We set a duty cycle of
// 50% below with PRST_PWM_FLIP_AT_COUNT to be half the max count.
#define PRST_PWM_MAX_COUNT 16
// We will toggle the PWM output when we reach this count.
// #define PRST_PWM_FLIP_AT_COUNT PRST_PWM_MAX_COUNT / 2
#define PRST_PWM_FLIP_AT_COUNT 8
static nrf_drv_pwm_t m_pwm0 = NRF_DRV_PWM_INSTANCE(0);
static nrf_pwm_values_common_t seq_values_[] = {PRST_PWM_FLIP_AT_COUNT};
static const nrf_pwm_sequence_t seq_ = {
.values.p_common = seq_values_,
.length = NRF_PWM_VALUES_LENGTH(seq_values_),
.repeats = 0,
.end_delay = 0};
void prst_pwm_init() {
// We set the PWM pin as output so we can control its state after the PWM is
// stopped. Without this, I'm seeing the PWM pin remaining high after stopped.
nrf_gpio_pin_dir_set(PRST_PWM_PIN, NRF_GPIO_PIN_DIR_OUTPUT);
nrf_drv_pwm_config_t const config0 = {
// We have to specify the state of the 4 channels. We only care about the
// first one, so we set all others to not used.
.output_pins =
{
PRST_PWM_PIN | NRF_DRV_PWM_PIN_INVERTED,
NRF_DRV_PWM_PIN_NOT_USED,
NRF_DRV_PWM_PIN_NOT_USED,
NRF_DRV_PWM_PIN_NOT_USED,
},
.irq_priority = APP_IRQ_PRIORITY_LOWEST,
// This is the hal PRESCALER
.base_clock = NRF_PWM_CLK_16MHz,
.count_mode = NRF_PWM_MODE_UP_AND_DOWN,
// This is the hal COUNTERTOP.
.top_value = PRST_PWM_MAX_COUNT,
.load_mode = NRF_PWM_LOAD_COMMON,
.step_mode = NRF_PWM_STEP_AUTO};
APP_ERROR_CHECK(nrf_drv_pwm_init(&m_pwm0, &config0, NULL));
}
void prst_pwm_start() {
// Loop until stopped.
APP_ERROR_CHECK(
nrf_drv_pwm_simple_playback(&m_pwm0, &seq_, 1, NRF_DRV_PWM_FLAG_LOOP));
}
void prst_pwm_stop() {
nrf_drv_pwm_stop(&m_pwm0, /*wait_until_stopped=*/true);
nrf_drv_pwm_uninit(&m_pwm0);
nrf_gpio_pin_clear(PRST_PWM_PIN);
}

View file

@ -1,10 +0,0 @@
#ifndef _PRST_PWM_H_
#define _PRST_PWM_H_
void prst_pwm_init();
void prst_pwm_start();
void prst_pwm_stop();
#endif // _PRST_PWM_H_

View file

@ -1,48 +0,0 @@
#include "prst/rtc.h"
#include <nrf_drv_rtc.h>
#include <nrf_log.h>
#include <nrf_log_ctrl.h>
#include "prst_config.h"
// RTC0 is used by softdevice, so we need to pick another instance.
static const nrf_drv_rtc_t rtc_ = NRF_DRV_RTC_INSTANCE(2);
static prst_rtc_callback_t callback_handler_ = NULL;
static void rtc_callback(nrf_drv_rtc_int_type_t int_type) {
if (int_type == NRF_DRV_RTC_INT_COMPARE2) {
if (callback_handler_ != NULL) {
callback_handler_();
}
// Reset RTC2 counter.
nrf_drv_rtc_counter_clear(&rtc_);
// We need to re-enable the RTC2 interrupt after rest.
nrf_drv_rtc_int_enable(&rtc_, NRF_RTC_INT_COMPARE2_MASK);
}
}
void prst_rtc_set_callback(prst_rtc_callback_t cb) { callback_handler_ = cb; }
void prst_rtc_init() {
uint32_t err_code;
nrf_drv_rtc_config_t config = NRF_DRV_RTC_DEFAULT_CONFIG;
config.prescaler = 4095;
err_code = nrf_drv_rtc_init(&rtc_, &config, rtc_callback);
APP_ERROR_CHECK(err_code);
// Disable events we're not interested in so they don't trigger interrupts.
nrf_drv_rtc_tick_disable(&rtc_);
nrf_drv_rtc_overflow_disable(&rtc_);
}
void prst_rtc_set_timer(uint16_t seconds) {
// Make sure we're counting from 0.
nrf_drv_rtc_counter_clear(&rtc_);
// Set compare channel to trigger interrupt after specified time.
APP_ERROR_CHECK(nrf_drv_rtc_cc_set(&rtc_, 2, seconds * 8, true));
// Power on RTC instance.
nrf_drv_rtc_enable(&rtc_);
}

View file

@ -1,14 +0,0 @@
#ifndef _PRST_RTC_H_
#define _PRST_RTC_H_
#include <stdint.h>
typedef void (*prst_rtc_callback_t)(void);
void prst_rtc_set_callback(prst_rtc_callback_t cb);
void prst_rtc_init();
void prst_rtc_set_timer(uint16_t seconds);
#endif // _PRST_RTC_H_

View file

@ -1,70 +0,0 @@
#include "prst/shtc3.h"
#include <app_error.h>
#include <nrf_delay.h>
#include <nrf_drv_twi.h>
#include <nrf_log.h>
#include <nrf_log_ctrl.h>
#include "prst_config.h"
static const nrf_drv_twi_t twi_ = NRF_DRV_TWI_INSTANCE(0);
static nrf_drv_twi_config_t twi_config_ = NRF_DRV_TWI_DEFAULT_CONFIG;
static uint8_t buff[6];
static void write_cmd(uint16_t command) {
uint8_t cmd[2];
cmd[0] = command >> 8;
cmd[1] = command & 0xff;
APP_ERROR_CHECK(nrf_drv_twi_tx(&twi_, PRST_SHTC3_ADDR, cmd, 2,
/*no_stop=*/false));
}
void prst_shtc3_init() {
twi_config_.scl = PRST_SHT3C_SCL_PIN;
twi_config_.sda = PRST_SHT3C_SDA_PIN;
twi_config_.frequency = NRF_TWI_FREQ_100K;
}
prst_shtc3_read_t prst_shtc3_read() {
APP_ERROR_CHECK(nrf_drv_twi_init(&twi_, &twi_config_, NULL, NULL));
nrf_drv_twi_enable(&twi_);
// Wake the sensor up.
write_cmd(PRST_SHTC3_CMD_WAKEUP);
nrf_delay_ms(1);
// Request measurement.
write_cmd(PRST_SHTC3_CMD_MEASURE_TFIRST_NORMAL);
// Reading in normal (not low power) mode can take up to 12.1 ms, according to
// the datasheet.
nrf_delay_ms(15);
// Read temp and humidity.
while (nrf_drv_twi_rx(&twi_, PRST_SHTC3_ADDR, buff, 6) != 0) {
nrf_delay_ms(10);
}
// Put the sensor in sleep mode.
write_cmd(PRST_SHTC3_CMD_SLEEP);
// Uninit i2c.
nrf_drv_twi_uninit(&twi_);
// TODO(rbaron): verify the CRC of the measurements. The function is described
// in the datasheet.
float temp_c = -45 + 175 * ((float)((buff[0] << 8) | buff[1])) / (1 << 16);
uint16_t humi = (buff[3] << 8) | buff[4];
prst_shtc3_read_t ret = {.temp_celsius = temp_c, .humidity = humi};
#if PRST_SHT3C_DEBUG
NRF_LOG_INFO("[sht3c] Read temp: " NRF_LOG_FLOAT_MARKER " oC",
NRF_LOG_FLOAT((float)ret.temp_celsius));
NRF_LOG_INFO("[sht3c] Read humi: " NRF_LOG_FLOAT_MARKER " %%",
NRF_LOG_FLOAT(100.0 * ret.humidity / 0xffff));
#endif
return ret;
}

View file

@ -1,26 +0,0 @@
#ifndef _PRST_SHT3C_H_
#define _PRST_SHT3C_H_
#include <nrf_gpio.h>
#define PRST_SHT3C_SDA_PIN NRF_GPIO_PIN_MAP(0, 24)
#define PRST_SHT3C_SCL_PIN NRF_GPIO_PIN_MAP(0, 13)
// Values from the SHTC3 datasheet.
#define PRST_SHTC3_ADDR 0x70
#define PRST_SHTC3_CMD_SLEEP 0xb098
#define PRST_SHTC3_CMD_WAKEUP 0x3517
#define PRST_SHTC3_CMD_MEASURE_TFIRST_LOW_POWER 0x609c
#define PRST_SHTC3_CMD_MEASURE_TFIRST_NORMAL 0x7866
typedef struct prst_shtc3_values {
// Temperature in degrees Celsius.
float temp_celsius;
// Relative humidity, from 0 to 2^16.
uint16_t humidity;
} prst_shtc3_read_t;
void prst_shtc3_init();
prst_shtc3_read_t prst_shtc3_read();
#endif // _PRST_SHT3C_H_

View file

@ -0,0 +1,14 @@
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
add_library(prstlib STATIC
src/adc.c
src/button.c
src/led.c
src/sensors.c
src/shtc3.c
)
target_include_directories(prstlib PRIVATE include)
target_link_libraries(prstlib PUBLIC zephyr_interface kernel)

View file

@ -0,0 +1,3 @@
module = PRSTLIB
module-str = prstlib
source "subsys/logging/Kconfig.template.log_config"

View file

@ -0,0 +1,9 @@
if BOARD_BPARASITE_NRF52833
config BOARD_ENABLE_DCDC
bool "DCDC mode"
select SOC_DCDC_NRF52X
default y
endif # BOARD_BPARASITE_NRF52833

View file

@ -0,0 +1,4 @@
config BOARD_BPARASITE_NRF52833
bool "b-parasite nRF52833 board"
depends on SOC_NRF52833_QIAA

View file

@ -0,0 +1,18 @@
if BOARD_BPARASITE_NRF52833
config BOARD
default "bparasite_nrf52833"
config BOARD_REVISION
string "Board revision."
default "1.0.0"
config BOARD_REVISION_CODE
int "Board revision code. An integer representation of the board revision."
default 1
config BT_CTLR
default BT
endif # BOARD_BPARASITE_NRF52833

View file

@ -0,0 +1,10 @@
# SPDX-License-Identifier: Apache-2.0
board_runner_args(jlink "--device=nRF52833_xxAA" "--speed=4000")
board_runner_args(pyocd "--target=nrf52833" "--frequency=4000000")
# set(OPENOCD_NRF5_SUBFAMILY "nrf52")
include(${ZEPHYR_BASE}/boards/common/nrfjprog.board.cmake)
include(${ZEPHYR_BASE}/boards/common/jlink.board.cmake)
include(${ZEPHYR_BASE}/boards/common/pyocd.board.cmake)
include(${ZEPHYR_BASE}/boards/common/openocd-nrf5.board.cmake)
include(${ZEPHYR_BASE}/boards/common/blackmagicprobe.board.cmake)

View file

@ -0,0 +1,48 @@
&pinctrl {
uart0_default: uart0_default {
group1 {
psels = <NRF_PSEL(UART_TX, 0, 25)>,
<NRF_PSEL(UART_RX, 0, 23)>;
};
};
uart0_sleep: uart0_sleep {
group1 {
psels = <NRF_PSEL(UART_TX, 0, 25)>,
<NRF_PSEL(UART_RX, 0, 23)>;
low-power-enable;
};
};
/* Configure pwm0 instance to use pin 5. */
pwm0_default: pwm0_default {
group1 {
psels = <NRF_PSEL(PWM_OUT0, 0, 5)>;
nordic,invert;
};
};
pwm0_sleep: pwm0_sleep {
group1 {
psels = <NRF_PSEL(PWM_OUT0, 0, 5)>;
low-power-enable;
};
};
/* Configure i2c0 instance to use pins 24 (SDA) & 13 (SCL). */
i2c0_default: i2c0_default {
group1 {
psels = <NRF_PSEL(TWIM_SDA, 0, 24)>,
<NRF_PSEL(TWIM_SCL, 0, 13)>;
};
};
i2c0_sleep: i2c0_sleep {
group1 {
psels = <NRF_PSEL(TWIM_SDA, 0, 24)>,
<NRF_PSEL(TWIM_SCL, 0, 13)>;
low-power-enable;
};
};
};

View file

@ -0,0 +1,168 @@
/dts-v1/;
#include <nordic/nrf52833_qiaa.dtsi>
#include "bparasite_nrf52833-pinctrl.dtsi"
/ {
model = "A soil moisture sensor based on Nordic's nRF52833";
compatible = "rbaron,bparasite_nrf52833";
chosen {
zephyr,sram = &sram0;
zephyr,flash = &flash0;
zephyr,code-partition = &slot0_partition;
zephyr,ieee802154 = &ieee802154;
};
zephyr,user {
io-channels = <&adc 0>, <&adc 2>;
};
leds {
compatible = "gpio-leds";
led0: led_0 {
// P0.28.
gpios = <&gpio0 28 GPIO_ACTIVE_HIGH>;
};
};
buttons {
compatible = "gpio-keys";
button0: button_0 {
// P0.30.
gpios = <&gpio0 30 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
label = "Push button SW1";
};
};
soil_pwm: soil_pwm {
compatible = "pwm-fixed";
pwms = <&pwm0 0 PWM_USEC(2) PWM_POLARITY_NORMAL>;
pulse = <PWM_USEC(1)>;
};
soil_calibration_coeffs: soil_calibration_coeffs {
compatible = "soil-calibration-coeffs";
dry = <306000 101000 (-11700)>;
wet = <19000 (-4980) 3420>;
};
ctrl {
compatible = "gpio-keys";
fast_disch: fast_disch {
// P0.25.
gpios = <&gpio0 25 GPIO_ACTIVE_HIGH>;
label = "Fast discharge circuitry";
};
};
};
&uicr {
gpio-as-nreset;
};
&gpiote {
status = "okay";
};
&gpio0 {
status = "okay";
// For low-power EDGE interrupts.
// See github.com/zephyrproject-rtos/zephyr/issues/28499.
sense-edge-mask = <0xffffffff>;
};
&gpio1 {
status = "okay";
};
&ieee802154 {
status = "okay";
};
&uart0 {
compatible = "nordic,nrf-uart";
status = "disabled";
current-speed = <115200>;
pinctrl-0 = <&uart0_default>;
pinctrl-1 = <&uart0_sleep>;
pinctrl-names = "default", "sleep";
};
&i2c0 {
compatible = "nordic,nrf-twi";
status = "okay";
pinctrl-0 = <&i2c0_default>;
pinctrl-1 = <&i2c0_sleep>;
pinctrl-names = "default", "sleep";
shtc3: shtc3@70 {
compatible = "i2c-device";
reg = <0x70>;
label = "SHTC3";
};
};
&pwm0 {
status = "okay";
pinctrl-0 = <&pwm0_default>;
pinctrl-1 = <&pwm0_sleep>;
pinctrl-names = "default", "sleep";
};
&adc {
#address-cells = <1>;
#size-cells = <0>;
status = "ok";
// Soil.
channel@0 {
reg = <0>;
zephyr,gain = "ADC_GAIN_1_6";
zephyr,reference = "ADC_REF_VDD_1_4";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
// P0.03.
zephyr,input-positive = <NRF_SAADC_AIN1>;
zephyr,resolution = <10>;
};
// Battery.
channel@2 {
reg = <2>;
zephyr,gain = "ADC_GAIN_1_6";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,input-positive = <NRF_SAADC_VDD>;
zephyr,resolution = <10>;
};
};
&flash0 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
boot_partition: partition@0 {
label = "mcuboot";
reg = <0x000000000 0xC000>;
};
slot0_partition: partition@c000 {
label = "image-0";
reg = <0x0000C000 0x32000>;
};
slot1_partition: partition@3e000 {
label = "image-1";
reg = <0x0003E000 0x32000>;
};
scratch_partition: partition@70000 {
label = "image-scratch";
reg = <0x00070000 0xA000>;
};
storage_partition: partition@7a000 {
label = "storage";
reg = <0x0007A000 0x00006000>;
};
};
};

View file

@ -0,0 +1,2 @@
CONFIG_BOARD_REVISION="1.1.0"
CONFIG_BOARD_REVISION_CODE=2

View file

@ -0,0 +1,30 @@
/ {
// Light dependant resistor.
ldr: ldr {
compatible = "voltage-divider";
output-ohms = <10000>;
io-channels = <&adc 1>;
};
ctrl {
compatible = "gpio-keys";
ldr_enable: ldr_enable {
// P0.29.
gpios = <&gpio0 29 GPIO_ACTIVE_HIGH>;
label = "LDR supply";
};
};
};
&adc {
channel@1 {
reg = <1>;
zephyr,gain = "ADC_GAIN_1_6";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
// P0.02.
zephyr,input-positive = <NRF_SAADC_AIN0>;
zephyr,resolution = <10>;
};
};

View file

@ -0,0 +1,2 @@
CONFIG_BOARD_REVISION="1.2.0"
CONFIG_BOARD_REVISION_CODE=3

View file

@ -0,0 +1,29 @@
/ {
photo_transistor: photo_transistor {
compatible = "voltage-divider";
output-ohms = <470>;
io-channels = <&adc 1>;
};
ctrl {
compatible = "gpio-keys";
photo_transistor_enable: photo_transistor_enable {
// P0.29.
gpios = <&gpio0 29 GPIO_ACTIVE_HIGH>;
label = "Phototransistor supply";
};
};
};
&adc {
channel@1 {
reg = <1>;
zephyr,gain = "ADC_GAIN_1_6";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
// P0.02.
zephyr,input-positive = <NRF_SAADC_AIN0>;
zephyr,resolution = <10>;
};
};

View file

@ -0,0 +1,2 @@
CONFIG_BOARD_REVISION="2.0.0"
CONFIG_BOARD_REVISION_CODE=4

View file

@ -0,0 +1,35 @@
/ {
photo_transistor: photo_transistor {
compatible = "voltage-divider";
output-ohms = <470>;
io-channels = <&adc 1>;
};
soil_calibration_coeffs: soil_calibration_coeffs {
compatible = "soil-calibration-coeffs";
dry = <334000 110000 (-15300)>;
wet = <299000 (-83100) 11200>;
};
ctrl {
compatible = "gpio-keys";
photo_transistor_enable: photo_transistor_enable {
// P0.29.
gpios = <&gpio0 29 GPIO_ACTIVE_HIGH>;
label = "Phototransistor supply";
};
};
};
&adc {
channel@1 {
reg = <1>;
zephyr,gain = "ADC_GAIN_1_6";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
// P0.02.
zephyr,input-positive = <NRF_SAADC_AIN0>;
zephyr,resolution = <10>;
};
};

View file

@ -0,0 +1,19 @@
CONFIG_SOC_SERIES_NRF52X=y
CONFIG_SOC_NRF52833_QIAA=y
CONFIG_BOARD_BPARASITE_NRF52833=y
# Enable MPU
CONFIG_ARM_MPU=y
# Enable RTT
CONFIG_USE_SEGGER_RTT=y
# enable GPIO
CONFIG_GPIO=y
# enable console
CONFIG_CONSOLE=y
CONFIG_RTT_CONSOLE=y
CONFIG_PINCTRL=y

View file

@ -0,0 +1,7 @@
# Copyright (c) 2022 Nordic Semiconductor
# SPDX-License-Identifier: Apache-2.0
# Suppress "unique_unit_address_if_enabled" to handle the following overlaps:
# - power@40000000 & clock@40000000 & bprot@40000000
# - acl@4001e000 & flash-controller@4001e000
list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled")

View file

@ -0,0 +1 @@
board_check_revision(FORMAT MAJOR.MINOR.PATCH)

View file

@ -0,0 +1,14 @@
if BOARD_BPARASITE_NRF52840
config BOARD_ENABLE_DCDC
bool "DCDC mode"
select SOC_DCDC_NRF52X
default y
config BOARD_ENABLE_DCDC_HV
bool "High Voltage DCDC converter"
select SOC_DCDC_NRF52X_HV
default y
endif # BOARD_BPARASITE_NRF52840

View file

@ -0,0 +1,4 @@
config BOARD_BPARASITE_NRF52840
bool "b-parasite nRF52840 board"
depends on SOC_NRF52840_QIAA

View file

@ -0,0 +1,18 @@
if BOARD_BPARASITE_NRF52840
config BOARD
default "bparasite_nrf52840"
config BOARD_REVISION
string "Board revision."
default "1.0.0"
config BOARD_REVISION_CODE
int "Board revision code. An integer representation of the board revision."
default 1
config BT_CTLR
default BT
endif # BOARD_BPARASITE_NRF52840

View file

@ -0,0 +1,10 @@
# SPDX-License-Identifier: Apache-2.0
board_runner_args(jlink "--device=nRF52840_xxAA" "--speed=4000")
board_runner_args(pyocd "--target=nrf52840" "--frequency=4000000")
# set(OPENOCD_NRF5_SUBFAMILY "nrf52")
include(${ZEPHYR_BASE}/boards/common/nrfjprog.board.cmake)
include(${ZEPHYR_BASE}/boards/common/jlink.board.cmake)
include(${ZEPHYR_BASE}/boards/common/pyocd.board.cmake)
include(${ZEPHYR_BASE}/boards/common/openocd-nrf5.board.cmake)
include(${ZEPHYR_BASE}/boards/common/blackmagicprobe.board.cmake)

View file

@ -0,0 +1,48 @@
&pinctrl {
uart0_default: uart0_default {
group1 {
psels = <NRF_PSEL(UART_TX, 0, 25)>,
<NRF_PSEL(UART_RX, 0, 23)>;
};
};
uart0_sleep: uart0_sleep {
group1 {
psels = <NRF_PSEL(UART_TX, 0, 25)>,
<NRF_PSEL(UART_RX, 0, 23)>;
low-power-enable;
};
};
/* Configure pwm0 instance to use pin 5. */
pwm0_default: pwm0_default {
group1 {
psels = <NRF_PSEL(PWM_OUT0, 0, 5)>;
nordic,invert;
};
};
pwm0_sleep: pwm0_sleep {
group1 {
psels = <NRF_PSEL(PWM_OUT0, 0, 5)>;
low-power-enable;
};
};
/* Configure i2c0 instance to use pins 24 (SDA) & 13 (SCL). */
i2c0_default: i2c0_default {
group1 {
psels = <NRF_PSEL(TWIM_SDA, 0, 24)>,
<NRF_PSEL(TWIM_SCL, 0, 13)>;
};
};
i2c0_sleep: i2c0_sleep {
group1 {
psels = <NRF_PSEL(TWIM_SDA, 0, 24)>,
<NRF_PSEL(TWIM_SCL, 0, 13)>;
low-power-enable;
};
};
};

View file

@ -0,0 +1,179 @@
/dts-v1/;
#include <nordic/nrf52840_qiaa.dtsi>
#include "bparasite_nrf52840-pinctrl.dtsi"
/ {
model = "A soil moisture sensor based on Nordic's nRF52840";
compatible = "rbaron,bparasite_nrf52840";
chosen {
zephyr,sram = &sram0;
zephyr,flash = &flash0;
zephyr,code-partition = &slot0_partition;
zephyr,ieee802154 = &ieee802154;
};
zephyr,user {
io-channels = <&adc 0>, <&adc 2>;
};
leds {
compatible = "gpio-leds";
led0: led_0 {
// P0.28.
gpios = <&gpio0 28 GPIO_ACTIVE_HIGH>;
};
};
buttons {
compatible = "gpio-keys";
button0: button_0 {
// P0.30.
gpios = <&gpio0 30 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
label = "Push button SW1";
};
};
soil_pwm: soil_pwm {
compatible = "pwm-fixed";
pwms = <&pwm0 0 PWM_USEC(2) PWM_POLARITY_NORMAL>;
pulse = <PWM_USEC(1)>;
};
soil_calibration_coeffs: soil_calibration_coeffs {
compatible = "soil-calibration-coeffs";
dry = <306000 101000 (-11700)>;
wet = <19000 (-4980) 3420>;
};
ctrl {
compatible = "gpio-keys";
fast_disch: fast_disch {
// P1.10.
gpios = <&gpio1 10 GPIO_ACTIVE_HIGH>;
label = "Fast discharge circuitry";
};
};
};
&uicr {
gpio-as-nreset;
};
&gpiote {
status = "okay";
};
&gpio0 {
status = "okay";
// For low-power EDGE interrupts.
// github.com/zephyrproject-rtos/zephyr/issues/28499
sense-edge-mask = <0xffffffff>;
};
&gpio1 {
status = "okay";
};
&ieee802154 {
status = "okay";
};
&uart0 {
compatible = "nordic,nrf-uart";
status = "disabled";
current-speed = <115200>;
pinctrl-0 = <&uart0_default>;
pinctrl-1 = <&uart0_sleep>;
pinctrl-names = "default", "sleep";
};
&i2c0 {
compatible = "nordic,nrf-twi";
status = "okay";
pinctrl-0 = <&i2c0_default>;
pinctrl-1 = <&i2c0_sleep>;
pinctrl-names = "default", "sleep";
shtc3: shtc3@70 {
compatible = "i2c-device";
reg = <0x70>;
label = "SHTC3";
};
};
&pwm0 {
status = "okay";
pinctrl-0 = <&pwm0_default>;
pinctrl-1 = <&pwm0_sleep>;
pinctrl-names = "default", "sleep";
};
&adc {
#address-cells = <1>;
#size-cells = <0>;
status = "ok";
// Soil.
channel@0 {
reg = <0>;
zephyr,gain = "ADC_GAIN_1_6";
zephyr,reference = "ADC_REF_VDD_1_4";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
// P0.03.
zephyr,input-positive = <NRF_SAADC_AIN1>;
zephyr,resolution = <10>;
};
// Battery.
channel@2 {
reg = <2>;
zephyr,gain = "ADC_GAIN_1_6";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,input-positive = <NRF_SAADC_VDD>;
zephyr,resolution = <10>;
};
};
&flash0 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
boot_partition: partition@0 {
label = "mcuboot";
reg = <0x00000000 0x0000C000>;
};
slot0_partition: partition@c000 {
label = "image-0";
reg = <0x0000C000 0x00067000>;
};
slot1_partition: partition@73000 {
label = "image-1";
reg = <0x00073000 0x00067000>;
};
scratch_partition: partition@da000 {
label = "image-scratch";
reg = <0x000da000 0x0001e000>;
};
/*
* The flash starting at 0x000f8000 and ending at
* 0x000fffff is reserved for use by the application.
*/
/*
* Storage partition will be used by FCB/LittleFS/NVS
* if enabled.
*/
storage_partition: partition@f8000 {
label = "storage";
reg = <0x000f8000 0x00008000>;
};
};
};

View file

@ -0,0 +1,2 @@
CONFIG_BOARD_REVISION="1.1.0"
CONFIG_BOARD_REVISION_CODE=2

View file

@ -0,0 +1,30 @@
/ {
// Light dependant resistor.
ldr: ldr {
compatible = "voltage-divider";
output-ohms = <10000>;
io-channels = <&adc 1>;
};
ctrl {
compatible = "gpio-keys";
ldr_enable: ldr_enable {
// P0.29.
gpios = <&gpio0 29 GPIO_ACTIVE_HIGH>;
label = "LDR supply";
};
};
};
&adc {
channel@1 {
reg = <1>;
zephyr,gain = "ADC_GAIN_1_6";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
// P0.02.
zephyr,input-positive = <NRF_SAADC_AIN0>;
zephyr,resolution = <10>;
};
};

View file

@ -0,0 +1,2 @@
CONFIG_BOARD_REVISION="1.2.0"
CONFIG_BOARD_REVISION_CODE=3

View file

@ -0,0 +1,29 @@
/ {
photo_transistor: photo_transistor {
compatible = "voltage-divider";
output-ohms = <470>;
io-channels = <&adc 1>;
};
ctrl {
compatible = "gpio-keys";
photo_transistor_enable: photo_transistor_enable {
// P0.29.
gpios = <&gpio0 29 GPIO_ACTIVE_HIGH>;
label = "Phototransistor supply";
};
};
};
&adc {
channel@1 {
reg = <1>;
zephyr,gain = "ADC_GAIN_1_6";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
// P0.02.
zephyr,input-positive = <NRF_SAADC_AIN0>;
zephyr,resolution = <10>;
};
};

View file

@ -0,0 +1,2 @@
CONFIG_BOARD_REVISION="2.0.0"
CONFIG_BOARD_REVISION_CODE=4

View file

@ -0,0 +1,35 @@
/ {
photo_transistor: photo_transistor {
compatible = "voltage-divider";
output-ohms = <470>;
io-channels = <&adc 1>;
};
soil_calibration_coeffs: soil_calibration_coeffs {
compatible = "soil-calibration-coeffs";
dry = <334000 110000 (-15300)>;
wet = <299000 (-83100) 11200>;
};
ctrl {
compatible = "gpio-keys";
photo_transistor_enable: photo_transistor_enable {
// P0.29.
gpios = <&gpio0 29 GPIO_ACTIVE_HIGH>;
label = "Phototransistor supply";
};
};
};
&adc {
channel@1 {
reg = <1>;
zephyr,gain = "ADC_GAIN_1_6";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
// P0.02.
zephyr,input-positive = <NRF_SAADC_AIN0>;
zephyr,resolution = <10>;
};
};

View file

@ -0,0 +1,19 @@
CONFIG_SOC_SERIES_NRF52X=y
CONFIG_SOC_NRF52840_QIAA=y
CONFIG_BOARD_BPARASITE_NRF52840=y
# Enable MPU
CONFIG_ARM_MPU=y
# Enable RTT
CONFIG_USE_SEGGER_RTT=y
# enable GPIO
CONFIG_GPIO=y
# enable console
CONFIG_CONSOLE=y
CONFIG_RTT_CONSOLE=y
CONFIG_PINCTRL=y

View file

@ -0,0 +1,7 @@
# Copyright (c) 2022 Nordic Semiconductor
# SPDX-License-Identifier: Apache-2.0
# Suppress "unique_unit_address_if_enabled" to handle the following overlaps:
# - power@40000000 & clock@40000000 & bprot@40000000
# - acl@4001e000 & flash-controller@4001e000
list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled")

View file

@ -0,0 +1 @@
board_check_revision(FORMAT MAJOR.MINOR.PATCH)

View file

@ -0,0 +1,17 @@
description: A fixed frequency & pulse PWM.
compatible: pwm-fixed
include: base.yaml
properties:
pwms:
type: phandle-array
required: true
description: the PWM spec.
pulse:
required: true
type: int
description: The period of the PWM pulse.

View file

@ -0,0 +1,25 @@
description: >
Calibration coefficients for the soil sensing circuit. We have two polynomials whose input variable
is the battery voltage in Volts. The evaluated polynomial corresponds to the estimated ADC value,
assumed to be 10 bits, so in [0, 1024). The two polynomials are:
dry: For estimation of the fully dry state, given the battery voltage
wet: For estimation of the fully dry state, given the battery voltage
Note that each poly coefficient is specified as 1000 times its real values. This is because we can't
directly represent floating points in devicetree. The final evaluated value should be divided by
1000.0f in code.
compatible: soil-calibration-coeffs
include: base.yaml
properties:
dry:
type: array
required: true
description: The coefficients * 1000, as integers. 0th degree first, then 1st and 2nd last.
wet:
type: array
required: true
description: The coefficients * 1000, as integers. 0th degree first, then 1st and 2nd last.

View file

@ -0,0 +1,39 @@
#ifndef _PRST_ADC_H_
#define _PRST_ADC_H_
#include <stdint.h>
typedef struct {
int16_t raw;
int32_t millivolts;
float voltage;
} prst_adc_read_t;
typedef struct {
prst_adc_read_t adc_read;
float percentage;
} prst_batt_t;
typedef struct {
prst_adc_read_t adc_read;
// A value from 0 (completely dry) to 2^10 (completely wet).
uint16_t relative;
// In [0, 1.0].
float percentage;
} prst_adc_soil_moisture_t;
typedef struct prst_adc_photo_sensor {
prst_adc_read_t adc_read;
// Value in lux.
uint16_t brightness;
} prst_adc_photo_sensor_t;
int prst_adc_init();
int prst_adc_batt_read(prst_batt_t* out);
int prst_adc_soil_read(float battery_voltage, prst_adc_soil_moisture_t* out);
int prst_adc_photo_read(float battery_voltage, prst_adc_photo_sensor_t* out);
#endif // _PRST_ADC_H_

View file

@ -0,0 +1,24 @@
#ifndef _PRST_BUTTON_H_
#define _PRST_BUTTON_H_
#include <stdbool.h>
typedef enum {
PRST_BUTTON_SW1 = 0,
} prst_button_t;
typedef void (*prst_button_callback_t)(prst_button_t button, bool is_active);
// Inits button driver.
int prst_button_init();
// Configures ISR and calls callback on debounced button press/release.
int prst_button_register_callback(prst_button_callback_t callback);
// Returns:
// 1 if button is active
// 0 if button is inactive
// -1 on error
int prst_button_poll(prst_button_t prst_button);
#endif // _PRST_BUTTON_H_

View file

@ -0,0 +1,38 @@
#ifndef _PRST_LED_H_
#define _PRST_LED_H_
#include <zephyr/drivers/gpio.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include "prstlib/macros.h"
#define PRST_LED_FLASH_PERIOD_MS 400
extern struct gpio_dt_spec led;
int prst_led_init();
static inline int prst_led_on() {
return gpio_pin_set_dt(&led, 1);
}
static inline int prst_led_off() {
return gpio_pin_set_dt(&led, 0);
}
static inline int prst_led_toggle() {
return gpio_pin_toggle_dt(&led);
}
static inline int prst_led_flash(int times) {
LOG_MODULE_DECLARE(led, LOG_LEVEL_DBG);
RET_IF_ERR(prst_led_off());
for (int i = 0; i < 2 * times; i++) {
RET_IF_ERR(prst_led_toggle());
k_msleep(PRST_LED_FLASH_PERIOD_MS / 2);
}
return 0;
}
#endif // _PRST_LED_H_

View file

@ -0,0 +1,24 @@
#ifndef _PRST_MACROS_H_
#define _PRST_MACROS_H_
#define PRST_STRINGIFY(x) #x
#define PRST_TO_STRING(x) PRST_STRINGIFY(x)
#define PRST_LOCATION __FILE__ ":" PRST_TO_STRING(__LINE__)
#define RET_IF_ERR_MSG(expr, msg) \
{ \
int err = (expr); \
if (err) { \
LOG_ERR("Error %d: " msg " in " PRST_LOCATION, err); \
return err; \
} \
}
#define RET_IF_ERR(expr) RET_IF_ERR_MSG(expr, "")
// Checks that expr evaluates to true, otherwise return 1.
#define RET_CHECK(expr, msg) RET_IF_ERR_MSG(!(expr), msg)
#define UNUSED_OK(expr) (void)expr;
#endif // _PRST_MACROS_H_

View file

@ -0,0 +1,16 @@
#ifndef _PRST_DATA_H_
#define _PRST_DATA_H_
#include "prstlib/adc.h"
#include "prstlib/shtc3.h"
typedef struct {
prst_adc_soil_moisture_t soil;
prst_adc_photo_sensor_t photo;
prst_batt_t batt;
prst_shtc3_read_t shtc3;
} prst_sensors_t;
int prst_sensors_read_all(prst_sensors_t *out);
#endif // _PRST_DATA_H_

View file

@ -0,0 +1,20 @@
#ifndef _PRST_SHT3C_H_
#define _PRST_SHT3C_H_
// Values from the SHTC3 datasheet.
#define PRST_SHTC3_ADDR 0x70
#define PRST_SHTC3_CMD_SLEEP 0xb098
#define PRST_SHTC3_CMD_WAKEUP 0x3517
#define PRST_SHTC3_CMD_MEASURE_TFIRST_LOW_POWER 0x609c
#define PRST_SHTC3_CMD_MEASURE_TFIRST_NORMAL 0x7866
typedef struct {
// Temperature in Celcius.
float temp_c;
// Relative humidity in [0, 1.0].
float rel_humi;
} prst_shtc3_read_t;
int prst_shtc3_read(prst_shtc3_read_t *out);
#endif // _PRST_SHT3C_H_

View file

@ -0,0 +1,209 @@
#include "prstlib/adc.h"
#include <math.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/pwm.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include "prstlib/macros.h"
LOG_MODULE_REGISTER(adc, CONFIG_PRSTLIB_LOG_LEVEL);
// PWM spec for square wave. Input to the soil sensing circuit.
static const struct pwm_dt_spec soil_pwm_dt =
PWM_DT_SPEC_GET(DT_NODELABEL(soil_pwm));
static const uint32_t pulse = DT_PROP(DT_NODELABEL(soil_pwm), pulse);
// Calibration coefficients for the soil sensing circuit.
static const int dry_coeffs[3] = DT_PROP(DT_NODELABEL(soil_calibration_coeffs), dry);
static const int wet_coeffs[3] = DT_PROP(DT_NODELABEL(soil_calibration_coeffs), wet);
struct gpio_dt_spec fast_disch_dt =
GPIO_DT_SPEC_GET(DT_NODELABEL(fast_disch), gpios);
// Shared buffer and adc_sequennce.
static int16_t buf;
static struct adc_sequence sequence = {
.buffer = &buf,
.buffer_size = sizeof(buf),
};
static const struct adc_dt_spec adc_soil_spec =
ADC_DT_SPEC_GET_BY_IDX(DT_PATH(zephyr_user), 0);
static const struct adc_dt_spec adc_batt_spec =
ADC_DT_SPEC_GET_BY_IDX(DT_PATH(zephyr_user), 1);
#if DT_NODE_EXISTS(DT_NODELABEL(photo_transistor))
static const struct adc_dt_spec adc_photo_transistor_spec =
ADC_DT_SPEC_GET(DT_NODELABEL(photo_transistor));
struct gpio_dt_spec photo_transistor_enable_dt =
GPIO_DT_SPEC_GET(DT_NODELABEL(photo_transistor_enable), gpios);
#elif DT_NODE_EXISTS(DT_NODELABEL(ldr))
static const struct adc_dt_spec adc_ldr_spec =
ADC_DT_SPEC_GET(DT_NODELABEL(ldr));
struct gpio_dt_spec ldr_enable_dt =
GPIO_DT_SPEC_GET(DT_NODELABEL(ldr_enable), gpios);
#endif
typedef struct {
// High (h) and low (p) voltage (v) and % (p) points.
float vh, vl, ph, pl;
} batt_disch_linear_section_t;
static void set_battery_percent(const prst_adc_read_t* read, prst_batt_t* out) {
// Must be sorted by .vh.
static const batt_disch_linear_section_t sections[] = {
{.vh = 3.00f, .vl = 2.90f, .ph = 1.00f, .pl = 0.42f},
{.vh = 2.90f, .vl = 2.74f, .ph = 0.42f, .pl = 0.18f},
{.vh = 2.74f, .vl = 2.44f, .ph = 0.18f, .pl = 0.06f},
{.vh = 2.44f, .vl = 2.01f, .ph = 0.06f, .pl = 0.00f},
};
const float v = read->voltage;
if (v > sections[0].vh) {
out->percentage = 1.0f;
return;
}
for (int i = 0; i < ARRAY_SIZE(sections); i++) {
const batt_disch_linear_section_t* s = &sections[i];
if (v > s->vl) {
out->percentage = s->pl + (v - s->vl) * ((s->ph - s->pl) / (s->vh - s->vl));
return;
}
}
out->percentage = 0.0f;
return;
}
static inline float eval_poly(const int coeffs[3], float x) {
// The coefficients are specified times 1000, as a workaround the lack of support for floating
// points in devicetree bindings.
return (coeffs[0] + coeffs[1] * x + coeffs[2] * x * x) / 1000.0f;
}
static inline float get_soil_moisture_percent(float battery_voltage,
int16_t raw_adc_output) {
const float x = battery_voltage;
const float dry = eval_poly(dry_coeffs, x);
const float wet = eval_poly(wet_coeffs, x);
const float percent = (raw_adc_output - dry) / (wet - dry);
LOG_DBG("Read soil moisture 2: %.2f | Raw %u | Batt: %.2f | Dry: %.2f | Wet: %.2f",
100.0f * percent, raw_adc_output, x, dry, wet);
return percent;
}
static int read_adc_spec(const struct adc_dt_spec* spec, prst_adc_read_t* out) {
RET_IF_ERR(adc_sequence_init_dt(spec, &sequence));
RET_IF_ERR(adc_read(spec->dev, &sequence));
int32_t val_mv = buf;
RET_IF_ERR(adc_raw_to_millivolts_dt(spec, &val_mv));
val_mv = MAX(0, val_mv);
out->raw = buf;
out->millivolts = val_mv;
out->voltage = val_mv / 1000.0f;
return 0;
}
int prst_adc_init() {
RET_IF_ERR(adc_channel_setup_dt(&adc_soil_spec));
RET_IF_ERR(adc_channel_setup_dt(&adc_batt_spec));
RET_IF_ERR(!device_is_ready(fast_disch_dt.port));
RET_IF_ERR(gpio_pin_configure_dt(&fast_disch_dt, GPIO_OUTPUT));
#if DT_NODE_EXISTS(DT_NODELABEL(photo_transistor))
RET_IF_ERR(adc_channel_setup_dt(&adc_photo_transistor_spec));
RET_IF_ERR(!device_is_ready(photo_transistor_enable_dt.port));
RET_IF_ERR(gpio_pin_configure_dt(&photo_transistor_enable_dt, GPIO_OUTPUT));
#elif DT_NODE_EXISTS(DT_NODELABEL(ldr))
RET_IF_ERR(adc_channel_setup_dt(&adc_ldr_spec));
RET_IF_ERR(!device_is_ready(ldr_enable_dt.port));
RET_IF_ERR(gpio_pin_configure_dt(&ldr_enable_dt, GPIO_OUTPUT));
#endif
for (size_t idx = 0; idx < ARRAY_SIZE(dry_coeffs); idx++) {
LOG_DBG("Dry coeff %d: %d\n", idx, dry_coeffs[idx]);
}
for (size_t idx = 0; idx < ARRAY_SIZE(wet_coeffs); idx++) {
LOG_DBG("Wet coeff %d: %d\n", idx, wet_coeffs[idx]);
}
return 0;
}
int prst_adc_batt_read(prst_batt_t* out) {
RET_IF_ERR(read_adc_spec(&adc_batt_spec, &out->adc_read));
set_battery_percent(&out->adc_read, out);
return 0;
}
int prst_adc_soil_read(float battery_voltage, prst_adc_soil_moisture_t* out) {
// Enable fast discharge circuit.
RET_IF_ERR(gpio_pin_set_dt(&fast_disch_dt, 1));
// Start PWM.
RET_IF_ERR(pwm_set_dt(&soil_pwm_dt, soil_pwm_dt.period, pulse));
k_msleep(30);
RET_IF_ERR(read_adc_spec(&adc_soil_spec, &out->adc_read));
// Stop PWM.
RET_IF_ERR(pwm_set_dt(&soil_pwm_dt, 0, 0));
// Turn off fast discharge circuit.
RET_IF_ERR(gpio_pin_set_dt(&fast_disch_dt, 0));
out->percentage =
MAX(0.0f, MIN(1.0f, get_soil_moisture_percent(battery_voltage, buf)));
return 0;
}
int prst_adc_photo_read(float battery_voltage, prst_adc_photo_sensor_t* out) {
#if DT_NODE_EXISTS(DT_NODELABEL(photo_transistor))
RET_IF_ERR(gpio_pin_set_dt(&photo_transistor_enable_dt, 1));
k_msleep(10);
RET_IF_ERR(read_adc_spec(&adc_photo_transistor_spec, &out->adc_read));
RET_IF_ERR(gpio_pin_set_dt(&photo_transistor_enable_dt, 0));
const float phototransistor_resistor = DT_PROP(DT_NODELABEL(photo_transistor), output_ohms);
// Assuming 10000 lux for the saturation test. Calibration with a proper light
// meter would be better.
const float lux_sun = 10000.0f;
const float current_sun = 3.59e-3f;
const float current = out->adc_read.voltage / phototransistor_resistor;
out->brightness = MAX(0, MIN(lux_sun * current / current_sun, UINT16_MAX));
LOG_DBG("Read phototransistor: %u lx | %.2f V", out->brightness, out->adc_read.voltage);
#elif DT_NODE_EXISTS(DT_NODELABEL(ldr))
RET_IF_ERR(gpio_pin_set_dt(&ldr_enable_dt, 1));
k_msleep(10);
RET_IF_ERR(read_adc_spec(&adc_ldr_spec, &out->adc_read));
RET_IF_ERR(gpio_pin_set_dt(&ldr_enable_dt, 0));
// The photo resistor forms a voltage divider with R.
// The voltage here is measured in the middle of the voltage divider.
// Vcc ---- (R_photo) ---|--- (R) ---- GND
// Vout
// So we can estimate R_photo = R * (Vcc - Vout) / Vout
const float r = DT_PROP(DT_NODELABEL(ldr), output_ohms);
const float photo_resistance =
r * (battery_voltage - out->adc_read.voltage) / out->adc_read.voltage;
// The relationship between the LDR resistance and the lux level is
// logarithmic. We need to solve a logarithmic equation to find the lux
// level, given the LDR resistance we just measured.
// These values work for the GL5528 LDR and were borrowed from
// https://github.com/QuentinCG/Arduino-Light-Dependent-Resistor-Library.
const float mult_value = 32017200.0f;
const float pow_value = 1.5832f;
out->brightness =
MAX(0, MIN(mult_value / powf(photo_resistance, pow_value), UINT16_MAX));
LOG_DBG("Read LDR: %u lx | %.2f V", out->brightness, out->adc_read.voltage);
#endif
return 0;
}

View file

@ -0,0 +1,68 @@
#include "prstlib/button.h"
#include <zephyr/drivers/gpio.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include "prstlib/led.h"
#include "prstlib/macros.h"
LOG_MODULE_REGISTER(button, CONFIG_PRSTLIB_LOG_LEVEL);
static struct k_work_delayable button_pressed_delayable;
static struct gpio_dt_spec button =
GPIO_DT_SPEC_GET(DT_NODELABEL(button0), gpios);
static struct gpio_callback cb_data;
static prst_button_callback_t user_callback = NULL;
static void maybe_call_user_callback(prst_button_t button, bool is_active) {
if (user_callback != NULL) {
user_callback(button, is_active);
} else {
LOG_WRN("No user callback registered for button %d", button);
}
}
static void button_pressed_cb(struct k_work *work) {
int button_state = prst_button_poll(PRST_BUTTON_SW1);
if (button_state < 0) {
LOG_ERR("Failed to poll button");
return;
}
return maybe_call_user_callback(PRST_BUTTON_SW1, button_state);
}
static void button_pressed_isr(const struct device *dev, struct gpio_callback *cb,
uint32_t pins) {
k_work_reschedule(&button_pressed_delayable, K_MSEC(10));
}
int prst_button_init() {
RET_IF_ERR(!device_is_ready(button.port));
RET_IF_ERR(gpio_pin_configure_dt(&button, GPIO_INPUT));
return 0;
}
int prst_button_register_callback(prst_button_callback_t callback) {
k_work_init_delayable(&button_pressed_delayable, button_pressed_cb);
// EDGE interrupts seem to consume more power than LEVEL ones.
// For GPIO_INT_EDGE_BOTH: 16 uA idle.
// For GPIO_INT_LEVEL_ACTIVE: 3 uA idle.
// Related issue:
// https://github.com/zephyrproject-rtos/zephyr/issues/28499
// Apparently sense-edge-mask brings the power consumption down to
// 3 uA for EDGE interrupts too.
RET_IF_ERR(gpio_pin_interrupt_configure_dt(&button, GPIO_INT_EDGE_BOTH));
gpio_init_callback(&cb_data, button_pressed_isr, BIT(button.pin));
RET_IF_ERR(gpio_add_callback(button.port, &cb_data));
user_callback = callback;
return 0;
}
int prst_button_poll(prst_button_t prst_button) {
RET_CHECK(prst_button == PRST_BUTTON_SW1, "Invalid button");
return gpio_pin_get_dt(&button);
}

View file

@ -0,0 +1,14 @@
#include "prstlib/led.h"
#include <zephyr/drivers/gpio.h>
#include "prstlib/macros.h"
LOG_MODULE_REGISTER(led, CONFIG_PRSTLIB_LOG_LEVEL);
struct gpio_dt_spec led = GPIO_DT_SPEC_GET(DT_NODELABEL(led0), gpios);
int prst_led_init() {
RET_IF_ERR(!device_is_ready(led.port));
return gpio_pin_configure_dt(&led, GPIO_OUTPUT);
}

View file

@ -0,0 +1,27 @@
#include "prstlib/sensors.h"
#include <zephyr/logging/log.h>
#include "prstlib/adc.h"
#include "prstlib/led.h"
#include "prstlib/macros.h"
LOG_MODULE_REGISTER(sensors, CONFIG_PRSTLIB_LOG_LEVEL);
int prst_sensors_read_all(prst_sensors_t *sensors) {
RET_IF_ERR(prst_adc_batt_read(&sensors->batt));
RET_IF_ERR(prst_adc_soil_read(sensors->batt.adc_read.voltage, &sensors->soil));
RET_IF_ERR(prst_adc_photo_read(sensors->batt.adc_read.voltage, &sensors->photo));
RET_IF_ERR(prst_shtc3_read(&sensors->shtc3))
LOG_DBG("Batt: %d mV (%.2f%%)", sensors->batt.adc_read.millivolts,
100 * sensors->batt.percentage);
LOG_DBG("Soil: %.0f %%", 100 * sensors->soil.percentage);
LOG_DBG("Photo: %u lx (%d mV)", sensors->photo.brightness,
sensors->photo.adc_read.millivolts);
LOG_DBG("Temp: %f oC", sensors->shtc3.temp_c);
LOG_DBG("Humi: %.0f %%", 100 * sensors->shtc3.rel_humi);
LOG_DBG("--------------------------------------------------");
return 0;
}

View file

@ -0,0 +1,52 @@
#include "prstlib/shtc3.h"
#include <zephyr/drivers/i2c.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include "prstlib/macros.h"
LOG_MODULE_REGISTER(shtc3, CONFIG_PRSTLIB_LOG_LEVEL);
static const struct i2c_dt_spec shtc3 = I2C_DT_SPEC_GET(DT_NODELABEL(shtc3));
static uint8_t buff[6];
static int write_cmd(uint16_t command) {
static uint8_t cmd[2];
cmd[0] = command >> 8;
cmd[1] = command & 0xff;
RET_IF_ERR(i2c_write_dt(&shtc3, cmd, sizeof(cmd)));
return 0;
}
int prst_shtc3_read(prst_shtc3_read_t *out) {
RET_IF_ERR_MSG(!device_is_ready(shtc3.bus), "SHTC3 is not ready");
// Wake the sensor up.
RET_IF_ERR(write_cmd(PRST_SHTC3_CMD_WAKEUP));
k_msleep(1);
// Request measurement.
RET_IF_ERR(write_cmd(PRST_SHTC3_CMD_MEASURE_TFIRST_NORMAL));
// Reading in normal (not low power) mode can take up to 12.1 ms, according to
// the datasheet.
k_msleep(20);
// Read response.
RET_IF_ERR(i2c_read_dt(&shtc3, buff, 6));
// Put the sensor in sleep mode.
RET_IF_ERR(write_cmd(PRST_SHTC3_CMD_SLEEP));
// TODO: verify the CRC of the measurements. The function is described in the
// datasheet.
out->temp_c = -45 + 175 * ((float)((buff[0] << 8) | buff[1])) / (1 << 16);
out->rel_humi = ((float)((buff[3] << 8) | buff[4])) / UINT16_MAX;
LOG_DBG("Read temp: %f oC (%d)", out->temp_c, (int)out->temp_c);
LOG_DBG("Read humi: %.0f %%", 100.0 * out->rel_humi);
return 0;
}

View file

@ -0,0 +1,2 @@
build
build_*

View file

@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.20.0)
# Pull in the dts/ and boards/ from prstlib.
set(DTS_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../../prstlib)
set(BOARD_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../../prstlib)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(ble)
include_directories(src)
target_sources(app PRIVATE
src/main.c
src/ble.c
src/encoding.c
)
add_subdirectory(../../prstlib prstlib)
target_include_directories(app PRIVATE ../../prstlib/include)
target_link_libraries(app PUBLIC prstlib)

View file

@ -0,0 +1,52 @@
source "Kconfig.zephyr"
rsource "../../prstlib/Kconfig"
config PRST_SLEEP_DURATION_MSEC
int "Sleep duration in milliseconds"
default 600000
config PRST_BLE_ADV_DURATION_MSEC
int "Advertising duration in milliseconds"
default 1000
config PRST_BLE_MIN_ADV_INTERVAL
int "Minimum advertising interval in milliseconds"
default 30
config PRST_BLE_MAX_ADV_INTERVAL
int "Maximum advertising interval in milliseconds"
default 40
choice PRST_BLE_ENCODING
prompt "b-parasite BLE encoding"
default PRST_BLE_ENCODING_BTHOME_V2
config PRST_BLE_ENCODING_BPARASITE_V2
bool "Uses the custom b-parasite protocol v2 for encoding advertising packets"
config PRST_BLE_ENCODING_BTHOME_V1
bool "Uses the BTHome (bthome.io) BLE encoding (v1)"
config PRST_BLE_ENCODING_BTHOME_V2
bool "Uses the BTHome (bthome.io) BLE encoding (v2)"
endchoice # PRST_BLE_ENCODING
config PRST_BLE_ENCODING_SERVICE_DATA_LEN
int
help
Size of the service data buffer.
default 20 if PRST_BLE_ENCODING_BPARASITE_V2
default 18 if PRST_BLE_ENCODING_BTHOME_V1
default 19 if PRST_BLE_ENCODING_BTHOME_V2
config PRST_BLE_HAS_USER_DEFINED_RANDOM_STATIC_ADDR
bool "Whether to use a custom BLE address"
config PRST_BLE_USER_DEFINED_RANDOM_STATIC_ADDR
string "Overrides the device's BT address"
help
This address has to be random static (e.g.: f0:ca:f0:ca:01:d5).
depends on PRST_BLE_HAS_USER_DEFINED_RANDOM_STATIC_ADDR

View file

@ -0,0 +1,55 @@
# Bluetooth Low Energy (BLE)
In this sample, b-parasite sensors are periodically read and broadcast using Bluetooth Low Energy (BLE) dvertising packets.
## Configuration
Available configurations and their default values are in [`Kconfig`](./Kconfig). They are set in [`prj.conf`](./prj.conf). Here are some notable examples.
### Sleep Interval
To save energy, the board spends most of the time in a "deep sleep" state, in which most peripherals and radio are completely turned off. The period of sleep is controlled by the `PRST_SLEEP_DURATION_SEC` config.
### Advertising Duration
When it wakes up, the sample reads all sensors and keep broadcasting advertising packets for `PRST_BLE_ADV_DURATION_MSEC` before going back to sleep.
### Advertising Packet Encoding
There are different ways to encode the sensor data in a BLE advertising packet.
#### BTHome Encoding
[BTHome](https://bthome.io) is a new (as of Dec/2022) open standard for encoding sensor data in BLE applications. [Home Assistant](https://www.home-assistant.io/integrations/bthome/) supports it out of the box. This makes the deployment extra convenient, since no additional configuration is needed - Home Assistant will automatically detect b-parasites in range.
What's even more interesting is that by employing [ESPHome](https://esphome.io/) bridges with the [`bluetooth_proxy`](https://esphome.io/components/bluetooth_proxy.html) component, the range of BLE coverage can be transparently increased. Multiple ESPHome bridges will forward received BLE broadcasts to Home Assistant.
This is what a typical deployment with BTHome looks like:
![Topology with BTHome + Home Assistant](./media/drawings/ble-bthome-encoding.png)
There are two versions of BTHome encodings supported by this sample:
* `PRST_BLE_ENCODING_BTHOME_V2=y` (**default**) uses the [BTHome V2](https://bthome.io/format/), supported by Home Assistant since version `2022.12`
* `PRST_BLE_ENCODING_BTHOME_V1=y` uses the [legacy BTHome V1](https://bthome.io/v1/), which was briefly in use
#### b-parasite Encoding
`PRST_BLE_ENCODING_BPARASITE_V2=y` selects the legacy encoding, used historically in this project. This is the encoding that the [`b_parasite`](https://esphome.io/components/sensor/b_parasite.html) ESPHome component understands.
With this encoding and a ESPHome + `b_parasite` component, this is an usual deployment topology:
![Topology with Legacy encoding + Home Assistant](./media/drawings/ble-bparasite-encoding.png)
The disadvantages of this encoding are:
- Each b-parasite has to be configured in the ESPHome component
- Range is limited, unless multiple ESPHome bridges are deployed with the same static configuration
## Battery Life
**tl;dr**: Theoretically well over two years with default settings.
While in deep sleep, the board consumes around `3.0 uA`:
![Sleep current consumption](./media/power-profile/sleep.png)
In the active broadcasting state, the average power consumption is highly dependant on the advertising interval.
With the default settings (in the `[30, 40] ms` range), we see an average of around `810 uA`:
![Broadcasting with 30 ms current consumption](./media/power-profile/broadcasting-30.png)
If for example we lower the connection interval to the SDK defaults (`[100, 150] ms`, roughly trading off range for power), the average current consumption is around `345 uA`:
![Broadcasting with 100ms current consumption](./media/power-profile/broadcasting-100.png)
With a `200 mAh` CR2032 battery, we can use [this spreadsheet](https://docs.google.com/spreadsheets/d/157JQiX20bGkTrlbvWbWRrs_WViL3MgVZffSCWRR7uAI/edit#gid=0) to estimate the battery life to over two years. Note that this is a simplified model and results in practice may vary.

View file

@ -0,0 +1,16 @@
{
"folders": [
{
"path": "."
},
{
"path": "../../prstlib"
}
],
"settings": {
"C_Cpp.autoAddFileAssociations": false,
"nrf-connect.applications": [
"${workspaceFolder}"
]
}
}

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View file

@ -0,0 +1,86 @@
&pinctrl {
/* Configure pwm0 instance to use pin 5. */
pwm0_default: pwm0_default {
group1 {
psels = <NRF_PSEL(PWM_OUT0, 0, 5)>;
nordic,invert;
};
};
pwm0_sleep: pwm0_sleep {
group1 {
psels = <NRF_PSEL(PWM_OUT0, 0, 5)>;
low-power-enable;
};
};
/* Configure i2c0 instance to use pins 24 (SDA) & 13 (SCL). */
i2c0_default: i2c0_default {
group1 {
psels = <NRF_PSEL(TWIM_SDA, 0, 24)>,
<NRF_PSEL(TWIM_SCL, 0, 13)>;
};
};
i2c0_sleep: i2c0_sleep {
group1 {
psels = <NRF_PSEL(TWIM_SDA, 0, 24)>,
<NRF_PSEL(TWIM_SCL, 0, 13)>;
low-power-enable;
};
};
};
&i2c0 {
shtc3: shtc3@70 {
compatible = "i2c-device";
reg = <0x70>;
label = "SHTC3";
};
};
&adc {
#address-cells = <1>;
#size-cells = <0>;
channel@0 {
reg = <0>;
zephyr,gain = "ADC_GAIN_1_6";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
// P0.03.
zephyr,input-positive = <NRF_SAADC_AIN1>;
zephyr,resolution = <10>;
};
channel@1 {
reg = <1>;
zephyr,gain = "ADC_GAIN_1_6";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
// P0.02.
zephyr,input-positive = <NRF_SAADC_AIN0>;
zephyr,resolution = <10>;
};
channel@2 {
reg = <2>;
zephyr,gain = "ADC_GAIN_1_6";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,input-positive = <NRF_SAADC_VDD>;
zephyr,resolution = <10>;
};
};
/ {
zephyr,user {
io-channels = <&adc 0>, <&adc 1>, <&adc 2>;
};
soil_pwm: soil_pwm {
compatible = "pwm-fixed";
pwms = <&pwm0 0 PWM_MSEC(100) PWM_POLARITY_INVERTED>;
pulse = <PWM_MSEC(50)>;
};
};

View file

@ -0,0 +1,27 @@
CONFIG_LOG=y
CONFIG_PWM=y
CONFIG_CBPRINTF_FP_SUPPORT=y
CONFIG_I2C=y
CONFIG_ADC=y
CONFIG_GPIO=y
CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_DEVICE_NAME="prst"
CONFIG_BT_CTLR_TX_PWR_PLUS_8=y
CONFIG_SERIAL=n
CONFIG_USE_SEGGER_RTT=y
CONFIG_NEWLIB_LIBC=y
CONFIG_NEWLIB_LIBC_FLOAT_PRINTF=y
CONFIG_ASSERT=y
# Application config - see all options in Kconfig.
# CONFIG_PRST_BLE_ENCODING_BTHOME_V2=y
# CONFIG_PRST_SLEEP_DURATION_MSEC=1000
# prstlib config - ser all options in prstlib/Kconfig.
# CONFIG_PRSTLIB_LOG_LEVEL_DBG=y

View file

@ -0,0 +1,80 @@
#include "ble.h"
#include <prstlib/macros.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/logging/log.h>
#include <zephyr/settings/settings.h>
#include "encoding.h"
LOG_MODULE_REGISTER(ble, LOG_LEVEL_INF);
static uint8_t service_data[CONFIG_PRST_BLE_ENCODING_SERVICE_DATA_LEN] = {0};
static const struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA(BT_DATA_SVC_DATA16, service_data, ARRAY_SIZE(service_data)),
BT_DATA_BYTES(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME),
};
static bt_addr_le_t mac_addr;
static int set_user_defined_bt_addr(const char *addr_str) {
bt_addr_le_t addr;
RET_IF_ERR(bt_addr_le_from_str(addr_str, "random", &addr));
RET_IF_ERR(bt_id_create(&addr, NULL));
return 0;
}
// bt_addr_le_t.a holds the MAC address in big-endian.
static int get_mac_addr(bt_addr_le_t *out) {
struct bt_le_oob oob;
RET_IF_ERR(bt_le_oob_get_local(BT_ID_DEFAULT, &oob));
const uint8_t *addr = oob.addr.a.val;
LOG_INF("MAC Address: %02x:%02x:%02x:%02x:%02x:%02x",
addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]);
*out = oob.addr;
return 0;
}
int prst_ble_init() {
#if CONFIG_PRST_BLE_HAS_USER_DEFINED_RANDOM_STATIC_ADDR
RET_IF_ERR(set_user_defined_bt_addr(CONFIG_PRST_BLE_USER_DEFINED_RANDOM_STATIC_ADDR));
#else
UNUSED_OK(set_user_defined_bt_addr);
#endif // PRST_BLE_HAS_USER_DEFINED_MAC_ADDR
RET_IF_ERR(bt_enable(/*bt_reader_cb_t=*/NULL));
if (IS_ENABLED(CONFIG_SETTINGS)) {
RET_IF_ERR_MSG(settings_load(), "Error in settings_load()");
}
RET_IF_ERR(get_mac_addr(&mac_addr));
return 0;
}
#define PRST_MS_TO_INTERVAL(value_ms) ((uint16_t)(value_ms) / 0.625f)
int prst_ble_adv_start() {
return bt_le_adv_start(
BT_LE_ADV_PARAM(
BT_LE_ADV_OPT_USE_IDENTITY,
PRST_MS_TO_INTERVAL(CONFIG_PRST_BLE_MIN_ADV_INTERVAL),
PRST_MS_TO_INTERVAL(CONFIG_PRST_BLE_MAX_ADV_INTERVAL),
/*_peer=*/NULL),
ad,
ARRAY_SIZE(ad),
// sd == NULL is required to set advertising to non-connectable. See
// https://github.com/zephyrproject-rtos/zephyr/blob/c0fcd35531611bbe35376c62a9e50744d6904940/subsys/bluetooth/host/adv.c#L860
/*sd=*/NULL,
/*sd_len=*/0);
}
int prst_ble_adv_stop() {
return bt_le_adv_stop();
}
int prst_ble_adv_set_data(const prst_sensors_t *sensors) {
return prst_ble_encode_service_data(sensors, &mac_addr, service_data,
sizeof(service_data));
}

View file

@ -0,0 +1,11 @@
#ifndef _PRST_BLE_BLE_H_
#define _PRST_BLE_BLE_H_
#include <prstlib/sensors.h>
int prst_ble_init();
int prst_ble_adv_start();
int prst_ble_adv_stop();
int prst_ble_adv_set_data(const prst_sensors_t *sensors);
#endif // _PRST_BLE_BLE_H_

View file

@ -0,0 +1,139 @@
#include "encoding.h"
#include <prstlib/macros.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(ble, LOG_LEVEL_INF);
int prst_ble_encode_service_data(const prst_sensors_t* sensors,
const bt_addr_le_t* bt_addr, uint8_t* out,
uint8_t out_len) {
RET_CHECK(out_len >= CONFIG_PRST_BLE_ENCODING_SERVICE_DATA_LEN,
"Buffer is not large enough");
#if CONFIG_PRST_BLE_ENCODING_BPARASITE_V2
// 0x181a - Environmental sensing service UUID.
out[0] = 0x1a;
out[1] = 0x18;
// Four bits for the protocol version.
out[2] |= (2 << 4) & 0xf0;
// Bit 0 of byte 0 specifies whether or not ambient light data exists in the
// payload.
#if DT_NODE_EXISTS(DT_NODELABEL(photo_transistor)) || DT_NODE_EXISTS(DT_NODELABEL(ldr))
out[2] |= 1;
#endif
// 4 bits for a small wrap-around counter for deduplicating messages on the
// receiver.
static uint8_t run_counter;
out[3] = run_counter++ & 0x0f;
out[4] = sensors->batt.adc_read.millivolts >> 8;
out[5] = sensors->batt.adc_read.millivolts & 0xff;
int16_t temp_centicelsius = 100 * sensors->shtc3.temp_c;
out[6] = temp_centicelsius >> 8;
out[7] = temp_centicelsius & 0xff;
uint16_t humi = sensors->shtc3.rel_humi * UINT16_MAX;
out[8] = humi >> 8;
out[9] = humi & 0xff;
uint16_t soil_moisture = sensors->soil.percentage * UINT16_MAX;
out[10] = soil_moisture >> 8;
out[11] = soil_moisture & 0xff;
// MAC address in big-endian.
memcpy(out + 12, bt_addr->a.val, BT_ADDR_SIZE);
out[18] = sensors->photo.brightness >> 8;
out[19] = sensors->photo.brightness & 0xff;
// https://bthome.io/v1/
#elif CONFIG_PRST_BLE_ENCODING_BTHOME_V1
// 0x181c - User data service UUID.
out[0] = 0x1c;
out[1] = 0x18;
// 1. Soil moisture.
// uint16_t.
out[2] = (0b000 << 5) | 3;
// Type of measurement - Moisture.
out[3] = 0x14;
// Value. Factor of 0.01, so we need to multiply our the value in 100% by
// 1/0.01 = 100.
uint16_t soil_val = 10000 * sensors->soil.percentage;
out[4] = soil_val & 0xff;
out[5] = soil_val >> 8;
// 2. Temp.
// int16_t.
out[6] = (0b001 << 5) | 3;
// Type of measurement - temperature.
out[7] = 0x02;
// Value. Factor 0.01.
int16_t temp_val = 100 * sensors->shtc3.temp_c;
out[8] = temp_val & 0xff;
out[9] = temp_val >> 8;
// 3. Humidity
// uint16_t.
out[10] = (0b000 << 5) | 3;
// Type - humidity.
out[11] = 0x03;
// Value. Factor 0.01, over 100%.
uint16_t humi_val = 10000 * sensors->shtc3.rel_humi;
out[12] = humi_val & 0xff;
out[13] = humi_val >> 8;
// 4. Battery voltage.
// uint16_t.
out[14] = (0b000 << 5) | 3;
// Type - voltage.
out[15] = 0x0c;
// Value. Factor of 0.001.
uint16_t batt_val = sensors->batt.adc_read.millivolts;
out[16] = batt_val & 0xff;
out[17] = batt_val >> 8;
// https://bthome.io/format/
#elif CONFIG_PRST_BLE_ENCODING_BTHOME_V2
// 0xfcd2 - bthome.io service UUID.
out[0] = 0xd2;
out[1] = 0xfc;
// Service header - no encryption, bt home v2.
out[2] = 0x40;
// Temperature.
out[3] = 0x02;
int16_t temp_val = 100 * sensors->shtc3.temp_c;
out[4] = temp_val & 0xff;
out[5] = temp_val >> 8;
// Humidity.
out[6] = 0x2E;
// Value. Factor 1 over 100%.
uint8_t humi_val = 100 * sensors->shtc3.rel_humi + 0.5f;
out[7] = humi_val;
// Illuminance.
out[8] = 0x05;
// Value. Factor of 0.01.
uint32_t lux_val = sensors->photo.brightness * 100;
out[9] = lux_val & 0xff;
out[10] = (lux_val >> 8) & 0xff;
out[11] = (lux_val >> 16) & 0xff;
// Battery voltage.
out[12] = 0x0c;
// Value. Factor of 0.001.
uint16_t batt_val = sensors->batt.adc_read.millivolts;
out[13] = batt_val & 0xff;
out[14] = batt_val >> 8;
// Soil moisture.
out[15] = 0x2F;
// Factor of 1 over 100%
uint8_t soil_val = 100 * sensors->soil.percentage + 0.5f;
out[16] = soil_val;
// Battery percentage.
out[17] = 0x01;
// Value. Factor 1 over 100%
uint8_t batt_percentage_val = 100 * sensors->batt.percentage + 0.5f;
out[18] = batt_percentage_val;
#endif // Encoding protocols
LOG_HEXDUMP_DBG(out, out_len, "Encoded BLE adv: ");
return 0;
}

View file

@ -0,0 +1,11 @@
#ifndef _PRST_BLE_ENCODING_H_
#define _PRST_BLE_ENCODING_H_
#include <prstlib/sensors.h>
#include <zephyr/bluetooth/bluetooth.h>
int prst_ble_encode_service_data(const prst_sensors_t* sensors,
const bt_addr_le_t* bt_addr, uint8_t* out,
uint8_t out_len);
#endif // _PRST_BLE_ENCODING_H_

View file

@ -0,0 +1,42 @@
#include <prstlib/adc.h>
#include <prstlib/button.h>
#include <prstlib/led.h>
#include <prstlib/macros.h>
#include <prstlib/sensors.h>
#include <prstlib/shtc3.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/pm/device.h>
#include <zephyr/pm/pm.h>
#include <zephyr/pm/policy.h>
#include "ble.h"
LOG_MODULE_REGISTER(main, LOG_LEVEL_DBG);
static int prst_init() {
RET_IF_ERR(prst_adc_init());
RET_IF_ERR(prst_led_init());
RET_IF_ERR(prst_button_init());
RET_IF_ERR(prst_ble_init());
return 0;
}
static int prst_loop(prst_sensors_t *sensors) {
RET_IF_ERR(prst_sensors_read_all(sensors));
RET_IF_ERR(prst_ble_adv_set_data(sensors));
RET_IF_ERR(prst_ble_adv_start());
k_msleep(CONFIG_PRST_BLE_ADV_DURATION_MSEC);
RET_IF_ERR(prst_ble_adv_stop());
return 0;
}
int main(void) {
__ASSERT(!prst_init(), "Error in prst_init()");
prst_led_flash(2);
prst_sensors_t sensors;
while (true) {
__ASSERT(!prst_loop(&sensors), "Error in prst_loop()");
k_msleep(CONFIG_PRST_SLEEP_DURATION_MSEC);
}
}

View file

@ -0,0 +1,2 @@
build
build_*

View file

@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.20.0)
# Pull in the dts/ and boards/ from prstlib.
set(DTS_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../../prstlib)
set(BOARD_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../../prstlib)
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(blinky)
target_sources(app PRIVATE src/main.c)

Some files were not shown because too many files have changed in this diff Show more