Ad-social Bot

Smmok Bot

Vkserfing Bot

Vkstorm Bot

Vktarget Bot

Все программы

Запись опубликована: 25.04.2024

8 негативных фраз, которые пора перестать говорить себе

[ comments ]

Чем больше вы внушаете себе, что у вас ничего не получится, тем выше вероятность, что так и случится.
Мужики

1. «Я идиот»

В третий раз переделываете важный проект? Не получается разобраться, что написано в учебнике? Решили заняться программированием на Python, но голова пошла кругом от новой информации? В такие моменты нетрудно усомниться в своих интеллектуальных способностях и наградить себя какими‑нибудь обидными эпитетами.

Но вместо того чтобы ругать себя попробуйте использовать более бережные фразы. Например, «У меня есть сильные и слабые стороны. Программирование — это и правда сложно. Придётся уделить этому больше времени и сил». Иначе вы действительно со временем убедите себя, что не блещете умом — и таким образом отрежете себе путь к интересным проектам и новым знаниям.

2. «Я неудачник! У меня ничего не получается»

Обычно мы говорим так, когда устали и мир предстаёт в очень мрачных красках. В такие моменты достаточно одной, последней, мелочи, чтобы опустить руки и воскликнуть: «Ну почему мне вечно не везёт

Однако это очень общая и категоричная фраза, и за ней обычно нет никаких существенных фактов.

Попробуйте заменить подобные выражения на более нейтральный вариант: «Да, в моей жизни есть и взлёты, и падения. Но я делаю ровно столько, сколько могу, и так хорошо, как могу в данный момент».

3. «Я сам во всём виноват»

Иногда мы пытаемся спихнуть ответственность на других, а иногда впадаем в другую крайность и начинаем винить себя во всех своих, а заодно и чужих, бедах. Это неконструктивно и может надолго испортить настроение и разрушить мотивацию. Попробуйте сказать: «В том, что случилось, есть моя роль. Но я несу ответственность только за свои поступки и решения, а не за всю ситуацию в целом».

4. «Они наверняка думают, что я…»

Ох уж это наше вечное заблуждение — считать себя центром мироздания и думать, что всех вокруг очень заботит, как мы выглядим, что говорим и делаем. Всё это, конечно, идёт от неуверенности в себе: мы, по сути, приписываем другим собственные мысли.

То есть это не ваши одноклассники на встрече выпускников думают, что вы неудачник, а вы сами себя таковым считаете.

И даже если какие‑то посторонние люди действительно от вас не в восторге, это ещё ничего не значит. Так что замените тревожное «Они думают, что я…» на вот такую формулировку: «Они могут думать что угодно, это их право. Но их мнение — только их мнение, обо мне оно ничего не говорит».

5. «Я лентяй и прокрастинатор»

С каждым случалось: собирался поработать, зашёл в интернет поискать важную информацию, ссылка за ссылкой — и вот прошло три часа, а ты читаешь о пластических операциях Ким Кардашьян или смотришь документальный фильм о рыбе‑капле.

После этого на любого обрушится чувство вины: как же так, я должен был делать что‑то полезное, а вместо этого… я ленивый, унылый и пассивный, я никогда ничего не добьюсь. Только вот от такого самобичевания никому не станет лучше.

Именно чувство вины — одна из причин затяжной прокрастинации. Мы тратим время впустую, потом виним себя в этом и считаем, что день уже испорчен и приниматься за дела бессмысленно. Поэтому неконструктивные утверждения лучше заменить чем‑то вроде «Сегодня просто такой день, мне нужно было отдохнуть. И завтра я наверстаю упущенное».

6. «У меня никогда ничего не выйдет!»

Все, конечно, мечтают о светлом, ярком и комфортном будущем. Но верить в это не всегда просто — особенно если со всех сторон сыплются неудачи. В голову сразу начинают закрадываться упаднические мысли: «Я никогда ничего не добьюсь, ни в чём не преуспею и умру в нищете».

Вероятность, что так всё и случится, будет куда выше, если вы продолжите себя ругать.

Учёные опрашивали 117 атлетов, каждому из которых давали инструкции о том, как вести внутренний диалог. Часть участников давали себе инструкции, никак не окрашенные эмоционально, спортсмены из второй группы старались себя мотивировать. Третья группа хвалила себя, четвёртая ругала и запугивала. Показатели не слишком отличались друг от друга, но атлеты из первых трёх групп всё же продемонстрировали более высокие спортивные результаты и были более уверены в себе, чем те, кто себя критиковал.

Если очень хочется отчаяться и посомневаться в себе, можно сделать это в более щадящей форме: «Да, я понимаю, что меня может ждать провал. Но это не повод не пробовать. Я в любом случае вынесу из этой истории ценный опыт».

7. «Я упустил такую возможность! А ведь мог немного постараться!»

От нас порой уплывают впечатляющие заработки, интересные предложения и полезные знакомства. Иногда мы сами в этом виноваты, а иногда так складываются обстоятельства. Но прежде чем погружаться в сожаления, вспомните, что неудачи случаются абсолютно со всеми.

К примеру, когда-то в соцсетях был популярен хештег #меняневзяли. Под ним самые разные люди, даже успешные и знаменитые, рассказывали, как потерпели неудачу при поступлении в вуз, приёме не работу или во время важных переговоров.

Поэтому, прежде чем вздыхать об упущенном и терзать себя, попробуйте выразить эту мысль по‑другому: «Здесь у меня не получилось. Поэтому я немного погорюю, а потом проанализирую свои ошибки и буду над ними работать». А ещё можно вспомнить, что случилось в вашей жизни благодаря «провалу». Например, если бы вас взяли на работу мечты, вы бы не устроились в маленькую компанию и не встретили там свою половину.

8. «У них всегда всё получается. Не то что у меня…»

Интересно, есть ли в мире хоть один человек, которого не сравнивали с другими с самого детства? Петя уже съел кашу, а ты нет. Маша пятёрку получила, а ты тройку. Все твои одноклассницы уже замужем, а ты так и просидишь одна.

Само собой, мы привыкаем, что вокруг всё время есть какие‑то Маши и Пети, которые априори лучше нас. И настойчиво сравниваем себя с ними, надеясь, что ни в чём им не уступаем. И, конечно, часто проигрываем сравнение, потому что у кого‑то трава обязательно окажется зеленее.

Вместо того чтобы завидовать другим и жестоко ругать себя, посмотрите на ситуацию под другим углом: «Он хорошо постарался, и вот к чему он пришёл. Мне есть чему у него поучиться». В мире хватит успеха, денег и любви для каждого из нас.

Аника Турчан / Лайфхакер

[ comments ]

Метки:





Запись опубликована: 25.04.2024

Обратный инжиниринг дешевой мини видеокамеры из семейства А9

Обновить

sergios52 2 часа назад

Уровень сложностиПростой
Время на прочтение21 мин

Количество просмотров365

Туториал

В данной статье речь пойдет о Wi-Fi мини видеокамере из семейства А9 от китайских производителей. Цель исследования этих камер – расширить возможность их применения, которая ограничена использованием только стандартных приложений для мобильных устройств на базе Android или iOS.

О камерах семейства А9

Стоимость данных видеокамер варьируется от 2$ до 4$ (в зависимости от производителя, типа процессора, наличия ИК подсветки и др.). На известной экспресс торговой площадке её можно найти по запросу «мини-камера A9». Ссылка на англоязычный форум по теме разбора камер А9.

Внешний вид моих подопытных камер представлен на фотографии ниже (и, да, внутри они немного разные).

Внешний вид камер А9 от разных производителей (показаны без элементов крепления)

Внешний вид камер А9 от разных производителей (показаны без элементов крепления)

Типовая видеокамера А9 имеет встроенный аккумулятор, а внешнее питание подается через разъем microUSB. Она может обеспечивать два режима работы с сетью: как точка доступа (AP), и как клиент сети (STA). Режим работы настраивается через стандартное приложение на смартфоне. По умолчанию всегда включен режим АР. IP адрес для камеры в режиме АР может отличаться в зависимости от производителя, у моих подопытных – 192.168.1.1. При проведении исследований этих камер использовалось приложение FtyCamPro рекомендованное производителем, хотя в «интернетах» пишут, что работают эти камеры и с более известным приложением Little Stars. Пользовательский обзор в достаточном объеме представлен на YouTube по запросу «Подключение китайской камеры А9».

Рассматриваемые здесь дешевые видеокамеры построены на основе процессора TXW806-840. Ссылка на сайт производителя чипа TWX806 (только китайский язык) https://www.taixin-semi.com/. В продаже имеются также и аналоги с процессором BK7231, которые несколько дороже. Электронная часть камеры выглядит вот так:

Плата одной из моих видеокамер с обеих сторон

Плата одной из моих видеокамер с обеих сторон

Структурная схема процессора, взятая из официального даташита показана ниже.

Структурная схема SoC TXW806

Структурная схема SoC TXW806

Согласно данным от производителя TXW806 — это высокоинтегрированный небольшой многорежимный чип для IoT с частотой 2,4 ГГц. Чип включает в себя 32-битный микроконтроллер, встроенный MJPEG (поддерживает VGA/720P), имеет DVP интерфейс, высокоскоростной хост USB2.0, хост SDMMC, ведомое устройство SDIO2.0, интерфейс RMII MAC, ведущее устройство SPI, UART, IIC, IIS, IR Send/Receive, PWM, GPIO и ADC/DAC, поддерживают запуск программ на SPI Flash. Базовый модуль Wi-Fi TXW806 реализует технологию мультиплексирования с ортогональным частотным разделением каналов (OFDM), обратно совместима с технологией расширения спектра прямой последовательностью (DSSS), дополнительной кодовой манипуляцией (CCK) и поддерживает протокол IEEE 802.11 b/g/n. Wi-Fi поддерживает стандартную полосу пропускания 20 МГц и узкую полосу пропускания 5 МГц/10 МГц, обеспечивая скорость физического уровня 72,2 Мбит/с. Имеет усилитель мощности, малошумящий усилитель LNA, радиочастотный балун, антенный переключатель, модуль управления питанием и т. д. Поддерживает RTOS и сторонние операционные системы, а также предоставляет открытую и простую в использовании среду разработки и отладки.

Используется этот чип в основном для беспроводных аудио и видео устройств, аэрофотосъемки, видео глазков для домофонов и т.п. Богатый функционал, за такую стоимость, не правда ли? От себя могу похвалить этот чип за его стрессоустойчивость, которую он проявил во время пыток его паяльником и тестером.

Безоперационное определение сознания камеры

До препарирования камеры было проведено сканирование доступных портов утилитой nmap в Ubuntu, при этом камера была подключена к роутеру в режиме STA. По результатам сканирования, были определены открытые порты камеры:

PORT

STATE

SERVICE

68/udp

open|filtered

dhcpc

7070/tcp

open

rtsp/realserver

32108/udp

open|filtered

unknown

7070/tcp open rtsp
| fingerprint-strings:
| RTSPRequest:
| RTSP/1.0 200 OK
|_ Public: DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE

