[{"content":"Что это за приложение? ValedoMotion — ПК-компонент реабилитационной системы Valedo швейцарской компании Hocoma AG (сейчас входит в DIH Group). Предназначен для терапии пациентов с хроническими болями в спине.\nКак работает:\nНа тело пациента крепятся 3 беспроводных BLE-датчика (грудь, поясница, бедро) Датчики передают данные о движении в реальном времени через Bluetooth-донгл ПО показывает движение пациента визуально, проводит через игровые упражнения Врач получает объективные метрики: амплитуду движений (ROM), прогресс терапии, отчёты Архитектура: .NET Framework 4.5 + WPF + Prism MVVM + DevExpress 14.2 + Entity Framework 6. Два процесса на ПК: ValedoMotion (GUI) и IMUSensorAPI.ServiceHost (работа с датчиками через BLE-донгл), общаются через WCF Named Pipes.\nДополнительные модули Модуль Назначение ValedoShape Тренировка с биологической обратной связью (отдельная установка, регистрируется в реестре) ValedoMPI Обмен данными с Medical Practice Integration Где хранится лицензионный ключ? Путь к файлу 1 C:\\ProgramData\\Hocoma_AG\\ValedoMotion\\Configuration.xml Как образуется путь (App.xaml.cs:19-27):\nЧитается CompanyName и ProductName из метаданных сборки: Hocoma AG → Hocoma_AG, ValedoMotion → ValedoMotion Путь: Environment.SpecialFolder.CommonApplicationData + \\Hocoma_AG\\ValedoMotion Структура файла 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 \u0026lt;ValedoConfiguration\u0026gt; \u0026lt;HasMotion\u0026gt;true\u0026lt;/HasMotion\u0026gt; \u0026lt;HasShape\u0026gt;false\u0026lt;/HasShape\u0026gt; \u0026lt;License Key=\u0026#34;GEspqbSBJMnc4WN\u0026#34;\u0026gt; \u0026lt;LicensedSensorIds\u0026gt; \u0026lt;LicensedSensor\u0026gt;00:11:22:33:44:55\u0026lt;/LicensedSensor\u0026gt; \u0026lt;LicensedSensor\u0026gt;AA:BB:CC:DD:EE:FF\u0026lt;/LicensedSensor\u0026gt; \u0026lt;LicensedSensor\u0026gt;11:22:33:44:55:66\u0026lt;/LicensedSensor\u0026gt; \u0026lt;/LicensedSensorIds\u0026gt; \u0026lt;/License\u0026gt; \u0026lt;Signature xmlns=\u0026#34;http://www.w3.org/2000/09/xmldsig#\u0026#34;\u0026gt; \u0026lt;SignedInfo\u0026gt;...\u0026lt;/SignedInfo\u0026gt; \u0026lt;SignatureValue\u0026gt;...\u0026lt;/SignatureValue\u0026gt; \u0026lt;KeyInfo\u0026gt;...\u0026lt;/KeyInfo\u0026gt; \u0026lt;/Signature\u0026gt; \u0026lt;/ValedoConfiguration\u0026gt; Примечание: хотя [XmlRoot] объявляет namespace http://www.hocoma.com/valedomotion, сериализация через Serialize(true, true) вырезает все пространства имён (XmlSerializerNamespaces.Add(\u0026quot;\u0026quot;, \u0026quot;\u0026quot;)). Файл на диске — без xmlns у ValedoConfiguration.\nКак записывается в файл Пользователь вводит ключ в поле License Manager → нажимает «Apply» LicenseManagerPresenter.SaveLicenseKeyToConfig() (LicenseManagerPresenter.cs:184): Отключает все датчики Отправляет ключ через WCF Named Pipes в процесс IMUSensorAPI.ServiceHost для проверки При успехе сохраняет ключ и список сенсоров в SensorWhitelist SensorWhitelist.LicenseKey = key → ConfigurationService.Save() (SensorWhitelist.cs:28-32) ConfigurationService.Save() (ConfigurationService.cs:103-115): File.WriteAllText() — запись XML xmlSigner.Sign() — подпись RSA (защита от редактирования блокнотом) Защита от подделки После сохранения файл подписывается XML-цифровой подписью — enveloped signature (XML DSig), RSA, при каждом сохранении генерируется новый эфемерный ключ (new RSACryptoServiceProvider()). Публичный ключ встраивается в подписанный файл через RSAKeyValue — подпись самодостаточна.\nПри загрузке (в конструкторе ConfigurationService) вызывается ReadConfig() → ValidateXml() → XMLValidator.Validate(). Если подпись отсутствует или недействительна — выбрасывается XmlException: \u0026quot;Missing or invalid xml signature\u0026quot;.\nПеренос с бэкапа: файл можно скопировать как есть — подпись встроена в сам XML и не зависит от тайминга/пути. Но практичнее через UI:\nОткройте старый Configuration.xml Скопируйте значение атрибута License Key=\u0026quot;...\u0026quot; Вставьте его в интерфейс License Manager на новой установке Нажмите «Apply» — приложение само проверит ключ, определит датчики, перезапишет файл и поставит новую подпись Как проверяется лицензионный ключ? Коротко: да, привязан к MAC-адресам сенсоров Ключ — ровно 15 символов (пример: GEspqbSBJMnc4WN). Кодирует частичную информацию о MAC-адресах 3 конкретных датчиков с помощью подстановочного шифра.\nЕсли у вас новые сенсоры (другие MAC-адреса) — старый ключ не подойдёт. Ключ привязан к конкретным физическим датчикам.\nПолная схема проверки ValedoMotion не общается с датчиками напрямую. Он шлёт запросы в отдельный процесс-сервис (Hocoma.IMUSensorAPI.ServiceHost.exe), который работает на том же ПК и взаимодействует с датчиками через BLE-донгл. Общение ValedoMotion ↔ ServiceHost идёт через WCF Named Pipes (net.pipe://localhost/Hocoma/IMUSensorService/API/).\nВалидация ключа происходит на ПК, в процессе IMUSensorAPI.ServiceHost, но требует наличия физических датчиков в BLE-эфире — без их MAC-адресов проверять нечего.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ValedoMotion (GUI) IMUSensorAPI.ServiceHost (консольный процесс, ПК) BLE-датчики │ │ │ │ 1. ValidateLicenseKey(key) │ │ │ ──── Named Pipe WCF ──────► │ │ │ │ │ │ │ 2. Сканирует BLE-эфир через донгл │ │ │ Получает MAC-адреса датчиков │ │ │◄────────────────────────────────────────────────│ │ │ │ │ │ 3. Вызывает нативную C++ DLL (на ПК): │ │ │ validateSensors_C(mac[], key) │ │ │ (Hocoma.Common.SensorValidation.dll) │ │ │ │ │ │ 4. DLL декодирует ключ в 3 шаблона │ │ │ Сравнивает с MAC-адресами датчиков │ │ │ │ │ 5. OnLicenseKeyValidated() │ │ │ ◄──── Named Pipe WCF ────── │ │ │ │ │ │ 6. sensors.Length == 3 ? │ │ │ ок : ошибка │ │ │ MAC-адреса → Configuration.xml (whitelist) │ Реальный алгоритм проверки (из нативной DLL) Алгоритм реконструирован по декомпилированному коду Hocoma.Common.SensorValidation.dll (Hex-Rays, строки 452–824):\nШаг 1. Таблицы подстановки — 5 таблиц по 16 символов:\nИндекс Таблица 0 627GDFMKnp5bxPuY 1 Zv4K9p5jAEWTUScB 2 Nf5ryP78Hsk3WSB4 3 HCa7xR64uhpJZKAW 4 tJTdS9rYghqpM5PN Каждая таблица — это перестановка 16 символов, соответствующая hex-цифрам 0–f. Функция invmap(c, codeNum) (SensorValidator::invmap, строка 832):\nНаходит позицию символа c в таблице codes[codeNum] (0–15) Возвращает hex-цифру: 0→'0', 1→'1', ..., 9→'9', 10→'a', ..., 15→'f' Если символ не найден — '-' Шаг 2. Декодирование ключа — 15 символов ключа на 3 шаблона по 12 hex-цифр:\n1 2 3 4 Ключ: G E s p q | b S B J M | n c 4 W N Позиция: 0 1 2 3 4 | 5 6 7 8 9 | 10 11 12 13 14 Таблица №: 0 1 2 3 4 | 0 1 2 3 4 | 0 1 2 3 4 Hex-цифра: 3 9 9 a a | b d e b c | 8 e f f f Каждый символ ключа ищется в таблице с номером position % 5, полученная hex-цифра записывается в один из трёх 12-символьных шаблонов (invkey[0..2]).\nШаг 3. Позиции в шаблоне — карта индексов (indices[3][5]):\nШаблон Позиции в шаблоне Смысл в MAC invkey[0] 0, 1, 2, 3, 6 Байты 0–1 + старший полубайт байта 3 invkey[1] 0, 1, 2, 3, 7 Байты 0–1 + младший полубайт байта 3 invkey[2] 0, 1, 2, 3, 8 Байты 0–1 + старший полубайт байта 4 Незаполненные позиции (4, 5, 9, 10, 11) остаются '0' — они не участвуют в сравнении.\nШаг 4. Сравнение с датчиками — каждый из трёх шаблонов сравнивается со всеми обнаруженными MAC-адресами (из которых удалены пробелы/двоеточия). Проверяются только 5 указанных позиций. Если все 5 совпадают — датчик засчитывается.\nШаг 5. Результат: три строки с ID датчиков, прошедших проверку, разделённые /. Невалидные предваряются -.\nPython: генерация и проверка ключа 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 \u0026#34;\u0026#34;\u0026#34; Реализация алгоритма лицензионного ключа ValedoMotion на Python. Основана на реверс-инжиниринге Hocoma.Common.SensorValidation.dll (Hex-Rays). \u0026#34;\u0026#34;\u0026#34; # Таблицы подстановки (codes[5][16]) — 5 перестановок 16 символов, соответствующих hex-цифрам 0..f CODES = [ \u0026#34;627GDFMKnp5bxPuY\u0026#34;, \u0026#34;Zv4K9p5jAEWTUScB\u0026#34;, \u0026#34;Nf5ryP78Hsk3WSB4\u0026#34;, \u0026#34;HCa7xR64uhpJZKAW\u0026#34;, \u0026#34;tJTdS9rYghqpM5PN\u0026#34;, ] # Карта позиций: какие индексы 12-символьного шаблона заполняются для каждого из 3 сенсоров INDICES = [ [0, 1, 2, 3, 6], # сенсор 0: байты 0-1 + старший полубайт байта 3 [0, 1, 2, 3, 7], # сенсор 1: байты 0-1 + младший полубайт байта 3 [0, 1, 2, 3, 8], # сенсор 2: байты 0-1 + старший полубайт байта 4 ] def invmap(char, code_num): \u0026#34;\u0026#34;\u0026#34; Поиск символа в таблице CODES[code_num]. Позиция (0..15) → hex-цифра: 0→\u0026#39;0\u0026#39;...9→\u0026#39;9\u0026#39;, 10→\u0026#39;a\u0026#39;...15→\u0026#39;f\u0026#39;. Не найден → \u0026#39;-\u0026#39;. \u0026#34;\u0026#34;\u0026#34; table = CODES[code_num] for pos, c in enumerate(table): if c == char: return \u0026#34;0123456789abcdef\u0026#34;[pos] return \u0026#34;-\u0026#34; def generate_license_key(sensor_macs): \u0026#34;\u0026#34;\u0026#34; Генерация 15-символьного лицензионного ключа по трём MAC-адресам датчиков. Аргументы: sensor_macs: список 3 строк, каждая — MAC без разделителей (12 hex-цифр), например [\u0026#34;AABBCCDDEEFF\u0026#34;, \u0026#34;112233445566\u0026#34;, \u0026#34;001122334455\u0026#34;] Возвращает: 15-символьный лицензионный ключ \u0026#34;\u0026#34;\u0026#34; assert len(sensor_macs) == 3, \u0026#34;Нужно ровно 3 MAC-адреса\u0026#34; for mac in sensor_macs: assert len(mac) == 12 and all(c in \u0026#34;0123456789abcdefABCDEF\u0026#34; for c in mac), \\ f\u0026#34;Некорректный MAC: {mac}\u0026#34; key_chars = [] for slot, mac in enumerate(sensor_macs): mac_lower = mac.lower() for col in range(5): # Извлекаем hex-цифру из MAC по индексу INDICES[slot][col] mac_pos = INDICES[slot][col] hex_char = mac_lower[mac_pos] # Преобразуем hex-цифру в позицию (0..15) hex_val = int(hex_char, 16) # Берём символ из соответствующей таблицы key_char = CODES[col][hex_val] key_chars.append(key_char) return \u0026#34;\u0026#34;.join(key_chars) def validate_license_key(key, discovered_macs): \u0026#34;\u0026#34;\u0026#34; Проверка лицензионного ключа против обнаруженных MAC-адресов датчиков. Аргументы: key: 15-символьный лицензионный ключ discovered_macs: список MAC-адресов без разделителей (12 hex-цифр каждый), полученных через BLE-сканирование Возвращает: (is_valid, validated_macs) — кортеж: - is_valid: bool, True если найдено ровно 3 совпадения - validated_macs: список ID валидированных датчиков (пустые строки для несовпавших) \u0026#34;\u0026#34;\u0026#34; assert len(key) == 15, f\u0026#34;Ключ должен быть 15 символов, получено {len(key)}\u0026#34; # Очищаем MAC-адреса от пробелов, двоеточий, тире clean_macs = [] for mac in discovered_macs: clean = mac.replace(\u0026#34;:\u0026#34;, \u0026#34;\u0026#34;).replace(\u0026#34;-\u0026#34;, \u0026#34;\u0026#34;).replace(\u0026#34; \u0026#34;, \u0026#34;\u0026#34;).lower() if len(clean) == 12: clean_macs.append(clean) # Декодируем ключ в 3 шаблона по 12 hex-цифр invkeys = [list(\u0026#34;000000000000\u0026#34;) for _ in range(3)] for slot in range(3): for col in range(5): key_char = key[slot * 5 + col] hex_digit = invmap(key_char, col) invkeys[slot][INDICES[slot][col]] = hex_digit # Сравниваем каждый шаблон со всеми обнаруженными MAC validated = [\u0026#34;------------------\u0026#34;, \u0026#34;------------------\u0026#34;, \u0026#34;------------------\u0026#34;] for slot in range(3): positions = INDICES[slot] for mac in clean_macs: if all(invkeys[slot][pos] == mac[pos] for pos in positions): validated[slot] = mac break valid_count = sum(1 for v in validated if v != \u0026#34;------------------\u0026#34;) is_valid = valid_count == 3 return is_valid, validated # ─── Пример использования ───────────────────────────────────────────── if __name__ == \u0026#34;__main__\u0026#34;: # Три MAC-адреса датчиков Valedo (вымышленные) sensors = [\u0026#34;AABBCCDDEEFF\u0026#34;, \u0026#34;112233445566\u0026#34;, \u0026#34;001122334455\u0026#34;] # Генерация ключа license_key = generate_license_key(sensors) print(f\u0026#34;Сгенерированный ключ: {license_key}\u0026#34;) # Пример вывода: 15 символов, каждый из своей таблицы подстановки # Проверка ключа (имитация BLE-сканирования — все 3 датчика в эфире) ok, validated = validate_license_key(license_key, sensors) print(f\u0026#34;Все три датчика: valid={ok}, sensors={validated}\u0026#34;) # Проверка с чужими датчиками ok, validated = validate_license_key(license_key, [\u0026#34;FFEEDDCCBBAA\u0026#34;, \u0026#34;000000000000\u0026#34;, \u0026#34;DEADBEEF1234\u0026#34;]) print(f\u0026#34;Чужие датчики: valid={ok}, sensors={validated}\u0026#34;) # Проверка с неполным набором (только 2 из 3) ok, validated = validate_license_key(license_key, sensors[:2]) print(f\u0026#34;Только 2 датчика: valid={ok}, sensors={validated}\u0026#34;) Принцип работы кода:\ngenerate_license_key: для каждого из 3 слотов берёт 5 hex-цифр MAC по карте INDICES, преобразует каждую hex-цифру (0–f) в позицию (0–15), выбирает символ из таблицы CODES[col][pos]. Результат — 15-символьная строка. validate_license_key: обратная операция — через invmap восстанавливает hex-цифры из символов ключа, формирует 3 шаблона по 12 hex-цифр, сравнивает каждый с обнаруженными MAC-адресами на 5 позициях. Ключ валиден только если все 3 слота нашли совпадение. Ограничение: сверяются только 5 из 12 позиций MAC, поэтому ключ не содержит полного MAC-адреса — только первые 2 байта + 1 полубайт. Ожидается ровно 3 датчика 1 2 3 // SensorService2.cs:998 bool isEqual = sensors.Length == 3; // Если не 3 — лицензия не валидна Примечание о двойном механизме: в кодовой базе присутствует также C# класс LicenseGenerator (Hocoma.Valedo.Licensing/LicenseGenerator.cs), использующий MD5 и 64-байтный XOR-обфусцированный ключ. Похоже, это либо устаревший механизм, либо внутренний инструмент генерации ключей, не используемый в реальной валидации. Фактическая проверка ключа происходит исключительно в нативной DLL через описанный выше подстановочный шифр.\nЧто проверяется при старте приложения 1 2 3 4 5 6 7 8 9 // Hocoma.Valedo.Startup/LicensePresenter.cs:39-41 — полная логика: private bool CheckNextButtonState() { return (HasMotion || HasShape) \u0026amp;\u0026amp; ( (HasShape \u0026amp;\u0026amp; !HasMotion) // ValedoShape без Motion → ок || (HasMotion \u0026amp;\u0026amp; !string.IsNullOrEmpty(LicenseKey) // ValedoMotion → нужен ключ \u0026amp;\u0026amp; SensorIds.Any()) // и минимум 1 сенсор ); } Кнопка «Далее» на стартовом экране неактивна, пока не введён и не проверен валидный ключ (если используется ValedoMotion). Если установлен только ValedoShape — ключ не требуется.\nДополнительная система: USB-флеш-ключ Помимо основного механизма, существует вторая, независимая система — сканирование USB-накопителей (Hocoma.Framework.Licensing/LicenseService.cs):\nФоновый поток раз в 5 секунд проверяет все сменные диски Ищет файл с заданным именем (keyFile) в корне флешки Читает и расшифровывает Rijndael (AES-192, CBC, ISO10126 padding): Passphrase: \u0026quot;HocomaSecretPassPhraseIsTheBest\u0026quot; Salt: \u0026quot;HocomaSalt\u0026quot; IV: \u0026quot;iHSoNVtdfg7BRXga\u0026quot; PBKDF2 iterations: 1 (очень мало — слабая защита) Если расшифрованное содержимое совпадает с privateKey — флаг isLicenseKeyEnabled = true Если начинается с privateKey — остаток становится ServiceId Эта система, видимо, использовалась для корпоративных USB-ключей лицензий (сервис-инженеры).\nИтоговая таблица Что Где / Как Файл с ключом C:\\ProgramData\\Hocoma_AG\\ValedoMotion\\Configuration.xml Элемент Атрибут License Key=\u0026quot;...\u0026quot; внутри \u0026lt;License\u0026gt; Привязка к железу К MAC-адресам 3 BLE-датчиков Алгоритм Подстановочный шифр: каждый из 15 символов ключа → позиция в одной из 5 таблиц-перестановок → hex-цифра (0-f). Формируется 3 шаблона по 5 hex-цифр, сравнивается с позициями {0,1,2,3,6}, {0,1,2,3,7}, {0,1,2,3,8} MAC-адресов датчиков Где проверка На ПК, в процессе IMUSensorAPI.ServiceHost, через WCF Named Pipes → нативная C++ DLL Родная DLL Hocoma.Common.SensorValidation.dll — нативный C++ (Visual C++), декомпилирован Hex-Rays \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;- \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash; Секретный ключ Таблицы подстановки: 5 строк по 16 символов, вшиты в DLL как константные массивы codes[5][16]. Никакого «секретного ключа» в привычном смысле — только таблицы-перестановки Защита файла XML Digital Signature (RSA, эфемерный ключ на каждое сохранение) Резервная система USB-флешка с AES-192 зашифрованным файлом, сканируется раз в 5 сек Крипто флешки Rijndael, passphrase: HocomaSecretPassPhraseIsTheBest, salt: HocomaSalt, IV: iHSoNVtdfg7BRXga Блокировка старта Кнопка «Далее» неактивна без валидного ключа и лицензированных сенсоров Практическая инструкция: перенос лицензии на новую установку Найдите старый компьютер или бэкап, откройте: 1 C:\\ProgramData\\Hocoma_AG\\ValedoMotion\\Configuration.xml Найдите строку вида License Key=\u0026quot;GEspqbSBJMnc4WN\u0026quot; (ровно 15 символов) — скопируйте значение ключа На новой установке запустите ValedoMotion На стартовом экране (License Manager) вставьте ключ в поле ввода Убедитесь, что BLE-донгл подключен и датчики включены Нажмите «Apply» — приложение само проверит ключ, определит датчики и сохранит конфигурацию Важно: ключ сработает только с теми же самыми физическими датчиками (их MAC-адресами), для которых он был выдан. Если у вас новые датчики — нужен новый ключ от Hocoma/DIH.\nP.S. Ошибка «Login not successful» при загрузке Если после ввода лицензии и обнаружения датчиков (видны индикаторы заряда в заголовке) их индикаторы внезапно пропадают и появляется диалог «Error logging in» — проблема не в лицензии и не в датчиках.\nПричина При нажатии «ValedoMotion» на экране логина (LoginPresenter.cs:114) запускается таймер на 10 секунд. За это время должно произойти:\nСтарт IMUSensorAPI.ServiceHost.exe — работа с датчиками (BLE) Старт Hocoma.ExerciseHost.Main.exe — игровой движок (GAL) ExerciseHost подключается к ValedoMotion через WebSocket (ws://localhost:46544) и WCF Named Pipe Если ExerciseHost не запускается или не подключается за 10 секунд — таймер срабатывает:\n1 2 BeforeTimeOutActions → StopExternalServices → IMUSensorAPI убит → индикаторы пропали AfterTimeOutActions → диалог «Error logging in» Корневая причина: ExerciseHost (GAL-процесс) не стартовал. Чаще всего — завис глобальный мьютекс Global\\58e916b7-b43a-49ec-81ee-c4e8642aaf96 от предыдущего аварийного завершения. ValedoMotion проверяет мьютекс и думает, что ExerciseHost уже работает (ExerciseHostController.cs:56), поэтому не запускает его заново.\nРешение Открой Диспетчер задач, найди процессы Hocoma.ExerciseHost.Main.exe — заверши их все Запусти Hocoma.ExerciseHost.Main.exe вручную из папки установки (окно будет скрыто ValedoMotion) Запусти ValedoMotion — ошибки не будет Или просто перезагрузи компьютер — мьютекс освободится, ValedoMotion сам поднимет ExerciseHost при следующем запуске.\nНо в моём случае это не помогало, и я просто добавил файл в автозагрузку.\nАрхитектура двух процессов 1 2 3 4 5 6 7 8 9 10 11 12 ValedoMotion.exe Hocoma.ExerciseHost.Main.exe (GAL) │ │ │ WebSocket ws://localhost:46544 │ │ «\u0026lt;ExerciseHostServiceReady/\u0026gt;» ◄────────── │ я запустился │ «\u0026lt;ControlRunning\u0026gt;ValedoMotion» ──────────►│ бери управление │ «\u0026lt;Hide/\u0026gt;» ───────────────────────────────►│ спрячь окно │ │ │ WCF Named Pipe │ │ net.pipe://localhost/Hocoma/ExerciseHost/ │ │ GetAvailableExercises() ─────────────────►│ │ GetGameVersions() ───────────────────────►│ │ StopApplication() ───────────────────────►│ Оба процесса — обычные .exe, не службы. ExerciseHost запускается ValedoMotion через Process.Start() и работает в фоне (окно скрыто командой \u0026lt;Hide/\u0026gt;).\n","permalink":"https://dojekon.ru/posts/2026-05-27-valedo/","summary":"\u003ch2 id=\"что-это-за-приложение\"\u003eЧто это за приложение?\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003eValedoMotion\u003c/strong\u003e — ПК-компонент реабилитационной системы \u003cstrong\u003eValedo\u003c/strong\u003e швейцарской компании \u003cstrong\u003eHocoma AG\u003c/strong\u003e (сейчас входит в DIH Group). Предназначен для терапии пациентов с хроническими болями в спине.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eКак работает:\u003c/strong\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eНа тело пациента крепятся 3 беспроводных BLE-датчика (грудь, поясница, бедро)\u003c/li\u003e\n\u003cli\u003eДатчики передают данные о движении в реальном времени через Bluetooth-донгл\u003c/li\u003e\n\u003cli\u003eПО показывает движение пациента визуально, проводит через игровые упражнения\u003c/li\u003e\n\u003cli\u003eВрач получает объективные метрики: амплитуду движений (ROM), прогресс терапии, отчёты\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cstrong\u003eАрхитектура:\u003c/strong\u003e .NET Framework 4.5 + WPF + Prism MVVM + DevExpress 14.2 + Entity Framework 6. Два процесса на ПК: ValedoMotion (GUI) и IMUSensorAPI.ServiceHost (работа с датчиками через BLE-донгл), общаются через WCF Named Pipes.\u003c/p\u003e","title":"ValedoMotion: как найти лицензионный ключ и как он проверяется"}]