...

суббота, 14 ноября 2020 г.

Учим железки разговаривать, или ESP32 DAC и немного таймера

В ходе разработки одного очень интересного устройства (эх, лишь бы силенок хватило) я решил, что будет неплохо, если устройство это будет говорящим. Как нельзя кстати здесь пригодилось наличие в целевом микроконтроллере, ESP32 компании Espressif Systems, двухканального 8-битного ЦАПа.

В этом туториале (если его можно так назвать) я покажу, как можно быстро и довольно просто организовать проигрывание аудиофайла силами микроконтроллера ESP32.
Немного теории

Как нам сообщает Википедия, ESP32 — это серия недорогих микроконтроллеров с низким энергопотреблением. Они представляют собой систему на кристалле (SoC) с интегрированными контроллерами Wi-Fi и Bluetooth, и антеннами. Основаны на ядре Tensilica Xtensa LX6 в вариантах с одним и двумя ядрами. В систему интегрирован радиочастотный тракт. МК создан и разработан китайской компанией Espressif Systems, а производится компанией TSMC по техпроцессу 40 нм. Подробнее о возможностях чипа можно прочитать на странице в Википедии и в официальной документации.

Однажды в рамках освоения этого контроллера мне захотелось воспроизвести на нем звук. Поначалу я думал, что придется использовать ШИМ. Однако, внимательнее почитав документацию, я обнаружил наличие двух каналов 8-битного ЦАПа. Разумеется, это в корне меняло дело.

В Technical Reference сказано, что ЦАП в ESP32 построен на цепочке резисторов (видимо, имеется ввиду R2R цепочка) с применением некоего буфера. Выходное напряжение может изменяться в пределах от 0 вольт до напряжения питания (3,3 вольта) с разрешением 8 бит (то есть 256 значений). Преобразование двух каналов независимое. Также имеется встроенный генератор косинуса (CW generator) и поддерживается DMA.

Я решил пока что не лезть в DMA, ограничившись построением проигрывателя на основе таймера. Как известно, чтобы воспроизвести простейший WAV-файл формата PCM, достаточно с указанной в файле частотой дискретизации читать из него сырые данные и распихивать по каналам ЦАПа, предварительно приводя (при необходимости) разрядность данных к разрядности ЦАП. Мне повезло: у меня нашелся набор звуков в формате WAV PCM 8 bit 11025 Hz mono, выдранный из ресурсов одной старой игры. Значит, будем использовать только один канал ЦАП.

Также нам понадобится таймер, способный генерировать прерывания с частотой 11025 Гц. Согласно все тому же Technical Reference, ESP32 имеет на борту два модуля таймеров по два таймера в каждом, итого четыре таймера. Они имеют разрядность 64 бита, у каждого имеется 16-битный предделитель и возможность генерации прерывания по уровню или фронту.

От теории к практике

Вооружившись примером wave_gen из esp-idf, я отправился писать код. Я не стал упарываться созданием файловой системы: цель была получить звук, а не сделать из ESP32 полноценный плеер.

Для начала перегнал один из WAV-файлов в сишный массив. В этом мне очень помогла встроенная в Дебиан утилита xxd. Простой командой

$ xxd -i file.wav > file.c

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

Далее я закомментировал первые 44 байта массива — заголовок WAV-файла. Попутно я разобрал его по полям и узнал всю необходимую мне информацию о нем:

const uint8_t sound_wav[] = {
//  0x52, 0x49, 0x46, 0x46,     // chunk "RIFF"
//  0xaa, 0xb4, 0x01, 0x00,     // chunk length
//  0x57, 0x41, 0x56, 0x45,     // "WAVE"
//  0x66, 0x6d, 0x74, 0x20,     // subchunk1 "fmt"
//  0x10, 0x00, 0x00, 0x00,     // subchunk1 length
//  0x01, 0x00,                         // audio format PCM
//  0x01, 0x00,                         // 1 channel, mono
//  0x11, 0x2b, 0x00, 0x00,     // sample rate
//  0x11, 0x2b, 0x00, 0x00,     // byte rate
//  0x01, 0x00,                         // bytes per sample
//  0x08, 0x00,                         // bits per sample per channel
//  0x64, 0x61, 0x74, 0x61,     // subchunk2 "data"
//  0x33, 0xb4, 0x01, 0x00,     // subchunk2 length, bytes

Отсюда видно, что наш файл имеет один канал, частоту дискретизации 11025 герц и разрешение 8 бит на семпл. Заметим, что если бы я захотел анализировать заголовок программно, то мне нужно было бы учитывать порядок байт: в WAV это Little-endian, то есть младшим байтом вперед.

В итоге я создал тип структуры для хранения информации о звуке:

typedef struct _audio_info
{
        uint32_t sampleRate;
        uint32_t dataLength;
        const uint8_t *data;
} audio_info_t;

И создал собственно экземпляр структуры, заполнив ее следующим образом:
const audio_info_t sound_wav_info =
{
        11025, // sampleRate
        111667, // dataLength
        sound_wav // data
};

В этой структуре поле sampleRate — это значение одноименного поля заголовка, поле dataLength — это значение поля subchunk2 length, а поле data — это указатель на массив с данными.

Далее я подключил заголовочные файлы

#include "driver/timer.h"
#include "driver/dac.h"

и создал прототипы функций для инициализации таймера и обработчика его прерывания Alarm, как это сделано в примере wave_gen:
static void IRAM_ATTR timer0_ISR(void *ptr)
{

}

static void timerInit()
{

}

После чего принялся за наполнение функции инициализации.
Таймеры в ESP32 тактируются в конечном итоге от APB_CLK_FREQ, равного 80 МГц:

driver/timer.h:

#define TIMER_BASE_CLK   (APB_CLK_FREQ)  /*!< Frequency of the clock on the input of the timer groups */

soc/soc.h:
#define  APB_CLK_FREQ    ( 80*1000000 )       //unit: Hz

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

Таким образом, функция инициализации таймера получает следующий вид:

static void timerInit()
{
        timer_config_t config = {
                .divider = 8, // Предделитель
                .counter_dir = TIMER_COUNT_UP, // Считать вверх
                .counter_en = TIMER_PAUSE, // Состояние - пауза
                .alarm_en = TIMER_ALARM_EN, // Включить прерывание Alarm
                .intr_type = TIMER_INTR_LEVEL, // Прерывание по уровню
                .auto_reload = 1, // Автоматически перезапускать счет
        };

        // Применить конфиг
        ESP_ERROR_CHECK(timer_init(TIMER_GROUP_0, TIMER_0, &config));
        // Установить начальное значение счетчика
        ESP_ERROR_CHECK(timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0x00000000ULL));
        // Установить значение счетчика для срабатывания прерывания Alarm
        ESP_ERROR_CHECK(timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, TIMER_BASE_CLK / config.divider / sound_wav_info.sampleRate));
        // Разрешить прерывания
        ESP_ERROR_CHECK(timer_enable_intr(TIMER_GROUP_0, TIMER_0));
        // Зарегистрировать обработчик прерывания
        timer_isr_register(TIMER_GROUP_0, TIMER_0, timer0_ISR, (void *)&sound_wav_info, ESP_INTR_FLAG_IRAM, NULL);
        // Запустить таймер
        timer_start(TIMER_GROUP_0, TIMER_0);
}

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

Теперь переходим к написанию обработчика прерывания. Здесь все просто: берем очередной байт из массива, скармливаем его ЦАПу, продвигаемся по массиву дальше. Однако прежде всего нужно очистить флаги прерываний таймера и перезапустить прерывание Alarm:

static uint32_t wav_pos = 0;

static void IRAM_ATTR timer0_ISR(void *ptr)
{
        // Очистить флаги прерываний
        timer_group_clr_intr_status_in_isr(TIMER_GROUP_0, TIMER_0);
        // Перезапустить прерывание Alarm
        timer_group_enable_alarm_in_isr(TIMER_GROUP_0, TIMER_0);

        audio_info_t *audio = (audio_info_t *)ptr;
        if (wav_pos >= audio->dataLength) wav_pos = 0;
        dac_output_voltage(DAC_CHANNEL_1, *(audio->data + wav_pos));
        wav_pos ++;
}

