From db50711ba4a1f41f4082981bae58f213d48d96a1 Mon Sep 17 00:00:00 2001 From: jaseg Date: Tue, 5 May 2020 17:50:09 +0200 Subject: fw: Tie together all parts for an end-to-end demo --- controller/fw/Makefile | 11 +- controller/fw/src/crypto.c | 2 +- controller/fw/src/crypto.h | 3 +- controller/fw/src/main.c | 51 ++++++--- controller/fw/src/protocol.c | 31 +++++- controller/fw/src/rscode-config.h | 3 +- controller/fw/src/rslib.c | 5 +- controller/fw/src/rslib.h | 5 +- controller/fw/tools/dsss_demod_test_runner.py | 17 +-- .../fw/tools/dsss_demod_test_waveform_gen.py | 21 +++- controller/fw/tools/hum_generator.py | 114 +++++++++++++++++++++ controller/fw/tools/presig_gen.py | 2 +- controller/fw/tools/reed_solomon.py | 14 ++- 13 files changed, 234 insertions(+), 45 deletions(-) create mode 100755 controller/fw/tools/hum_generator.py diff --git a/controller/fw/Makefile b/controller/fw/Makefile index b6fa43f..c0f01d5 100644 --- a/controller/fw/Makefile +++ b/controller/fw/Makefile @@ -32,7 +32,8 @@ DSSS_WAVELET_LUT_SIZE ?= 69 DSSS_FILTER_FC ?= 3e-3 DSSS_FILTER_ORDER ?= 12 -TRANSMISSION_SYMBOLS ?= 32 +# Transmission symbols: 20 for 20*6=120 bit key + 10 for reed-solomon ECC +TRANSMISSION_SYMBOLS ?= 30 PRESIG_STORE_SIZE ?= 3 # will be generated if necessary @@ -47,6 +48,8 @@ C_SOURCES += src/mspdebug_wrapper.c C_SOURCES += src/spi_flash.c C_SOURCES += src/freq_meas.c C_SOURCES += src/dsss_demod.c +C_SOURCES += src/rslib.c +C_SOURCES += src/crypto.c C_SOURCES += src/adc.c C_SOURCES += src/protocol.c C_SOURCES += src/serial.c @@ -77,6 +80,8 @@ MUSL_SOURCES += math/fabsf.c MUSL_SOURCES += stdlib/abs.c MUSL_SOURCES += string/memset.c MUSL_SOURCES += string/memcpy.c +MUSL_SOURCES += string/memcmp.c +MUSL_SOURCES += string/strlen.c MUSL_SOURCES += math/__math_oflowf.c MUSL_SOURCES += math/__math_uflowf.c MUSL_SOURCES += math/__math_xflowf.c @@ -139,6 +144,8 @@ CFLAGS += -I$(abspath musl_include_shims) CFLAGS += -Itinyprintf COMMON_CFLAGS += -I$(BUILDDIR) -Isrc -Itinyaes CFLAGS += -I$(CUBE_DIR)/Drivers/CMSIS/Device/ST/STM32F4xx/Include +COMMON_CFLAGS += -I$(LIBSODIUM_DIR_ABS)/src/libsodium/include -I$(BUILDDIR)/libsodium/src/libsodium/include -I$(LIBSODIUM_DIR_ABS)/src/libsodium/include/sodium +COMMON_CFLAGS += -I$(RSLIB_DIR)/src COMMON_CFLAGS += -O0 -std=gnu11 -g -DSTM32F407xx -DSTM32F4 -DDEBUG=$(DEBUG) CFLAGS += $(ARCH_FLAGS) $(SYSTEM_FLAGS) @@ -187,11 +194,11 @@ ALL_OBJS := $(OBJS) ALL_OBJS += $(BUILDDIR)/src/startup_stm32f407xx.o ALL_OBJS += $(BUILDDIR)/src/system_stm32f4xx.o ALL_OBJS += $(BUILDDIR)/libsodium/src/libsodium/.libs/libsodium.a -ALL_OBJS += $(BUILDDIR)/tinyaes/aes.o ALL_OBJS += $(BUILDDIR)/levmarq/levmarq.o ALL_OBJS += $(BUILDDIR)/generated/gold_code_$(DSSS_GOLD_CODE_NBITS).o ALL_OBJS += $(BUILDDIR)/generated/fmeas_fft_window.o ALL_OBJS += $(BUILDDIR)/generated/dsss_cwt_wavelet.o +ALL_OBJS += $(BUILDDIR)/generated/crypto_presig_data.o ######################################################################################################################## # Rules diff --git a/controller/fw/src/crypto.c b/controller/fw/src/crypto.c index db35745..f4f79a4 100644 --- a/controller/fw/src/crypto.c +++ b/controller/fw/src/crypto.c @@ -26,7 +26,7 @@ void debug_hexdump(const char *name, const uint8_t *buf, size_t len) { DEBUG_PRINTN("\n"); } -/* Returns 1 for correct trigger */ +/* Returns trigger sig height for correct trigger */ int verify_trigger_dom(const uint8_t inkey[PRESIG_MSG_LEN], const char *domain_string, const uint8_t refkey[PRESIG_MSG_LEN]) { uint8_t key[crypto_auth_hmacsha512_KEYBYTES]; diff --git a/controller/fw/src/crypto.h b/controller/fw/src/crypto.h index 05a3c0d..18a9816 100644 --- a/controller/fw/src/crypto.h +++ b/controller/fw/src/crypto.h @@ -3,7 +3,8 @@ #include -#define PRESIG_MSG_LEN 16 +/* Presig message length: 15 byte = 120 bit ^= 20 * 6-bit symbols of 5-bit bipolar DSSS */ +#define PRESIG_MSG_LEN 15 #define OOB_TRIGGER_LEN PRESIG_MSG_LEN enum trigger_domain { diff --git a/controller/fw/src/main.c b/controller/fw/src/main.c index 6547c38..e2f7353 100644 --- a/controller/fw/src/main.c +++ b/controller/fw/src/main.c @@ -15,6 +15,7 @@ #include "dsss_demod.h" #include "con_usart.h" #include "mspdebug_wrapper.h" +#include "crypto.h" static struct spi_flash_if spif; @@ -28,6 +29,7 @@ unsigned int apb2_timer_speed = 0; struct leds leds; ssize_t jt_spi_flash_read_block(void *usr, int addr, size_t len, uint8_t *out); +static void update_image_flash_counter(void); void __libc_init_array(void) { /* we don't need this. */ } void __assert_func (unused_a const char *file, unused_a int line, unused_a const char *function, unused_a const char *expr) { @@ -208,7 +210,7 @@ ssize_t jt_spi_flash_read_block(void *usr, int addr, size_t len, uint8_t *out) { return len; } -void update_image_flash_counter(void) { +void update_image_flash_counter() { static int flash_counter = 0; flash_counter ++; fw_dump[row2_offx + 0] = flash_counter/10000 + '0'; @@ -222,6 +224,24 @@ void update_image_flash_counter(void) { fw_dump[row2_offx + 4] = flash_counter + '0'; } +/* Callback from crypto.c:oob_message_received */ +void oob_trigger_activated(enum trigger_domain domain, int serial) { + con_printf("oob_trigger_activated(%d, %d)\r\n", domain, serial); + con_printf("Attempting to flash meter...\r\n"); + update_image_flash_counter(); + + int flash_tries = 0; + while (flash_tries++ < 25) { + mspd_jtag_init(); + if (!mspd_jtag_flash_and_reset(jtag_img.devmem_img_start, jtag_img.img_len, jt_spi_flash_read_block, &jtag_img)) + break; + for (int j=0; j<168*1000*5; j++) + asm volatile ("nop"); + } + if (flash_tries == 25) + con_printf("Giving up.\r\n"); +} + static unsigned int measurement_errors = 0; static struct dsss_demod_state demod_state; static uint32_t freq_sample_ts = 0; @@ -264,17 +284,24 @@ int main(void) dsss_demod_init(&demod_state); con_printf("Booted.\r\n"); - con_printf("Attempting to flash meter...\r\n"); - int flash_tries = 0; - while (flash_tries++ < 25) { - mspd_jtag_init(); - if (!mspd_jtag_flash_and_reset(jtag_img.devmem_img_start, jtag_img.img_len, jt_spi_flash_read_block, &jtag_img)) - break; - for (int j=0; j<168*1000*5; j++) - asm volatile ("nop"); - } - if (flash_tries == 25) - con_printf("Giving up.\r\n"); + + + /* FIXME DEBUG */ +#if 0 + uint8_t test_data[TRANSMISSION_SYMBOLS] = { + 0 + }; + con_printf("Test 0\r\n"); + handle_dsss_received(test_data); + + uint8_t test_data2[TRANSMISSION_SYMBOLS] = { + 0x24, 0x0f, 0x3b, 0x10, 0x27, 0x0e, 0x22, 0x30, 0x01, 0x2c, 0x1c, 0x0b, 0x35, 0x0a, 0x12, 0x27, 0x11, 0x20, + 0x0c, 0x10, 0xc0, 0x08, 0xa4, 0x72, 0xa9, 0x9b, 0x7b, 0x27, 0xee, 0xcd + }; + con_printf("Test 1\r\n"); + handle_dsss_received(test_data2); +#endif + /* END DEBUG */ while (23) { if (adc_fft_buf_ready_idx != -1) { diff --git a/controller/fw/src/protocol.c b/controller/fw/src/protocol.c index 4740917..6b7d8b7 100644 --- a/controller/fw/src/protocol.c +++ b/controller/fw/src/protocol.c @@ -1,9 +1,14 @@ +#include + #include "sr_global.h" #include "dsss_demod.h" #include "con_usart.h" +#include "rslib.h" +#include "crypto.h" void handle_dsss_received(uint8_t data[static TRANSMISSION_SYMBOLS]) { + /* Console status output */ con_printf("DSSS data received: "); for (int i=0; i>1) * (data[i]&1 ? 1 : -1); @@ -11,5 +16,29 @@ void handle_dsss_received(uint8_t data[static TRANSMISSION_SYMBOLS]) { } con_printf("\r\n"); - update_image_flash_counter(); + /* Run reed-solomon error correction */ + const int sym_bits = DSSS_GOLD_CODE_NBITS + 1; /* +1 for amplitude sign bit */ + /* TODO identify erasures in DSSS demod layer */ + (void) rslib_decode(sym_bits, TRANSMISSION_SYMBOLS, (char *)data); + /* TODO error detection & handling */ + + /* Re-bit-pack data buffer to be bit-continuous: + * [ . . a b c d e f ] [ . . g h i j k l ] [ . . m n o p q r ] ... + * ==> [ a b c d e f g h ] [ i j k l m n o p ] [ q r ... ] ... + */ + static_assert((TRANSMISSION_SYMBOLS - NPAR) * (DSSS_GOLD_CODE_NBITS + 1) == OOB_TRIGGER_LEN * 8); + for (uint8_t i=0, j=0; i < TRANSMISSION_SYMBOLS - NPAR; i++, j += sym_bits) { + uint32_t sym = data[i]; /* [ ... | . . X X X X X X ] for 5-bit dsss */ + data[i] = 0; /* clear for output */ + + sym <<= 8-sym_bits; /* left-align: [ ... | X X X X X X . . ] */ + sym <<= 8; /* shift to second byte: [ ... | X X X X X X . . | . . . . . . . . ]*/ + sym >>= (j%8); /* shift to bit write offset: [ ... | . . . . X X X X | X X . . . . . . ] for offset 4 */ + data[j/8] |= sym >> 8; /* write upper byte */ + data[j/8 + 1] |= sym & 0xff; /* write lower byte */ + } + + /* hand off to crypto.c */ + oob_message_received(data); } + diff --git a/controller/fw/src/rscode-config.h b/controller/fw/src/rscode-config.h index 922aca9..ea5183b 100644 --- a/controller/fw/src/rscode-config.h +++ b/controller/fw/src/rscode-config.h @@ -3,7 +3,6 @@ #ifndef __RSCODE_CONFIG_H__ #define __RSCODE_CONFIG_H__ -#define NPAR 4 -#define NBITS 6 +#define NPAR 10 #endif /* __RSCODE_CONFIG_H__ */ diff --git a/controller/fw/src/rslib.c b/controller/fw/src/rslib.c index ce54a6f..aa0db2c 100644 --- a/controller/fw/src/rslib.c +++ b/controller/fw/src/rslib.c @@ -6,20 +6,19 @@ #include "rslib.h" +static struct rscode_driver driver; + void rslib_encode(int nbits, size_t msglen, char msg[static msglen], char out[msglen + NPAR]) { - struct rscode_driver driver; rscode_init(&driver, nbits); rscode_encode(&driver, (unsigned char *)msg, msglen, (unsigned char *)out); } int rslib_decode(int nbits, size_t msglen, char msg_inout[static msglen]) { - struct rscode_driver driver; rscode_init(&driver, nbits); return rscode_decode(&driver, (unsigned char *)msg_inout, msglen); } int rslib_gexp(int z, int nbits) { - struct rscode_driver driver; rscode_init(&driver, nbits); return gexp(&driver, z); } diff --git a/controller/fw/src/rslib.h b/controller/fw/src/rslib.h index 3ef6d19..bba8bb0 100644 --- a/controller/fw/src/rslib.h +++ b/controller/fw/src/rslib.h @@ -1,9 +1,12 @@ #ifndef __RSLIB_H__ #define __RSLIB_H__ +/* parity length configuration */ +#include "rscode-config.h" + void rslib_encode(int nbits, size_t msglen, char msg[static msglen], char out[msglen + NPAR]); int rslib_decode(int nbits, size_t msglen, char msg_inout[static msglen]); int rslib_gexp(int z, int nbits); -size_t rslib_npar(); +size_t rslib_npar(void); #endif /* __RSLIB_H__ */ diff --git a/controller/fw/tools/dsss_demod_test_runner.py b/controller/fw/tools/dsss_demod_test_runner.py index 27a0c8e..d3c3cfc 100644 --- a/controller/fw/tools/dsss_demod_test_runner.py +++ b/controller/fw/tools/dsss_demod_test_runner.py @@ -12,15 +12,13 @@ import multiprocessing import sqlite3 import time from urllib.parse import urlparse -import functools import tempfile import itertools import numpy as np np.set_printoptions(linewidth=240) -from dsss_demod_test_waveform_gen import load_noise_meas_params, load_noise_synth_params,\ - mains_noise_measured, mains_noise_synthetic, modulate as dsss_modulate +from dsss_demod_test_waveform_gen import load_noise_gen, modulate as dsss_modulate def build_test_binary(nbits, thf, decimation, symbols, cachedir): @@ -46,19 +44,6 @@ def build_test_binary(nbits, thf, decimation, symbols, cachedir): return build_id -@functools.lru_cache() -def load_noise_gen(url): - schema, refpath = url.split('://') - if not path.isabs(refpath): - refpath = path.abspath(path.join(path.dirname(__file__), refpath)) - - if schema == 'meas': - return mains_noise_measured, load_noise_meas_params(refpath) - elif schema == 'synth': - return mains_noise_synthetic, load_noise_synth_params(refpath) - else: - raise ValueError('Invalid schema', schema) - def sequence_matcher(test_data, decoded, max_shift=3): match_result = [] for shift in range(-max_shift, max_shift): diff --git a/controller/fw/tools/dsss_demod_test_waveform_gen.py b/controller/fw/tools/dsss_demod_test_waveform_gen.py index 1749bd7..414c553 100644 --- a/controller/fw/tools/dsss_demod_test_waveform_gen.py +++ b/controller/fw/tools/dsss_demod_test_waveform_gen.py @@ -1,4 +1,6 @@ +from os import path +import json import functools import numpy as np @@ -53,9 +55,9 @@ def mains_noise_measured(seed, n, meas_data): def load_noise_synth_params(specfile): with open(specfile) as f: d = json.load(f) - return (np.linspace(*d['x_spec']), # spl_x - d['x_spec'][2], # spl_N - (d['t'], d['c'], d['k'])) # psd_spl + return {'spl_x': np.linspace(*d['x_spec']), + 'spl_N': d['x_spec'][2], + 'psd_spl': (d['t'], d['c'], d['k']) } def mains_noise_synthetic(seed, n, psd_spl, spl_N, spl_x): st = np.random.RandomState(seed) @@ -69,3 +71,16 @@ def mains_noise_synthetic(seed, n, psd_spl, spl_N, spl_x): renoise = scipy.fftpack.ifft(spec) return renoise[10000:][:n] + 50.00 +@functools.lru_cache() +def load_noise_gen(url): + schema, refpath = url.split('://') + if not path.isabs(refpath): + refpath = path.abspath(path.join(path.dirname(__file__), refpath)) + + if schema == 'meas': + return mains_noise_measured, load_noise_meas_params(refpath) + elif schema == 'synth': + return mains_noise_synthetic, load_noise_synth_params(refpath) + else: + raise ValueError('Invalid schema', schema) + diff --git a/controller/fw/tools/hum_generator.py b/controller/fw/tools/hum_generator.py new file mode 100755 index 0000000..a139491 --- /dev/null +++ b/controller/fw/tools/hum_generator.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python +# coding: utf-8 + +import binascii +import struct + +import numpy as np +import pydub + +from dsss_demod_test_waveform_gen import load_noise_gen, modulate as dsss_modulate + +np.set_printoptions(linewidth=240) + +def generate_noisy_signal( + test_data=32, + test_nbits=5, + test_decimation=10, + test_signal_amplitude=20e-3, + noise_level=10e-3, + noise_spec='synth://grid_freq_psd_spl_108pt.json', + f_nom=50.0, + seed=0): + + #test_data = np.random.RandomState(seed=0).randint(0, 2 * (2**test_nbits), test_duration) + #test_data = np.array([0, 1, 2, 3] * 50) + if isinstance(test_data, int): + test_data = np.array(range(test_data)) + + + signal = np.repeat(dsss_modulate(test_data, test_nbits) * 2.0 - 1, test_decimation) + + noise_gen, noise_params = load_noise_gen(noise_spec) + noise = noise_gen(seed, len(signal), **noise_params) + return np.absolute(noise + f_nom + signal*test_signal_amplitude) + +def write_raw_frequencies_bin(outfile, **kwargs): + with open(outfile, 'wb') as f: + for x in generate_noisy_signal(**kwargs): + f.write(struct.pack('f', x)) + +def synthesize_sine(freqs, freqs_sampling_rate=10.0, output_sampling_rate=44100): + duration = len(freqs) / freqs_sampling_rate # seconds + afreq_out = np.interp(np.linspace(0, duration, int(duration*output_sampling_rate)), np.linspace(0, duration, len(freqs)), freqs) + return np.sin(np.cumsum(2*np.pi * afreq_out / output_sampling_rate)) + +def write_flac(filename, signal, sampling_rate=44100): + signal -= np.min(signal) + signal /= np.max(signal) + signal -= 0.5 + signal *= 2**16 - 1 + le_bytes = signal.astype(np.int16).tobytes() + seg = pydub.AudioSegment(data=le_bytes, sample_width=2, frame_rate=sampling_rate, channels=1) + seg.export(filename, format='flac') + +def write_synthetic_hum_flac(filename, output_sampling_rate=44100, freqs_sampling_rate=10.0, **kwargs): + signal = generate_noisy_signal(**kwargs) + print(signal) + write_flac(filename, synthesize_sine(signal, freqs_sampling_rate, output_sampling_rate), + sampling_rate=output_sampling_rate) + +def emulate_adc_signal(adc_bits=12, adc_offset=0.4, adc_amplitude=0.25, freq_sampling_rate=10.0, output_sampling_rate=1000, **kwargs): + signal = synthesize_sine(generate_noisy_signal(), freq_sampling_rate, output_sampling_rate) + signal = signal*adc_amplitude + adc_offset + smin, smax = np.min(signal), np.max(signal) + if smin < 0.0 or smax > 1.0: + raise UserWarning('Amplitude or offset too large: Signal out of bounds with min/max [{smin}, {smax}] of ADC range') + signal *= 2**adc_bits -1 + return signal + +def save_adc_signal(fn, signal, dtype=np.uint16): + with open(fn, 'wb') as f: + f.write(signal.astype(dtype).tobytes()) + +def write_emulated_adc_signal_bin(filename, **kwargs): + save_adc_signal(filename, emulate_adc_signal(**kwargs)) + +def hum_cmd(args): + write_synthetic_hum_flac(args.out_flac, + output_sampling_rate=args.audio_sampling_rate, + freqs_sampling_rate=args.frequency_sampling_rate, + test_data = np.array(list(binascii.unhexlify(args.data))), + test_nbits = args.symbol_bits, + test_decimation = args.decimation, + test_signal_amplitude = args.signal_level/1e3, + noise_level = args.noise_level/1e3, + noise_spec=args.noise_spec, + f_nom = args.nominal_frequency, + seed = args.random_seed) + + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser() + cmd_parser = parser.add_subparsers(required=True) + hum_parser = cmd_parser.add_parser('hum', help='Generated artificial modulated mains hum') + # output parameters + hum_parser.add_argument('-a', '--audio-sampling-rate', type=int, default=44100) + + # modulation parameters + hum_parser.add_argument('-f', '--frequency-sampling-rate', type=float, default=10.0*100/128) + hum_parser.add_argument('-b', '--symbol-bits', type=int, default=5, help='bits per symbol (excluding sign bit)') + hum_parser.add_argument('-n', '--noise-level', type=float, default=1.0, help='Scale synthetic noise level') + hum_parser.add_argument('-s', '--signal-level', type=float, default=20.0, help='Synthetic noise level in mHz') + hum_parser.add_argument('-d', '--decimation', type=int, default=10, help='DSSS modulation decimation in frequency measurement cycles') + hum_parser.add_argument('-o', '--nominal-frequency', type=float, default=50.0, help='Nominal mains frequency') + hum_parser.add_argument('-r', '--random-seed', type=int, default=0) + hum_parser.add_argument('--noise-spec', type=str, default='synth://grid_freq_psd_spl_108pt.json') + hum_parser.add_argument('out_flac', metavar='out.flac', help='FLAC output file') + hum_parser.add_argument('data', help='modulation data hex string') + hum_parser.set_defaults(func=hum_cmd) + + args = parser.parse_args() + args.func(args) + diff --git a/controller/fw/tools/presig_gen.py b/controller/fw/tools/presig_gen.py index 3f85522..c5dafe7 100644 --- a/controller/fw/tools/presig_gen.py +++ b/controller/fw/tools/presig_gen.py @@ -9,7 +9,7 @@ import binascii import time from datetime import datetime -LINKING_KEY_SIZE = 16 +LINKING_KEY_SIZE = 15 PRESIG_VERSION = '000.001' DOMAINS = ['all', 'country', 'region', 'vendor', 'series'] diff --git a/controller/fw/tools/reed_solomon.py b/controller/fw/tools/reed_solomon.py index 9eee6be..c4ca6e4 100644 --- a/controller/fw/tools/reed_solomon.py +++ b/controller/fw/tools/reed_solomon.py @@ -64,6 +64,14 @@ def cmdline_func_test(args, print=lambda *args, **kwargs: None, benchmark=False) ).decode().replace('0', '.')) assert test_data == decoded +def cmdline_func_encode(args, **kwargs): + data = np.frombuffer(binascii.unhexlify(args.hex_str), dtype=np.uint8) + # Map 8 bit input to 6 bit symbol string + data = np.packbits(np.pad(np.unpackbits(data).reshape((-1, 6)), ((0,0),(2, 0))).flatten()) + encoded = encode(data.tobytes(), nbits=args.bits) + print('symbol array:', ', '.join(f'0x{x:02x}' for x in encoded)) + print('hex string:', binascii.hexlify(encoded).decode()) + if __name__ == '__main__': parser = argparse.ArgumentParser() cmd_parser = parser.add_subparsers(required=True) @@ -75,7 +83,9 @@ if __name__ == '__main__': test_parser.add_argument('-s', '--seed', type=int, default=0, help='Random seed') test_parser.set_defaults(func=cmdline_func_test) enc_parser = cmd_parser.add_parser('encode', help='RS-Encode given hex string') - enc_parser.add_argument('hex_str') + enc_parser.set_defaults(func=cmdline_func_encode) + enc_parser.add_argument('-b', '--bits', type=int, default=8, help='Symbol bit size') + enc_parser.add_argument('hex_str', type=str, help='Input data as hex string') args = parser.parse_args() - args.func(args, print=print) + args.func(args) -- cgit