Из доступных для чтения видеопотока оказался порт 7070 по которому предоставляется потоковое видео и аудио по RTSP. Первые попытки получить видео посредством ПО работающего с протоколом RTSP не принесли успеха. Для этого использовались VLC player и openRTSP под Ubuntu (http://www.live555.com/openRTSP/).
На запрос: /home/live/testProgs# ./openRTSP ‑T 7070 rtsp://192.168.1.1:7070
Камера отвечала:

Hidden text

Created new TCP socket 3 for connection
Connecting to 192.168.1.1, port 7070 on socket 3…
…remote connection opened
Sending request: OPTIONS rtsp://192.168.1.1:7070 RTSP/1.0
CSeq: 2
User-Agent: ./openRTSP (LIVE555 Streaming Media v2023.11.30)
Received 76 new bytes of response data.
Received a complete OPTIONS response:
RTSP/1.0 200 OK
CSeq: 2
Public: DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE
Sending request: DESCRIBE rtsp://192.168.1.1:7070 RTSP/1.0
CSeq: 3
User-Agent: ./openRTSP (LIVE555 Streaming Media v2023.11.30)
Accept: application/sdp
Received 35 new bytes of response data.
Received a complete DESCRIBE response:
RTSP/1.0 404 Not Found
CSeq: 3

Итогом такого общения был ответ от камеры:
Failed to get a SDP description for the URL «rtsp://192.168.1.1:7070»: 404 Not Found
При использовании VLC результат был примерно такой же.

Еще отмечу, что камера в режиме STA интенсивно соединялась со сторонними серверами по UDP, IP которых: 170.106.50.82, 146.56.226.66 и 35.156.204.247. Это общение пришлось закрыть фаерволом, на всякий случай. Кстати, фаервол был включен на эти IP уже после тестирования с помощью утилиты openRTSP, на случай если при отсутствии соединения с этими серверами камера будет уходить в глухую оборону и в полное молчание.
P.S. Печально конечно, но IP адреса сторонних серверов потом меняются.

Вскрытие

Следующим шагом исследования было вскрытие корпуса и привлечение паяльника. На фотографиях ниже представлены PCB двух моих камер. Слева уже распаянная для работы, справа – новая, не тронутая. Обратите внимание на контактные площадки.

Контрольные (тестовые) контактные площадки на плате подопытных камер

Контрольные (тестовые) контактные площадки на плате подопытных камер

Да, и давайте камеру слева назовем FTYB, а камеру справа – BATC. Не спрашивайте почему. Дальше, надеюсь, будет понятно.

Контакты на плате слева имеют следующие обозначения: RX_PA9, TX_PA10, PA8, GND и CEN (chip enable). На плате справа аналогичные контакты обозначены несколько иначе: HCK, без обозначения, HDA, GND и nRST. В даташите на процессор TXW806 есть следующая информация:

Обозначение вывода

Тип

Назначение

PA8

I/O

LEDTMR2_PWM_OUT, ADKEY1_N0, ADKEY0_P1, ADKEY1_P0, TK8, LED_SEG4, QSPI_CLK

PA9

I/O

ADKEY0_P1, ADKEY1_P0, TK9, LED_SEG9, QSPI_IO2

PA10

I/O

ADKEY0_P1, ADKEY1_P0, TK10, LED_SEG8, QSPI_IO1

CHIP_EN

I

Chip enable:0:Chip off,   1:Chip enable

И таблица о порядке подключения чипа к программатору:

Кроме того, производитель сообщает, что имеется два интерфейса отладки: PA9 (DebugIO) и PA10 (DebugCLK) с внутренним сопротивлением подтягивающего резистора 10 кОм. Когда функция отладки не используется, интерфейс отладки можно использовать как обычный GPIO, и его необходимо настроить с помощью программного обеспечения. Во время компоновки печатной платы должны быть зарезервированы контрольные точки на плате, чтобы облегчить отладку. В то же время PA8, CHIP_EN также необходимо зарезервировать для вспомогательного тестирования.

С помощью переходника USB-TTL (в моем случае на микросхеме CP2102) камера была подключена к последовательному порту компьютера. Кстати! Контакт TX на плате это PA8, а на другой камере TX это HDA! Общение с платой осуществлялось посредством Putty с настройкой COM порта на скорость 1 000 000 (106) бот, все остальные параметры по умолчанию. Было задействовано три точки: GND, PA8 и PA9. Скорость взаимодействия определена опытным путем. На стандартной максимальной скорости 512 000 бот и менее в терминал сыпались нечитаемые знаки. Думаю, что весь лог загрузки приводить не целесообразно, здесь акцентирую внимание лишь на некоторых моментах. В логах загрузки и работы камеры содержатся наименование сборки и дата сборки ПО, наименование клиента (станции), пароль, наименование прошивки, калибровочные параметры для видео сенсора, настройки для АЦП, для TF (SD) карты памяти (пытается ее найти, создать там рабочие папки для записи видео и фото), настройки сети Wi-Fi (MAC, FLAGS, UP LINK_UP ETHARP IGMP, ip address 192.168.1.1, gateway, net mask), информация о состоянии системы (температура чипа, рабочая частота, свободная память, работающие процессы и уровни загрузки ими ЦП), например:

• начало загрузки, с наименованием сборки ПО выглядит так:

Hidden text

** hgSDK-v2.2.0.7-22619, app-0, build time: Apr 13 2023 10:15:54 **
 ——- system restart fault ————
 ——- lvd fault ————
 —————————————
[1]time=0, tick=1

get flash addr:fd000 size:4096,
[9]col_flag:1
[9]get_parse_cloud_id:126
[10]selfId:FTYB931188RLTOV:15
[10]col_flag:1
[10]col_flag:2
[10]get_parse_cloud_id: 126
[11]parId: WRAWEJ: 6
[IpcCfgInit][ 448] 1:616 —> get from  ezConfig FTYB931188RLTOV:ZZZZZ:
[IpcCfgInit][ 462] 0 —> get from  ezConfig FTYB931188RLTOV:ZZZZZ:
<— FTYB931188RLTOV – имя камеры и имя точки доступа

• сообщение о разрешении матрицы сенсора:

Hidden text

[148] SENSR ident ok: 640*480
[148] csi set size ====>640*480

•  информация о том, что открытым является порт 7070:

Hidden text

t_s> spook init
listening on tcp port 7070
port:7070       fd:3
jpeg set_output
live start_block
live set_path /webcam
      <‑- /webcam – имя папки для работы по протоколу rtsp!
live set_track jpeg_dvp
jpeg get_framerate
jpeg set_running: 1
live end_block
len:1200

Соответственно, полный путь к устройству для приема потокового видео будет rtsp://192.168.1.1:7070/webcam
• данные о соединении с сетью:

Hidden text

[sys_wifi_init][ 419] CONFIG_UMAC4 —- yes
——-ssid: FTYB931188RLTOV, key: 12345678
[52] lmac_bgn_lo_freq_set: 2457
[54] vif2 state WPA_DISCONNECTED -> WPA_COMPLETED

network interface: w0 (Default)
MTU: 1600
MAC: d8 83 32 8e a8 24
FLAGS: UP LINK_UP ETHARP IGMP
ip address: 192.168.1.1
gw address: 192.168.1.1
net mask: 255.255.255.0
network interface: lo
MTU: 0
MAC:
FLAGS: UP LINK_UP
ip address: 127.0.0.1
gw address: 127.0.0.1
net mask: 255.0.0.0

• данные о состоянии системы:

Hidden text

local:d8:83:32:8e:a8:24
    chip-temperature: 28
    freq:2457, bg_rssi:-73
    gain_table:0
    cca: -63, -53, -55
    tx: txq:0, ps:0, tx_stat_q:0,
        tx dma:12, total tx:12, retry:32, tx lost:3, tx err:0
    rx: rx irq:148
    rx dma free: 17232
    rx err: dma err:7, phy err:6714, rx frm err:467,

• данные о запущенных процессах:

Hidden text

[6244]ip:101a8c0  freemem:40224
[6244]—————————————————
[6245]Task Runtime Statistic, interval: 6244ms
[6245]PID      Name                          %CPU(Time)              Stack               Prio
[6246]—————————————————————————————
[6247] 1          idle_task                     94%(5930)                  1024                61
[6247] 2          timer_task                   0%(1)                          800                  5
[6248] 3          MAIN                         2%(173)                      2048                43
[6248] 4          lmac tx                        0%(3)                          640                  27
[6249] 5          lmac tx status             0%(1)                          512                  27
[6250] 6          lmac beacon task        0%(9)                          640                  26
[6250] 7          lmac rx                        0%(11)                        1024                19
[6251] 8          lmac_bgn_test            0%(1)                          512                  51
[6251] 9          lmac main                   0%(5)                          1024                43
[6252]10         hw                               0%(21)                        2048                26
[6253]12         tcpip_thread               0%(2)                          1024                49
[6253]13         hg_sdh_test                 0%(11)                        1024                43
[6254]14         hgpdm_sample_task   0%(61)                        1024                43
[6255]17         ThLstn                        0%(5)                          1024                49
[6255]20         ThSysMon                  0%(5)                          3072                49
[6256]—————————————————
[6256]WARNING: work 0x20004248 (func:0x1800438c) use 12 ticks
host->flags:14
host->flags:14

При подключении смартфона и открытии приложения FtyCamPro:
• настройка соединения со смартфоном:

Hidden text

[26499]lmac_bgn_add_sta: if:2, aid1, addr:b4:хх:хх:хх:хх:58  ß хх – это для конфиденциальности 😊
[26500]inteface2: sta  b4: хх:хх:хх:хх:58 connected
[26501]user_sta_add: b4 хх хх хх хх 58
[26813]send DHCP_OFFER …
[26813]Next IP: 192.168.1.101
[26814]Assign IP 192.168.1.100 for b4:хх:хх:хх:хх:58, flags=0 (next:192.168.1.101)
[26847]send DHCP_ACK …
[26847]Assign IP 192.168.1.100 for b4: хх:хх:хх:хх:58, flags=0 (next:192.168.1.101)
[26849]EVENT 10007 IGNORED
[26849]IP Pool:
[26850]    ip:192.168.1.100 — b4 хх:хх:хх:хх:58

• вход пользователя и начало передачи видео:

Hidden text

[cbEvenNoteFun][ 109] p2pSession [privilege=0,status=1,peerAddr=]
[_P2pSessionEvent][  29] p2pSession.status=[1]
[_P2pSessionEvent][  70] session[0] connected from []
[IlnkConnAdd][  23] set a connection—>[0]->2
[IlnkConnAdd][  40] set a connection—>[0]
[cbEvenNoteFun][ 123] exter———
[cbSysCtrlFun][ 915] SysCtrl->ctrlType->(3)
[_SysUsrChk][ 675] usrchk s [admin, admin], d [admin, admin]
[_SysUsrChk][ 727] usrchk [
name=admin, pass=admin] [len=8, privilege=255, ticket=ZtsS] ß обратите внимание на name, pass и ticket!
firstRTT=6, srtt=48

[_CustomCmdPrc][2531] PUSHPARAM Set—————
[PushParamSet][ 358] failed[336]—CfgItemGet
[PushParamSet][ 364] GPushSet:
access_id=0
xsecret_key=AIzaSyB-boxOG5n6AbKMLAOwmm1PZzqPkyZjMwc <— обратите внимание на xsecret_key!
msgType=2
environment=1
[PushParamSet][ 396] Gpush——index=0

• запрос на синхронизацию времени:

Hidden text

[_SysTimeSet][ 820]now: 1705692126
ntpEnable: 0
ntpSvr: time.windows.com
timezone:-10800
dayLight:0
[IpcTimeSyncFromStamp][ 307]timeStamp=1705702926—>cur_time=1705702926, Fri Jan 19 22:22:06 2024
[IpcTimeSyncFromStamp][ 329] 1705702926—> datetime [2024-01-19 22:22:06]
[_SysTimeSet][ 835] now: [1705702926:1705702926]
ntpEnable:0
ntpSvr: time.windows.com
timezone:-10800
daylight: 0
[_SysTimeSet][ 844] xqTime = 1705702926

Ну, наверное, это все что мне показалось понятным и интересным. Далее камера была протестирована на предмет приема информации по линии RX камеры, и, о чудо, она откликалась на нажатие кнопок на клавиатуре ПК. Изучение даташитов сильно не помогло, разве что в описании для платы разработчика (development board) для TXW806 было вскользь отмечено об АТ командах. На простой запрос к камере «AT+» (через «ctrl+c – ctrl+v») камера выдала список из 40 возможных команд:

Hidden text

Acmd_input_user_deal: AT+, len:3
valid cmds:
 0. AT+BSS_BW
 1. AT+CCA
 2. AT+CFG_RX_AGC
 3. AT+CLR_RX_CNT
 4. AT+DBG_LEVEL
 5. AT+EDCA_AIFS
 6. AT+EDCA_CW
 7. AT+EDCA_TXOP
 8. AT+FCC
 9. AT+TX_FRM_TYPE
10. AT+TX_GAIN
11. AT+GET_RX_CNT
12. AT+LO_FREQ
13. AT+MAC_ADDR
14. AT+PRINT_PERIOD
15. AT+REG_RD
16. AT+REG_WT
17. AT+SET_XOSC
18. AT+SET_TX_DPD_GAIN
19. AT+TEST_START
20. AT+TEST_ADDR
21. AT+TEMP_EN=1
22. AT+TX_CONT
23. AT+TX_DELAY
24. AT+TX_MCS
25. AT+TX_PROBE
26. AT+SLEEP
27. AT+TX_DUTY_CYCLE
28. AT+BSS_SCAN
29. AT+TX_PHA_AMP
30. AT+TX_PWR_AUTO
31. AT+TX_PWR_SUPER
32. AT+TX_PWR_TEST
33. AT+TX_START
34. AT+TX_STEP
35. AT+TX_TRIG
36. AT+TX_TYPE
37. AT+WAVE_DUMP
38. AT+WRITE_MAC_ADDR
39. AT+WRITE_TX_DPD_GAIN
40. AT+WRITE_XOSC

Конечно, из любопытства пришлось немного с ними побаловаться, так как поиск смысла этих команд по просторам сети ничего путного не дал. В результате опытов было выявлено несколько интересных (но не сильно полезных) команд. Вот некоторые из них:
AT+PRINT_PERIOD=1000 – установка задержки по времени выдачи в терминал информации, единица измерения – мс. Если установить значение 0, печать отключается;
AT+REG_RD= 0x01 – чтение данных из регистра 0x01;
AT+REG_WT=0x20001000, 0x123 – запись 0x123 в регистр 0x20001000;
AT+BSS_BW=20 – изменение полосы пропускания Wi-Fi.
В общем, для выполнения задачи по трансляции видео выявить пользу АТ команд мне не удалось.

Результаты перехвата потока информации по беспроводной сети

Для перехвата сетевого трафика между камерой и смартфоном использовалось приложение PCAPdroid (установленное на смартфоне), для перехвата трафика между ПК и камерой – Wireshark. Для анализа шестнадцатеричного кода использовался HxD Hex Editor. (https://mh-nexus.de/en/hxd/ редактор hex, https://www.wireshark.org Wireshark).
По умолчанию номер порта на моих подопытных камерах для обмена по UDP был постоянный – 32108 (см. выше результаты nmap), номер порта на смартфоне назначался автоматически и отличался в зависимости от сессии. Из полученных данных выделялись только UDP пакеты, а из них бралась только часть пакета с данными.

Полный дамп трафика, наверное, приводить не имеет смыла, здесь приведу только анализ части дампа общения камеры со смартфоном.

Обозначения: # – пакет от смартфона, @ – пакет от камеры. Данные представлены в шестнадцатеричном виде.

Номера пакетов Wireshark естественно будут отличаться от ваших. В первой группе данных (выделено желтым цветом) первый байт всегда f1, это так называемый «magic number». Второй байт этой группы идентифицирует запрос, следующим образом:
42 – «приветствие», P2pRdy
e0 – «запрос проверки состояния», P2PAlive
e1 – «ответ на запрос проверки состояния», P2PAliveAck
Вторая группа данных (выделено голубым цветом) это два байта обозначающие размер основных данных:

Порядок байт в группе – big endian, то есть 0x0014 – 20 байт, 0х0123 – 291 байт
В третьем (от смартфона) и седьмом (от камеры) пакетах, обозначающих, в моем понимании, обмен «приветствиями», в основных данных содержится наименование камеры или сети FTYB931188RLTOV, где цифры указаны в hex (0x0E3574 – 931188).

Наименование второй подопытной камеры BATC-185207-YDEIB и соответствующая часть дампа пакета UDP.

Ну и наконец, проверка состояния f1e0: пакет 4 от смартфона это запрос, пакет 9 от камеры f1e1 – ответ, и наоборот пакет 6 – запрос от камеры, пакет 8 – ответ смартфона. В пакетах с запросом проверки состояния передаются только заголовки, данные отсутствуют.
Следующие после пакетов «приветствия» и опроса состояния идут пакеты авторизации.

В пакете 10 от смартфона сообщается:
f1d0 (поз. группы 1) – данные необходимо принять и на них ответить;
00b0 (поз. гр. 2) – размер основных данных 176 байт;
d100 (поз. гр. 3) – маркер;
0000 (поз. гр. 4) – номер отправляемого пакета, который необходимо будет подтвердить при ответе;
110a (поз. гр. 5) – стартовые байты;
2010 (поз. гр. 6) – байты, сообщающие что будет проводиться авторизация пользователя
Байты 6065 6c68 6f01 (выделены песочным цветом, поз. 12,13,14) это ничто иное как логин и пароль (они между собой совпадают, см. выше логи с последовательного порта). Обратите внимание, что тут применяется простой метод шифрования:
my_byte=(my_byte%2==0)?my_byte+1:my_byte-1 и наш «’elho» становится «admin»:

В пакете 11 содержится ответ камеры, что она приняла пакет с номером 0000 от смартфона, поэтому и размер основных данных 0х0006, куда входят 0000 (позиция 5 группы данных). В пакете 12 камера отправляет запрос (f1d0) смартфону с данными о том, что пользователь авторизован (2011 поз. 6) и «выдаёт» смартфону «ticket» (см. выше логи последовательного порта) – «7669 3942» (поз.11, 12, выделено розовым цветом). «Ticket» зашифрован тем же методом что и имя пользователя и выдается один раз на всю сессию, но каждый раз новый. В пакете 13 смартфон отвечает камере (f1d1), что принял пакет с номером 0000 (поз. 5) от камеры.

Пакет 14 от смартфона к камере как раз и отличает одну камеру от другой. Этот дамп для камеры FTYB. В пакете содержится xsecret_key=AIzaSyB-boxOG5n6AbKMLAOwmm1PZzqPkyZjMwc (см. выше дамп последовательного порта). Этот «секретный ключ» выделен шрифтом красного цвета. В пакете 14 он представлен в зашифрованном виде. Камера BATC не имеет xsecret_key и поэтому в дампах трафика сетевого обмена между камерой и смартфоном такой пакет отсутствует. Желтым выделено название камеры/сети – FTYB931188RLTOV, часть, содержащая цифры теперь представлена как текстовая в формате UTF-8. Также в пакете 14 указан расшифрованный ticket – «7768 3843» (выделено коричневым цветом, первая строка 9 и 10 группы), в формате UTF-8 это текст «wh8C».

В пакете 15 смартфон в запросе (f1d0) сообщает, что приложение пытается синхронизировать время для камеры на сайте time.windows.com (зашифрованные байты, выделены красным), но смартфон подключен к камере как к AP, и поэтому попасть туда никак не может! Следовательно, время на камере не синхронизируется.
В пакете 16 смартфон делает запрос на соединение уже имея при себе билет (0810, поз. 6) и его указывая (поз.9 и 10). Пакет 17 от смартфона дублирует пакет 15 только уже без ссылки на time.windows.com.
Пакетом 18 камера «отчитывается» перед смартфоном, что получила все его пакеты (номера полученных пакетов показаны красным цветом, поз. 5-8).
В общем-то дальше уже не важно что отправляет нам камера, главное перед ней отчитываться, что получены все ее пакеты и на вопрос «как дела? – f1e0», всегда отвечать «все отлично» – f1e1. Это будет понятно из кода на Python представленного далее.
В определенный момент камера перестанет присылать вопросы «как дела?». Это видно по логам с любой камеры и будет ожидать от смартфона «серьезных» предложений. Которые выглядят следующим образом:

После чего камера подтвердит их получение, пришлёт пару «ненужных» запросов, на которые также надо будет просто отчитаться о получении. А затем камера пришлет первый пакет, в котором так называемый маркер измениться. Это и будет пакет с полезными данными!

Маркер (поз. 3, показано красным) с d100 сменился на d101, что говорит о пакете с полезной для нас информацией (аудио или видео). В данном случае, в пакете 59 передан фрагмент звука 55aa 15a8 (выделено желтым, поз. 5 и 6) – AUDIO_HEADER.

И в пакете 60 смартфон отвечает, что принял пакет 0001 (показано желтым, поз. 5) от камеры. При этом маркер у него будет d201 (поз. 3). Дальше камера беспрерывно начнет присылать jpeg и звук. А смартфону важно отчитываться за полученные пакеты.

Видно, что в пакете 61 размером 1028 байт с маркером d101 есть заголовок jpeg – ffd8. Пакеты с jpeg идут с размером 1028 байт и только у пакета, завершающего полный видеокадр, размер может быть меньше (в конце пакета должно быть ffd9).
Для примера, восстановленный (путем копирования пакетов из Wireshark в редактор HxD) фрагмент видео с моей кошкой!

Подробнее о структуре jpeg файла можно почитать на https://habr.com/ru/articles/454944/ (автор @SLY_G)

Реализация на Python

Программа имитирует общение камеры BATC с приложением на смартфоне по логике обмена пакетами описанной выше. Камера включена в режиме AP. В программе не предусмотрена отправка пакета с xsecret_key. Полученные кадры потока MJPEG сохраняются в локальную папку на ПК. Звук игнорируется. Для завершения работы программы используется магическое сочетание клавиш «ctrl+Break». Вот сам текст программы:

Hidden text
import socket
from PIL import Image, ImageDraw, ImageFont
import time, io
def pack_size(mydata) :
    #преобразуем размер данных в 2 байта hex для вставки в пакет
    return (len(bytes.fromhex(mydata)).to_bytes(2, byteorder='big'))
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
BROADCAST_CAM_PORT = 32108
cam_name='42415443000000000002d3775944454942000000' # имя камеры/сети BATC185207YDEIB, 185207 в hex
MSG_HELLO=bytes.fromhex('f1420014')+bytes.fromhex(cam_name)
MSG_E0_Q=b'xf1xe0x00x00' # Ты как? Q-question
MSG_E1_ANSW=b'xf1xe1x00x00' # Норм! ANSW - answer
HDR_D0=b'xf1xd0' # принимай!
HDR_D1=b'xf1xd1' # принял!
data_login='d100'+'0000'+'110a2010a400ff'+'00'*5+'6f'+'01'*27+'60656c686f'+'01'*123+'60656c68'
MSG_LGN_ADMIN=HDR_D0+pack_size(data_login)+bytes.fromhex(data_login)
MSG_TICKET_TIME_START=HDR_D0+bytes.fromhex('0060'+'d100'+'0001'+'110a'+'401054000000')
MSG_TICKET_TIME_END=bytes.fromhex('d1d4fefe'+'01'*8+'75686c642f76686f656e76722f626e6c'+
'01'*48+'230e0e67')
MSG_TICKET_START=HDR_D0+bytes.fromhex('0010'+'d100'+'0002'+'110a'+'081004000000')
##########################
MSG_TICKET_START_2=HDR_D0+bytes.fromhex('0018d1000003'+'110a18300c000000')
MSG_TICKET_END_2=bytes.fromhex('0301010100010101')
MSG_TICKET_LONG_START=HDR_D0+bytes.fromhex('0114d1000004'+'110a103008010000')
MSG_TICKET_LONG_END=bytes.fromhex('01'*260)
MSG_TICKET_TIME_START_2=HDR_D0+bytes.fromhex('0060'+'d100'+'0005'+'110a'+'401054000000')
MSG_TICKET_TIME_END_2=bytes.fromhex('d1d4fefe'+'01'*8+'75686c642f76686f656e76722f626e6c'+
'01'*48+'240e0e67')
###########################
def take_ticket (indata):
    ticket=indata.hex(' ').split(' ')
    hex_numbers = [ticket[len(ticket)-8], ticket[len(ticket)-7], ticket[len(ticket)-6], ticket[len(ticket)-5]]
    dec_numbers = [int(hex, 16) for hex in hex_numbers]
    for i in range(0, len(dec_numbers)):
        if dec_numbers[i]%2==0:
            dec_numbers[i]=(dec_numbers[i]+1)
        else:
            dec_numbers[i]=(dec_numbers[i]-1)
    return (bytearray(dec_numbers)) # да, можно было бы напрямую работать с hex
def my_send (mybyte):
    s.sendto(mybyte, ('192.168.1.1', BROADCAST_CAM_PORT))
    print ('I SEND: ', mybyte)
fnum=0 # счетчик для кадров MJPEG
def take_full_jpeg (jpeg_data):
    jpeg_return=bytes.fromhex(''.join(jpeg_data[4:]))
    data_send=['d201','0001']
    data_send.append(jpeg_data[3])
    my_send (HDR_D1+pack_size(''.join(data_send))+bytes.fromhex(''.join(data_send)))
    jpeg_recvdata=s.recvfrom(4096)[0]
    while jpeg_recvdata.find(b'xffxd9') == -1 :
        jpeg_return=jpeg_return+jpeg_recvdata[8:]
        data_send.clear()
        data_send=['d201','0001']
        data_send.append(jpeg_recvdata[6:8].hex())
        my_send (HDR_D1+pack_size(''.join(data_send))+bytes.fromhex(''.join(data_send)))
        if (jpeg_recvdata[4:6]!= b'xd1x01') :
            tmp=s.recvfrom(4096)[0]
            recive_explain(tmp)
        jpeg_recvdata=s.recvfrom(4096)[0]
    if jpeg_recvdata.find(b'xffxd9') != -1 :
        print ('This is the end!')
        jpeg_return=jpeg_return+jpeg_recvdata[8:]
    global fnum
    fnum=fnum+1
    t = time.localtime()
    current_time = time.strftime("%D %H:%M:%S", t) # на каждый кадр наносим метку с датой и временем
    myfont = ImageFont.truetype("arial.ttf", 20)
    try:
        io_bytes = io.BytesIO(jpeg_return)
        im = Image.open(io_bytes)
        draw_text = ImageDraw.Draw(im)
        draw_text.text((10,10),current_time,fill=('#1C0606'), font=myfont)
        im.save('D:pywrkcammy_jpegpic'+str(fnum)+'.jpeg')
    except:
        pass
pac_num=list() # список с номерами полученных от камеры пакетов
def answer_to_cam(datinlist):
    k='d200'
    global pac_num
    if datinlist[2] == 'd101' :
        k='d201'
        pac_num.clear()
        if (datinlist[4]=='ffd8') : take_full_jpeg(datinlist)
    if datinlist[3] in pac_num :
        pac_num.clear()
        pac_num.append (datinlist[3])
    else :
        pac_num.append (datinlist[3])
    data_send=[k,'0001']
    for x in range(0, len(pac_num)):data_send.append(pac_num[x])
    my_send (HDR_D1+pack_size(''.join(data_send))+bytes.fromhex(''.join(data_send)))
def recive_explain(mydata):
    partion=mydata.hex()
    data_in_list=[]
    for x in range (0, len (partion), 4) :
        if (x>len (partion)) : break
        data_in_list.append(partion[x:x+4])
    if data_in_list[0]=='f1d0' :
        answer_to_cam(data_in_list)
    if data_in_list[0]=='f1d1' :
        print ('')
    if data_in_list[0]=='f1e0' :
        my_send (MSG_E1_ANSW)
        my_send (MSG_E0_Q)
    if data_in_list[0]=='f1e1' :
        print ('#cam answer tel - Fine!')
    if data_in_list[0]=='f142' :
        print ('#cam send to tel MSG_HELLO')
    return (data_in_list[0])
def main():
    i=0
    my_send (MSG_HELLO)
    my_send (MSG_E0_Q)
    while (recive_explain(s.recvfrom(4096)[0])) != 'f1e1' : print ('In while f1e0')
    my_send (MSG_LGN_ADMIN)
    data=s.recvfrom(4096)
    while data[0].find(b'xf1xd0x00x18') != 0 :
        data=s.recvfrom(4096)
        recive_explain(data[0])
    my_ticket=take_ticket(data[0])
    print(f"my_ticket - {my_ticket}")
    my_send (MSG_E0_Q)
    recive_explain(s.recvfrom(4096)[0])
    my_send (MSG_TICKET_TIME_START+my_ticket+MSG_TICKET_TIME_END)
    my_send (MSG_TICKET_START+my_ticket)
    while (recive_explain(s.recvfrom(4096)[0])) != 'f1d0' : print ('In while d0')
    for i in range(0, 15):
        what_rec=recive_explain(s.recvfrom(4096)[0])
        if what_rec == 'f1e0' : break
    my_send (MSG_TICKET_START_2+my_ticket+MSG_TICKET_END_2) #то, самое "серьезное" предложение
    my_send (MSG_TICKET_LONG_START+my_ticket+MSG_TICKET_LONG_END)
    my_send (MSG_TICKET_TIME_START_2+my_ticket+MSG_TICKET_TIME_END_2)
    while True:
        tt=recive_explain(s.recvfrom(4096)[0]) #получаем постоянный поток кадров в формате jpeg
if __name__ == "__main__":
    main()

Результат работы программы, мой Кадр – кошка Фрося.

P.S. 2
Нашёл это https://github.com/DavidVentura/cam-reverse «сокровище» уже после своих мучений.

Конференция «Я.Железо»

Дата
Время14:00 – 23:59
Место

МоскваОнлайн

Конференция «IT IS CONF 2024»

Дата
Время09:00 – 19:00
Место

Екатеринбург

Метки:





Запись опубликована: 24.04.2024

Как сделать и настроить свой CDN

Обновить

perezanov 54 минуты назад

Уровень сложностиСредний
Время на прочтение7 мин

Количество просмотров381

Туториал
CDN (сеть доставки контента) представляет собой группу серверов, размещаемых в разных географических регионах с целью обеспечить быструю загрузку контента для пользователей из этих регионов. Чаще всего сети доставки контента используются для ускорения загрузки статических файлов: картинок, видео, скриптов, zip-архивов. Каждый из CDN серверов просто хранит одни и те же файлы, а пользователь получает их с ближайшего сервера.

Хранение контента у большинства сетей доставки контента организовано так: CDN сервер, получив в первый раз от пользователя запрос на отдачу файла, загружает его с оригинального сервера к себе, кэширует и тут же отдает пользователю. Для всех последующих запросов файл уже выдается из кэша. Некоторые сервисы позволяют настраивать длительность хранения кэшируемых данных, а также их предварительную загрузку (прекэш).

Иногда может понадобиться настроить собственную сеть доставки контента. Давайте рассмотрим, для чего это нужно и как это сделать.


Это наша будущая CDN из 5 серверов, которая будет раздавать контент на весь мир

Зачем настраивать собственный CDN

Приведем несколько примеров, когда стоит задуматься о создании своей собственной сети доставки контента:

  • расходы на услуги стороннего CDN сервиса существенно превышают затраты на создание своего
  • когда хотим иметь гарантированный канал и фиксированное дисковое пространство для постоянного кэша, а не разделять их с другими клиентами
  • если необходимы особые настройки хранения и доставки контента
  • в случае размещения нескольких production-серверов в разных регионах для ускорения доставки динамических данных
  • если не хотим, чтобы сторонние сервисы собирали и хранили данные о наших пользователях, например: IP адрес, запрашиваемые URL, и т.п.
  • когда в нужном нам регионе у других сервисов нет точек присутствия

В большинстве других случаев лучше воспользоваться услугами платного CDN сервиса.

 

Готовимся к запуску

Для реализации задуманного нам понадобится:

  1. Несколько серверов в разных регионах, можно виртуальных (VPS)
  2. Отдельный субдомен. В нашем примере это будет cdn.nashsait.org
  3. geoDNS сервис, с помощью которого пользователь, обращаясь к субдомену, будет направлен на ближайший в его регионе сервер

Арендуем сервера и настраиваем geoDNS

Сперва определимся, где преимущественно находится основная пользовательская аудитория. В нашем примере это Казахстан, поэтому мы определенно должны иметь точку присутствия в этом регионе, чтобы у большинства пользователей всё работало максимально быстро. Остальные сервера будут «разбросаны» по миру. Арендовать их удобнее всего у хостинг-провайдеров, предлагающих сразу несколько локаций для размещения.

Мы закажем 5 виртуальных серверов с 25GB диска, безлимитным трафиком и последним Debian или Ubuntu:

Казахстан, ip: 86.104.73.235

Нидерланды, ip: 94.232.245.17

США, ip: 45.89.53.214

Бразилия, ip: 95.164.5.110

Япония, ip: 5.253.41.115

Чтобы пользователь при обращении к cdn.nashsait.org направлялся на ближайший для него сервер, нам нужен рабочий DNS с функцией geoDNS. Его можно настроить самому или использовать готовый сервис, например СlouDNS.

Мы будем использовать СlouDNS: регистрируемся, выбираем тариф GeoDNS и в личном кабинете добавляем новую DNS-зону, указав наш главный домен nashsait.org. В процессе создания зоны будет предложено выбрать для домена будущие NS-сервера. Отметим все доступные и на будущее скопируем их себе в отдельный текстовый файлик.

Если главный домен уже нами используется (например, на нем размещен сайт или работает электронная почта), то сразу после создания зоны нужно добавить существующие рабочие DNS-записи.

Затем для субдомена cdn.nashsait.org нужно создать несколько A-записей, каждая из которых в зависимости от региона пользователя будет указывать на один из наших CDN серверов. В качестве регионов можно указывать континенты, страны или отдельные штаты (для США и Канады). Начнем с Южной Америки и направим все запросы оттуда на сервер в Бразилии:

Сделаем то же самое для других регионов, при этом один из них рекомендуется добавить как регион «по умолчанию». В итоге список A-записей будет выглядеть так:

Самая нижняя запись «Default» означает, что все остальные, не указанные в других записях регионы (Европа, Африка, спутниковый интернет и т.п.), будут направляться на сервер в Нидерландах.

На этом настройка geoDNS завершена, осталось зайти на сайт регистратора нашего домена и изменить NS-сервера для nashsait.org на те, что мы ранее скопировали в отдельный текстовый файл.

 

Добавление SSL сертификатов

Чтобы CDN работал по протоколу HTTPS, мы установим бесплатный SSL сертификат от Let’s Encrypt. Это удобно делать с помощью ACME Shell скрипта, который позволяет валидировать домен по DNS через ClouDNS API.

Достаточно установить acme.sh на одном из серверов, а затем скопировать полученный сертификат на все остальные. Выполним установку на сервере в Нидерландах:

root@cdn:~# wget -O - https://get.acme.sh | bash; source ~/.bashrc

Стоит заметить, что во время установки создается отдельная CRON задача для автоматического обновления сертификатов в будущем.

При выдаче сертификата проверка домена будет происходить через DNS, а необходимые для этого записи будут автоматически добавлены на ClouDNS через их API. Поэтому в учетной записи на ClouDNS в меню «API&Resellers» нам нужно создать нового API пользователя, придумав для него пароль. Полученный <auth-id> с паролем укажем в файле ~/.acme.sh/dnsapi/dns_cloudns.sh (не перепутайте с похожим dns_clouddns.sh). Вот строки файла, которые нужно раскоментировать и отредактировать:

CLOUDNS_AUTH_ID=<auth-id>
CLOUDNS_AUTH_PASSWORD="<пароль>"

Далее запустим получение SSL сертификата для cdn.nashsait.org

root@cdn:~# acme.sh --issue --dns dns_cloudns -d cdn.nashsait.org --server letsencrypt --reloadcmd "service nginx reload"

Процесс может занимать до нескольких минут и если возникнет ошибка проверки субдомена, попробуйте запустить команду повторно. При успешном результате на экране появится список файлов установленного сертификата:

Запомним эти пути, они будут нужны при копировании сертификата на другие CDN локации и для настройки веб-сервера. Ошибка «Reload error for» не существенна и при дальнейшем обновлении сертификатов на полностью настроенном сервере ее не будет.

Войдем на четыре других сервера и скопируем на каждый полученный сертификат, создав соответствующие директории, чтобы пути к файлам были везде одинаковы:

root@cdn:~# mkdir -p /root/.acme.sh/cdn.nashsait.org_ecc/
root@cdn:~# scp -r root@94.232.245.17:/root/.acme.sh/cdn.nashsait.org_ecc/* /root/.acme.sh/cdn.nashsait.org_ecc/

Это копирование нужно сделать регулярным, поэтому на этих же четырех серверах в CRON добавляем ежедневный запуск команды:

scp -r root@94.232.245.17:/root/.acme.sh/cdn.nashsait.org_ecc/* /root/.acme.sh/cdn.nashsait.org_ecc/ && service nginx reload

Чтобы всё работало, доступ нидерландскому серверу со всех остальных четырех должен быть настроен по ключу, без необходимости ввода пароля. Обязательно сделайте это.

 

Настройка Nginx

На всех пяти CDN точках, в качестве веб-сервера для раздачи контента мы установим Nginx и настроим его как кэширующий proxy-сервер:

root@cdn:~# apt update
root@cdn:~# apt install nginx

Дефолтный файл конфига /etc/nginx/nginx.conf заменим на приведенный ниже:

nginx.conf

user www-data;
worker_processes auto;
pid /run/nginx.pid;
events {
    worker_connections 4096;
    multi_accept on;
}
http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    types_hash_max_size 2048;
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    access_log off;
    error_log /var/log/nginx/error.log;
    gzip on;
    gzip_disable "msie6";
    gzip_comp_level 6;
    gzip_proxied any;
    gzip_vary on;
    gzip_types text/plain application/javascript text/javascript text/css application/json application/xml text/xml application/rss+xml;
    gunzip on;
    proxy_temp_path    /var/cache/tmp;
    proxy_cache_path   /var/cache/cdn levels=1:2 keys_zone=cdn:64m max_size=20g inactive=14d;
    proxy_cache_bypass $http_x_update;
server {
  listen 443 ssl;
  server_name cdn.nashsait.org;
  ssl_certificate /root/.acme.sh/cdn.nashsait.org_ecc/fullchain.cer;
  ssl_certificate_key /root/.acme.sh/cdn.nashsait.org_ecc/cdn.nashsait.org.key;
  location / {
    proxy_cache cdn;
    proxy_cache_key $uri$is_args$args;
    proxy_cache_valid 90d;
    proxy_pass https://nashsait.org;
    }
  }
}

В этом конфиге отредактируем:

  • max_size — размер кэша, не больше доступного на диске места
  • inactive — срок хранения кэшированных файлов, к которым не было обращений
  • ssl_certificate и ssl_certificate_key — абсолютные пути к файлам SSL сертификата
  • proxy_cache_valid — срок хранения кэшированных файлов
  • proxy_pass — URL сайта, с которого CDN загружает и кэширует файлы. У нас это nashsait.org

Обратите внимание на схожесть директив inactive и proxy_cache_valid. Чтобы не запутаться, рассмотрим их на простом примере. Вот что происходит при inactive=14d и proxy_cache_valid 90d:

  • если файл не будет запрошен в течение 14 дней, то он удаляется
  • если файл будет запрашиваться хотя бы раз в 14 дней, то он начнет считаться устаревшим только по истечении 90 дней, и после этого срока при очередном запросе Nginx загрузит его по новой с оригинального сервера

Указав нужные значения в nginx.conf, применим их:

root@cdn:~# service nginx reload

Обратите внимание, что Nginx не будет кэшировать данные, если они получены с оригинального сервера с какими-либо cookies (заголовок «Set-Cookie»). Игнорировать это условие можно, добавив в конфиг следующие директивы:

proxy_ignore_headers "Set-Cookie";
proxy_hide_header "Set-Cookie";

На этом настройка завершена. Дополнительно можно создать bash-скрипт для очищения кэша:

purge.sh

#!/bin/bash
if [ -z "$1" ]
then
    echo "Purging all cache"
    rm -rf /var/cache/cdn/*
else
    echo "Purging $1"
    FILE=`echo -n "$1" | md5sum | awk '{print $1}'`
    FULLPATH=/var/cache/cdn/${FILE:31:1}/${FILE:29:2}/${FILE}
    rm -f "${FULLPATH}"
fi

Запуск этого скрипта удалит весь кэш, отдельный файл удаляется так:

root@cdn:~# ./purge.sh /test.jpg

 

Тестируем наш CDN

С помощью онлайн ping-сервисов можно проверить пинги к нашей сети доставки контента из разных мест:

Место запуска Host IP Время, мсек
Германия, Франкфурт cdn.nashsait.org 94.232.245.17 9.6
Испания, Мадрид cdn.nashsait.org 94.232.245.17 37.5
США, Чикаго cdn.nashsait.org 45.89.53.214 16.5
США, Атланта cdn.nashsait.org 45.89.53.214 23.8
Бразилия, Сан-Паулу cdn.nashsait.org 95.164.5.110 1.01
Казахстан, Астана cdn.nashsait.org 86.104.73.235 21.9
Южная Корея, Сеул cdn.nashsait.org 5.253.41.115 31.5

Пинг хороший, теперь разместим в корне основного сайта картинку test.jpg и на сервисе Ping-Admin посмотрим на скорость ее загрузки через CDN:

Место запуска IP Время, сек Скорость загрузки
Бразилия, Сан-Паулу 95.164.5.110 0,233338 12,37 МБ/с
США, Ашберн 45.89.53.214 0,130310 11,24 МБ/с
США, Чикаго 45.89.53.214 0,222213 5,19 МБ/с
Канада, Монреаль 45.89.53.214 0,180502 5,51 МБ/с
Япония, Токио 5.253.41.115 0,091587 43,88 МБ/с
Гонконг 5.253.41.115 0,532564 1,56 МБ/с
Казахстан, Алматы 86.104.73.235 0,195468 4,86 МБ/с
Великобритания, Хэмпшир 94.232.245.17 0,191698 6,57 МБ/с
Нидерланды, Налдвейк 94.232.245.17 0,156096 11,67 МБ/с
Франция, Париж 94.232.245.17 0,301658 4,15 МБ/с

Всё работает, контент раздается быстро. Теперь у нас есть собственный рабочий CDN с безлимитным трафиком и точками присутствия на всех континентах.
 

Конференция «Я.Железо»

Дата
Время14:00 – 23:59
Место

МоскваОнлайн

Конференция «IT IS CONF 2024»

Дата
Время09:00 – 19:00
Место

Екатеринбург

Метки:





Запись опубликована: 24.04.2024

Почему сперма вытекает из влагалища после секса и может ли это мешать зачатию

[ comments ]

Скорее всего, беременность не наступает из-за других факторов.
Почему сперма вытекает из влагалища после секса и может ли это мешать зачатию

Почему сперма вытекает из влагалища

Во время эякуляции у мужчины выделяется от 1,5 до 5 мл спермы, но не весь этот объём достигает сначала матки, а потом фаллопиевых труб. Основная часть остаётся во влагалище, в основном на заднем его своде. Там сперму ничто не держит, и в итоге она вытекает наружу под действием силы тяжести.

Может ли вытекание спермы помешать зачатию

В некоторых случаях забеременеть можно, даже если в матку попал предэякулят — жидкость, которая выделяется из головки пениса во время секса до семяизвержения. В ней тоже могут быть сперматозоиды, но в небольшом количестве.

Во время самой эякуляции часть семенной жидкости всё равно попадёт в матку и фаллопиевы трубы. И если у пары всё в порядке с репродуктивным здоровьем, а яйцеклетка готова к оплодотворению, то проблем из-за вытекания спермы после секса быть не должно.

Принимать после коитуса позу плуга, берёзки, просто лежать на спине или животе, чтобы специально удержать сперму, не обязательно.

В одном исследовании 2016 года учёные пытались выяснить, помогает ли отдых повысить шансы забеременеть после искусственной инсеминации — введения в матку спермы, полученной вне секса. Они разделили женщин на две группы: одни лежали 15–20 минут после процедуры, а другие — нет. В итоге в первой группе беременность наступила в 32% случаев, а во второй — в 40%.

Учёные обработали результаты и заявили, что лежание не показало положительного влияния на частоту наступления беременности. Вероятнее всего, вывод применим и к зачатию естественным путём.

Позы с поднятыми ногами тоже не нужны. Идея метода заключается в том, чтобы помочь сперматозоидам двигаться в правильном направлении, но с этим они отлично справляются самостоятельно.

Что на самом деле может помешать зачатию

Шансы на беременность зависят от множества факторов: возраст, частота секса, общее состояние здоровья.

О бесплодии говорят только, если пара не может зачать ребёнка год. А если женщина старше 35 лет, то достаточно шести месяцев безуспешных попыток.

У мужчин к бесплодию приводят:

  • Проблемы с попаданием спермы в женские репродуктивные пути. Они могут возникать из-за преждевременной эякуляции, муковисцидоза, закупорки яичка, а также травм мужских репродуктивных органов.
  • Заболевания или состояния, при которых спермы вырабатывается мало или её качество снижено. Это неопущение яичек, расширенные вены на них, генетические дефекты, гормональные проблемы и проблемы со здоровьем, такие как диабет и различные инфекции.
  • Внешние факторы — воздействие радиации, некоторых пестицидов и химикатов (хлорка, средства от насекомых), высокая температура, например во время частого посещения бань или саун, а также приём лекарств от инфекций, высокого давления или депрессии.

Мужское бесплодие может быть также идиопатическим. Это значит, что все анализы спермы и другие показатели в норме, но беременность у партнёрши всё равно не наступает.

У женщин причин, которые могут привести к бесплодию, больше. К ним относятся нарушение овуляции, повреждения или непроходимость фаллопиевых труб, эндометриоз, кисты и рубцы на органах малого таза и первичная яичниковая недостаточность, при которой половые железы перестают работать должным образом, а менструации заканчиваются до 40 лет.

Также шанс на успешное зачатие падает, если любой из партнёров сталкивается с онкологическими заболеваниями, особенно теми, которые поражают репродуктивные органы. На фертильность также могут влиять лучевая и химиотерапия.

Что делать паре, если не получается зачать ребёнка

Если говорить о бесплодии пока рано, но вы собираетесь завести ребёнка, можно увеличить шансы на зачатие.

Запишитесь к врачу

Сходить к врачу стоит ещё до того, как вы решили зачать ребёнка. Важно обсудить со специалистом лекарства, которые вы принимаете сейчас, и необходимые прививки. Также доктор расскажет, что стоит сделать до беременности, чтобы предотвратить врождённые проблемы у будущего ребёнка.

Если сейчас есть какие-либо заболевания, их нужно либо вылечить, либо взять под контроль. Это касается ИППП, диабета, гипертонии и болезней щитовидки.

А если вы уже какое-то время пытаетесь зачать, но не получается, врачи выяснят причину проблемы и помогут устранить её.

Попытайтесь сохранить здоровье спермы

Вот как мужчина может это сделать:

  • Избегать спортивных травм паха — для этого носите защитное снаряжение.
  • Выбирать свободное нижнее бельё и брюки.
  • Отказаться от горячих ванн, бань и саун.
  • Не класть ноутбук или телефон на колени.
  • Не принимать анаболические стероиды.

Регулярно занимайтесь сексом

Совет очевидный, но всё же регулярность очень важна. Самый высокий уровень зачатия наблюдается у пар, которые занимаются сексом каждый день. Если ежедневно не получается или не хочется, старайтесь планировать коитус раз в три дня, начиная вскоре после окончания менструации.

Идеально для зачатия заниматься сексом во время овуляции. В этот период яйцеклетка готова к оплодотворению. Обычно овуляция наступает за 12–16 дней до начала менструации. Чтобы не пропустить её, можно воспользоваться аптечным тестом или ориентироваться на характерные признаки:

  • увеличивается количество прозрачных, влажных и тягучих выделений из влагалища;
  • температура тела в состоянии покоя немного выше, чем обычно.

Ведите здоровый образ жизни

Откажитесь от алкоголя и курения. Эти факторы не только снижают фертильность обоих партнёров, но и влияют на здоровье будущего ребёнка.

Также на возможность зачать влияет лишний вес: у женщин он может остановить овуляцию, а у мужчин — снизить количество сперматозоидов в эякуляте.

Избегайте токсичных веществ и загрязнителей окружающей среды

К ним относятся табачный дым, хлорка, синтетические краски и растворители, некоторые металлы, удобрения, спреи от насекомых и фекалии кошек или грызунов. Эти вещества могут повредить репродуктивную систему и затруднить зачатие.

Каролина Вилья / Лайфхакер

[ comments ]

Метки:





Запись опубликована: 24.04.2024

Исследование: садоводство и ходьба могут снизить риск депрессии на 23%

[ comments ]

Как раз дачный сезон начался.
Исследование: садоводство и ходьба могут снизить риск депрессии на 23%

Умеренная физическая активность, такая как работа в саду, гольф и ходьба, связана с более низким риском депрессии. К такому выводу пришли учёные из Университета Англии Раскина (Великобритания).

Они изучили данные более чем четырёх миллионов человек для выявления связи между физической активностью и эпизодами депрессии. Дополнительно проверили связь между физическими упражнениями и тревогой с участием более 65 000 человек, а также между психозом, шизофренией и физической активностью с участием более чем 30 000 человек.

В итоге выяснилось, что упражнения низкой и средней интенсивности снижают риск депрессии на 23%, тревоги — на 26%, психоза и шизофрении — на 27%. Интересно, что тренировки высокой интенсивности, наоборот, могут ухудшать ситуацию и приводить к стрессу в отдельных случаях.

Учёные отметили, что их результаты были последовательными вне зависимости от пола, возраста и страны проживания.

кадр из фильма «Роскошный цветок»

[ comments ]

Метки:





Запись опубликована: 24.04.2024

Знаете ли вы, почему жуки и мухи не падают с потолка?

[ comments ]

Эта суперспособность есть не только у насекомых.
Znaete li vy, pochemu zhuki i muhi ne padayut s potolka?

Возможно, прямо сейчас вы лежите на диване, читаете Лайфхакер и смотрите в потолок. И над вами проползает муха, жук, таракан, паук или другой представитель удивительного мира членистоногих. У вас никогда не возникало вопроса: как им это удаётся — передвигаться по абсолютно гладким вертикальным поверхностям или вовсе по потолку вниз головой? Давайте разбираться.

Конечности насекомых, паукообразных, а также некоторых видов рептилий — например, гекконов — и амфибий способны к адгезии. Так по-научному называется сцепление двух разнородных поверхностей.

Вам может показаться, что потолок, стекло или другая поверхность, по которой ползёт муха или паук, абсолютно ровные и гладкие и там не за что зацепиться. Но это только с вашей точки зрения. Если бы вы были размером с муху, то могли бы почувствовать, что они усеяны множеством микроскопических трещинок, за которые можно уцепиться.

Электронная микрофотография пульвиллы комнатной мухи — волосистой подушечки на лапе, которые позволяют прикрепляться к стенам и потолку
Электронная микрофотография пульвиллы комнатной мухи — волосистой подушечки на лапе, которые позволяют прикрепляться к стенам и потолку. Изображение: Wikimedia Commons

Конечности пауков, муравьёв, жуков и прочих созданий покрыты микроскопическими волосками и щетинками. Они проникают в неровности поверхности, на которых оказалось животное, достаточно крепко удерживая его.

В таких маленьких масштабах работает сила, которую физики называют силой Ван-дер-Ваальса. Она отвечает за взаимодействие между атомами и молекулами. Сила эта не только позволяет щетинкам насекомых прицепляться к потолку, но и влияет на образование капель, стекающих с крыши, когда одни молекулы воды прилипают к другим. Или на работу микрофибровых щёток и салфеток — они покрыты крошечными волокнами, к которым цепляется грязь.

У разных видов животных механизмы сцепления могут различаться. Например, гекконы и пауки пользуются так называемой сухой адгезией — у них очень много плотно расположенных микроволосков на лапах.

Лапа геккона, прикрепившегося к стеклу
Лапа геккона, прикрепившегося к стеклу. Изображение: Wikimedia Commons

Но насекомые, например мухи, муравьи, тараканы и сверчки, а также некоторые лягушки предпочитают так называемую влажную адгезию. У них волоски расположены не так плотно и кончики лап более гладкие, но при этом покрыты специальными выделениями, позволяющими более эффективно прилипать к поверхностям. Они состоят из смеси белков, полисахаридов, полифенолов и липидов.

Выделения на лапах насекомых нужны им не только для того, чтобы ползать по потолку, но и для других целей. Например, для отпугивания хищников, спаривания, склеивания яиц в коконы, удержания добычи или самки во время полового акта.

При этом нельзя сказать, что насекомые могут передвигаться вообще по любым стенкам. Учёные проводили эксперименты и выяснили, что по вертикальным поверхностям с трещинками размером 1,4 микрометра жуки бегают вполне бодро, а вот на очень гладких материалах с разрывами не более 0,5 микрометра делать это уже не способны.

Кроме того, ползать по отвесным поверхностям насекомым, гекконам и прочим маленьким созданиям помогают их маленькие размеры. Были бы мошки размером с кошку, а стрекозы — метр длиной, как в старом добром каменноугольном периоде, и никакие волоски на потолке их бы уже не удержали.

Лайфхакер

[ comments ]

Метки:





Запись опубликована: 24.04.2024

Triton Submarines представила прозрачную «подлодку-пузырь» для круизов у дна моря

[ comments ]

Одновременно в капсуле могут разместиться до девяти человек, включая пилота.
подводная лодка

Компания Triton Submarines представила свою первую туристическую подводную лодку Triton 660/9 AVA. Необычный аппарат имеет форму вытянутого прозрачного пузыря, который позволяет пассажирам насладиться панорамным обзором морских глубин.

В названии модели зашифровали вместимость подлодки (9 человек), максимальную дистанцию погружения (660 футов или 200 метров), а также используемую технологию изготовления корпуса — Advanced Versatile Acrylics (AVA).

подводная лодка
Изображение: Triton Submarines

Капсула позиционируется в качестве дополнения к круизным надводным лайнерам. Она может оборудоваться под коктейль-бар, игровой клуб и даже обустраиваться под спа-процедуры. Пассажиры могут расслабляться и заниматься своими делами, находясь у морского дна и любуясь видами подводной природы. Впрочем, на борту капсулы не нашлось места для туалета. Поэтому клиентам придётся побеспокоиться об этом заранее.

Подводная лодка прошла все испытания и получила необходимый сертификат. Triton Submarines поставила первую капсулу туристической компании Scenic Luxury Cruises & Tours. Эта подлодка получила название Scenic Neptune II. Её установили на борту лайнера Scenic Eclipse II, откуда аппарат будет опускаться в море во время путешествий.

подводная лодка
Фото: Triton Submarines

Корабль прибыл в Австралию и в течение двух ближайших лет будет совершать круизы у побережья континента, а также в Индонезии, Новой Зеландии, по южной части Тихого океана и в районе Восточной Антарктиды. Подлодка может находиться в автономном плавании до 12 часов и развивать скорость до 5,5 километра в час.

Ранее австралийская Migaloo представила первую в мире подводную яхту на 60 человек.

Triton Submarines

[ comments ]

Метки:





Запись опубликована: 24.04.2024

Деплой с помощью github actions и пакетов

Обновить

MyGodIsHe 14 часов назад

Уровень сложностиПростой
Время на прочтение2 мин

Количество просмотров597

Туториал

Гайд по настройке деплоя через ssh и docker контейнеры в github. Развертывать будем на сервер под управлением Ubuntu 23.04.

Предустановка сервера

Нам понадобится docker-compose.

Настроим доступы к нашему серверу

Для этого создадим нового пользователя, под которым будем ходить из github actions:

sudo adduser smith

Разрешим для нашего юзера выполнять docker команды без пароля

sudo usermod -aG docker smith

Для доступа из github, создадим ssh ключ и его же добавим в авторизованные ключи:

ssh-keygen
cp .ssh/id_rsa.pub .ssh/authorized_keys

Настройка github

Создадим PAT (персональный токен доступа) на странице — Personal Access Tokens (Classic) (github.com) . Важно его сохранить на время куда-нибудь. Если делаете для организации, то у неё другая страница.

Добавляем секреты в репу (settings -> Secrets -> Actions):

  • PAT — персональный токен

  • SSH_HOST — хост машины, на которой будем разворачивать контейнер

  • SSH_PRIVATE_KEY — приватный ключ юзера smith

  • SSH_USER — имя юзера, smith

Создаем в репе файл docker-compose.yml с нашим сервисом:

services:
  example_app:
    container_name: example_app
    image: ghcr.io/your_nick/example_app:latest
    volumes:
      - ~/data:/app/data

Настраиваем github workflow, файл .github/workflow/deploy.yml:

name: deploy
on:
  pull_request:
    types:
      - closed
    branches: [ "main" ]
jobs:
    build:
        name: Build
        runs-on: ubuntu-latest
        steps:
        - uses: actions/checkout@v3
        - name: Login
          run: |
            echo ${{ secrets.PAT }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
        - name: Build and Publish
          run: |
            docker build . --tag ${{ env.DOCKER_TAG }}
            docker push ${{ env.DOCKER_TAG }}
    deploy:
        name: Deploy
        runs-on: ubuntu-latest
        steps:
        - uses: actions/checkout@v4
        - name: install ssh keys
          # check this thread to understand why its needed:
          # <https://stackoverflow.com/a/70447517>
          run: |
            install -m 600 -D /dev/null ~/.ssh/id_rsa
            echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
            ssh-keyscan -H ${{ secrets.SSH_HOST }} > ~/.ssh/known_hosts
        - name: create docker compose config
          run: |
            cat docker-compose.yml | envsubst > docker-compose-secret.yml
        - name: copy docker compose config
          run: scp docker-compose-secret.yml ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:docker-compose.yml
        - name: connect and pull
          run: ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} "docker-compose pull && docker-compose up -d && exit"
        - name: cleanup config
          if: always()
          run: ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} "rm docker-compose.yml"
        - name: cleanup keys
          if: always()
          run: rm -rf ~/.ssh docker-compose-secret.yml

В данном случае сборка и деплой будет при мёрже пул-реквеста.

Docker-compose будет скачивать образ из ghcr.io без кредов, по этому нужно сделать наш пакет публичным. На странице профиля github смотрим вкладку packages. Выбираем пакет и в разделе Package settings открываем доступ.

Есть опция сделать работу с приватным пакетом, тогда нужно PAT везде использовать, будет чуть сложнее конфиг workflow.

P.S.:

На этом всё. Напишите, как вы разворачиваете с помощью github свои проекты. Вероятно мой вариант не самый удобный.

Конференция «Я.Железо»

Дата
Время14:00 – 23:59
Место

МоскваОнлайн

Конференция «IT IS CONF 2024»

Дата
Время09:00 – 19:00
Место

Екатеринбург

Метки:





Запись опубликована: 24.04.2024

[recovery mode] Jetpack Compose для ленивых

Обновить

e-legion 11 часов назад

Уровень сложностиСредний
Время на прочтение4 мин

Количество просмотров995

Туториал
Recovery Mode

Про Jetpack Compose на сегодняшний день слышал, пожалуй, каждый android-разработчик. Некоторым уже удалось «затащить его в прод», кто-то пробовал его в своих пет-проектах, а кто-то до сих пор сомневается в его целесообразности. Ведь на первый взгляд мы имеем все тоже самое: <TextView> заменили на Text(), Box() очень похож на <FrameLayout>  и др. Единственное, что сразу произвело впечатление — новые Lazy-списки, которые являются заменой привычного RecyclerView и позволяют писать меньше кода. Но, опять же, скопировать и подправить адаптер под очередной экран это уже привычное действие и оно не занимает много времени, а само действие отработано до автоматизма. К числу сомневающихся и, возможно совсем немного lazy, можно было отнести и меня. Но несколько задач, отличных от рутинного перекрашивания кнопок, заставили меня пересмотреть свое отношение к Compose. 

Случай первый. Дано: макет в фигме следующего вида

Если у вас, как и у меня в свое время, появились сомнения в увиденном, то скорее вы все поняли правильно. Необходимо реализовать таблицу с горизонтальным скроллом, при этом первая ячейка должна быть жестко фиксирована. Зачем? «Увидели, понравилось, хотим себе». 

Как такое делать я не знал. Stackoverflow выдал несколько решений, от которых я начал нервничать. В одном из них была реализация через <Table>. Когда про него вспоминали последний раз? Другие решения были на базе RecyclerView (уже лучше) — кастомные LayoutManager или декораторы. Но главная причина, по которой я отверг эти решения, — ячейки имели статические размеры, а в моем случае высота зависит от текста в первой колонке. Желания прокидывать размеры и пересчитывать высоты ячеек в списке у меня не было, и я снова вернулся к гуглу.

На этом этапе я решил не игнорировать ссылки, которые вели на статьи с Jetpack Compose. Оказывается в LazyColumn/LazyRow помимо item-элементов были добавлены (пока еще Experimental) stickyHeaders (https://developer.android.com/jetpack/compose/lists#sticky-headers). 

Собственно это та штука, которая «прилипает» к верху экрана при скролле. Данная находка открыла во мне второе дыхание. Я посчитал, что для реализации задуманного будет удобно ответ сервера представить ввиде нескольких списков, по списку на каждый столбец.

Тогда начало нашей Compose-функции (назовем ее по-простому DataTable) будет выглядеть так:

Теперь опишем элементы таблицы, их всего три: текстовый хедер, картинка с отображением статуса и ячейка с галочкой/ без галочки.

Время строить таблицу:

(я Lazy, а еще и Sticky!)

(я Lazy, а еще и Sticky!)

Используется LazyRow, так как нужен именно горизонтальный скролл. В stickyHeader задаем фиксированный столбец. В itemsIndexed будем отрисовывать остальные столбцы с «галочками». Если запустить наш код на данном этапе, то экран будет выглядеть вот так:

Некрасиво.

Как видно, изначальная проблема с высотами ячеек при таком подходе все равно остается, поэтому поищем способ ее решения. У Modifier в доступных функциях есть колбэк onGloballyPositioned. Он возвращает координаты элемента на экране после его отрисовки и через них мы будем находить высоту. Для «прокидывания» высот в другие ячейки я использовал mutableState:

Ключ — индекс элемента(можно позволить, поскольку кол-во ячеек во всех столбцах одинаково), значение — высота.

Конечный код LazyRow элемента будет иметь вид: 

Первым создается stickyHeader. После заполнения ячеек контентом, высчитываются высоты и сохраняются в mutableState. 

После отрисовываются столбцы с «галочками». Из mutableState берутся высоты. В верху столбца (для index == 0) ставится картинка.

Запускаем, получаем такой экран, радуемся результату:

Вот так ленивый список сэкономил кучу времени ленивому разработчику и дал мощный пинокзаряд мотивации для изучения Jetpack Compose.

Посмотреть как работает можно тут: https://play.google.com/store/apps/details?id=su.art.spbrealty&hl=ru 

Случай второй. Буквально через пару недель мне поручили «небольшой» редизайн экрана.  Из макета видно, что планируется блок со списком услуг по категроиям. При скролле он фиксируется вверху, в нем перелистываются категории, пор нажатию на категорию мы скроллим до нужной позиции в списке услуг:

В stickyHeader можно поместить любую @Composable — функцию, а значит подход из первого случая будет работать и здесь. Список будет иметь вид:

Сначала размещаем элементы, которые будут скрываться под тулбаром, затем stickyHeader, после — все остальное.

По итогу имеем такой экран:

Посмотреть можно здесь : https://play.google.com/store/search?q=мой%20теле2&c=apps&hl=ru

Случай третий. Кнопка. В данном случае было необходимо разработать экран объявления. При создании макета дизайнеры вдохновлялись Авито, в частности поведением кнопки «написать автору объявления»:

Длинный экран со скроллом. У кнопки «Написать» есть свое место на этом экране. Но если пользователь не дошел до этого места,  то кнопка должна быть видна поверх контента. 

Первой мыслью было поискать в документации stickyFooter, поскольку это как stickyHeader, но внизу. Но, к сожалению, разработчики Google не посчитали нужным такой элемент. И тут у меня появилась безумная идея. 

Что будет, если у LazyColumn задать параметр reverseLayout = true? Будет ли stickyHeader «липнуть» к низу экрана? Как оказалось — будет. Дело за малым: переворачиваем порядок в списке задом наперед и получаем желаемый вид экрана:

Если вы еще не используете Compose в своих проектах, то определенно стоит подумать о том, что бы начать, поскольку он позволяет делать сложные вещи простыми и, что самое главное, экономит время (и нервы).

Хочу надеяться, что мой опыт окажется полезным и поможет другим разработчикам в создании нестандартных экранов. И не бойтесь изучать что-то новое. Возможно когда-нибудь это новое сделает вашу жизнь чуточку легче.

Статью подготовил Илья Кубышкин, Android- разработчик в e-legion

Конференция «Я.Железо»

Дата
Время14:00 – 23:59
Место

МоскваОнлайн

Конференция «IT IS CONF 2024»

Дата
Время09:00 – 19:00
Место

Екатеринбург

Метки:





Запись опубликована: 24.04.2024

Моделирование курса валют методом Монте-Карло

Обновить

NoobodyKms 20 часов назад

Уровень сложностиПростой
Время на прочтение12 мин

Количество просмотров903

Метод Монте-Карло — это мощный инструмент стохастического моделирования, который используется в самых разнообразных областях науки и инженерии. В финансах, этот метод часто применяется для анализа и прогнозирования временных рядов, таких как курс валют или акций. Использование Монте-Карло позволяет оценить не только ожидаемые значения, но и распределение возможных исходов, что крайне важно для управления рисками и принятия обоснованных инвестиционных решений.

Принцип метода заключается в выполнении большого количества стохастических экспериментов (симуляций), основанных на случайных выборках из вероятностных распределений входных параметров. В контексте прогнозирования курса валют, это позволяет моделировать различные экономические сценарии и оценивать потенциальные колебания валютных пар, используя исторические данные.

Ключевой аспект использования Монте-Карло в финансах — это его способность учитывать и анализировать волатильность и дрейф курсов валют. Для повышения точности моделирования и реалистичности получаемых данных часто применяется ГАРЧ модель (Generalized Autoregressive Conditional Heteroskedasticity). ГАРЧ помогает адекватно оценить и моделировать изменчивость волатильности, что является критичным при анализе финансовых временных рядов.

Идейно код выполнялся без готовых реализованных методов из различных либ, такое решение было принято для ознакомления с данным математическим аппаратом.

Проект использует следующие библиотеки и инструменты:

  • yfinance для загрузки финансовых данных.

  • numpy и pandas для обработки данных.

  • matplotlib для визуализации данных.

  • scipy и arch для статистического анализа и моделирования.

Импорт и подготовка данных

Для анализа и симуляции курса валют мы используем исторические данные по валютным парам USD/RUB и EUR/RUB, загруженные через библиотеку yfinance. Эти данные обеспечивают основу для нашего стохастического моделирования, начиная с 2013 года и заканчивая апрелем 2023 года. Сей временной промежуток был выбран, для того чтобы при сравнении сгенерированных значений с реальным учитывалось меньше дискретных значений из «линейного» поведения курса и моделировалось наиболее волатильное поведение курса.

Я брал отрезки [2004;2023], [2008;2020], [2010;2023]и т.д, и каждый раз у меня получался схожий временной ряд с временным рядом «спокойного» поведения доллара. Надеюсь, объяснил. Продолжим!

Анализируемые данные включают закрытие дня для каждой валюты, что позволяет вычислить дневные возвраты и волатильность.

Коротко о данных
# Загрузка исторических данных о валютных курсах
data = yf.download("USDRUB=X EURRUB=X", start="2013-01-01", end="2023-04-13")
Сами данные

Из этих данных мы выбираем курс на закрытии

usd_rates = data['Close']['USDRUB=X'].dropna()
eur_rates = data['Close']['EURRUB=X'].dropna()

Описание функций для симуляции

1. Функция simulate_exchange_rates

Эта функция предназначена для симуляции будущих курсов валют на основе заданных параметров дрейфа и волатильности для каждой валюты. Она использует модель геометрического броуновского движения (GBM), которая часто применяется в финансовом моделировании для описания временных рядов цен или индексов.

Математическое описание GBM:

S_t = S_0 expleft((mu - frac{sigma^2}{2})t + sigma W_tright)

где:

  • ( St ) — цена актива в момент времени ( t ),

  • ( S0 ) — начальная цена актива,

  • ( μ ) — ожидаемый дрейф (среднее значение логарифмической доходности),

  • ( σ ) — стандартное отклонение логарифмической доходности (волатильность),

  • ( Wt ) — винеровский процесс (или стандартное броуновское движение).

В контексте симуляции валютных курсов:

  • Дневные возвраты рассчитываются как

( expleft((mu - frac{sigma^2}{2}) frac{1}{text{days}} + sigma sqrt{frac{1}{text{days}}} Zright) )

где ( Z ) — значения из стандартного нормального распределения.

  • Эти возвраты затем умножаются на последнее известное значение курса валюты для получения симулированных курсов на каждый из дней.

Код
def simulate_exchange_rates(historical_rates, days, simulations, drifts, volatilities):
    dt = 1 / days
    simulated_rates = {}
    for currency, rates in historical_rates.items():
        drift = drifts[currency]
        volatility = volatilities[currency]
        daily_returns = np.exp((drift - 0.5 * volatility**2) * dt +
                               volatility * np.random.normal(0, np.sqrt(dt), (days, simulations)))
        simulation = rates.iloc[-1] * np.cumprod(daily_returns, axis=0)
        simulated_rates[currency] = simulation
    return simulated_rates

2. Функция portfolio_value

Функция portfolio_value рассчитывает стоимость портфеля в рублях, учитывая симулированные курсы валют и количество каждой валюты в портфеле. Стоимость портфеля определяется как:

V = sum_{i=1}^n x_i cdot S_i(T)

где:

  • ( xi ) — количество валюты ( i ) в портфеле,

  • ( Si(T) ) — симулированный курс валюты ( i ) на конец периода ( T ),

  • ( n ) — количество различных валют в портфеле.

Код
def portfolio_value(simulated_rates, portfolio):
    simulations = next(iter(simulated_rates.values())).shape[1]
    values = np.full(simulations, portfolio.get('RUB', 0), dtype=float)
    for currency, amount in portfolio.items():
        if currency != 'RUB':
            values += amount * simulated_rates[currency][-1, :]
    return values

3. Функции calculate_var и calculate_cvar

  • Функция calculate_var оценивает Value at Risk (VaR) портфеля, который представляет собой потенциальную максимальную потерю в стоимости портфеля на заданном уровне доверия (например, 95%). VaR рассчитывается как:

text{VaR}_alpha = V_{(alpha)}

где V (α) — квантиль распределения стоимости портфеля на уровне α.

Код
def calculate_var(portfolio_values, confidence_level=0.95):
    sorted_values = np.sort(portfolio_values)
    index = int((1 - confidence_level) * len(sorted_values))
    return sorted_values[index]
  • Функция calculate_cvar рассчитывает Conditional Value at Risk (CVaR), который представляет среднюю потерю, превышающую VaR:

text{CVaR}_alpha = frac{1}{1-alpha} int_alpha^1 V_u , du

где ( Vu) — значение портфеля в точке квантиля (u).

Код
def calculate_cvar(portfolio_values, confidence_level=0.95):
    var = calculate_var(portfolio_values, confidence_level)
    cvar = portfolio_values[portfolio_values <= var].mean()
    return cvar

4. Функция calculate_confidence_interval

Функция calculate_confidence_interval предназначена для расчета доверительного интервала значений на основе симулированных данных. Доверительный интервал дает представление о том, в каком диапазоне могут колебаться значения с заданной вероятностью.

Математическое описание:

Для расчета доверительного интервала используется метод перцентилей. Он основан на следующих шагах:

  • Сначала симулированные данные сортируются по возрастанию.

  • Затем выбираются значения, соответствующие перцентилям, рассчитанным на основе заданного уровня доверия ( α ).

text{Доверительный интервал} = left( text{Перцентиль}_{left(frac{1-alpha}{2}right) times 100}, text{Перцентиль}_{left(1-frac{1-alpha}{2}right) times 100} right)

Этот метод позволяет оценить, например, верхнюю и нижнюю границу ожидаемых значений курса валюты с вероятностью ( α ).

Код
def calculate_confidence_interval(simulated_data, confidence_level=0.95):
    lower_percentile = (1 - confidence_level) / 2 * 100
    upper_percentile = (1 - (1 - confidence_level) / 2) * 100
    lower_bound = np.percentile(simulated_data, lower_percentile)
    upper_bound = np.percentile(simulated_data, upper_percentile)
    return lower_bound, upper_bound

5. Функция calculate_drift_volatility

Функция calculate_drift_volatility предназначена для расчета дрейфа и волатильности на основе исторических данных о курсах валют. Эти параметры критично важны для моделирования будущих цен по модели геометрического броуновского движения.

Процесс расчета:

  • Из исторических данных сначала рассчитываются логарифмические доходности:

 ( r_t = lnleft(frac{S_t}{S_{t-1}}right) )

  • Дрейф ( μ ) определяется как среднее значение этих доходностей.

  • Волатильность (σ) — это стандартное отклонение доходностей.

Обычно для расчета этих параметров используется скользящее окно (например, 252 дня, что соответствует количеству торговых дней в году), что позволяет учитывать последние изменения на рынке и делает модель более адаптивной к текущим условиям.

Код
def calculate_drift_volatility(rates, lookback_period=252):
    log_returns = np.log(rates / rates.shift(1)).dropna()
    rolling_mean = log_returns.rolling(window=lookback_period).mean()
    rolling_std = log_returns.rolling(window=lookback_period).std()
    drift = rolling_mean.iloc[-1]
    volatility = rolling_std.iloc[-1]
    return drift, volatility

6. Функция fit_garch_model

Функция fit_garch_model применяется для моделирования и оценки волатильности с использованием ГАРЧ модели. ГАРЧ модель особенно полезна для финансовых временных рядов, где волатильность не является постоянной, а изменяется со временем.

Процесс подгонки модели:

  • Входные данные для модели — это логарифмические доходности.

  • Модель ГАРЧ параметризуется значениями ( p ) и ( q ), которые представляют собой порядки модели для условной дисперсии и скользящего среднего соответственно.

  • Результат подгонки модели включает оценки долгосрочной волатильности и условного математического ожидания, что критично для точного прогнозирования будущих колебаний курсов валют.

ГАРЧ модель позволяет более точно оценить риск и управлять им, поскольку принимает во внимание изменчивость волатильности, что является стандартным явлением для финансовых рынков.

Инициализация и выполнение модели Монте-Карло для валютных курсов

После подготовки всех необходимых функций и данных, мы переходим к основному этапу анализа — инициализации и выполнению модели Монте-Карло. Этот процесс включает несколько ключевых шагов:

Расчет дрейфа и волатильности

  1. Извлечение логарифмических доходностей: Используя исторические данные курсов USD и EUR, рассчитываются ежедневные логарифмические доходности. Это первый шаг в процессе определения дрейфа и волатильности, которые понадобятся для симуляций.

    r_t = lnleft(frac{S_t}{S_{t-1}}right)

  2. Расчет скользящих средних и стандартных отклонений: На основе логарифмических доходностей с помощью скользящего окна в 252 дня (примерно количество торговых дней в году) рассчитываются скользящие средние и стандартные отклонения. Эти значения представляют собой дрейф и волатильность каждой валюты.

Секретик

Вообще, функция calculate_confidence_interval была написана мною до того, как я узнал про GARCH, так что я из досады решил оставить инициализацию дрейфа и волатильности двумя способами

Подгонка ГАРЧ модели

  1. Подгонка ГАРЧ модели: Для каждой валюты подгоняется ГАРЧ модель к логарифмическим доходностям. Модель ГАРЧ помогает оценить долгосрочную волатильность и условное среднее, что важно для точности симуляций.

Симуляция валютных курсов

  1. Инициализация симуляций: С использованием полученных параметров дрейфа и волатильности запускаются симуляции курсов валют для заданного количества дней (252 дня в примере) и количества симуляций (1,000,000 в примере). Каждая симуляция представляет возможное будущее состояние курса валюты.

    • Симуляции проводятся по модели геометрического броуновского движения, описанного выше.

Ввод данных пользователем

  1. Получение данных портфеля от пользователя: Запрашивается у пользователя ввод данных о количестве рублей, долларов и евро в его портфеле. Эти данные необходимы для дальнейшего расчета стоимости портфеля.

Весь код
historical_rates = {'USD': usd_rates, 'EUR': eur_rates}
days = 252
simulations = 1000000
# Расчет дрейфа и волатильности для Моделирования
# через calculate_drift_volatility
drifts = {}
volatilities = {}
for currency, rates in historical_rates.items():
    drifts[currency], volatilities[currency] = calculate_drift_volatility(rates)
# Расчет дрейфа и волатильности для Моделирования via garch
usd_log_returns = np.log(usd_rates / usd_rates.shift(1)).dropna()
eur_log_returns = np.log(eur_rates / eur_rates.shift(1)).dropna()
usd_drift_garch, usd_volatility_garch = fit_garch_model(usd_log_returns)
eur_drift_garch, eur_volatility_garch = fit_garch_model(eur_log_returns)
# Обновление словарей дрейфов и волатильности для использования в симуляции Монте-Карло
drifts['USD'] = usd_drift_garch
drifts['EUR'] = eur_drift_garch
volatilities['USD'] = usd_volatility_garch
volatilities['EUR'] = eur_volatility_garch
# Моделирование курсов валют
simulated_rates = simulate_exchange_rates(historical_rates, days, simulations, drifts, volatilities)
# Ввод данных пользователем
rub_amount = float(input("Введите количество рублей: "))
usd_amount = float(input("Введите количество долларов: "))
eur_amount = float(input("Введите количество евро: "))
# Получение данных пользователя
portfolio = {'RUB': rub_amount, 'USD': usd_amount, 'EUR': eur_amount}
# Расчет стоимости портфеля
portfolio_values = portfolio_value(simulated_rates, portfolio)

После этого через input запрашивается предполагаемый портфель в RUB, EUR, USD

портфель из 1 доллара выбрал для наглядности проверки var и cvar дальше

Расчет стоимости портфеля и рисков

  1. Расчет стоимости портфеля и рисков: Используя симулированные курсы валют и данные о количестве валют в портфеле пользователя, рассчитывается стоимость портфеля и ключевые показатели риска (VaR и CVaR).

Код
cvar = calculate_cvar(portfolio_values)
print(f"Максимальный риск портфеля (95% довер): {cvar}")

Ответ:

Анализ и визуализация результатов

  1. Статистический анализ и визуализация: Проводится сравнение исторических и симулированных данных с помощью статистических тестов, таких как Колмогорова-Смирнова тест и t-тест для независимых выборок. Результаты визуализируются, чтобы наглядно показать реальные и симулированные данные по курсам валют.

  2. Выводы и доверительные интервалы: Определяются доверительные интервалы для курсов валют на конец периода симуляций, что дает представление о возможных колебаниях курсов в будущем.

Код для тестов

Загрузка данных за последний год

data_recent = yf.download("USDRUB=X EURRUB=X", start="2023-04-12", end="2024-04-13")['Close'].dropna()
data_recent_usd = data_recent['USDRUB=X'].dropna()
data_recent_eur = data_recent['EURRUB=X'].dropna()
real_data_last_year = data_recent_usd[-days:]
simulated_data_first_path = simulated_rates['USD'][:, 0]
ks_stat, p_value_ks = ks_2samp(real_data_last_year, simulated_data_first_path)
t_stat, p_value_t = ttest_ind(real_data_last_year, simulated_data_first_path, equal_var=False)
print(f"KS Statistic: {ks_stat}, P-value (KS-test): {p_value_ks}")
print(f"T-statistic: {t_stat}, P-value (T-test): {p_value_t}")

Ответ:

Оба теста вместе показывают, что существует статистически значимое расхождение между реальными и симулированными данными, НО…

Код для доверительных интервалов
usd_confidence_interval = calculate_confidence_interval(simulated_rates['USD'][-1, :])
eur_confidence_interval = calculate_confidence_interval(simulated_rates['EUR'][-1, :])
print(f"Доверительный интервал 95% для курса доллара: {usd_confidence_interval}")
print(f"Доверительный интервал 95% для курса евро: {eur_confidence_interval}")

Ответ:

Визуализация
# Визуализация исторических и симулированных данных
plt.figure(figsize=(14, 7))
plt.plot(usd_rates.index, usd_rates,
         label='Реальные данные(2013-01-01 - 2023-04-12)')
plt.plot(data_recent_usd.index, data_recent_usd,
         label='Реальные данные (2023-04-13 - 2024-04-13)', color='green', alpha=0.7)
plt.plot(pd.date_range(start=usd_rates.index[-1], periods=days, freq='B'),
         simulated_rates['USD'][:, 0], label='Смоделированные данные', color='orange', alpha=0.7)
plt.legend()
plt.title('Реальные vs Смоделированные USD/RUB курсы')
plt.xlabel('Дата')
plt.ylabel('Курс')
plt.savefig('RUBUSD.png')
plt.show()
# Визуализация исторических и симулированных данных
plt.figure(figsize=(14, 7))
plt.plot(eur_rates.index, eur_rates,
         label='Реальные данные(2013-01-01 - 2023-04-12)')
plt.plot(data_recent_eur.index, data_recent_eur,
         label='Реальные данные (2023-04-13 - 2023-04-13)', color='green', alpha=0.7)
plt.plot(pd.date_range(start=eur_rates.index[-1], periods=days, freq='B'),
         simulated_rates['EUR'][:, 0], label='Смоделированные данные', color='orange', alpha=0.7)
plt.legend()
plt.title('Реальные vs Смоделированные EUR/RUB курсы')
plt.xlabel('Дата')
plt.ylabel('Курс')
plt.savefig('RUBEUR.png')
plt.show()
plt.figure(figsize=(10, 6))
for i in range(15):
    plt.plot(simulated_rates['USD'][:, i], linewidth=1, alpha=0.8)
plt.title('Моделирование траекторий обменного курса доллара к рублю')
plt.xlabel('Дни')
plt.ylabel('Курс')
plt.show()
plt.figure(figsize=(10, 6))
for i in range(15):
    plt.plot(simulated_rates['EUR'][:, i], linewidth=1, alpha=0.8)
plt.title('Моделирование траекторий обменного курса евро к рублю')
plt.xlabel('Дни')
plt.ylabel('Курс')
plt.show()

Бонус

Смоделируем поведение доллара на год вперед (04.2024 — 04.2025]

Прогноз на год вперед
historical_rates_pred = {'USD': data_recent_usd, 'EUR': data_recent_eur}
days_pred= 252
simulations_pred = 1000000
# Расчет дрейфа и волатильности для Моделирования
drifts_pred = {}
volatilities_pred = {}
for currency_pred, rates_pred in historical_rates_pred.items():
    drifts_pred[currency_pred], volatilities_pred[currency_pred] = calculate_drift_volatility(rates_pred)
usd_log_returns_pred = np.log(usd_rates / usd_rates.shift(1)).dropna()
eur_log_returns_pred = np.log(eur_rates / eur_rates.shift(1)).dropna()
usd_drift_garch_pred, usd_volatility_garch_pred = fit_garch_model(usd_log_returns_pred)
eur_drift_garch_pred, eur_volatility_garch_pred = fit_garch_model(eur_log_returns_pred)
# Обновление словарей дрейфов и волатильности для использования в симуляции Монте-Карло
drifts_pred['USD'] = usd_drift_garch_pred
drifts_pred['EUR'] = eur_drift_garch_pred
volatilities_pred['USD'] = usd_volatility_garch_pred
volatilities_pred['EUR'] = eur_volatility_garch_pred
# Моделирование курсов валют
simulated_rates_pred = simulate_exchange_rates(historical_rates_pred, days_pred, simulations_pred, drifts_pred, volatilities_pred)
# Ввод данных пользователем
rub_amount_pred = float(input("Введите количество рублей: "))
usd_amount_pred = float(input("Введите количество долларов: "))
eur_amount_pred = float(input("Введите количество евро: "))
# Получение данных пользователя
portfolio_pred = {'RUB': rub_amount_pred, 'USD': usd_amount_pred, 'EUR': eur_amount_pred}
# Расчет стоимости портфеля
portfolio_values_pred = portfolio_value(simulated_rates_pred, portfolio_pred)

Тут я создал портфель с 1000 USD

cvar_pred = calculate_cvar(portfolio_values_pred)
print(f"Максимальный риск портфеля (95% довер): {cvar_pred}")

Максимальный риск портфеля (95% довер): 71827.22023513853

usd_confidence_interval_pred = calculate_confidence_interval(simulated_rates_pred['USD'][-1, :])
eur_confidence_interval_pred = calculate_confidence_interval(simulated_rates_pred['EUR'][-1, :])
print(f"Доверительный интервал 95% для курса доллара: {usd_confidence_interval_pred }")
print(f"Доверительный интервал 95% для курса евро: {eur_confidence_interval_pred }")

Доверительный интервал 95% для курса доллара: (72.63404780533205, 116.80234915827717)

Доверительный интервал 95% для курса евро: (94.25965585147713, 104.5288904591821)

plt.figure(figsize=(14, 7))
plt.plot(pd.date_range(start=data_recent_usd.index[-1], periods=days, freq='B'),
         simulated_rates_pred['USD'][:, 0], label='Смоделированные данные', color='blue', alpha=0.7)
plt.legend()
plt.title('Смоделированные USD/RUB курс на год')
plt.xlabel('Дата')
plt.ylabel('Курс')
plt.grid()
plt.savefig('RUBUSD_pred.png')
plt.show()

Этот прогноз представляет собой обобщенный анализ на основе модели Монте-Карло и не учитывает все возможные будущие условия и изменения. Он демонстрирует потенциал метода для создания информированных и обоснованных прогнозов, подкрепленных статистическим анализом и симуляцией данных.

Заключение

В данной статье мы провели обширный анализ временных рядов курсов валют с использованием метода Монте-Карло, обогащенного моделями ГАРЧ для уточнения параметров дрейфа и волатильности. Доверять полученным значениям курсов не стоит — определенно, об этом говорит разность графиков, значения к-с и t тестов, но всё же для понимания принципов вышло очень хорошо.

Это лишь пример «на пальцах» с демонстрацией кода, чтобы зажечь затейливые умы к совершенствованию уже существующих методов, ну и похардкодить тоже было приятно.

Конференция «Я.Железо»

Дата
Время14:00 – 23:59
Место

МоскваОнлайн

Конференция «IT IS CONF 2024»

Дата
Время09:00 – 19:00
Место

Екатеринбург

Метки: