Инженерный тур. 2 этап
Задачи второго этапа делятся на командную и индивидуальную часть. Второй этап профиля проходит на платформе «Орбита.Челлендж» (https://orbita.education/ru/).
Уже начиная с первого этапа, идет подготовка к задаче заключительного этапа в рамках инженерного тура. Индивидуальная часть является продолжением задач инженерного тура. Помимо этого, решения индивидуальной части входят в состав первой командной части.
Индивидуальная часть может дать команде до 40 баллов: по 10 баллов в каждом направлении. В таблице 1.1 показаны компетенции по направлениям, развиваемые у участников по ходу участия в профиле. Можно увидеть, что они частично перекрывают компетенции, необходимые на финале.
| Программист МК | Инженер связи | Инженер-программист ПН | Баллистик |
|---|---|---|---|
| Написание алгоритмов управления КА | Общие принципы реализации протоколов связи | Понимание принципов работы датчиков; навыки программирования | Основы орбитальной механики; работа в ПО «Орбита» |
В рамках командного этапа, помимо отборочных испытаний, предусмотрена также тренировочная задача. Отборочные испытания предполагают проверку сразу нескольких компетенций, связанных с направлениями деятельности. Командный этап может принести команде до 60 баллов.
В таблице 1.2 представлены задачи для командного этапа.
| Первая командная задача | Вторая командная задача |
|---|---|
|
|
Облачность является одной из самых значимых проблем для спутниковой съемки поверхности Земли. Облака могут затруднять или полностью скрывать наблюдения, особенно если миссия нацелена на получение детализированных изображений для картографии, мониторинга сельскохозяйственных угодий или оценки природных ресурсов. Компании и космические миссии используют несколько стратегий для детектирования облаков и минимизации влияния облачности на результаты съемки.
Одним из ведущих методов является мультиспектральная съемка. Спутники, такие как Landsat, Sentinel и другие, оснащены камерами, которые работают в разных диапазонах электромагнитного спектра, включая видимый свет, ближний и средний инфракрасный диапазоны. В инфракрасном диапазоне облака хорошо различимы, поскольку их температура ниже температуры земной поверхности. Это позволяет отделить облака от объектов на земле, особенно если используется комбинация нескольких каналов спектра.
В реальных миссиях для детектирования облаков часто применяется метод анализа рефлектанса — оценки того, как свет отражается от объектов. Белые облака обладают высоким отражением в видимом диапазоне, в то время как для поверхности Земли это значение ниже. Например, для картографических миссий, таких как Sentinel-2, используются комбинации видимого и инфракрасного диапазонов для создания индексов облачности (в частности, NDSI — Normalized Difference Snow Index). Эти индексы помогают точно выделить облака на снимках. Также активно применяются алгоритмы машинного обучения и искусственного интеллекта для автоматической классификации изображений. Данные с низкого разрешения анализируются нейросетями, которые обучены выявлять облака на основании множества различных признаков: форма, яркость, спектральные характеристики и движение. Эти системы активно используются компаниями типа Planet Labs и в миссиях NASA, где задача состоит в том, чтобы эффективно фильтровать кадры с высоким уровнем облачности.
Существуют и другие подходы к обработке облаков. Например, миссии часто используют временные ряды изображений. В этом случае несколько снимков одного и того же участка делаются через короткие промежутки времени, и на их основе объединяются данные с минимальным уровнем облачности. Этот метод широко применяется в программах типа Google Earth Engine для составления карт Земли с использованием чистых участков снимков за длительный период времени.
Отдельно стоит упомянуть о проблемах, связанных с тонкими облаками или дымкой, которые могут не перекрывать поверхность полностью, но снижать качество снимков. Для таких ситуаций также применяются специализированные фильтры и алгоритмы, которые могут на основе спектрального анализа отличать полупрозрачные облака от чистой атмосферы. Системы картографической съемки с применением облачных фильтров представляют собой сложные решения, интегрирующие оптические технологии, алгоритмы обработки данных и умные системы принятия решений. Эти решения позволяют значительно повысить качество снимков, избегая съемок участков с высокой степенью облачности.
Космический аппарат (КА), предназначенный для картографической съемки поверхности Земли, оснащен двумя сенсорами. Основная камера обладает высоким разрешением, но ее использование требует значительного потребления энергии, поэтому для предварительной оценки условий съемки используется небольшой сенсор с матрицей \(100\times100\) пикселей. Этот сенсор постоянно делает снимки местности.
Каждый снимок с маленького сенсора содержит:
- время снимка UTC,
- географические координаты спутника на момент съемки (
lat_lon), - массив данных \(100\times100\) пикселей (каждый пиксель представлен ASCII символом).
Пример пакета:
2018-06-14_10:12:00.000_55.7522_37.6156_dlkfgvmih...vogrhgvo
Пример строки, задающей интересующий квадрат:
"55.7522_37.6156_55.7522_37.6156"
Формат:
"(lat min)_(lat max)_(lon min)_(lon max)"
Необходимо разработать систему фильтрации, которая будет анализировать снимки с маленького сенсора и принимать решение о том, когда включать основную камеру. Условия для включения следующие:
- снимок сделан в пределах заданных координат местности;
- на снимке не более 40% пикселей занято облаками.
Облака детектируются с помощью анализа яркости пикселей (пороговое значение яркости для облаков заранее известно).
Дополнительные указания:
- Квадрат местности задан географическими координатами его углов.
- Яркость пикселей, указывающая на облачность, варьируется от 0 до 127.
- Порог яркости для облаков: 100.
Программа должна возвращать следующую строку:
(процент облаков на снимке)_(флаг включения основной камеры)Процент облаков на снимке — целая часть от рассчитанной облачности без округлений (\(31{,}3\rightarrow 31, 31{,}8\rightarrow 31\)).
Пример выходной строки:
80_0(облачность: 80%, статус: 0 — выкл);15_1(облачность: 15%, статус: 1 — вкл);0_0(снимок сделан за пределами области интереса).
Тестовые данные
| Номер теста | Стандартный ввод | Стандартный вывод |
|---|---|---|
1 |
2018-06-14_10:12:00.000_37.6156_55.7522_bMLfgQEjkbX9UHmWngXxJ6F ZQWGvd79z1XWObV51oAvJTY2rqaj4XsRO3AClbsH8Tmh9i7KhxpPC95lHiSemNl GJZNGSrsgUvFkj 36.7000_37.6156_55.7522_59.6382 |
36_1 |
Максимально возможный балл — 10. Первые две попытки идут без штрафа. За каждую следующую попытку максимально возможный балл уменьшается на 10% (т. е. на третьей попытке до 9 баллов, на четвертой — до 8 и т. д.) до 1 балла и далее не уменьшается.
Ниже представлено решение на языке Python.
import sys
import math
image_packet = sys.stdin.readline().rstrip() # Чтение пакета
area = sys.stdin.readline() # Чтение области интереса
CLOUD_BRIGHTNESS_THRESHOLD = 100
MAX_CLOUD_COVERAGE = 0.4
def is_within_area(latitude, longitude, area_bounds):
lat_min, lat_max, lon_min, lon_max = area_bounds
return lat_min <= latitude <= lat_max and lon_min <= longitude <= lon_max
def calculate_cloud_coverage(pixel_data):
cloud_pixels = sum(1 for pixel in pixel_data if pixel >= CLOUD_BRIGHTNESS_THRESHOLD)
total_pixels = len(pixel_data)
return cloud_pixels / total_pixels * 100
def process_image_packet(packet, area_str):
date_str, timestamp_str, lat_str, lon_str, pixel_data_str = packet.split('_', 4)
latitude = float(lat_str)
longitude = float(lon_str)
pixel_data = [ord(char) for char in pixel_data_str]
lat_min, lat_max, lon_min, lon_max = map(float, area_str.split('_'))
area_bounds = (lat_min, lat_max, lon_min, lon_max)
if not is_within_area(latitude, longitude, area_bounds):
return "0_0"
cloud_coverage = calculate_cloud_coverage(pixel_data)
main_camera_status = 1 if cloud_coverage <= MAX_CLOUD_COVERAGE * 100 else 0
return f"{int(math.trunc(cloud_coverage))}_{main_camera_status}"
output = process_image_packet(image_packet, area)
sys.stdout.write(output) # Вывод ответа
36_1.
Всем уже знакомый космокот Кас планирует запустить научный спутник с важным научным оборудованием и передатчиком для передачи данных на Землю. Это оборудование формирует текстовую строку с данными. Помогите Касу разработать программы для передатчика и наземного приемника, которые бы обеспечили отправку ценных научных данных на Землю. Известно, что данные в процессе передачи могут искажаться из-за сторонних помех случайным образом.
На вход функции proceed программы передатчика поступают раз в \(0{,}015n~\pm~5\%\) секунд отдельные текстовые сообщения с научными данными переменной длины. Длина строки научных данных гарантированно \(n\) символов. Вывод функции отправляется на передатчик спутника. Скорость передатчика составляет 100 байт/с. Если передатчик не успел передать данные до прихода следующих данных, текущая отправка данных прерывается и запускается следующая. Программа передатчика не может и не должна иметь сквозного буфера между ее исполнениями в силу ограниченности памяти на спутнике. Временем выполнения программы передатчика пренебречь.
На вход функции proceed поступают данные с приемника. Скорость приема приемника соответствует скорости передатчика. Входные данные подаются частями по \(16n\) бит. В связи с ограничениями памяти на наземной станции приема, разрешается хранить в сквозном буфере не более \(80n\) бит. Если сообщение не было найдено или не было декодировано, функция должна вернуть пустую строку. Также в функцию передается признак окончания работы приемника, его истинность означает, что текущая часть данных — последняя. Учтите, что при истинности этого признака размер части данных может быть \(\leqslant 16n\). При истинности этого признака функция продолжит вызываться до тех пор, пока не вернет пустую строку, чтобы была возможность дообработать сквозной буфер. Функция должна вернуть строку типа str.
Примечания
Значение переменной \(n\) определяется в процессе выполнения программы. Известно, что ее значение находится в диапазоне \([130, 170]\).
Формат входных данных
Программа передатчика должна иметь функцию proceed, которой на вход будет подаваться строка (str) с научными данными. Функция должна возвращать объект типа bytes, содержимое которого передается на передатчик.
Пример объявления функции:
def proceed(data: str) -> bytes:
Пример объявления функции:
def proceed(inp, buf, end):
return ""
где:
inp— часть данных с приемника —numpy-массив с типом данныхboolдлиной \(16n\);buf— сквозной буфер —numpy-массив с типом данныхboolдлиной \(80n\);end— признак окончания работы типаbool.
Итоговый балл вычисляется как максимальный балл за задачу, умноженный на отношение корректно принятых сообщений к общему числу отправленных сообщений.
Сообщение является корректно принятым, если совпадает с любым сообщением, которое было подано в функцию программы передатчика, кроме тех, что уже были корректно приняты.
Первые четыре попытки без штрафа. С каждой следующей попыткой балл убывает на 10% от максимального балла (на 1 балл) до 1 балла, далее не убывает.
В решении задачи применяется opensource библиотека reedsolo, которая реализует код Рида-Соломона. Альтернативно, участники могли использовать код Хэмминга либо также в виде библиотеки, либо самостоятельно реализованный. Для применения библиотеки ее исходный код необходимо скопировать в начало программы.
Программа спутникового передатчика
Ниже представлено решение на языке Python.
#Содержимое https://github.com/tomerfiliba-org/reedsolomon/ blob/master/src/reedsolo/reedsolo.py
def proceed(msg):
n = len(msg)
l = 1.5*n
l -= 0.05*l
l = int(l)
print("n: ", n)
print("l: ", l)
codec = RSCodec(l - n - 4)
print("msg:", msg)
ret = bytes([3, 3, l-4, l-4]) + codec.encode(bytes(msg, "ASCII"))
print("ret:", ret)
return ret
Программа наземного приемника
Ниже представлено решение на языке Python.
#Содержимое https://github.com/tomerfiliba-org/reedsolomon/ blob/master/src/reedsolo/reedsolo.py
preambul = np.array([0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1], dtype=bool)
def check(len_chunk):
arr = np.packbits(len_chunk)
if arr[0] == arr[1]:
return int(arr[0])
return 0
def getMessage(chunk, c):
rsc = RSCodec(c)
arr = bytes(np.packbits(chunk))
try:
return rsc.decode(arr)[0]
except:
return None
def proceed(inp, buf, end):
li_orig = len(inp)
n = int(len(buf) / 10 / 8)
print("n recv:", n, "len buf", len(buf))
l = n * 1.5
l -= 0.05*l
l = int(l)
print("l recv:", l)
inp = np.concatenate([buf[-li_orig:], inp])
i = 16
li = len(inp)
while i < li:
if np.count_nonzero(inp[i-16:i] != preambul) > 2:
inp[i-16] = 0
i += 1
continue
if li - i < 16:
break
c = check(inp[i:i+16])
if c == 0:
inp[i-16] = 0
i += 1
continue
if li - i < c * 8 + 16:
break
inp[i:i+16] = np.zeros(16)
i += 16
msg = getMessage(inp[i:i+c*8], l - n - 4)
inp[i:i + c*8] = np.zeros(c*8)
i += c*8
if not msg:
continue
tl = max(-(li-i), -li_orig)
if tl != 0:
buf[tl:] = inp[tl:]
return str(msg, "ASCII")
buf[-li_orig:] = inp[-li_orig:]
return ""
Задача — обеспечить обзорную съемку с помощью малых космических аппаратов с камерой низкого разрешения части сибирской тайги с целью наблюдения за индексом вегетации. Используя два космических аппарата, обеспечьте полное покрытие указанной зоны съемкой аппаратами с мультиспектральными камерами в ИК-диапазоне с ПР (пространственным разрешением) не более 30 м. Такие условия можно обеспечить, если высота аппарата над зоной съемки не будет превышать 900 км и не будет находиться ниже 300 км. Ширина полосы съемки 30 км. Зона съемки: область, ограниченная 59° и 61° северной широты, 93° и 95° восточной долготы.
Зона разбивается по широте с севера на юг на полосы шириной 4 км, остаток отбрасывается. Дальше каждая полоса разбивается по долготе с запада на восток на ячейки \(4 \times 4\) км, остаток отбрасывается. Назовем узлом центр каждой такой ячейки. Узел считается снятым, если след аппарата в надире прошел от него не далее, чем на расстоянии половины ширины полосы съемки. «Вес» узла \(= 1/N\), где \(N\) — количество узлов в зоне интереса.
Старт работы КА на орбите начинается 01 июля 2025 года с 00:00 по UTC. Длительность симуляции — 24 часа.
Начальные данные:
- Радиус Земли, м: 6371008.8.
- Гравитационный параметр, \(\mu=G\cdot M\), м\(^3\)/с\(^2\): 3.986004418e14.
- Влияние атмосферы отключено. Прецессия орбиты отключена.
Начисление баллов:
- Начисляется 10/
grid.lengthбалла за прохождение над непройденным ранее узлом, гдеgrid.length— количество узлов в зоне съемки. - При снижении аппарата на высоту 300 км и менее баллы перестают начисляться.
- При подъеме аппарата на высоту 900 км и более баллы перестают начисляться.
Максимально возможный балл — 10. Первые четыре попытки идут без штрафа. За каждую следующую попытку максимально возможный балл уменьшается на 10% (т. е. на пятой попытке до 9 баллов, на шестой — до 8 и т. д.) до 1 балла и далее не уменьшается.
Первый аппарат:
- наклонение 90°;
- большая полуось 6775 км;
- эксцентриситет 0;
- долгота восходящего узла 201,6°;
- аргумент перицентра 0°;
- истинная аномалия 0°.
Второй аппарат:
- наклонение 90°;
- большая полуось 6775 км;
- эксцентриситет 0;
- долгота восходящего узла 200,6°;
- аргумент перицентра 0°;
- истинная аномалия 0°.
Перед тем как получить данные температуры с датчика, надо сначала изучить его документацию, узнать, какой интерфейс он использует, и какие данные можно получить.
Космический аппарат (КА) использует для измерения температуры датчик AMG8833. Нужно понять, по какому интерфейсу он общается с КА:
- Какие данные и по какому адресу необходимо записать в регистр управления питанием датчика, чтобы датчик был в режиме работы (
normal)? - Какие данные и по какому адресу необходимо записать в регистр сброса датчика, чтобы датчик перешел в первоначальное состояние?
- Какие данные и по какому адресу необходимо записать в регистр скорости работы датчика, чтобы датчик обновлял матрицу раз в секунду?
- С какого по какого адреса в памяти датчика надо считывать данные регистров, чтобы получить температуру с матрицы?
Как преобразовать данные для 1 пикселя, зная младшую и старшую часть значений пикселя?
Написать программу, которая преобразует младшую и старшую часть числа в температуру в градусах Цельсия, и при выходе за диапазон измерений надо возвращать NaN. В старшей части необходимо брать не больше 4 младших бит данных, а в младшей — не больше 8 младших бит данных, остальное необходимо обрезать и игнорировать.
- https://industrial.panasonic.com/cdbs/www-data/pdf/ADI8000/ADI8000C66.pdf;
- https://www.robot-electronics.co.uk/files/grideyeappnote.pdf.
Формат входных данных
Входные данные для программы — два целых положительных числа от 0 до 512, разделенные пробелом. Первое — старшая часть, второе — младшая часть.
Примеры:
- 20 410;
- 0 46;
- 120 0.
Формат выходных данных
Выходные данные — дробное число с точностью два знака после точки или NaN.
Примеры:
- 10.25;
- 30.00;
- \(-20.50\);
NaN.
- Адреса регистров должны быть указаны в 16-ричном формате, начиная с
0x. - 16-ричное число должно быть двухзначным. Например:
0xFF, 0x33, 0x6C. - Значения регистров должны быть указаны в 16-ричном формате, начиная с
0x. - 16-ричное число должно быть двухзначным. Например:
0xFF, 0x33, 0x6C.
- Баллы за правильный интерфейс — 1.
- Баллы за правильный адрес и значения регистра управления питанием — 1.
- Баллы за правильный адрес и значения регистра сброса — 1.
- Баллы за правильный адрес и значения регистра скорости работы — 1.
- Баллы за правильный первый и последний адрес регистров для считывания температуры матрицы с пикселей — 1.
- Баллы за преобразование данных начисляются в процентном соотношении пройденных тестов.
Всего баллов за тест — 5.
- Полный балл начисляется при прохождении 100% тестов.
- При прохождении меньше чем 100% теста, баллы начисляются как пятикратный процент пройденных тестов.
Всего баллов за программу — 5.
Всего баллов за задачу — 10.
Интерфейс: SPI, I2C, UART, OneWire.
Адрес регистра управления питанием: 0x00.
Значение регистра управления питанием: 0x00.
Адрес регистра сброса: 0x01.
Значение регистра сброса: 0x3F.
Адрес регистра скорости работы: 0x02.
Значение регистра скорости работы: 0x01.
Первый адрес регистра для считывания температуры с матрицы пикселей: 0x80.
Последний адрес регистра для считывания температуры матрицы с пикселей: 0xFF.
def convert(high: int, low: int) -> float:
pixel: int = (high & 0x0F) << 8 | low
temp: float
if pixel & 0x800 > 0:
temp = ((~pixel & 0x7FF) + 1) * -0.25
else:
temp = pixel * 0.25
if temp <= 80 and temp >= -0:
return temp
return float('NaN')
В рамках испытаний модуля приема-передачи данных для космических аппаратов две группы исследователей решили организовать спутниковую связь между научными центрами, расположенными в Ла-Кьяке \((-22{,1023}, 294{,40701})\) и Гонконге \((22{,28552}, 114{,15769})\). При этом Ла-Кьяка работает только на передачу, а Гонконг — только на прием.
Известно, что передаваемые данные являются случайным массивом байт типа Uint8Array размером 20 элементов. Также известно, что в процессе передачи сообщения между станцией в Ла-Кьяка и спутником пакет может быть поврежден сторонними помехами случайным образом. Приемопередающая аппаратура спутника не может функционировать нормально, если угловая скорость по оси \(X\) превышает 0,0003 рад/с.
Для корректной работы модуля приема-передачи данных необходимо свести начальную закрутку спутника до нуля, используя установленные на нем маховики.
Для успешного решения задачи необходимо:
- определить орбиту спутника связи (высота аппарата относительно центра Земли не может превышать 15000 км);
- стабилизировать его после начала работы на орбите (свести начальную угловую скорость по оси \(X\) к нулю);
- реализовать алгоритм работы спутника ретранслятора;
- написать программу для наземной станции в Ла-Кьяке по передаче сообщений на спутник;
- совершить успешную передачу 50000 пакетов.
Время начала моделирования: 1 декабря 2025 года 00:00 по UTC. Длительность моделирования: 24 ч.
Примечания
Модуль ориентации выдает скорость вращения спутника в градусах в секунду в двух байтах информации.
Знак \(q_{10}\) \(q_{9}\) \(q_{8}\) \(q_{7}\) \(q_{6}\) \(q_{5}\) \(q_{4}\) \(q_{3}\) \(q_{2}\) \(q_{1}\) \(q_{0}\) \(q_{-1}\) \(q_{-2}\) \(q_{-3}\) \(q_{-4}\) Знак \(2^{10}\) \(2^{9}\) \(2^{8}\) \(2^{7}\) \(2^{6}\) \(2^{5}\) \(2^{4}\) \(2^{3}\) \(2^{2}\) \(2^{1}\) \(2^{0}\) \(2^{-1}\) \(2^{-2}\) \(2^{-3}\) \(2^{-4}\) Первый байт — старшая часть, второй байт — младшая часть, данные записываются в дополнительном коде.
- Первые 320 с после вывода на орбиту отводятся на переходный процесс, в это время передача данных не происходит.
- На вход в программу для передающей наземной станции подается массив байт типа
Uint8Array, состоящий из 20 байт. Вывод программы передающей наземной станции направляется на передатчик. Спутник может принимать данные с помощью установленного на нем приемника. Во время работы спутник может отправить данные в виде массива байт типаUint8Arrayна передатчик. Эти данные принимает наземная станция в Гонконге. - Вероятность возникновения ошибки в пакете: 50%.
- Область памяти спутника, отведенная для хранения сообщений, ограничена 1 Мб.
- Область памяти наземных станций, отведенная для хранения данных, ограничена 1 Мб.
- Сообщение считается успешно принятым, только если оно полностью совпадает с сообщением, введенным в программу наземного передатчика.
- Все передатчики принимают данные на вход в виде массива байт типа
Uint8Array. - Скорость передачи между спутником и передающей станцией — 36 байт в 0,01 с.
- Скорость передачи на наземную станцию приема со спутника — 20 байт в 0,01 с.
Ссылка на API: https://disk.yandex.ru/i/lK8h9GH7OXc2bw.
Начальные данные:
- Координаты Ла-Кьяки: \(-22{,1023}, 294{,40701}\).
- Координаты Гонконга: \(22{,28552}, 114{,15769}\).
- Скорость передачи между спутником и наземными станциями: 20 байт в 0,1 с.
- Радиус Земли, м: 6371008,8.
- Гравитационный параметр, \(\mu=G\cdot M, \text{м}^3/\text{c}^3\): \(3{,}986004418\cdot 10^{14}\).
- Спутник считается прошедшим над указанной точкой, если он был виден из указанной точки в 60° над горизонтом или выше.
Правила начисления баллов:
- за каждый пакет без ошибок, переданный на наземную станцию, начисляется 0,0005 балла;
- за каждый пакет, содержащий ошибки, снимается 0,0005 балла, если он передан на наземную станцию.
Максимальный балл за задачу: 25.
Правила дисконтирования:
- Количество решений без штрафа — 3 на участника команды.
- За каждое последующее решение будет накладываться штраф 10% до убывания в 20%.
- Максимальная разница попыток между участниками команды — 3.
- При наличии ошибки в синтаксисе попытка не считается потраченной.
Орбита аппарата:
- наклонение: 22,28°;
- большая полуось: 14990 км;
- эксцентриситет: 0;
- долгота восходящего узла: 0°;
- аргумент перицентра: 0°;
- истинная аномалия: 0°.
let gyroGetter;
let transmitter;
let receiver;
var received_packet = Array.from([]);
function crc16_ccitt_false(data) {
let crc = 0xFFFF;
for (let i = 0; i < data.length; i++) {
crc ^= data[i] << 8;
for (let j = 0; j < 8; j++) {
if (crc & 0x8000) {
crc = (crc << 1) ^ 0x1021;
} else {
crc <<= 1;
}
}
}
return crc & 0xFFFF;
}
function check_packet(data) {
let crc = (data[data.length - 2] << 8) | data[data.length - 1];
let calculated_crc = crc16_ccitt_false(data.slice(0, -2));
return crc === calculated_crc;
}
function setup() {
gyroGetter = createAngularVelocityGetter(5);
transmitter = spacecraft.devices[0].functions[0];
receiver = spacecraft.devices[1].functions[0];
}
function loop() {
const motor = spacecraft.devices[4].functions;
const velocity = gyroGetter(spacecraft);
const velocityX = velocity[0];
motor[0].motor_torque += velocityX;
if (spacecraft.flight_time > 46350 && spacecraft.flight_time < 47949) {
let r = Array.from(receiver.receive(22));
if (r.length > 0){
if (check_packet(r)) {
received_packet.push(...r);
}
}
}
if (spacecraft.flight_time > 57240) {
if (received_packet.length > 0) {
let received_packet_n = received_packet.slice(0, 20);
received_packet.splice(0,22);
transmitter.transmit(new Uint8Array(received_packet_n));
}
}
}
function createAngularVelocityGetter(gyroId)
{
let lastResults = [0, 0, 0];
const convertFunc = (array) => {
if (array.length !== 2) { return undefined; }
const preResult = array[0] << 8 | array[1];
const result = preResult < 0x8000 ? preResult : -0x8000 + (preResult & 0x7FFF);
return result / 16;
};
return (spacecraft) => {
const gyro = spacecraft.devices[gyroId];
const functions = gyro.functions;
for (let i = 0; i < 3; i++)
{
const converted = convertFunc(functions[i].read(2));
if (converted !== undefined) { lastResults[i] = converted; }
}
return lastResults;
};
}
Программа передающей станции
let transmitter;
let receiver;
var crcTable = [0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5,
0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b,
0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210,
0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c,
0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401,
0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b,
0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6,
0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738,
0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5,
0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969,
0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96,
0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc,
0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03,
0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd,
0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6,
0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a,
0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb,
0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1,
0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c,
0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2,
0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb,
0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447,
0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8,
0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2,
0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9,
0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827,
0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c,
0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0,
0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d,
0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07,
0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba,
0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74,
0x2e93, 0x3eb2, 0x0ed1, 0x1ef0];
function crc16(s) {
var crc = 0xFFFF;
var j, i;
for (i = 0; i < s.length; i++) {
if (s[i] > 255) {
throw new RangeError();
}
j = (s[i] ^ (crc >> 8)) & 0xFF;
crc = crcTable[j] ^ (crc << 8);
}
let crcNum = ((crc ^ 0) & 0xFFFF);
return (new Uint8Array((new Uint16Array([crcNum])).buffer)).reverse();
}
function setup() {
transmitter = spacecraft.devices[0].functions[0];
receiver = spacecraft.devices[1].functions[0];
}
function loop() {
if (spacecraft.flight_time > 46350 && spacecraft.flight_time < 47949) {
let rx = receiver.receive(20);
let crcpart = crc16(rx);
let packet = new Uint8Array([...rx, ...crcpart]);
if(packet.length === 22){
transmitter.transmit(packet);
}
}
}
Целью группировки, состоящей из двух спутников, является сбор научных данных и их отправка на Землю. Один из спутников оснащен полезной нагрузкой, а второй — более развитой радиопередающей аппаратурой. Спутник, оснащенный мощной передающей аппаратурой, принадлежит компании товарища Каса и уже выведен на орбиту. Миссия научного спутника должна быть долгосрочной, а орбитальная скорость должна быть достаточно низкой для получения четких снимков Солнца, поэтому размещение его на низкой околоземной орбите исключено. Исходя из этого, для орбиты научного спутника выдвинуты следующие ограничения:
- снимок Солнца производится на высоте не менее 2000 км от поверхности Земли;
- научный спутник может передавать данные на расстояние, не превышающее 1500 км.
Орбита спутника-ретранслятора:
- большая полуось: 6771 км;
- эксцентриситет: 0;
- наклонение: 61,3°;
- ДВУ: 48°;
- аргумент перицентра: 0°;
- истинная аномалия на начало симуляции: 0°.
Для сбора научных данных используется специальная камера со светофильтром, которая может делать снимки Солнца. На одной грани вместе с камерой расположен матричный датчик Солнца, который работает постоянно и обновляет данные раз в 0,1 с.
Необходимо, анализируя данные датчика Солнца, определять, находится ли Солнце в поле видимости камеры или нет. Когда Солнце находится в поле видимости камеры, с нее необходимо запрашивать снимок, обрабатывать его и передавать по радиоканалу.
Требования к передаче данных:
- данные можно отправлять на наземные станции, расположенные в городах Анкоридж \(({61{,}2181}, -149{,}9)\), Мельбурн \((-37{,}814, 144{,}963)\) и Лиссабон \((38{,7167}\), \(-9{,13333})\);
- скорость передачи между спутниками и между спутником-ретранслятором и наземной станцией составляет 100 байт/c.
Время выполнения итерации функции loop соответствует 0,1 с симуляции.
Справка по радиосвязи
- Данные на передатчик всегда отправляются в байтах.
- Данные на приемник всегда передаются в битах.
- Все приемники и передатчики синхронизированы по всем параметрам, кроме оговоренных далее в условии.
- Приемники и передатчики могут быть рассинхронизированы по времени между отправкой и получением бита данных. Максимальная рассинхронизация может составить до 7 бит включительно.
- Данные между спутниками передаются без помех.
- Данные между спутником-ретранслятором и наземной станцией подвержены помехам.
Справка по датчику Солнца
Строение датчика соответствует датчику в индивидуальной задаче программиста ПН. А именно, датчик возвращает координаты пикселя матрицы, на который попало больше всего света.
- Размер матрицы: \(22{,}3 \times 14{,}9\) мм.
- Размер пикселя: 0,025 мм.
На расстоянии 5 мм от матрицы установлена металлическая пластина с небольшим отверстием точно по центру.
В качестве данных датчик возвращает два числа \([x; y]\) — в системе координат матрицы. Если все пиксели освещены одинаково (Солнце не в поле зрения), датчик вернет \([-1; -1]\).
Координаты пикселей считать от левого верхнего края: ось \(Y\) — длинная сторона матрицы (направлена вправо из левого верхнего края матрицы); ось \(X\) — короткая (направлена вниз из левого верхнего края матрицы).
Ноль системы координат датчика считать центром металлической пластины, оси \(X\) и \(Y\) сонаправлены с осями координат матрицы, ось \(Z\) дополняет систему до правой тройки. При расчете считать, что свет попадает точно в центр пикселя.
Справка по камере научного спутника
Угол обзора камеры — 30°, смещением датчика Солнца относительно оси камеры пренебречь.
Камера выдает сырые данные, представленные массивом из 1600 байт (каждый байт соответствует пикселю матрицы \(40\times 40\)). Чтобы получить сжатый снимок, необходимо произвести биннинг пикселей — разбить матрицу на квадраты, содержащие по 4 пикселя, и объединить пиксели внутри каждого квадрата в один, присвоив ему среднее арифметическое значение. На выходе получится массив из 400 байт, где каждый байт — среднее арифметическое по соответствующему квадрату.
Формат передачи снимка с камеры представлен следующей структурой: array_data[1600].
Данные представлены типом Uint8Array.
Формат входных данных
- Данные датчика Солнца генерируются по запросу.
- Снимок с камеры научного спутника генерируется по запросу.
- Время начала моделирования: 1 декабря 2025 года 00:00 по UTC.
- Длительность моделирования: 24 ч.
- Спутник считается прошедшим над указанной точкой, если он был виден из указанной точки в 10° над горизонтом или выше.
Ссылка на API: https://disk.yandex.ru/i/muLu7QyEAd7uLQ.
Правила начисления баллов:
- За каждую корректно обработанную фотографию и рассчитанный для нее угол (в градусах) отклонения Солнца от главной оптической оси датчика начисляется 0,0036 балла, если они помещены в хранилище на научном спутнике.
- Максимум за обработку показаний датчика Солнца и изображений с камеры начисляется 15 баллов.
- За каждый правильно переданный на наземную станцию пакет начисляется 0,096 балла. Для начисления баллов пакеты необходимо передавать по одному в исходном виде (сжатый снимок с камеры в формате
Uint8Array[400]). - Максимум за передачу пакетов начисляется 20 баллов.
Максимальный балл за задачу: 35.
Правила дисконтирования:
- Количество решений без штрафа — 3 на участника команды.
- За каждое последующее решение будет накладываться штраф 10% до убывания в 20%.
- Максимальная разница попыток между участниками команды — 3.
- При наличии ошибки в синтаксисе попытка не считается потраченной.
Орбита аппарата:
- наклонение: 61,3°;
- большая полуось: 10000 км;
- эксцентриситет: 0,33;
- долгота восходящего узла: 48°;
- аргумент перицентра: 0°;
- истинная аномалия: 0°.
let transmitter;
let receiver;
function setup() {
transmitter = spacecraft.devices[0].functions[0];
receiver = spacecraft.devices[1].functions[0];
}
function loop() {
const bits = receiver.receive(80);
let bytes = new Uint8Array(10);
for (let i = 0; i < 10; i++) {
bytes[i] = parseInt(Array.from(bits.slice(i*8, (i+1)*8)).map(el => el ? '1' : '0').join(''), 2);
}
transmitter.transmit(bytes);
}
Программа управления научным спутником const setConfig = ({ pow } = { pow: 4 }) => {
if (isNaN(pow)) {
throw new Error('please pass number');
}
if (pow < 4) {
throw new Error(`argument need > 4, now is ${pow}`);
}
const BlockLength = Math.pow(2, pow);
const ContainerLength = BlockLength - pow - 1;
const position = [0];
for (let i = 0; i < pow; i++) {
position.push(1 << i);
}
const xor = (array) => (array.length ? array.reduce((one, two) => one ^ two) : 0);
const handlePosition = (index) => position.includes(index);
const hammingList = (str) => {
if (str.length > ContainerLength) {
const message = `${str} length is too long, max ${ContainerLength}`;
throw new Error(message);
}
const hammingBlock = [];
const trueIndexList = [];
const strLength = str.length;
for (let i = 0, j = 0; j < strLength; i++) {
if (handlePosition(i)) {
hammingBlock[i] = 0;
} else {
if (str[j] && +str[j] !== 0) {
trueIndexList.push(i);
}
hammingBlock[i] = str[j] || 0;
j++;
}
}
const xorData = `${xor(trueIndexList).toString(2)}`.padStart(pow, 0);
for (let i = 0, xorLength = xorData.length; i < xorLength; i++) {
const position = Math.pow(2, i);
if (+xorData[xorLength - i - 1]) {
hammingBlock[position] = xorData[xorLength - i - 1];
}
}
hammingBlock[0] = xor(hammingBlock);
return hammingBlock;
};
const decodeHammingList = (str) => {
const numList = str.split('');
const totalCorrect = !xor(numList);
const trueList = [];
for (let i = 1; i < numList.length; i++) {
if (numList[i] && +numList[i]) {
trueList.push(i);
}
}
const xorData = xor(trueList);
let correct = false;
if (xorData === 0 && totalCorrect) {
correct = true;
}
if (xorData !== 0 && totalCorrect) {
correct = false;
}
if (xorData !== 0 && !totalCorrect) {
correct = true;
numList[xorData] = Number(!+numList[xorData]);
}
const code = [];
for (let i = 0; i < numList.length; i++) {
if (!handlePosition(i)) {
code.push(numList[i]);
}
}
return {
correct,
code: code.join(''),
originCode: str,
};
};
const sliceContainer = (data, lengh) => {
const result = [];
for (let i = 0, len = data.length; i < len; i += lengh) {
result.push(data.slice(i, i + lengh));
}
return result;
};
const encode = (str) => {
const sliceArray = sliceContainer(str, ContainerLength);
return sliceArray
.map((item) => hammingList(item))
.flat()
.join('');
};
const decode = (str) => {
const sliceArray = sliceContainer(str, BlockLength);
const decodeResult = sliceArray.map((item) => decodeHammingList(item));
const res = decodeResult.reduce((one, two) => {
return {
correct: one.correct && two.correct,
code: one.code + two.code,
originCode: one.originCode + two.originCode,
};
});
return {
...res,
decodeResult,
};
};
return {
encode,
decode,
};
}
const hamming = setConfig();
function bytes2bits(bytes) {
function bin(num) {
let payload = num.toString(2);
return "0".repeat(8 - payload.length) + payload;
}
return Array.from(bytes).map(bin).join("");
}
function bits2bytes(bits) {
let aligned = bits + "0".repeat(bits.length % 8);
let rezBuf = new Uint8Array(aligned.length / 8);
for (let i = 0; i < rezBuf.length; i++) {
rezBuf[i] = parseInt(aligned.slice(i*8, (i+1)*8), 2);
}
return rezBuf;
}
function proceed_message(msg) {
const bits_msg = bytes2bits(msg);
const encoded = hamming.encode(bits_msg);
const encoded_bytes = bits2bytes(encoded);
return encoded_bytes;
}
function decode_message(msg, bitslen) {
const bits_msg = bytes2bits(msg).slice(0, bitslen);
const decoded = hamming.decode(bits_msg);
const decoded_bytes = bits2bytes(decoded.code);
return decoded_bytes;
}
function encode(str) {
return new Uint8Array(Array.from(str).map(el => el.charCodeAt(0)));
}
let camera;
let sun_sensor;
let storage;
let sun_angle;
let transmitter;
let receiver;
let pixelX;
let pixelY;
function uint8ArrayToNums(arr) {
pixelX = 0;
pixelY = 0;
for (let i = 7; i >= 0; i--) {
pixelX = pixelX * 256 + arr[i];
pixelY = pixelY * 256 + arr[i+8];
}
}
function setup() {
transmitter = spacecraft.devices[0].functions[0];
receiver = spacecraft.devices[1].functions[0];
camera = spacecraft.devices[2].functions[0];
sun_sensor = spacecraft.devices[3].functions[0];
storage = spacecraft.devices[4].functions[0];
}
function loop() {
let pixels = sun_sensor.read(16);
let image;
let clear_image;
uint8ArrayToNums(pixels);
if(calculateSunDirection(pixelX, pixelY)){
image = camera.read(1600);
clear_image = removeNoise(image);
storage.write(clear_image);
let float = new Uint8Array(new Float32Array([sun_angle]).buffer);
storage.write(float);
const payload = proceed_message(clear_image);
let msg = new Uint8Array(2 + payload.length);
msg[0] = 3;
msg[1] = 3;
msg.set(payload, 2);
transmitter.transmit(msg);
}
}
function calculateSunDirection(pixelX, pixelY) {
if( pixelX < 0 || pixelY < 0 || pixelX > 596 || pixelY > 892){
return false;
}
const pixelSize = 0.025;
const zDistance = -5;
const xMid = (14.9 / 0.025) / 2;
const yMid = (22.3 / 0.025) / 2;
const deltaX = (pixelX - 0.5 - xMid) * pixelSize;
const deltaY = (pixelY - 0.5 - yMid) * pixelSize;
const vector = {
x: -deltaX,
y: -deltaY,
z: -zDistance
};
const vectorLength = Math.sqrt(vector.x ** 2 + vector.y ** 2 + vector.z ** 2);
const unitVector = {
x: vector.x / vectorLength,
y: vector.y / vectorLength,
z: vector.z / vectorLength
};
const referenceVector = {
x: 0,
y: 0,
z: 1
};
const dotProduct = unitVector.x * referenceVector.x + unitVector.y * referenceVector.y + unitVector.z * referenceVector.z;
sun_angle = Math.acos(dotProduct) * (180 / Math.PI);
if (sun_angle > 30) {
return false;
} else {
return true;
}
}
function removeNoise(array){
const width = 20;
const heigth = 20;
const noisedWidthPixelOnReal = 2;
const noisedHeigthPixelOnReal = 2;
const noisedWidth = width * noisedWidthPixelOnReal;
const noisedHeigth = heigth * noisedHeigthPixelOnReal;
const realImageSize = width * heigth;
const result = new Uint8Array(realImageSize);
for (let i = 0; i < realImageSize; i++)
{
const row = i % width;
const column = Math.floor(i / width);
const newRow = row * noisedWidthPixelOnReal;
const newColumn = column * noisedHeigthPixelOnReal;
const pixels = [array[newRow + (newColumn + 0) * noisedWidth + 0],
array[newRow + (newColumn + 0) * noisedWidth + 1],
array[newRow + (newColumn + 1) * noisedWidth + 0],
array[newRow + (newColumn + 1) * noisedWidth + 1]];
result[i] = pixels.reduce((partialSum, a) => partialSum + a, 0) / 4;
}
return result;
}
Программа принимающей станции const setConfig = ({ pow } = { pow: 4 }) => {
if (isNaN(pow)) {
throw new Error('please pass number');
}
if (pow < 4) {
throw new Error(`argument need > 4, now is ${pow}`);
}
const BlockLength = Math.pow(2, pow);
const ContainerLength = BlockLength - pow - 1;
const position = [0];
for (let i = 0; i < pow; i++) {
position.push(1 << i);
}
const xor = (array) => (array.length ? array.reduce((one, two) => one ^ two) : 0);
const handlePosition = (index) => position.includes(index);
const hammingList = (str) => {
if (str.length > ContainerLength) {
const message = `${str} length is too long, max ${ContainerLength}`;
throw new Error(message);
}
const hammingBlock = [];
const trueIndexList = [];
const strLength = str.length;
for (let i = 0, j = 0; j < strLength; i++) {
if (handlePosition(i)) {
hammingBlock[i] = 0;
} else {
if (str[j] && +str[j] !== 0) {
trueIndexList.push(i);
}
hammingBlock[i] = str[j] || 0;
j++;
}
}
const xorData = `${xor(trueIndexList).toString(2)}`.padStart(pow, 0);
for (let i = 0, xorLength = xorData.length; i < xorLength; i++) {
const position = Math.pow(2, i);
if (+xorData[xorLength - i - 1]) {
hammingBlock[position] = xorData[xorLength - i - 1];
}
}
hammingBlock[0] = xor(hammingBlock);
return hammingBlock;
};
const decodeHammingList = (str) => {
const numList = str.split('');
const totalCorrect = !xor(numList);
const trueList = [];
for (let i = 1; i < numList.length; i++) {
if (numList[i] && +numList[i]) {
trueList.push(i);
}
}
const xorData = xor(trueList);
let correct = false;
if (xorData === 0 && totalCorrect) {
correct = true;
}
if (xorData !== 0 && totalCorrect) {
correct = false;
}
if (xorData !== 0 && !totalCorrect) {
correct = true;
numList[xorData] = Number(!+numList[xorData]);
}
const code = [];
for (let i = 0; i < numList.length; i++) {
if (!handlePosition(i)) {
code.push(numList[i]);
}
}
return {
correct,
code: code.join(''),
originCode: str,
};
};
const sliceContainer = (data, lengh) => {
const result = [];
for (let i = 0, len = data.length; i < len; i += lengh) {
result.push(data.slice(i, i + lengh));
}
return result;
};
const encode = (str) => {
const sliceArray = sliceContainer(str, ContainerLength);
return sliceArray
.map((item) => hammingList(item))
.flat()
.join('');
};
const decode = (str) => {
const sliceArray = sliceContainer(str, BlockLength);
const decodeResult = sliceArray.map((item) => decodeHammingList(item));
const res = decodeResult.reduce((one, two) => {
return {
correct: one.correct && two.correct,
code: one.code + two.code,
originCode: one.originCode + two.originCode,
};
});
return {
...res,
decodeResult,
};
};
return {
encode,
decode,
};
};
const hamming = setConfig();
function bytes2bits(bytes) {
function bin(num) {
let payload = num.toString(2);
return "0".repeat(8 - payload.length) + payload;
}
return Array.from(bytes).map(bin).join("");
}
function bits2bytes(bits) {
let aligned = bits + "0".repeat(bits.length % 8);
let rezBuf = new Uint8Array(aligned.length / 8);
for (let i = 0; i < rezBuf.length; i++) {
rezBuf[i] = parseInt(aligned.slice(i*8, (i+1)*8), 2);
}
return rezBuf;
}
function decode_message(msg, bitslen) {
const bits_msg = bytes2bits(msg).slice(0, bitslen);
const decoded = hamming.decode(bits_msg);
const decoded_bytes = bits2bytes(decoded.code);
return decoded_bytes;
}
var receiver;
var consumer;
let buffer = new Uint8Array(80+16);
let offset = -1;
function setup() {
receiver = spacecraft.devices[0].functions[0];
consumer = spacecraft.devices[1].functions[0];
}
const preambul = new Uint8Array([0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1]);
const bytelen = 582;
const bitslen = 4655;
let debugi = 0;
function capture(off) {
let index = 0;
let buff = new Uint8Array(bytelen);
for (let i = off; i < 80; i+=8) {
const bytearr = buffer.slice(i, i+8);
buff[index] = parseInt(Array.from(bytearr).map(el => el ? '1' : '0').join(""), 2);
index++;
}
return () => {
for (let i = offset; i < 80; i+=8) {
const bytearr = buffer.slice(i, i+8);
buff[index] = parseInt(Array.from(bytearr).map(el => el ? '1' : '0').join(""), 2);
index++;
if (index === bytelen) {
proceed = findPreambul;
const msg = decode_message(buff, bitslen);
consumer.transmit(msg);
break;
}
}
};
}
function check(input) {
let rez = 0;
for (let i = 0; i < 16; i++) {
if (input[i] !== preambul[i]) {
rez++;
}
}
return rez;
}
function findPreambul() {
let minrez = [-1,0];
for (let off = 0; off < 80; off++) {
let rez = check(buffer.slice(off,16+off));
if (rez <= minrez[1]) {
minrez = [off, rez];
}
}
if (minrez[0] !== -1) {
offset = minrez[0] % 8;
proceed = capture(minrez[0] + 16);
}
}
let proceed = findPreambul;
function loop() {
const bits = receiver.receive(80);
buffer.set(buffer.slice(-16));
buffer.set(bits, 16);
proceed();
debugi++;
}
