mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2025-09-30 05:23:36 +02:00
Merge commit '0b19fd29e665484223bcae5d53f339b832c4c9a1' into yeet-lfs
This commit is contained in:
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -23,4 +23,4 @@ jobs:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: "Check code formatting"
|
||||
run: ./fbt lint lint_py
|
||||
run: ./fbt lint_all
|
||||
|
@@ -11,7 +11,9 @@
|
||||
- Furi: Re-enabled `FURI_TRACE` since LFS removal frees DFU, will get better crash messages with source code path (by @Willy-JL)
|
||||
- OFW: RFID: Add GProxII support (by @BarTenderNZ)
|
||||
- OFW: iButton: Support ID writing (by @Astrrra)
|
||||
- OFW: FBT: Add `-Wundef` to compiler options (by @hedger)
|
||||
- FBT:
|
||||
- OFW: Add `-Wundef` to compiler options (by @hedger)
|
||||
- OFW: Ensure that all images conform specification (by @skyhawkillusions & @hedger)
|
||||
|
||||
### Updated:
|
||||
- Apps:
|
||||
@@ -35,6 +37,7 @@
|
||||
### Fixed:
|
||||
- GUI: Fix Dark Mode after XOR canvas color, like in NFC dict attack (by @Willy-JL)
|
||||
- OFW: NFC: Fix plantain balance string (by @Astrrra)
|
||||
- OFW: Infrared: Fix cumulative error in infrared signals (by @gsurkov)
|
||||
- OFW: JS: Ensure proper closure of variadic function in `mjs_array` (by @derskythe)
|
||||
|
||||
### Removed:
|
||||
|
40
SConstruct
40
SConstruct
@@ -322,7 +322,12 @@ firmware_env.Append(
|
||||
"SConstruct",
|
||||
"firmware.scons",
|
||||
"fbt_options.py",
|
||||
]
|
||||
],
|
||||
IMG_LINT_SOURCES=[
|
||||
# Image assets
|
||||
"applications",
|
||||
"assets",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -359,6 +364,39 @@ distenv.PhonyTarget(
|
||||
PY_LINT_SOURCES=firmware_env["PY_LINT_SOURCES"],
|
||||
)
|
||||
|
||||
# Image assets linting
|
||||
distenv.PhonyTarget(
|
||||
"lint_img",
|
||||
[
|
||||
[
|
||||
"${PYTHON3}",
|
||||
"${FBT_SCRIPT_DIR}/imglint.py",
|
||||
"check",
|
||||
"${IMG_LINT_SOURCES}",
|
||||
"${ARGS}",
|
||||
]
|
||||
],
|
||||
IMG_LINT_SOURCES=firmware_env["IMG_LINT_SOURCES"],
|
||||
)
|
||||
|
||||
distenv.PhonyTarget(
|
||||
"format_img",
|
||||
[
|
||||
[
|
||||
"${PYTHON3}",
|
||||
"${FBT_SCRIPT_DIR}/imglint.py",
|
||||
"format",
|
||||
"${IMG_LINT_SOURCES}",
|
||||
"${ARGS}",
|
||||
]
|
||||
],
|
||||
IMG_LINT_SOURCES=firmware_env["IMG_LINT_SOURCES"],
|
||||
)
|
||||
|
||||
distenv.Alias("lint_all", ["lint", "lint_py", "lint_img"])
|
||||
distenv.Alias("format_all", ["format", "format_py", "format_img"])
|
||||
|
||||
|
||||
# Start Flipper CLI via PySerial's miniterm
|
||||
distenv.PhonyTarget(
|
||||
"cli",
|
||||
|
8
applications/debug/infrared_test/application.fam
Normal file
8
applications/debug/infrared_test/application.fam
Normal file
@@ -0,0 +1,8 @@
|
||||
App(
|
||||
appid="infrared_test",
|
||||
name="Infrared Test",
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
entry_point="infrared_test_app",
|
||||
fap_category="Debug",
|
||||
targets=["f7"],
|
||||
)
|
61
applications/debug/infrared_test/infrared_test.c
Normal file
61
applications/debug/infrared_test/infrared_test.c
Normal file
@@ -0,0 +1,61 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal_infrared.h>
|
||||
|
||||
#define TAG "InfraredTest"
|
||||
|
||||
#define CARRIER_FREQ_HZ (38000UL)
|
||||
#define CARRIER_DUTY (0.33f)
|
||||
|
||||
#define BURST_DURATION_US (600UL)
|
||||
#define BURST_COUNT (50UL)
|
||||
|
||||
typedef struct {
|
||||
bool level;
|
||||
uint32_t count;
|
||||
} InfraredTestApp;
|
||||
|
||||
static FuriHalInfraredTxGetDataState
|
||||
infrared_test_app_tx_data_callback(void* context, uint32_t* duration, bool* level) {
|
||||
furi_assert(context);
|
||||
furi_assert(duration);
|
||||
furi_assert(level);
|
||||
|
||||
InfraredTestApp* app = context;
|
||||
|
||||
*duration = BURST_DURATION_US;
|
||||
*level = app->level;
|
||||
|
||||
app->level = !app->level;
|
||||
app->count += 1;
|
||||
|
||||
if(app->count < BURST_COUNT * 2) {
|
||||
return FuriHalInfraredTxGetDataStateOk;
|
||||
} else {
|
||||
return FuriHalInfraredTxGetDataStateLastDone;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t infrared_test_app(void* arg) {
|
||||
UNUSED(arg);
|
||||
|
||||
InfraredTestApp app = {
|
||||
.level = true,
|
||||
};
|
||||
|
||||
FURI_LOG_I(TAG, "Starting test signal on PA7");
|
||||
|
||||
furi_hal_infrared_set_tx_output(FuriHalInfraredTxPinExtPA7);
|
||||
furi_hal_infrared_async_tx_set_data_isr_callback(infrared_test_app_tx_data_callback, &app);
|
||||
furi_hal_infrared_async_tx_start(CARRIER_FREQ_HZ, CARRIER_DUTY);
|
||||
furi_hal_infrared_async_tx_wait_termination();
|
||||
furi_hal_infrared_set_tx_output(FuriHalInfraredTxPinInternal);
|
||||
|
||||
FURI_LOG_I(TAG, "Test signal end");
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"The measured signal should be %luus +-%.1fus",
|
||||
(app.count - 1) * BURST_DURATION_US,
|
||||
(double)1000000.0 / CARRIER_FREQ_HZ);
|
||||
|
||||
return 0;
|
||||
}
|
@@ -101,6 +101,8 @@ Currently `fbt` supports the default language server (`cpptools`) and `clangd`.
|
||||
- `get_stlink` - output serial numbers for attached STLink probes. Used for specifying an adapter with `SWD_TRANSPORT_SERIAL=...`.
|
||||
- `lint`, `format` - run `clang-format` on the C source code to check and reformat it according to the `.clang-format` specs. Supports `ARGS="..."` to pass extra arguments to clang-format.
|
||||
- `lint_py`, `format_py` - run [black](https://black.readthedocs.io/en/stable/index.html) on the Python source code, build system files & application manifests. Supports `ARGS="..."` to pass extra arguments to black.
|
||||
- `lint_img`, `format_img` - check the image assets for errors and format them. Enforces color depth and strips metadata.
|
||||
- `lint_all`, `format_all` - run all linters and formatters.
|
||||
- `firmware_pvs` - generate a PVS Studio report for the firmware. Requires PVS Studio to be available on your system's `PATH`.
|
||||
- `doxygen` - generate Doxygen documentation for the firmware. `doxy` target also opens web browser to view the generated documentation.
|
||||
- `cli` - start a Flipper CLI session over USB.
|
||||
|
97
scripts/imglint.py
Normal file
97
scripts/imglint.py
Normal file
@@ -0,0 +1,97 @@
|
||||
import logging
|
||||
import multiprocessing
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from flipper.app import App
|
||||
from PIL import Image, ImageOps
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _check_image(image, do_fixup=False):
|
||||
failed_checks = []
|
||||
with Image.open(image) as img:
|
||||
# check that is's pure 1-bit B&W
|
||||
if img.mode != "1":
|
||||
failed_checks.append(f"not 1-bit B&W, but {img.mode}")
|
||||
if do_fixup:
|
||||
img = img.convert("1")
|
||||
|
||||
# ...and does not have any metadata or ICC profile
|
||||
if img.info:
|
||||
failed_checks.append(f"has metadata")
|
||||
if do_fixup:
|
||||
img.info = {}
|
||||
|
||||
if do_fixup:
|
||||
img.save(image)
|
||||
_logger.info(f"Fixed image {image}")
|
||||
|
||||
if failed_checks:
|
||||
_logger.warning(f"Image {image} issues: {'; '.join(failed_checks)}")
|
||||
return len(failed_checks) == 0
|
||||
|
||||
|
||||
class ImageLint(App):
|
||||
ICONS_SUPPORTED_FORMATS = [".png"]
|
||||
|
||||
def init(self):
|
||||
self.subparsers = self.parser.add_subparsers(help="sub-command help")
|
||||
|
||||
self.parser_check = self.subparsers.add_parser(
|
||||
"check", help="Check image format and file names"
|
||||
)
|
||||
self.parser_check.add_argument("input", nargs="+")
|
||||
self.parser_check.set_defaults(func=self.check)
|
||||
|
||||
self.parser_format = self.subparsers.add_parser(
|
||||
"format", help="Format image and fix file names"
|
||||
)
|
||||
self.parser_format.add_argument(
|
||||
"input",
|
||||
nargs="+",
|
||||
)
|
||||
self.parser_format.set_defaults(func=self.format)
|
||||
|
||||
def _gather_images(self, folders):
|
||||
images = []
|
||||
for folder in folders:
|
||||
for dirpath, _, filenames in os.walk(folder):
|
||||
for filename in filenames:
|
||||
if self.is_file_an_icon(filename):
|
||||
images.append(os.path.join(dirpath, filename))
|
||||
return images
|
||||
|
||||
def is_file_an_icon(self, filename):
|
||||
extension = Path(filename).suffix.lower()
|
||||
return extension in self.ICONS_SUPPORTED_FORMATS
|
||||
|
||||
def _process_images(self, images, do_fixup):
|
||||
with multiprocessing.Pool() as pool:
|
||||
image_checks = pool.starmap(
|
||||
_check_image, [(image, do_fixup) for image in images]
|
||||
)
|
||||
return all(image_checks)
|
||||
|
||||
def check(self):
|
||||
images = self._gather_images(self.args.input)
|
||||
self.logger.info(f"Found {len(images)} images")
|
||||
if not self._process_images(images, False):
|
||||
self.logger.error("Some images are not in the correct format")
|
||||
return 1
|
||||
self.logger.info("All images are in the correct format")
|
||||
return 0
|
||||
|
||||
def format(self):
|
||||
images = self._gather_images(self.args.input)
|
||||
self.logger.info(f"Found {len(images)} images")
|
||||
if not self._process_images(images, True):
|
||||
self.logger.warning("Applied fixes to some images")
|
||||
else:
|
||||
self.logger.info("All images were in the correct format")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
ImageLint()()
|
@@ -54,6 +54,7 @@ typedef struct {
|
||||
|
||||
typedef struct {
|
||||
float cycle_duration;
|
||||
float cycle_remainder;
|
||||
FuriHalInfraredTxGetDataISRCallback data_callback;
|
||||
FuriHalInfraredTxSignalSentISRCallback signal_sent_callback;
|
||||
void* data_context;
|
||||
@@ -512,7 +513,11 @@ static void furi_hal_infrared_tx_fill_buffer(uint8_t buf_num, uint8_t polarity_s
|
||||
|
||||
status = infrared_tim_tx.data_callback(infrared_tim_tx.data_context, &duration, &level);
|
||||
|
||||
uint32_t num_of_impulses = roundf(duration / infrared_tim_tx.cycle_duration);
|
||||
const float num_of_impulses_f =
|
||||
duration / infrared_tim_tx.cycle_duration + infrared_tim_tx.cycle_remainder;
|
||||
const uint32_t num_of_impulses = roundf(num_of_impulses_f);
|
||||
// Save the remainder (in carrier periods) for later use
|
||||
infrared_tim_tx.cycle_remainder = num_of_impulses_f - num_of_impulses;
|
||||
|
||||
if(num_of_impulses == 0) {
|
||||
if((*size == 0) && (status == FuriHalInfraredTxGetDataStateDone)) {
|
||||
@@ -521,7 +526,7 @@ static void furi_hal_infrared_tx_fill_buffer(uint8_t buf_num, uint8_t polarity_s
|
||||
*/
|
||||
status = FuriHalInfraredTxGetDataStateOk;
|
||||
}
|
||||
} else if((num_of_impulses - 1) > 0xFFFF) {
|
||||
} else if((num_of_impulses - 1) > UINT16_MAX) {
|
||||
infrared_tim_tx.tx_timing_rest_duration = num_of_impulses - 1;
|
||||
infrared_tim_tx.tx_timing_rest_status = status;
|
||||
infrared_tim_tx.tx_timing_rest_level = level;
|
||||
@@ -632,6 +637,7 @@ void furi_hal_infrared_async_tx_start(uint32_t freq, float duty_cycle) {
|
||||
infrared_tim_tx.stop_semaphore = furi_semaphore_alloc(1, 0);
|
||||
infrared_tim_tx.cycle_duration = 1000000.0 / freq;
|
||||
infrared_tim_tx.tx_timing_rest_duration = 0;
|
||||
infrared_tim_tx.cycle_remainder = 0;
|
||||
|
||||
furi_hal_infrared_tx_fill_buffer(0, INFRARED_POLARITY_SHIFT);
|
||||
|
||||
@@ -655,7 +661,7 @@ void furi_hal_infrared_async_tx_start(uint32_t freq, float duty_cycle) {
|
||||
const GpioPin* tx_gpio = infrared_tx_pins[infrared_tx_output];
|
||||
LL_GPIO_ResetOutputPin(tx_gpio->port, tx_gpio->pin); /* when disable it prevents false pulse */
|
||||
furi_hal_gpio_init_ex(
|
||||
tx_gpio, GpioModeAltFunctionPushPull, GpioPullUp, GpioSpeedHigh, GpioAltFn1TIM1);
|
||||
tx_gpio, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedHigh, GpioAltFn1TIM1);
|
||||
|
||||
FURI_CRITICAL_ENTER();
|
||||
LL_TIM_GenerateEvent_UPDATE(INFRARED_DMA_TIMER); /* TIMx_RCR -> Repetition counter */
|
||||
|
Reference in New Issue
Block a user