Skip to content

Commit 1091867

Browse files
committedOct 20, 2021
refactor: redo entire app build
- Pull in built assets from other repos - Added unit tests
1 parent 19dc602 commit 1091867

12 files changed

+531
-260
lines changed
 

‎Dockerfile

+63-89
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,88 @@
11
# Packet Forwarder Docker File
2-
# (C) Nebra Ltd 2019
2+
# (C) Nebra Ltd 2021
33
# Licensed under the MIT License.
44

55
####################################################################################################
6-
################################## Stage: builder ##################################################
6+
########################### Stage: PktFwd Python App Builder #######################################
7+
FROM balenalib/raspberry-pi-debian:buster-build as pktfwd-builder
78

8-
FROM balenalib/raspberry-pi-debian:buster-build as builder
9+
# Variables used internally to this stage
10+
ENV INPUT_DIR=/opt/input
911

10-
# Move to correct working directory
11-
WORKDIR /opt/iotloragateway/dev
12+
# Variables to be referenced pktfwd-runner stage
13+
ENV OUTPUT_DIR=/opt/output/pktfwd-dependencies
1214

13-
# Copy python dependencies for `pip install` later
14-
COPY requirements.txt requirements.txt
15-
16-
# This will be the path that venv uses for installation below
17-
ENV PATH="/opt/iotloragateway/dev/venv/bin:$PATH"
15+
WORKDIR "$INPUT_DIR"
1816