Да, работа со встроенным ЦАПом в ESP32 сводится к вызову одной встроенной функции dac_output_voltage (на самом деле нет).

Собственно, все. Теперь нужно внутри функции app_main() разрешить работу нужного нам канала ЦАП и инициализировать таймер:

void app_main(void)
{
    …
    
    ESP_ERROR_CHECK(dac_output_enable(DAC_CHANNEL_1));
    timerInit();

Собираем, прошиваем, слушаем :) В принципе, можно подключить динамик напрямую к ножке контроллера — играть будет. Но лучше все же воспользоваться усилителем. Я использовал завалявшуюся у меня в закромах TDA7050.

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

Может быть, когда-нибудь (и если эта недо-статья кому-нибудь понравится) я заведу ЦАП ESP32 с использованием DMA. Там все еще интереснее, потому что в этом случае работать придется со встроенным модулем I2S.

Let's block ads! (Why?)

[Из песочницы] Как я при помощи Google сделал OPC2WEB клиент

Я работаю инженером АСУТП и немного увлекаюсь программированием: при помощи Гугла и Stack Overflow делал несколько калькуляторов на HTML и javascript, делал бота для телеграма на php, даже немного программировал на c# по работе. В этот раз задача была куда интереснее и сложнее, хотя и звучала просто: «хочу видеть в своем браузере текущую скорость агрегата». Для начала я решил попробовать поискать готовый софт: естественно такое уже давно придумано, есть готовые и даже бесплатные SCADA системы, которые могут работать и в качестве веб сервера, но они все были сильно наворочены и сложны для моего понимания, к тому же нужно было просто вывести скорость. Поэтому я подумал что можно попробовать сделать это самому и вот что из этого вышло:

Backend


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


Поиски этого привели меня на хабр, где я узнал про бесплатную библиотеку OPCDOTNET. В архиве библиотеки лежал исходник консольного клиента, который я скомпилировал на своем компьютере, запустил простой OPC симулятор (gray-box)… и о чудо! я увидел в консоли изменяющиеся числа. Это значит, что теперь я смогу их отправлять в качестве ответа по вебзапросу. Следующим заходом в гугл стал запрос простого веб сервера где наткнулся на пример использования HttpListener. Запустил пример в отдельном проекте, понял как это работает, и стал добавлять все это к своему OPC клиенту. Через много попыток компиляций, поиска ошибок на Stack Overflow у меня все же получилось увидеть в браузере заветную «скорость». Это была победа! Но я сразу же понял, что одна лишь скорость это не серьезно, через время технологи захотят увидеть и другие параметры линии, поэтому нужно придумать как добавлять необходимые сигналы, без изменения программы. На помощь пришли файлы конфигурации, где можно заранее задать какие сигналы хотим видеть, задать порт прослушивания сервера, время обновления и прочее. Опыт в создании файлов конфигурации уже имелся, поэтому сделал так как ранее делал и проверенно работало. Так же в процессе пришлось обратиться к другу программисту, который подсказал что сделать чтобы передавался полный массив запрашиваемых данных, а не только те значения что менялись (в готовом примере OPC клиента в консоли отображались только изменяемые значения).

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

Frontend


Пришел я к этому не сразу: сначала стал гуглить как сделать так чтобы данные на странице обновлялись без перезагрузки. Как выяснилось нужно использовать AJAX, то есть изменять данные через javascript, а принимать их через JSON. В клиенте простой конкатенацией строк сделал генерацию JSON, причем для универсальности решил просто отсчитывать по порядку задаваемые в конфиге теги. Потом нашел пример в котором через javascript запрашивается ежесекундно JSON строка и выводятся значения из нее. Поменяв код под свои нужды и запустив страницу я увидел что все работает — данные обновляются без перезагрузки страницы (!). Это была еще одна победа. Теперь оставалось дело за малым — грамотно распределить на странице полученные данные, то есть сделать что-то в виде визуализации. Сначала я решил сделать так же таблицу, но потом понял что блочная структура смотрится красивее и функциональнее. Блоки можно окрашивать в разные цвета и менять их размер. А еще нужно сделать так чтобы пользователь мог самостоятельно добавлять и изменять структуру, не буду же я на каждую новую хотелку переписывать HTML файл. В итоге получился такой вот вариант, как на картинке ниже.

Здесь можно добавлять большие блоки, которые будут объединять малые блоки с одним признаком. Такие большие блоки можно озаглавливать так как нужно, менять их цвета (если щелкнуть по блоку с зажатой клавишей shift) и менять их размер. Блоки со значениями добавляются при двойном клике по большому блоку. В них так же можно задавать свои названия и единицы измерения. Если нечаянно добавил не тот элемент или не туда, то можно удалить его — я подсмотрел эту функцию в одном букмарклете, полностью перенеся его код на страницу. Конечно вся созданная структура после перезагрузки страницы исчезнет и для ее сохранения нашел такую возможность как локальное хранилище. А для того чтобы перенести готовую структуру на другой компьютер сделал импорт и экспорт экрана из локального хранилища.

Единственная проблема оставалась с перетаскиванием блоков — хотелось бы сделать красиво drag and drop, но для меня это оказалось непосильно. Вышел из ситуации так: если открыть страницу в панели разработчика в хроме, то блоки можно перетаскивать. Это натолкнуло на мысль что задействовав правую кнопку мыши можно просто менять блоки местами. Сейчас такая система вполне универсальная: чтобы добавить новый сигнал нужно просто добавить нужный OPC тег в конфиг и перезапустить клиента. Добавленный тег автоматически добавляется в JSON и на экране вывода появляется внизу новое значение, которое можно несколькими кликами добавить в существующий или новый блок на странице. На данный момент на странице выводится больше 60 тегов и больше половины из них добавлял уже не я, то есть процесс добавления может и не самый простой, но не требует переписывания программы и страницы вывода. Протестировать и посмотреть код этой страницы можно тут

Заключение


Поскольку данная статья должна быть вроде инструкции, как непрограммист вроде меня с помощью поисковиков может сделать что-то полезное, то наверное нужно добавить немного слов о том как именно я искал информацию. Тут впору говорить как на картинке в самом начале: думаешь что ты хочешь получить и спрашиваешь об этому у гугла, а если что-то где-то не получается, то смотришь на коды ошибок и спрашиваешь снова. Очень помогает поиск на английском языке — даже вбив просто ключевые слова можно получить ссылку на подобную решенную проблему на стаковерфлоу с вероятностью 80%. Для поиска готовых примеров, код из которого можно тупо взять и перенести в свою программу, можно добавлять такие ключевые слова как «example» или по-русски «пример». Несколько хороших идей нашлось на хабре, то есть можно попробовать в запрос вставить ключевое слово «habr», но я таким пользовался только тогда когда точно знал что на хабре видел решение которое ищу. Практически любая мелкая задача из всего того, что было сделано, решалась через поисковик: «change div color shift click js», «make div resizeable», «как редактировать веб страницу»… сотня вариаций разных запросов. Возможно в комментариях профи могут поделиться своими советами.

И да, раз уж речь зашла о советах, то мне бы еще хотелось получить от вас конструктивную критику и полезные советы. Возможно кто-то захочет размять мозги и сможет за пару часов накидать куда более функциональное решение. Или может кого-то этот пост натолкнет на интересные идеи, ведь таким способом можно принимать любой JSON запрос и сделать на его основе любую визуальную структуру. Было бы очень круто заиметь похожее универсальное решение, где можно любые данные распределять так как тебе это удобно, управляя простыми визуальными формами, drag and drop, resize и все такое прочее, чтобы красиво и функционально, а не вот это вот все. Хотя и так получилось неплохо, я считаю. Скорость агрегата, как и просил заказчик, теперь можно наблюдать из браузера и добавить что-то новое не составит большого труда.