19-
# Install build tools
17+
# Install python3 and pip3
2018
# hadolint ignore=DL3008
2119
RUN apt-get update && \
2220
apt-get -y install --no-install-recommends \
23-
automake \
24-
libtool \
25-
autoconf \
26-
git \
27-
ca-certificates \
28-
pkg-config \
29-
build-essential \
30-
python3 \
31-
python3-pip \
32-
python3-venv && \
33-
# Because the PATH is already updated above, this command creates a new venv AND activates it
34-
python3 -m venv /opt/iotloragateway/dev/venv && \
35-
# Given venv is active, this `pip` refers to the python3 variant
36-
pip install --no-cache-dir -r requirements.txt
37-
38-
# Copy the buildfiles and sx1302 concentrator fixes
39-
COPY buildfiles buildfiles
40-
COPY sx1302fixes sx1302fixes
41-
42-
# Clone the lora gateway and packet forwarder repos
43-
RUN git clone https://github.com/NebraLtd/lora_gateway.git
44-
RUN git clone https://github.com/NebraLtd/packet_forwarder.git
45-
46-
# Create folder needed by packetforwarder compiler
47-
RUN mkdir -p /opt/iotloragateway/packetforwarder
48-
49-
# Compile for sx1301 concentrator on all the necessary SPI buses
50-
RUN ./buildfiles/compileSX1301.sh spidev0.0
51-
RUN ./buildfiles/compileSX1301.sh spidev0.1
52-
RUN ./buildfiles/compileSX1301.sh spidev1.0
53-
RUN ./buildfiles/compileSX1301.sh spidev1.1
54-
RUN ./buildfiles/compileSX1301.sh spidev1.2
55-
RUN ./buildfiles/compileSX1301.sh spidev2.0
56-
RUN ./buildfiles/compileSX1301.sh spidev2.1
57-
RUN ./buildfiles/compileSX1301.sh spidev32766.0
58-
59-
# Compile for sx1302 concentrator
60-
RUN ./buildfiles/compileSX1302.sh
61-
62-
# No need to cleanup the builder
21+
python3=3.7.3-1 \
22+
python3-pip=18.1-5+rpt1 && \
23+
apt-get autoremove -y && \
24+
apt-get clean && \
25+
rm -rf /var/lib/apt/lists/*
6326

64-
####################################################################################################
65-
################################### Stage: runner ##################################################
27+
# Copy python dependencies for `pip3 install` later
28+
COPY requirements.txt requirements.txt
6629

67-
FROM balenalib/raspberry-pi-debian:buster-run as runner
30+
RUN pip3 install --target="$OUTPUT_DIR" --no-cache-dir -r requirements.txt
31+
# TODO remove once published
32+
RUN pip3 install setuptools wheel
33+
RUN pip3 install --target="$OUTPUT_DIR" git+https://github.com/NebraLtd/hm-pyhelper@marvinmarnold/fix-logger-packaging --upgrade --no-cache-dir
6834

69-
# Start in sx1301 directory
70-
WORKDIR /opt/iotloragateway/packet_forwarder/sx1301
35+
###################################################################################################
36+
################################## Stage: runner ##################################################
37+
FROM balenalib/raspberry-pi-debian:buster-run as pktfwd-runner
7138

72-
# Install python3-venv and python3-rpi.gpio
73-
# hadolint ignore=DL3008
74-
RUN apt-get update && \
75-
apt-get -y install \
76-
python3-venv \
77-
python3-rpi.gpio && \
78-
apt-get autoremove -y && \
79-
apt-get clean && \
80-
rm -rf /var/lib/apt/lists/*
39+
ENV ROOT_DIR=/opt
40+
41+
# Copy from: Locations of build assets within images of earlier stages
42+
ENV SX1301_LORA_GATEWAY_OUTPUT_RESET_LGW_FILEPATH=/opt/output/reset_lgw.sh
43+
ENV SX1301_PACKET_FORWARDER_OUTPUT_DIR=/opt/output
44+
ENV SX1302_HAL_OUTPUT_DIR=/opt/output
45+
ENV PKTFWD_BUILDER_OUTPUT_DIR=/opt/output/pktfwd-dependencies
8146

82-
# Copy sx1301 packetforwader from builder
83-
COPY --from=builder /opt/iotloragateway/packetforwarder .
47+
# Copy to: Locations build assets from earlier stages/source are copied into
48+
ENV SX1301_DIR="$ROOT_DIR/sx1301"
49+
ENV SX1302_DIR="$ROOT_DIR/sx1302"
50+
ENV PYTHON_APP_DIR="$ROOT_DIR/pktfwd"
51+
ENV PYTHON_DEPENDENCIES_DIR="$ROOT_DIR/pktfwd-dependencies"
52+
ENV SX1301_RESET_LGW_FILEPATH="$SX1301_DIR/reset_lgw.sh"
8453

85-
# Copy sx1301 regional config templates
86-
COPY lora_templates_sx1301 lora_templates_sx1301/
54+
# Variables required for pktfwd python app
55+
ENV SX1301_REGION_CONFIGS_DIR="$PYTHON_APP_DIR/config/lora_templates_sx1301"
56+
ENV SX1302_REGION_CONFIGS_DIR="$PYTHON_APP_DIR/config/lora_templates_sx1302"
57+
ENV SX1302_LORA_PKT_FWD_FILEPATH="$SX1302_DIR/lora_pkt_fwd"
8758

88-
# Use EU config as initial default
89-
COPY lora_templates_sx1301/local_conf.json local_conf.json
90-
COPY lora_templates_sx1301/EU-global_conf.json global_conf.json
59+
WORKDIR "$ROOT_DIR"
9160

92-
# Move to sx1302 directory
93-
WORKDIR /opt/iotloragateway/packet_forwarder/sx1302
61+
# Copy python app
62+
COPY pktfwd/ "$PYTHON_APP_DIR"
9463

95-
# Copy sx1302 hal from builder
96-
COPY --from=builder /opt/iotloragateway/dev/sx1302_hal-2.1.0 .
64+
# Copy sx1301 lora_pkt_fwd_SPI_BUS
65+
COPY --from=nebraltd/packet_forwarder:83fd72d2db4c69faffd387d757452cbe9d594a08 "$SX1301_PACKET_FORWARDER_OUTPUT_DIR" "$SX1301_DIR"
9766

98-
# Copy sx1302 regional config templates
99-
COPY lora_templates_sx1302 lora_templates_sx1302/
67+
# Copy sx1301 reset_lgw.sh
68+
COPY --from=nebraltd/lora_gateway:9c4b1d0c79645c3065aa4c2f3019c14da6cb2675 "$SX1301_LORA_GATEWAY_OUTPUT_RESET_LGW_FILEPATH" "$SX1301_RESET_LGW_FILEPATH"
10069

101-
# Use EU config as initial default
102-
COPY lora_templates_sx1302/local_conf.json packet_forwarder/local_conf.json
103-
COPY lora_templates_sx1302/EU-global_conf.json packet_forwarder/global_conf.json
70+
# Copy sx1302 chip_id, reset_lgw, and lora_pkt_fwd
71+
COPY --from=nebraltd/sx1302_hal:3d73e6af43535f700ff7b6c2b49cc79d388cd70f "$SX1302_HAL_OUTPUT_DIR" "$SX1302_DIR"
10472

105-
# Move to main packet forwarder directory and copy source code
106-
WORKDIR /opt/iotloragateway/packet_forwarder
107-
COPY files/* .
73+
# Copy pktfwd python app dependencies
74+
COPY --from=pktfwd-builder "$PKTFWD_BUILDER_OUTPUT_DIR" "$PYTHON_DEPENDENCIES_DIR"
75+
76+
# hadolint ignore=DL3008
77+
# Install python3 then cleanup
78+
RUN apt-get update && \
79+
apt-get -y install --no-install-recommends python3=3.7.3-1 && \
80+
apt-get autoremove -y && \
81+
apt-get clean && \
82+
rm -rf /var/lib/apt/lists/*
10883

109-
# Copy venv from builder and update PATH to activate it
110-
COPY --from=builder /opt/iotloragateway/dev/venv /opt/iotloragateway/dev/venv
111-
ENV PATH="/opt/iotloragateway/dev/venv/bin:$PATH"
84+
# Add python dependencies to PYTHONPATH
85+
ENV PYTHONPATH="${PYTHONPATH}:${PYTHON_DEPENDENCIES_DIR}"
11286

113-
# Run run_pkt script
114-
ENTRYPOINT ["sh", "/opt/iotloragateway/packet_forwarder/run_pkt.sh"]
87+
# Run pktfwd/__main__.py
88+
ENTRYPOINT ["python3", "pktfwd"]

‎README.md

+27-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
# hm-pktfwd
22
Helium Miner Packet Forwarder
33

4-
This compiles the packet forwarder container on the Nebra Miners.
4+
This is a Python app that uses prebuilt utilities to detect the correct concentrator chip and region, then start the concentrator accordingly.
55

6-
# Supported Region Plans
6+
hm-pktfwd builds off three other repos which each built a portion of the code required to run the packet forwarder.
7+
8+
- [lora_gateway](https://github.com/NebraLtd/lora_gateway)
9+
- [packet_forwarder](https://github.com/NebraLtd/packet_forwarder)
10+
- [sx1302_hal](https://github.com/NebraLtd/sx1302_hal)
11+
12+
## Supported Region Plans
713

814
You can typically find the exact region plan you need to use at [What Helium Region](https://whatheliumregion.xyz/) or on the [Helium Miner GitHub repo](https://github.com/helium/miner/blob/master/priv/countries_reg_domains.csv) however the table below provides a rough guide...
915

@@ -27,10 +33,28 @@ Please note:
2733
| --- | --- |
2834
| CN779 | NOT YET SUPPORTED |
2935

30-
## Pre built containers
36+
## Building
37+
38+
### Pre built containers
3139

3240
This repo automatically builds docker containers and uploads them to two repositories for easy access:
3341
- [hm-pktfwd on DockerHub](https://hub.docker.com/r/nebraltd/hm-pktfwd)
3442
- [hm-pktfwd on GitHub Packages](https://github.com/NebraLtd/hm-pktfwd/pkgs/container/hm-pktfwd)
3543

3644
The images are tagged using the docker long and short commit SHAs for that release. The current version deployed to miners can be found in the [helium-miner-software repo](https://github.com/NebraLtd/helium-miner-software/blob/production/docker-compose.yml).
45+
46+
### Manual build
47+
48+
When developing, it is faster to build locally instead of relying on the pre-built container to generate.
49+
50+
```bash
51+
# Cross-compile
52+
docker buildx build --platform linux/arm64/v8 --progress=plain -t DOCKERHUB_USER/hm-pktfwd .
53+
54+
# To stop at an intermediary stage
55+
docker buildx build --platform linux/arm64/v8 --progress=plain --target pktfwd-builder -t pktfwd-builder .
56+
57+
# Tag and push image
58+
docker image tag docker.io/DOCKERHUB_USER/hm-pktfwd DOCKERHUB_USER/hm-pktfwd:0.0.X
59+
docker push DOCKERHUB_USER/hm-pktfwd:0.0.X
60+
```

‎pktfwd/__main__.py

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import os
2+
from hm_pyhelper.logger import get_logger
3+
from pktfwd.pktfwd_app import PktfwdApp
4+
5+
6+
LOGGER = get_logger(__name__)
7+
8+
9+
#
10+
# Mandatory
11+
#
12+
VARIANT = os.environ['VARIANT']
13+
SX1301_REGION_CONFIGS_DIR = os.environ['SX1301_REGION_CONFIGS_DIR']
14+
SX1302_REGION_CONFIGS_DIR = os.environ['SX1302_REGION_CONFIGS_DIR']
15+
UTIL_CHIP_ID_FILEPATH = os.environ['UTIL_CHIP_ID_FILEPATH']
16+
SX1301_RESET_LGW_FILEPATH = os.environ['SX1301_RESET_LGW_FILEPATH']
17+
SX1302_RESET_LGW_FILEPATH = os.environ['SX1302_RESET_LGW_FILEPATH']
18+
# Directory the python script will be run from.
19+
# global_conf.json will also be copied here
20+
ROOT_DIR = os.environ['ROOT_DIR']
21+
SX1302_LORA_PKT_FWD_FILEPATH = os.environ['SX1302_LORA_PKT_FWD_FILEPATH']
22+
SX1301_LORA_PKT_FWD_DIR = os.environ['SX1301_DIR']
23+
24+
#
25+
# Defaults
26+
#
27+
28+
# File where hm-miner outputs region info
29+
# https://github.com/NebraLtd/hm-miner/blob/98107f50257de420a42dec50cca6e2f667f899ad/gen-region.sh#L9
30+
REGION_FILEPATH = os.getenv('REGION_FILEPATH', '/var/pktfwd/region')
31+
32+
# File that pktfwd outputs diagnostic info to.
33+
# This is different than diagnostics managed by hm-diag.
34+
# Currently this file only contains "true" or "false",
35+
# corresponding to whether pktfwd is actively running.
36+
DIAGNOSTICS_FILEPATH = os.getenv('DIAGNOSTICS_FILEPATH', '/var/pktfwd/diagnostics') # noqa
37+
38+
# Sleep time before attempting to start concentrator.
39+
AWAIT_SYSTEM_SLEEP_SECONDS = int(os.getenv('AWAIT_SYSTEM_SLEEP_SECONDS', '5'))
40+
41+
# If False, Sentry will not be enabled
42+
SENTRY_KEY = os.getenv('SENTRY_PKTFWD', False)
43+
44+
#
45+
# Optional
46+
#
47+
48+
# Overrides the region detected in $REGION_FILEPATH, if defined
49+
REGION_OVERRIDE = os.getenv('REGION_OVERRIDE', False)
50+
51+
# Balena vars used with Sentry
52+
BALENA_ID = os.getenv('BALENA_DEVICE_UUID')
53+
BALENA_APP = os.getenv('BALENA_APP_NAME')
54+
55+
56+
def main():
57+
validate_env()
58+
start()
59+
60+
61+
def validate_env():
62+
LOGGER.debug("Starting with the following ENV:\n\
63+
VARIANT=%s\n\
64+
REGION_OVERRIDE=%s\n\
65+
REGION_FILEPATH=%s\n\
66+
SX1301_REGION_CONFIGS_DIR=%s\n\
67+
SX1302_REGION_CONFIGS_DIR=%s\n\
68+
SENTRY_KEY=%s\n\
69+
BALENA_ID=%s\n\
70+
BALENA_APP=%s\n\
71+
DIAGNOSTICS_FILEPATH=%s\n\
72+
AWAIT_SYSTEM_SLEEP_SECONDS=%s\n\
73+
SX1301_RESET_LGW_FILEPATH=%s\n\
74+
SX1302_RESET_LGW_FILEPATH=%s\n\
75+
UTIL_CHIP_ID_FILEPATH=%s\n\
76+
ROOT_DIR=%s\n\
77+
SX1302_LORA_PKT_FWD_FILEPATH=%s\n\
78+
SX1301_LORA_PKT_FWD_DIR=%s\n" %
79+
(VARIANT, REGION_OVERRIDE, REGION_FILEPATH, # noqa E128
80+
SX1301_REGION_CONFIGS_DIR, SX1302_REGION_CONFIGS_DIR, SENTRY_KEY,
81+
BALENA_ID, BALENA_APP, DIAGNOSTICS_FILEPATH,
82+
AWAIT_SYSTEM_SLEEP_SECONDS, SX1301_RESET_LGW_FILEPATH,
83+
SX1302_RESET_LGW_FILEPATH, UTIL_CHIP_ID_FILEPATH, ROOT_DIR,
84+
SX1302_LORA_PKT_FWD_FILEPATH, SX1301_LORA_PKT_FWD_DIR))
85+
86+
87+
def start():
88+
pktfwd_app = PktfwdApp(VARIANT, REGION_OVERRIDE, REGION_FILEPATH,
89+
SX1301_REGION_CONFIGS_DIR,
90+
SX1302_REGION_CONFIGS_DIR, SENTRY_KEY,
91+
BALENA_ID, BALENA_APP,
92+
DIAGNOSTICS_FILEPATH,
93+
AWAIT_SYSTEM_SLEEP_SECONDS,
94+
SX1301_RESET_LGW_FILEPATH,
95+
SX1302_RESET_LGW_FILEPATH,
96+
UTIL_CHIP_ID_FILEPATH, ROOT_DIR,
97+
SX1302_LORA_PKT_FWD_FILEPATH,
98+
SX1301_LORA_PKT_FWD_DIR)
99+
100+
try:
101+
pktfwd_app.start()
102+
except Exception:
103+
LOGGER.exception('__main__ failed for unknown reason')
104+
pktfwd_app.stop()
105+
106+
107+
if __name__ == "__main__":
108+
main()

‎pktfwd/config/__init__.py

Whitespace-only changes.
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
REGION_CONFIG_FILENAMES = {
2+
"AS923_1": "AS923-1-global_conf.json",
3+
"AS923_2": "AS923-2-global_conf.json",
4+
"AS923_3": "AS923-3-global_conf.json",
5+
"AS923_4": "AS923-4-global_conf.json",
6+
"AU915": "AU-global_conf.json",
7+
"CN470": "CN-global_conf.json",
8+
"EU433": "EU433-global_conf.json",
9+
"EU868": "EU-global_conf.json",
10+
"IN865": "IN-global_conf.json",
11+
"KR920": "KR-global_conf.json",
12+
"RU864": "RU-global_conf.json",
13+
"US915": "US-global_conf.json"
14+
}

‎pktfwd/configurePktFwd.py

-160
This file was deleted.

‎pktfwd/pktfwd_app.py

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
from hm_pyhelper.hardware_definitions import variant_definitions
2+
from pktfwd.utils import init_sentry, is_concentrator_sx1302, \
3+
update_global_conf, write_diagnostics, \
4+
await_system_ready, retry_start_concentrator
5+
from hm_pyhelper.logger import get_logger
6+
from hm_pyhelper.miner_param import retry_get_region, await_spi_available
7+
8+
9+
LOGGER = get_logger(__name__)
10+
11+
12+
class PktfwdApp:
13+
def __init__(self, variant, region_override, region_filepath,
14+
sx1301_region_configs_dir, sx1302_region_configs_dir,
15+
sentry_key, balena_id, balena_app,
16+
diagnostics_filepath, await_system_sleep_seconds,
17+
sx1301_reset_lgw_filepath, sx1302_reset_lgw_filepath,
18+
util_chip_id_filepath, root_dir,
19+
sx1302_lora_pkt_fwd_filepath, sx1301_lora_pkt_fwd_dir): # noqa
20+
21+
init_sentry(sentry_key, balena_id, balena_app)
22+
self.set_variant_attributes(variant)
23+
self.sx1301_region_configs_dir = sx1301_region_configs_dir
24+
self.sx1302_region_configs_dir = sx1302_region_configs_dir
25+
self.region_override = region_override
26+
self.region_filepath = region_filepath
27+
self.diagnostics_filepath = diagnostics_filepath
28+
self.await_system_sleep_seconds = await_system_sleep_seconds
29+
self.sx1301_reset_lgw_filepath = sx1301_reset_lgw_filepath
30+
self.sx1302_reset_lgw_filepath = sx1302_reset_lgw_filepath
31+
self.util_chip_id_filepath = util_chip_id_filepath
32+
self.root_dir = root_dir
33+
self.sx1301_lora_pkt_fwd_dir = sx1301_lora_pkt_fwd_dir
34+
self.sx1302_lora_pkt_fwd_filepath = sx1302_lora_pkt_fwd_filepath
35+
36+
def start(self):
37+
LOGGER.debug("STARTING PKTFWD")
38+
self.prepare_to_start()
39+
40+
is_sx1302 = is_concentrator_sx1302(self.util_chip_id_filepath,
41+
self.spi_bus)
42+
43+
update_global_conf(is_sx1302, self.root_dir,
44+
self.sx1301_region_configs_dir,
45+
self.sx1302_region_configs_dir, self.region,
46+
self.spi_bus)
47+
48+
retry_start_concentrator(is_sx1302, self.spi_bus,
49+
self.sx1302_lora_pkt_fwd_filepath,
50+
self.sx1301_lora_pkt_fwd_dir,
51+
self.sx1301_reset_lgw_filepath,
52+
self.sx1302_reset_lgw_filepath,
53+
self.reset_pin)
54+
55+
# retry_start_concentrator will hang indefinitely while the
56+
# upstream packet_forwarder runs. The lines below will only
57+
# be reached if the concentrator exits unexpectedly.
58+
LOGGER.error("Unable to start concentrator. Shutting down.")
59+
self.stop()
60+
61+
def prepare_to_start(self):
62+
"""
63+
Performs additional initialization not done in __init__
64+
because it depends on the filesystem being available.
65+
"""
66+
write_diagnostics(self.diagnostics_filepath, True)
67+
await_spi_available(self.spi_bus)
68+
69+
self.region = retry_get_region(self.region_override,
70+
self.region_filepath)
71+
72+
LOGGER.debug("Region set to %s" % self.region)
73+
74+
await_system_ready(self.await_system_sleep_seconds)
75+
LOGGER.debug("Finished preparing pktfwd")
76+
77+
def stop(self):
78+
LOGGER.debug("STOPPING PKTFWD")
79+
write_diagnostics(self.diagnostics_filepath, False)
80+
81+
def set_variant_attributes(self, variant):
82+
self.variant = variant
83+
self.variant_attributes = variant_definitions[self.variant]
84+
self.reset_pin = self.variant_attributes['RESET']
85+
self.spi_bus = self.variant_attributes['SPIBUS']
86+
LOGGER.debug("Variant %s set with reset_pin %s and spi_bus %s" %
87+
(self.variant, self.reset_pin, self.spi_bus))

‎pktfwd/run_pkt.sh

-7
This file was deleted.

‎pktfwd/utils.py

+171
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import json
2+
import subprocess
3+
import sentry_sdk
4+
import logging
5+
from time import sleep
6+
from shutil import copyfile
7+
from tenacity import retry, wait_fixed, stop_after_attempt, before_sleep_log
8+
from hm_pyhelper.logger import get_logger, LOGLEVEL
9+
from pktfwd.config.region_config_filenames import REGION_CONFIG_FILENAMES
10+
11+
12+
LOGGER = get_logger(__name__)
13+
LOGLEVEL_INT = getattr(logging, LOGLEVEL)
14+
LORA_PKT_FWD_RETRY_SLEEP_SECONDS = 2
15+
LORA_PKT_FWD_MAX_TRIES = 5
16+
17+
18+
def init_sentry(sentry_key, balena_id, balena_app):
19+
"""
20+
Initialize sentry with balena_app as environment and
21+
balenda_id as the user's id. If sentry_key is not set,
22+
do nothing.
23+
"""
24+
if(sentry_key):
25+
sentry_sdk.init(sentry_key, environment=balena_app)
26+
sentry_sdk.set_user({"id": balena_id})
27+
28+
29+
def write_diagnostics(diagnostics_filepath, is_running):
30+
"""
31+
Write "true" to diagnostics_filepath if pktfwd is running,
32+
"false" otherwise.
33+
"""
34+
with open(diagnostics_filepath, 'w') as diagnostics_stream:
35+
if (is_running):
36+
diagnostics_stream.write("true")
37+
else:
38+
diagnostics_stream.write("false")
39+
40+
41+
def await_system_ready(sleep_seconds):
42+
"""
43+
Sleep before starting core functions.
44+
TODO: Get more information about why.
45+
Original code: https://github.com/NebraLtd/hm-pktfwd/blob/5a0178341e69ecbf6b1dbc8463f6bd1231e9e657/files/configurePktFwd.py#L77 # noqa: E501
46+
"""
47+
LOGGER.debug("Waiting %s seconds for systems to be ready" % sleep_seconds)
48+
sleep(sleep_seconds)
49+
LOGGER.debug("System now ready")
50+
51+
52+
def run_reset_lgw(is_sx1302, sx1301_reset_lgw_filepath,
53+
sx1302_reset_lgw_filepath, reset_lgw_pin):
54+
"""
55+
Invokes reset_lgw.sh script with the reset pin value.
56+
"""
57+
# Use the correct reset depending on chip version
58+
reset_lgw_filepath = sx1301_reset_lgw_filepath
59+
if is_sx1302:
60+
reset_lgw_filepath = sx1302_reset_lgw_filepath
61+
62+
# reset_lgw script is expecting a string, not an int
63+
reset_lgw_pin_str = str(reset_lgw_pin)
64+
LOGGER.debug("Executing %s with reset pin %s" %
65+
(reset_lgw_filepath, reset_lgw_pin_str))
66+
67+
subprocess.run([reset_lgw_filepath, "stop", reset_lgw_pin_str])
68+
subprocess.run([reset_lgw_filepath, "start", reset_lgw_pin_str])
69+
70+
71+
def is_concentrator_sx1302(util_chip_id_filepath, spi_bus):
72+
"""
73+
Use the util_chip_id to determine if concentrator is sx1302.
74+
util_chip_id calls the sx1302_hal reset_lgw.sh script during execution.
75+
"""
76+
util_chip_id_cmd = [util_chip_id_filepath, "-d", "/dev/{}".format(spi_bus)]
77+
78+
try:
79+
subprocess.run(util_chip_id_cmd, capture_output=True,
80+
text=True, check=True).stdout
81+
return True
82+
# CalledProcessError raised if there is a non-zero exit code
83+
# https://docs.python.org/3/library/subprocess.html#using-the-subprocess-module
84+
except Exception:
85+
return False
86+
87+
88+
def get_region_filename(region):
89+
"""
90+
Return filename for config corresponding to region.
91+
"""
92+
return REGION_CONFIG_FILENAMES[region]
93+
94+
95+
def update_global_conf(is_sx1302, root_dir, sx1301_region_configs_dir,
96+
sx1302_region_configs_dir, region, spi_bus):
97+
"""
98+
Replace global_conf.json with the configuration necessary given
99+
the concentrator chip type, region, and spi_bus.
100+
"""
101+
if is_sx1302:
102+
replace_sx1302_global_conf_with_regional(sx1302_region_configs_dir,
103+
region, spi_bus)
104+
else:
105+
replace_sx1301_global_conf_with_regional(root_dir,
106+
sx1301_region_configs_dir,
107+
region)
108+
109+
110+
def replace_sx1301_global_conf_with_regional(root_dir,
111+
sx1301_region_configs_dir,
112+
region):
113+
"""
114+
Copy the regional configuration file to global_conf.json
115+
"""
116+
region_config_filepath = "%s/%s" % \
117+
(sx1301_region_configs_dir,
118+
get_region_filename(region))
119+
120+
global_config_filepath = "%s/%s" % (root_dir, "global_conf.json")
121+
LOGGER.debug("Copying SX1301 conf from %s to %s" %
122+
(region_config_filepath, global_config_filepath))
123+
copyfile(region_config_filepath, global_config_filepath)
124+
125+
126+
def replace_sx1302_global_conf_with_regional(sx1302_region_configs_dir,
127+
region, spi_bus):
128+
"""
129+
Parses the regional configuration file in order to make changes
130+
and save them to global_conf.json
131+
"""
132+
# Write the configuration files
133+
region_config_filepath = "%s/%s" % \
134+
(sx1302_region_configs_dir,
135+
get_region_filename(region))
136+
137+
global_config_filepath = "%s/%s" % \
138+
(sx1302_region_configs_dir,
139+
"global_conf.json")
140+
141+
with open(region_config_filepath) as region_config_file:
142+
new_global_conf = json.load(region_config_file)
143+
144+
# Inject SPI Bus
145+
new_global_conf['SX130x_conf']['com_dir'] = "/dev/%s" % spi_bus
146+
147+
with open(global_config_filepath, 'w') as global_config_file:
148+
json.dump(new_global_conf, global_config_file)
149+
150+
151+
@retry(wait=wait_fixed(LORA_PKT_FWD_RETRY_SLEEP_SECONDS),
152+
stop=stop_after_attempt(LORA_PKT_FWD_MAX_TRIES),
153+
before_sleep=before_sleep_log(LOGGER, LOGLEVEL_INT))
154+
def retry_start_concentrator(is_sx1302, spi_bus,
155+
sx1302_lora_pkt_fwd_filepath,
156+
sx1301_lora_pkt_fwd_dir,
157+
sx1301_reset_lgw_filepath,
158+
sx1302_reset_lgw_filepath, reset_lgw_pin):
159+
"""
160+
Retry to start lora_pkt_fwd for the corresponding concentrator model.
161+
Runs the reset_lgw script before every attempt.
162+
"""
163+
run_reset_lgw(is_sx1302, sx1301_reset_lgw_filepath,
164+
sx1302_reset_lgw_filepath, reset_lgw_pin)
165+
166+
if is_sx1302:
167+
subprocess.run(sx1302_lora_pkt_fwd_filepath)
168+
else:
169+
sx1301_lora_pkt_fwd_filepath = "%s/lora_pkt_fwd_%s" % \
170+
(sx1301_lora_pkt_fwd_dir, spi_bus)
171+
subprocess.run(sx1301_lora_pkt_fwd_filepath)

‎requirements.txt

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
1-
hm-pyhelper==0.6
21
sentry-sdk==1.1.0
2+
tenacity==8.0.1
3+
# TODO uncomment
4+
# hm-pyhelper==0.6

‎tests/test_pktfwd_app.py

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from unittest import TestCase
2+
from pktfwd.pktfwd_app import PktfwdApp
3+
4+
5+
class TestPktfwdApp(TestCase):
6+
def test_can_instantiate(self):
7+
pktfwd_app = PktfwdApp("NEBHNT-IN1", "US915", "/var/pktfwd/region",
8+
"/opt/pktfwd/config/lora_templates_sx1301",
9+
"/opt/pktfwd/config/lora_templates_sx1302",
10+
False, "", "", "/var/pktfwd/diagnostics", "5",
11+
"/opt", "/opt/outputs/sx1301/reset_lgw.sh",
12+
"UTIL_CHIP_ID_FILEPATH",
13+
"SX1302_LORA_PKT_FWD_FILEPATH",
14+
"/opt/outputs/sx1301")
15+
16+
self.assertEqual(pktfwd_app.variant, 'NEBHNT-IN1')

‎tests/test_utils.py

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import tempfile
2+
import io
3+
from unittest import TestCase
4+
from pktfwd.utils import write_diagnostics, get_region_filename, \
5+
replace_sx1301_global_conf_with_regional
6+
7+
8+
class TestUtils(TestCase):
9+
def test_write_diagnotics_is_running(self):
10+
diagnostics_filepath = tempfile.mkstemp()[1]
11+
write_diagnostics(diagnostics_filepath, True)
12+
13+
contents = open(diagnostics_filepath).read()
14+
self.assertEqual(contents, "true")
15+
16+
def test_write_diagnotics_not_running(self):
17+
diagnostics_filepath = tempfile.mkstemp()[1]
18+
write_diagnostics(diagnostics_filepath, False)
19+
20+
contents = open(diagnostics_filepath).read()
21+
self.assertEqual(contents, "false")
22+
23+
def test_get_region_filename(self):
24+
self.assertEqual(get_region_filename("AS923_4"),
25+
"AS923-4-global_conf.json")
26+
self.assertEqual(get_region_filename("EU868"),
27+
"EU-global_conf.json")
28+
self.assertEqual(get_region_filename("US915"),
29+
"US-global_conf.json")
30+
31+
def test_replace_sx1301_global_conf_with_regional(self):
32+
configs_dir = tempfile.mkdtemp()
33+
expected_config_filepath = "{}/IN-global_conf.json".format(configs_dir)
34+
with open(expected_config_filepath, "w") as expected_config_file:
35+
expected_config_file.write("foobar")
36+
replace_sx1301_global_conf_with_regional(configs_dir,
37+
configs_dir, "IN865")
38+
39+
global_conf_filepath = "{}/global_conf.json".format(configs_dir)
40+
self.assertListEqual(
41+
list(io.open(global_conf_filepath)),
42+
list(io.open(expected_config_filepath)))

0 commit comments

Comments
 (0)
Please sign in to comment.