Ссылка на код клиента на C#

Либо под спойлером
/*=====================================================================
  File:      OPCCSharp.cs

  Summary:   OPC sample client for C#

-----------------------------------------------------------------------
  This file is part of the Viscom OPC Code Samples.

  Copyright(c) 2001 Viscom (www.viscomvisual.com) All rights reserved.

THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
======================================================================*/

using System;
using System.Threading;
using System.Runtime.InteropServices;
using System.Configuration;
using OPC.Common;
using OPC.Data;
using System.Net;
using System.Globalization;
using System.Data.SqlClient;
using System.Data;
using System.Net.Sockets;


namespace CSSample
{
    class Tester
    {
        // ***********************************************************  EDIT THIS :
        string serverProgID = ConfigurationManager.AppSettings["opcID"];         // ProgID of OPC server

        private OpcServer theSrv;
        private OpcGroup theGrp;
        private static float[] currentValues;
        private static string responseStringG ="";
        private static HttpListener listener = new HttpListener();

        private static string consoleOut = ConfigurationManager.AppSettings["consoleOutput"];
        private static string answerType = ConfigurationManager.AppSettings["answerType"];
        private static string portNumb = ConfigurationManager.AppSettings["portNumber"];
        private static int timeref = Int32.Parse(ConfigurationManager.AppSettings["refreshTime"]);
        private static string[] tagsNames = ConfigurationManager.AppSettings["tagsNames"].Split(','); // tags from config
        private static string[] ratios = ConfigurationManager.AppSettings["ratios"].Split(',');

        private static string sqlSend = ConfigurationManager.AppSettings["sqlSend"];
        private static string udpSend = ConfigurationManager.AppSettings["udpSend"];
        private static string webSend = ConfigurationManager.AppSettings["webSend"];
        private static string table_name = ConfigurationManager.AppSettings["table"]; // название таблицы из конфига;
        private static string column_name = ConfigurationManager.AppSettings["column"];
        private static int sendtags = Int32.Parse(ConfigurationManager.AppSettings["tags2send"]);
        
        private static IPAddress remoteIPAddress = IPAddress.Parse(ConfigurationManager.AppSettings["remoteIP"]); // Ip from config
        private static int remotePort = Convert.ToInt16(ConfigurationManager.AppSettings["remotePort"]); // remote port from config

        public static SqlConnection myConn = new SqlConnection(ConfigurationManager.ConnectionStrings["connstr"].ConnectionString); //строка соединения с SQL которая берется из конфига
        SqlCommand myCommand = new SqlCommand("Command String", myConn);

        public void Work()
        {
            /*  try                                             // disabled for debugging
                {       */

            theSrv = new OpcServer();
            theSrv.Connect(serverProgID);
            Thread.Sleep(500);              // we are faster then some servers!

            // add our only working group
            theGrp = theSrv.AddGroup("OPCCSharp-Group", false, timeref);

            string[] tags = ConfigurationManager.AppSettings["tags"].Split(','); // tags from config
            if (sendtags > tags.Length) sendtags = tags.Length;

                var itemDefs = new OPCItemDef[tags.Length];
            for (var i = 0; i < tags.Length; i++)
            {
                itemDefs[i] = new OPCItemDef(tags[i], true, i, VarEnum.VT_EMPTY);
            }

            OPCItemResult[] rItm;
            theGrp.AddItems(itemDefs, out rItm);
            if (rItm == null)
                return;
            if (HRESULTS.Failed(rItm[0].Error) || HRESULTS.Failed(rItm[1].Error))
            {
                Console.WriteLine("OPC Tester: AddItems - some failed"); theGrp.Remove(true); theSrv.Disconnect(); return;

            };

            var handlesSrv = new int[itemDefs.Length];
            for (var i = 0; i < itemDefs.Length; i++)
            {
                handlesSrv[i] = rItm[i].HandleServer;
            }

            currentValues = new Single[itemDefs.Length];

            // asynch read our two items
            theGrp.SetEnable(true);
            theGrp.Active = true;
            theGrp.DataChanged += new DataChangeEventHandler(this.theGrp_DataChange);
            theGrp.ReadCompleted += new ReadCompleteEventHandler(this.theGrp_ReadComplete);


            int CancelID;

            int[] aE;
            theGrp.Read(handlesSrv, 55667788, out CancelID, out aE);

            // some delay for asynch read-complete callback (simplification)
            Thread.Sleep(500);

            while (webSend=="yes")
            {
                HttpListenerContext context = listener.GetContext();
                HttpListenerRequest request = context.Request;
                HttpListenerResponse response = context.Response;
                context.Response.AddHeader("Access-Control-Allow-Origin", "*");


                byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseStringG);
                // Get a response stream and write the response to it.
                response.ContentLength64 = buffer.Length;
                System.IO.Stream output = response.OutputStream;
                output.Write(buffer, 0, buffer.Length);
                // You must close the output stream.
                output.Close();
            }
            // disconnect and close
            Console.WriteLine("************************************** hit <return> to close...");
            Console.ReadLine();
            theGrp.ReadCompleted -= new ReadCompleteEventHandler(this.theGrp_ReadComplete);
            theGrp.RemoveItems(handlesSrv, out aE);
            theGrp.Remove(false);
            theSrv.Disconnect();
            theGrp = null;
            theSrv = null;


            /*  }
            catch( Exception e )
                {
                Console.WriteLine( "EXCEPTION : OPC Tester " + e.ToString() );
                return;
                }       */
        }

        // ------------------------------ events -----------------------------

        public void theGrp_DataChange(object sender, DataChangeEventArgs e)
        {

            foreach (OPCItemState s in e.sts)
            {
                if (HRESULTS.Succeeded(s.Error))
                {
                    if (consoleOut == "yes")
                    {
                        Console.WriteLine(" ih={0} v={1} q={2} t={3}", s.HandleClient, s.DataValue, s.Quality, s.TimeStamp); // выводит данные при изменении в консоль
                    }
                    currentValues[s.HandleClient] = Convert.ToSingle(s.DataValue) * Single.Parse(ratios[s.HandleClient], CultureInfo.InvariantCulture.NumberFormat); //добавляет в массив измененное значение тега
                }
                else
                    Console.WriteLine(" ih={0}    ERROR=0x{1:x} !", s.HandleClient, s.Error);
            }
            string responseString = "{";
            if (answerType == "table")
            {
                responseString = "<HTML><head><meta charset=\"UTF-8\"><meta http-equiv=\"Refresh\" content=\"" + timeref / 1000 + "\"/></head>" +
            "<BODY><table border><tr><td>" + string.Join("<br>", tagsNames) + "</td><td >" + string.Join("<br>", currentValues) + "</td></tr></table></BODY></HTML>";
                responseStringG = responseString;
            }
            else
            {
                for (int i = 0; i < currentValues.Length - 1; i++) responseString = responseString + "\"tag" + i + "\":\"" + currentValues[i] + "\", ";
                responseString = responseString + "\"tag" + (currentValues.Length - 1) + "\":\"" + currentValues[currentValues.Length - 1] + "\"}";
                responseStringG = responseString;
            }
            byte[] byteArray = new byte[sendtags * 4];
            Buffer.BlockCopy(currentValues, 0, byteArray, 0, byteArray.Length);
            if (sqlSend == "yes")
            {
                try
                {
                    SqlCommand cmd = new SqlCommand("INSERT INTO " + table_name + " (" + column_name + ") values (@bindata)", myConn);
                    myConn.Open();
                    var param = new SqlParameter("@bindata", SqlDbType.Binary)
                    { Value = byteArray };
                    cmd.Parameters.Add(param);
                    cmd.ExecuteNonQuery();
                    myConn.Close();
                }
                catch (Exception err)
                {
                    Console.WriteLine("SQL-exception: " + err.ToString());
                    return;
                }
            }

            if (udpSend == "yes")  UDPsend(byteArray);
        }

        private static void UDPsend(byte[] datagram)
        {
            // Создаем UdpClient
            UdpClient sender = new UdpClient();

            // Создаем endPoint по информации об удаленном хосте
            IPEndPoint endPoint = new IPEndPoint(remoteIPAddress, remotePort);

            try
            {

                sender.Send(datagram, datagram.Length, endPoint);
                //Console.WriteLine("Sended", datagram);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Возникло исключение: " + ex.ToString() + "\n  " + ex.Message);
            }
            finally
            {
                // Закрыть соединение
                sender.Close();
            }
        }
        public void theGrp_ReadComplete(object sender, ReadCompleteEventArgs e)
        {
            Console.WriteLine("ReadComplete event: gh={0} id={1} me={2} mq={3}", e.groupHandleClient, e.transactionID, e.masterError, e.masterQuality);
            foreach (OPCItemState s in e.sts)
            {
                if (HRESULTS.Succeeded(s.Error))
                {
                    Console.WriteLine(" ih={0} v={1} q={2} t={3}", s.HandleClient, s.DataValue, s.Quality, s.TimeStamp);
                }
                else
                    Console.WriteLine(" ih={0}    ERROR=0x{1:x} !", s.HandleClient, s.Error);
            }
        }

        static void Main(string[] args)
        {
            string url = "http://*";
            string port = portNumb;
            string prefix = String.Format("{0}:{1}/", url, port);
            listener.Prefixes.Add(prefix);
            listener.Start();
            
            Tester tst = new Tester();
            tst.Work();
        }
    }
}

/* add this code to app.exe.config file
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
  </startup>
  <appSettings>
    <add key="opcID" value="Graybox.Simulator" />
    <add key="tagsNames" value="Line Speed,Any name,Любое имя" />
    <add key="tags" value="numeric.sin.int16,numeric.sin.int16,numeric.sin.int16" />
    <!-- ratios for tags -->
    <add key="ratios" value="1,0.5,0.1" />
    <add key="portNumber" value="45455" />
    <add key="refreshTime" value="1000" />
    <!-- "yes" or no to show values in console-->
    <add key="consoleOutput" value="yes" />
    <add key="webSend" value="no" /> 
    <!-- "table" or json (actually any other word for json)-->
    <add key="answerType" value="json" />

    <add key="sqlSend" value="no" />
    <add key="table" value="raw_tbl" />
    <add key="column" value="data" />
    
    <add key="udpSend" value="yes" />
    <add key="remotePort" value="3310"/>
    <add key="remoteIP" value="127.0.0.1"/>

    <add key="tags2send" value="2" />
    
  </appSettings>
  
  <connectionStrings>
    <add connectionString="Password=12345;Persist Security Info=True;User ID=user12345;Initial Catalog=amt;Data Source=W7-VS2017" name="connstr" />
  </connectionStrings>
   
</configuration>
     */


Let's block ads! (Why?)

Использование Obj библиотек в KolibriOS в языках высокого уровня

Вступление

В KolibriOS системные библиотеки имеют формат MS COFF и расширение Obj. В этой статье будет рассказано как их импортировать и использовать в C--, GCC и TinyC.


SVN

Всегда когда делаете что либо для KolibriOS нужно иметь выкачанный SVN.

svn co svn://kolibrios.org

Он выкачается в текущую папку.


C_Layer

Для того чтобы удобно использовать библиотеки в kos32-gcc был создан C_Layer (тема на форуме, на WebSVN).


GCC

console.obj
Это просто консоль в KolibriOS. Пример загрузки и использования в SVN/contrib/sdk/samples/cpp_hello (К тому же он на С++, но можно использовать и C). Загрузка происходит в файле console_obj.h
Другие библиотеки посредством C_Layer
Перейдите в папку где у вас выкачан SVN. Далее перейдите в /contrib/C_Layer/ASM и выполните там make (чтобы оно сработало нужно иметь установленный fasm). Далее перейдите в папку /contrib/C_Layer/EXAMPLE. Там находятся пару примеров. Для компиляции соответственно тоже make.


TinyC

console.obj
Здесь все проще. В /programs/develop/ktcc/trunk/samples/consoleio.c пример использования. Здесь загрузка происходит в conio.h
Другие библиотеки
Импорт одних библиотек был сделан самодельно, для других перенесена реализация из C_Layer. Примеры использования библиотек в /programs/develop/ktcc/trunk/samples/clayer. Импорт boxlib и пример к нему писал я. Компилируются примеры скриптом для KolibriOS build_all.sh. Найти его можно и в самом iso, /kolibrios/develop/ktcc/, и на SVN. Но можно и из-под windows или linux.


C--

(Компилятор лежит в /programs/cmm/c--
c--.elf версия для linux, c--.exe для windows и c-- для KolibriOS. Если не получается компилировать, пишите в комментарии, тогда будет статья).
Несомненно в этом языке проще чем в других делать импорт библиотек.
Для начала нужно приинклудить соответствующий файл. Например для boxlib:

#include "../lib/obj/box_lib.h"

Для других библиотек соответственно. Доступные .h:


  • box_lib.h
  • libio.h
  • console.h
  • librasterworks.h
  • http.h
  • netcode.h
  • iconv.h
  • network.h
  • libimg.h
  • proc_lib.h
  • libini.h
  • xml.h

А далее нужно загрузить:

load_dll(boxlib, #box_lib_init, 0);

Последний параметр 1 для библиотек:


  • libgfx
  • libimg
  • libini
  • libio
    А для остальных библиотек 0.
    Какой второй параметр, можно найти в соответствующем .h файле, или в других настоящих программах на C--.

Об неточностях и вопросах пишите в комментариях

Let's block ads! (Why?)

«Безумное» прошлое — история психиатрии

Сегодня, если вбить поисковый запрос "Asylum" (с современного англ. - дурка, псих.больница) в строку браузера и нажать "искать", можно найти большое количество музыкальных альбомов самых разных жанров - от трэш-металла до хард-рока и, возможно (но это не точно) - до попсы. А ещё там будут фильмы, книги, журналы и комиксы. Но старина ScientaeVulgaris (написано с ошибками, отчего у многих подгорает, и это симптом) сразу спросит: кино это, конечно, здорово, а книжки, конечно, тоже полезно… но где всё остальное? Где старый недобрый галоперидол, на фоне побочных эффектов от которого самое мрачное будущее антиутопий Стругацких будет казаться райским обитаемым островом с говорящими стаями павианов-людоедов. Где обливания холодной водой? Где медицинская асфиксия (удушение)? Лоботомия? Удары током? Вертикальная и горизонтальная карусель для больных (центрифуга)? Классическая порка? Лечебное голодание? Где история карательной психиатрии СССР и Китая 20-го века? Где опыты над пациентами? Где персидские "дурдомы" 9-го века с балалайками и финиками? А где лондонские пыточные камеры для "лунатиков"? Где вся эта история терапевтических процедур для восстановления ментальной целостности? Нету! Хоть шаром по палате прокати. Ну что же, восполняем пробелы знаний с SV, дабы не ударить в грязь лицом при госпитализации в психиатрию.

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

Тогда в силу вступила новейшая медицинская теория о четырех жидкостях, вызывающих своим дисбалансом все известные болезни. Придумал эту ахинею, собственно, Гиппократ. Она выросла в 4 темперамента - характера. И вот, согласно этой идее, избыток черной желчи заставлял человека быть меланхоличным, а преобладание обычной желчи - чрезмерно горячим и импульсивным. Ещё были слизь - медлительные флегматики, и кровь - весёлые сангвиники. Это важный нюанс, а не тупая "цитатка" из Вики. Следующие 2000 лет лечить будут по принципу: меланхолия у вас или возбужденность холерика, выдавливая из вас желчь и внимательно её разглядывая, выпучив ученые глаза.

Вообще, греко-римское право предписывало изолировать от общества сумасшедших и откровенно странных личностей. Но эта обязанность или никому не определялась вовсе, или ложилась на плечи родных и близких. В Римской империи при серьезных психических нарушениях запрещалось даже выходить из дома. Если же заботиться о таком человеке было некому, то город мог назначить смотрителя, если у больного были бабки. Если не было ни родни, ни дома, ни денег, его могли изолировать единственным оставшимся способом - в тюрьме. Для римлян туллианум - тюрьма (от изобретателя Тулла Костиллия) - дело редкое. По нашим представлениям это вообще СИЗО, а не место заключения. В них обычно ждали казни или суда, и ключевое слово - ждали. Так, чтобы ожидание само по себе было наказанием - редкое исключение. Абсолютное большинство преступников вкалывало на каторге, а особо упоротых убивали. Согласитесь, в эту систему сложно вписать сумасшедших. Туллианум вообще не приспособлен для оказания помощи - это подземная камера на манер септика или водохранилища со спуском сверху. А не палата с медсестрой и афобазолом на завтрак.

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

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

Между 344 и 358 годами н.э. епископ Антиохии санкционировал строительство общежитий для бездомных и больных, а в 372 году епископ Кесарийский построил гигантский госпиталь Basileias, описываемый современниками как город для больных. В-основном там, конечно, тусовались больные проказой, но психбольных никто не выгонял в отдельные учреждения, и они скрашивали будни остальных пациентов своими собственными симптомами, патиями и фобиями. Вторым нюансом христианства стали попытки рассматривать безумие с религиозной точки зрения, и в этом случае лучшее, на что могли надеяться пациенты, это на закрытую форму содержания при монастыре. Ну а в худшем... инквизиция тоже боролась с сумасшедшими, но по-своему. В 4-ом веке эстафету смертельных приговоров начали с казни Манихейских жрецов (секта в государстве Сасанидов), а с 11-го века разожгли костры для Патаренов и Катаров. Ну а создав претендент смертной казни за нарушение религиозных догм, оставалось только правильно выбрать догму для несчастных, которые из-за Паркинсона или шизофрении не могли, например, повторить крестное знамение.

Некоторые вспышки "озарения" произошли в период расцвета арабской медицины в самый разгар Средневековья, в начале 9-го века. Тогда в Багдаде существовали Бимаристаны - специализированные госпитали общего назначения, и в них наконец-то стали делать палаты, где не тяжёлые психически больные люди могли меланхолично коротать время без особо вредного вмешательства со стороны врачей или пациентов с инфекционными болезнями. Психические заболевания и психически больных людей к этому времени начали изучать, симптомы документировать и описывать. Если вы фанат темы и хотите удивить психиатра, то назовите ему имена, например, Мухаммед ибн Закария аль-Рази, Наджаб уд-дин Мухаммад, Абу Али аль-Хусейн ибн Абдалла ибн Сина, и многие другие.

Ахмад ибн Тулун в 872 году, например, в Каире построил больницу, где безумцам успокаивающе бренчали на удах (древняя арабская балалайка) и поддерживали на здоровой диете - финики, лепешки, булгур, все дела. Прямо 4-ый стол по Певзнеру. В общем, прогресс шёл своим чередом. Календарики в первых дурдомах обновляли, а методы нет.

Первые замысловатые попытки нормализовать поведение, выбивающееся из общественной нормы, берут начало в 1247 году. Нет, я не про ледовое побоище. Оно в 1242 году было. В 47-ом Александр Ярославович Невский уже вовсю сидел на Киевском престоле, а в Лондоне в это же самое время закладывали фундамент здания, которое станет первым в мире домом сумасшедших. Причем, ни много ни мало, самым легендарным из всех. Это тот самый будущий Бедлам, тот самый нарицательный Бедлам, синоним кавардака и чистого сумасшествия. Его история великолепна во всём. Одно, это здание за 600 лет истории прошло научный путь прогресса и познания от господа до ворот в ад. И это не просто путь - это целое, мать его, шоссе, выложенное деньгами от первого до последнего километра.

Судите сами. На момент постройки здание было общежитием религиозного ордена Вифлеемской Богоматери, затем его перестроили в госпиталь Марии Вифлеемской, а затем переименовали в Вифлеемскую королевскую больницу. Bethlehem - Вифлеем постепенно сократилось в Bethlem - Вифлем, а читается это как "Бетлем", ну или Бедлам. До 17 века Бедлам, как именно дурка, - больше легенда, чем реальная история. Именно больницей для психических больных она стала в 1377 году. И то, к началу 15 века в ней содержалось целых 9 больных, а до 16-го века эта информация подтверждается только косвенно, путем наличия в инвентаризационной ведомости 11 цепей, 6 замков и 2х пар наручников. А кого там и куда пристегивали, а главное - зачем, и что это было - ролевые игры, заключение в подвале неугодных, или первые попытки исправить биполярное расстройство - чёрт его знает.

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

Всё изменилось в начале 17 го века. Меняется эпоха. Расцветает театральное искусство, поэзия, и вернулся интерес к медицине. Начавшиеся поборы Чарльза Первого, гражданская война, противостояние парламента и короны выбили собственность из городских рук, превратив её в бизнес. А народная реклама эффективно поставила этот бизнес на ноги. Единственный дом сумасшедших становится знаменитым как место заключения тех, кого ментально уже не вернуть в наш мир. Британские блокбастеры срывают кассу: «Ярмарка Варфоломея», «Безмолвная женщина», «Новый способ выплатить старые долги» вводят образ сумасшедшего в оборот, как символ простого бедного человека, не способного вынести давление ноги современного времени. А попасть в больницу на лечение можно заплатив нужную сумму на входе. За себя, за друга или за тёщу... или за всех сразу.

И вот в 1675 году здание - потрёпанное, переполненное больными и отчаянно нуждающееся в дополнительном пространстве - переместилось к северу от Лондона в Мурфилдс. Перед его входом установили две зловещие статуи: «Меланхолия», которая выглядела спокойной, и «Безумное безумие», которая была прикована цепью и зло косилась на всех входящих. Началась новая эпоха психиатрии - эпоха торговли сумасшествием. В 1713 году открывается частная клиника Вефиль в Норвиче. В 1728 году в больнице Гая строят дополнительное психиатрическое отделение. Всего за пятьдесят лет в одном Лондоне количество психиатрических клиник, остававшееся неизменным 300 лет, увеличивается в 20 раз. И так вплоть до средины 19 века, и до не менее знаменитого учреждения Бродмурской психиатрической больницы, закрывшей в своих стенах с десяток серийных убийц, и больницы Эшворт, про которую вы могли услышать в контексте Чарльза Бронсона. Эти две жемчужины здравоохранения - больницы строго режима - вещь во многом уникальная, а известны они благодаря фильмам о маньяках, убийцах и шизофрениках, которых там держали.

Не то чтобы психиатрия вообще никого не интересовало за пределами Англии до 19 века, но почти что да. Мы можем найти только единичные упоминания вроде дополнительного психиатрического отделения в Отель-Дьё в Париже, да например Башни дураков - Narrenturm в кампусе Венского университета, в прошлом бывшем центральной больнице Вены. Нет денег - нет веселья. Из Англии мода на новый вид прибыльных учреждений расходится по колониям. А в самой крупной из них, колыбели демократии и капитализма, она вообще выходит на новый уровень.

Обратите внимание на название Lester Lunatic Asylum, г.Лестер, графство Лестершилд, Англия, основана в 1794 году. «Asylum» происходит от греческого «безопасный», и до момента популяризации сумасшедших домов означало просто - «приют», «убежище». Лестер, ёжечку понятно, "город". А вот Lunatic? Лунатик это от "lunaticus", и это - второй по популярности миф средневековой и античной психиатрии после 4х темпераментов весельчака Гиппо. Собственно, вы наверняка догадались, с чем он связан. С красавицей луной: идея о том, что эта оладья на чёрном небе как-то на нас влияет, не давала покоя многим. Но что именно она ответственна за нарушения сна, которые косвенно вызывают приступы эпилепсии, некоторые виды ночных лихорадок и ревматизм - это была системная мысль. Эта идея, которую выдвинули Аристотель и Плиний Старший, а остальные ученые с умным видом подхватили и одобрительно кивали аж с 4 века до нашей эры до 18 века нашей, как минимум. Казалось бы, ну бред полной воды. Но в этом-то и дело - полная вода, приливы и отливы, луна влияет на воду - может и с башкой работает? Чтобы отвергнуть эту идею, нужно было осмыслить законы и механизм воздействия гравитации. Хотя о чем я вообще говорю… астрология же до сих пор жива. В общем, так или иначе, где-то до начала Второй Мировой Войны использовался термин "Лунатик" для определения людей с психическими заболеваниями. И лишь с начала 20 го века вводится в оборот термин "non compos mentis" - вне ясного ума, или невменяемый.

Подобные мифы вызывали новые идеи о лечении. Одно из многих новшеств, подаренное миром Бедламом - ротационная терапия. Эта терапия, изобретенная Эразмом Дарвином (дедулей Чарльза Дарвина), предполагает сидение пациента на стуле или привязывание к кровати. Стул или кровать прифигачивают к оси, или используют веревку. Затем стул вращается в соответствии с предписанием доктора. Недорогой, простой в организации аттракцион позволял достичь больным 100 оборотов в минуту. И если вам эта идея кажется веселой и вы представляете какие-нибудь карусельки, то представьте себя подавленным, голодным, напуганным и привязанным к этому аттракциону не на 5 минут, не на 10 или 15, а на пару часов. Как думаете, что с вами будет?

Бесчисленные пациенты были подвергнуты данному типу лечения в Бедламе, а затем и в других больницах. Индуцирующие сильнейшие головокружения мероприятия никак не влияют на тяжесть психических заболеваний. Результаты ротационной терапии включали рвоту, бледность и недержание мочи. В то время они считались полезными, особенно рвота, которая считалась терапевтической. Почему? Если когда-нибудь у вас было инфекционное отравление и вас рвало несколько дней подряд, вы быстро ответите - желчь. Если выходить уже нечему, вас настигают судороги и горький вкус во рту, вкус психиатрической терапии, вкус самой истории и отсылка к Гиппократу.

Следом, с теми же мыслями о желчи, изобрели гидротерапию. Взяли идею о том, что здоровый организм не должен плохо пахнуть, оно и логично (привет гнойной хирургии!). И развили ароматические ванны древнего Египта, рекомендации Гиппократа по долгим купаниям, опыт римских бань, и решили, что вода может помогать при самых разных кризисных состояниях. Ведь она приникает во все трещины, раны, складки кожи, отверстия и сфинктеры, смешивается с нечистыми жидкостями и помогает их вымывать, будь то избытки желчи или гноя. К 18-му веку стали всплывать первые научные труды, систематизирующие мысли о целебных купаниях. Джон Флойер опубликовал в 1702 году книгу об истории холодных купаний. В 1738 идею подхватил Дж.С.Хан из Силезии, и издал пособие для чайников «О целительных достоинствах холодной воды, применяемой внутренне и внешне, на основе опытов».

Вообще, врачебные истории начала 19 века это сплошной огонь. Взять, например, Винсента Прейссница (1799-1851 гг). Сын фермера, в детстве он увидел оленя с раной в груди, омывающего её в пруду. Природное любопытство заставило Винсента приходить к пруду в течение следующего месяца, чтобы пронаблюдать за судьбой рогатого. Через неделю он снова увидел оленя. Рана уже зажила. Затем, будучи подростком, он попал в дтп и, то ли его переехала повозка, то ли сбросила лошадь, то ли сначала одно, а потом второе, в общем: он обзавелся переломом минимум трёх рёбер. Местный доктор сказал ему, что выздороветь до конца он никогда не сможет. Прейсниц вспомнил пример из детства и стал обматываться мокрыми повязками и усиленно пил воду, подражая благородному животному, а попутно он делал дыхательную гимнастику для того, чтобы ребра приняли правильную форму. Через год его рёбра-таки срослись, как он сказал, и он стал известен как доктор. Через несколько лет практики, не имея медицинского образования, в свои 19  лет он становится врачом общей специализации, путешествует по Моравии и Богемии, налево и направо выдавая советы по гидротерапии. А в 1826 году он становится главой гидропатической клиники в Графенберге, обретает бешеный успех, тысячи пациентов и бабки. Здорово, да? От оленя до начальника курорта, известного медика с патентованной системой лечения и олигархического состояния.

В общем, к концу 19-го века таких энтузиастов купальных терапий стало пруд пруди. Не самый мой удачный каламбур, ну да ладно... Процедуры логично разделили на две части: горячие ванны и холодные. Горячие помогали потеть и, соответственно, обосновывались выводом плохих жижей из организма. Холодные тормозили жизнедеятельность тела и предотвращали приток вредоносных "жиж" в голову. Применительно к психиатрии это означало принудительное помещение пациентов в паровые шкафы, приём длительных многочасовых горячих ванн, когда близко к поверхности натягивали простынь, предотвращая всплытие, а самих пациентов связывали, помогая наладить перемещение жидкостей в теле, а с ними - баланс активности и буйства. Приём холодных ванн, ванн со льдом прописывали для лечения маниакально депрессивных психозов. Под холодной имеется ввиду температура в 15 градусов цельсия. Всё-таки это не моржевание, хотя бубенцы, как говориться в одной сказке, звенят.

В 1843 году бывшая Британская колония отличилась открытием Центра Психиатрии им. Ютика. Шикарное здание в Нью-Йорке в стиле древнегреческой античности. Первым директором клиники стал Амарий Бригхам. У директора были как светлые идеи, так и не очень. Вот, например, его «труд - самое важное из лечебных средств» отчасти весьма продуктивное утверждение. А с другой стороны, мистер Амария в попытках избавить пациентов от оков и цепей придумал "Люльку Юпика" - так её потом назовут. Это кровать с высокими бортами со всех четырех сторон, как натуральная люлька для взрослых, с одним нюансом. У неё была крышка, запиравшая пациента на замок, на манер гроба, только вживую… в больнице, в исполнении людей, которые говорят, что хотят помочь вам избавиться от депрессии. Забавно, но рассказ Алана По - преждевременное погребение - вышел всего год спустя открытия клиники. Даже если они не связаны, что, скорее всего, так и есть, какого чёрта, Бригхам вводит способ лечения, описанный в рассказе ужасов?

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

Надо ли говорить, что эта люлька стала последней кроватью огромного множества людей. Их приступы или инсульты трактовали как психические припадки, и помещали таких людей туда. Или же сначала помещали их в люльку, а затем у них происходил сердечный приступ или инсульт на фоне паники или клаустрофобии. Идея обездвижить пациента не была в Штатах чем-то прямо сверхновым, а логично проистекала из предыдущих открытий. Например, признанный отец американской психиатрии  - Бенджамен Раш - задолго до появления люлек Ютика выдвинул теорию о том, что безумие это не желчное заболевание, а артериальное. Буйные пациенты много двигаются, у них растет частота сердечных сокращений, кровь лупит в голову, и они испытывают помешательство. Значит, чтобы вылечить помешательство, нужно идти в обратном порядке и перво-наперво обездвижить пациента. Для чего он и изобрел транквилизирующее кресло где-то в начале 1790-х.  Штука интересная, но побочные эффекты у неё точно такие же. Ещё, кстати, пеленание применяли в весьма интересной форме...

С электротерапией отдельная история и достаточно длинная. Прежде, чем придумали бить током приложив контакты к вискам, наука прошла длинный и не самый приятный путь. Что может быть неприятнее, чем удар током в голову? Ну, например, постоянный ток вместо переменного. Первые попытки что-то вылечить ударами тока относят к человеку с говорящей фамилией Крюгер - Иоганну Готтлобу в 1743 году. Бедлам закупил целебные машины аж в 1777 году. И это при том, что на переменный ток перешли только после наблюдений Гийома Дюшена в 1856 году, который подметил, что «согревающий эффект» постоянного тока приводит к появлению волдырей на аноде и ямок на катоде. Кроме того, целебное напряжение мышц происходит только при включении, и оно явно слабее, чем при переменном токе. Для понимания ужаса происходящего придется вникнуть в основы школьной физики ещё раз и вспомнить, как можно оксидировать кусок железа за пару минут в ржавый прах, если в воду поместить железный анод и катод и подать постоянный ток. Агонии больного это не передаст, но сомнения насчет терапевтической пользы в вас заложит. Только не смешивайте банальное подключение к электроприборам с электрофорезом и ионофорезом.

Как ни странно, большинство картинок в простецких блогах, где вроде как пациентов лупят током в терапевтических целях, это не электротерапия, а диатермия. Это условно лечебная процедура, связанная с побочным эффектом воздействия электромагнитных токов. Придумал её чуть позже, где-то в 1890-1891 годах, французский врач и биофизик Жак Арсен д’Арсонвал. И это, по сути, тепло, индуцированное слабыми токами, проходящими через организм. Но точность и безопасность оборудования, вкупе с тем, что использовали такое прогревание в височной области, было жутко нестабильным - люди впадали в кому и умирали направо и налево, и в 1926 году Крис М. Сэмпсон в своей книге «Практика физиотерапии» призвал от процедуры отказаться.

Уровень осведомленности о функциях мозга и связи этих функций с психическими отклонениями рос. Первым, кстати, кто отделил религиозную бесноватость от проблем мозга, как органа, был тот самый изобретатель транквилизаторного стула - Бенджамин Раш. Приблизительное понимание, что разные отделы и части мозга отвечают за разные функции поведения и личности, привели к изобретению такой процедуры, как лоботомия. В 1935 году португалец Эгаш Мониш выдвинул теорию о том, что разрезание соединительных волокон между разными отделами мозга может быть полезным в лечении психических болезней. Первую операцию провели в 1936 году. Мониш в-основном занялся активной рекламой и, проведя больше ста операций, весь остаток жизни рекламировал эффект, при этом даже не наблюдая пациентов после проведения экзекуции. За что, кстати, безбожно критиковался коллегами.

В 1949 году ему дали Нобелевскую премию по физиологии и медицине «за открытие терапевтического воздействия лейкотомии при некоторых психических заболеваниях». И это спустя 4 года после успеха Манхэтаннского проекта. Казалось бы, мощь атома покорили, а лоботомия ещё в моде. В 1950-х занялись более научными исследованиями последствий лоботомии и выяснили, что, оказывается, существует множество побочных эффектов. От недержания мочи и потери личности, до снижения интеллекта, потери моторной координации, паралича и смерти вплоть до 6% пациентов. Нобелевскую премию, кстати, не отозвали.

На смену отрезанию некоторых частей мозга от организма пришли психотропные препараты, подавляющие весь мозг целиком и влияющие на весь организм вплоть до каждой клетки. Это был по своему длинный и интересный путь. Начался он с инсулиновой комы, предложенной Манфредом Сакелем в психиатрической клинике в Берлине в 1933 году. Суть которой была во введении бешеных доз инсулина и отключении мозга из-за резкого падения сахара - до 10% пациентов могли после этого больше не выйти из неё. В это же время Ладислав фон Медуна из центра психиатрических исследований в Будапеште предположил, что если нарушения здоровья у эпилептиков приводят к судорогам, то если искусственно вызвать судороги медицинскими препаратами, можно будет вылечить и шизофрению, и эпилепсию, и вообще что угодно. С 1934 года он испытал на людях огромное количество препаратов и их вариаций, от камфоры до стрихнина. Остановился Медуна на пентилентетразоле, более известном как метразол - введение вещества вызывало судорожное напряжение мышц, а в зависимости от дозы и состояния больного, судороги достигали такой силы, что пациенты ломали себе кости... Только к 1982 году от неё отказались, окончательно заменив электросудорожной терапией. Но настоящее веселье началось, когда в 1957 году бельгийская компания Janssen Pharmaceutica запатентовала галоперидол...

Спасибо, что дочитали до конца.

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

Ваш - SV.

Let's block ads! (Why?)

Как правильно и легко рассчитать прибыль на инвестиции или калькулятор ROI на Python

Суть проблемы

Пусть у вас есть вложения активов в некую стратегию (даже если buy and hold), и вы хотите рассчитать ROI(return on investment).

Если вы не производили никаких выводов или депозитов, тогда легко рассчитать прибыль
по формуле:

\\ROI = NAV / initial \: investments\\

где NAV- текущая стоимость наших активов, а initial \: investments - исходная стоимость активов.

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

Немного формул

При первом депозите надо создать "виртуальный" паевой фонд, начальное количество акций (паёв) в котором равно депонированным активам N(в акциях) с ценой за акцию P=1

Любой депозит или вывод средств в момент времени t эквивалентен покупке или продаже акций по цене P_t. Далее меняем состояние ПИФа при изменении счета по следующему алгоритму:

  1. ПустьXактивов было добавлено к фонду в момент времени T, где
    X > 0 при депозите и X < 0при выводе.

  2. В T_0 = T - \varepsilon ПИФ состоял из N акций с ценой P_{T_0} = {NAV}_{T_0} / N

  3. После выполнения транзакции, в момент времени T_1 = T + \varepsilonновое количество акций составит M = N + X / P_{T_0} а цена акции останется той же: P_{T_0} = P_{T_1} = NAV_{T_1} / M

Таким образом, для каждого момента времени tимеем:

  1. стоимость активов NAV_t

  2. количество виртуальных акций N_t

  3. цену одной акции P_t = NAV_t / N_t

В итоге, можно рассчитать доходность от начального момента времени
по формуле:

ROI = P_t / P_{t_0} - 1


Более того, также можно легко рассчитать ROI по этой формуле на любой период времени (t_0, t), t > t_0, в чем и заключается суть данного метода.

Пример

Допустим мы положили 100$ в стратегию. Сразу отметим, что в этот момент времени "покупаем" 100 акций за 1$. Далее стратегия за какое-то время заработала 20% и наш баланс теперь стал 120$, а следовательно изменилась и цена акции, она стала 120$ / 100 = 1.2$ (количество акций не изменилось, потому что никаких новых вложений или выводов не было).

Пусть в этот же момент времени мы решили положить ещё 210$, чтобы увеличить абсолютный доход. Депозит эквивалентен увелечению акций на 210$ / 1.2$ = 175. Таким образом, цена акции осталась (120 + 210)$ / (100 + 175) = 1.2$, а стоимость активов изменилась. Спустя время стратегия заработала ещё 10% от нового баланса, то есть стоимость активов стала равна 363$, следовательно стоимость акции стала равна 363$ / 275 = 1,32$.

Посчитаем доходность с начального момента до момента депозита: (1.2 / 1 - 1) * 100 = 20%
Посчитаем доходность от момента депозита: (1.32 / 1.2 - 1) * 100 = 10%
Посчитаем общую доходность на ивестиции: (1.32 / 1 - 1) * 100 = 32%

Наконец-то про код

Здесь мы будем манипулировать тремя простыми сущностями.

  1. транзакция (Transaction)

  2. ивестор (Investor)

  3. ПИФ (ROICalculator)

Транзакция является структурой с двумя полями, где funding - это вывод или депозит с соответсвующим знаком (X из формул)

class Transaction:
    '''
    Transaction model.

    timestamp: datetime.datetime - transaction timestamp
    funding: float - deposit or withdrawal
    {
        deposit: +X in asset [U]
        withdrawal: -X in asset [U]
    }
    '''
    def __init__(self, timestamp: datetime, funding: float):
        self.timestamp = timestamp
        self.funding = funding

Далее, модель инвестора - самая важная в рамках использования. Для расчетов нам важно иметь:

  1. начальный депозит

  2. дату первых инвестиций

  3. список транзакций

Cамое главное - переопределить метод доступа к балансу по временной метке. Best practice здесь запрос к БД или pandas.DataFrame

Transactions = List[Transaction]

class Investor(ABC):
    '''
    Investor model.

    1. Attributes
    investment_timestamp: datetime.datetime - investment timestamp (deposit timestamp)
    deposit: float - deposit amount in asset [U]
    transactions: Transactions - list of transactions with fundings and timestamp

    2. get_nav_by_timestamp - investor's net asset value

    '''

    def __init__(self, investment_timestamp: datetime, deposit: float, transactions: Transactions, *args, **kwargs):
        self.investment_timestamp = investment_timestamp
        self.deposit = deposit

        # sort transactions by timestamp
        # from first transaction to last
        #
        # EXCEPT DEPOSIT TRANSACTION
        #
        self.transactions = sorted(
            transactions, key=lambda x: x.timestamp, reverse=False)

    @abstractmethod
    def get_nav_by_timestamp(self, timestamp: datetime) -> float:
        '''returns NAV'''
        raise NotImplementedError

И последнее - сам ROICalculator. В целом, он полностью повторяет алгоритм, описанный выше, сохраняя состояние ПИФа в атрибуты объекта, что позволяет достаточно быстро рассчитывать share price на любой момент времени tдаже на больших данных с большим количеством движений по счету (проверял на боевых данных).

class ROICalculator:
    '''
    ROICalculator.

    1. Create virtual pif __init_pif 
    {
        init shares = deposit quantity of asset[U]
        share price = 1
    }

    2. System go through 3 conditions while getting funding
    {
        Let funding X[U] was added to virtual pif at T;

        T - transaction timestamp,
        T0 = T - eps - timestamp before transaction
        T1 = T + eps - timestamp after transaction

        pif consisted of N SHARES with share price P_0[U] = NAV_T0[U] / N.

        Add X[U] to virtual pif: M = N + X[U] / P_0[U],
        where M - new shares amount

        Update share price P[U] = NAV_T1[U] / M

    }
    '''

    def __init__(self, investor: Investor, eps_hours=1):
        # eps is used while getting nav_before
        # and nav_after transaction
        self.investor = investor
        self.eps_hours = eps_hours
        self.__init_pif()

    def __init_pif(self):
        self.shares = self.investor.deposit
        self.share_price = 1

    def __calculate_shares(self, funding: float):
        self.shares += funding / self.share_price

    def __calculate_share_price(self, nav: float):
        self.share_price = nav / self.shares

    def __calculate_shares_by_timestamp(self, timestamp: datetime):

        # create virtual pif each time calculating shares
        self.__init_pif()

        for transaction in self.investor.transactions:
            if transaction.timestamp > timestamp:
                break

            # 1 condition: before transaction
            # T0
            timestamp_before_transtaction = transaction.timestamp - \
                timedelta(hours=self.eps_hours)

            if timestamp_before_transtaction < self.investor.investment_timestamp:
                nav_before = self.investor.deposit

            # NAV_T0
            try:
                nav_before = self.investor.get_nav_by_timestamp(
                    timestamp_before_transtaction)
            except Exception as e:
                print(e)

            # P0 = NAV_T0 / N
            self.__calculate_share_price(nav_before)

            # 2 condition: add funding to virtual pif
            # shares = M
            self.__calculate_shares(transaction.funding)

            # T1
            timestamp_after_transtaction = transaction.timestamp + \
                timedelta(hours=self.eps_hours)

            # NAV_T
            try:
                nav_after = self.investor.get_nav_by_timestamp(
                    timestamp_after_transtaction)
            except Exception as e:
                print(e)

            # update share price
            # P[U] = NAV_T1[U] / M
            self.__calculate_share_price(nav_after)

    def __calculate_share_price_by_timestamp(self, timestamp: datetime):
        # update shares N in self.shares
        self.__calculate_shares_by_timestamp(timestamp)

        # get NAV from data
        nav = self.investor.get_nav_by_timestamp(timestamp)

        # update share_price in self.share_price
        self.__calculate_share_price(nav)

    def get_share_price_perfomance(self, t0: datetime, t: datetime) -> float:
        '''
        t  - end_timestamp
        t0 - start_timestamp, t > t0

        t = datetime.utcnow(), t0 = investment_timestamp to get ROI
        '''
        self.__calculate_share_price_by_timestamp(t)
        # fix share_price at t
        k = self.share_price

        self.__calculate_share_price_by_timestamp(t0)
        # fix share_price at t0
        k0 = self.share_price

        return k / k0 - 1

Как можно использовать

Допустим, вы положили средства в лендинговую стратегию с доходом около 0.05% в день на инвестированные средства. Это означает, что наш P&L на стоимость активов будет рассчитываться как:

periods_i - days \: between \: transaction_i \: and\: transaction_{i-1}, \\ investments_i - total \: investments \: in \: the \: period_iPnL_t = 0.005 * \sum_{i=1}^n investments_i * period_i, \: n - transactions \: number \: before \: t

Это нужно для правильного определения доступа к балансам по временной метке.

Пусть 2020/1/1 было депонировано 100$, а 2020/4/1, было депонировано ещё 200$, тогда, с учетом описанной выше формулы получаем такую модель инвестора:

class ExampleInvestor(Investor):
    '''

    Simple lending (static) strategy with 0.05% profit daily
    on investments without reinvestment

    '''

    def __init__(self, investment_timestamp, deposit, transactions):
        super().__init__(investment_timestamp, deposit, transactions)

    def lending_assets(self, timestamp):
        # before transaction
        if timestamp <= datetime(2020, 4, 1):
            return 100
        # after transaction
        else:
            return 300

    def get_nav_by_timestamp(self, timestamp):
        '''

        NAV = investments + PnL
        daily PnL = 0.0005 * investments =>
        total PnL = 0.0005 * sum(invesmetns_i * period_i)

        '''
        if timestamp < datetime(2020, 4, 1):
            pnl = 0.0005 * \
                self.lending_assets(timestamp) * \
                (timestamp - self.investment_timestamp).days
            return self.lending_assets(timestamp) + pnl

        elif timestamp > datetime(2020, 4, 1):
            # redefine investments_i and daily PnL
            transaction_timestamp = datetime(2020, 4, 1)
            acc_pnl_before_transaction = 0.0005 * self.lending_assets(
                transaction_timestamp) * (transaction_timestamp - self.investment_timestamp).days
            pnl =  0.0005 * self.lending_assets(timestamp) * (timestamp - transaction_timestamp).days +\
                acc_pnl_before_transaction

            return self.lending_assets(timestamp) + pnl

Определим модель инвестора:

transaction = Transaction(datetime(2020, 4, 1), funding=200)
investor = ExampleInvestor(investment_timestamp=datetime(2020, 1, 1),
                           deposit=100, transactions=[transaction])

Создадим модель ПИФа:

pif = ROICalculator(investor)

И теперь при помощи метода get_share_price_perfomance можем получить ROI на любой период времени. В качестве примера посчитаем 1D%, MTD% и YTD% до и после депозита и получим:

1D return on 2020-03-31 = 0.05 %
MTD return on 2020-03-31 = 1.51 %
YTD return on 2020-03-31 = 4.50 %

1D return on 2020-04-30 = 0.05 %
MTD return on 2020-04-30 = 1.44 %
YTD return on 2020-04-30 = 6.01 %

Делюсь кодом в надежде на то, что это кому-нибудь ещё пригодится и пару часов моих выходных не прошли впустую. Лично у меня получилось очень удачно совместить эту небольшую модель с API бирж, а также используя известную питоновскую ORM - sqlalchemy для доступа к балансам.

Let's block ads! (Why?)