C # рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдмреНрд▓реВрдЯреВрде (BLE) рдбрд┐рд╡рд╛рдЗрд╕ рд╕реЗ рдбреЗрдЯрд╛ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╣реИрд▓реЛ рд╡рд░реНрд▓реНрдб

рд╢реБрдн рджреЛрдкрд╣рд░

рдХреБрдЫ рд╣рдлрд╝реНрддреЗ рдкрд╣рд▓реЗ, рдореБрдЭреЗ рдЗрд╕ рд╕рдорд╕реНрдпрд╛ рдХрд╛ рд╕рд╛рдордирд╛ рдХрд░рдирд╛ рдкрдбрд╝рд╛ рдерд╛ рдХрд┐ рдкрд╣рд▓реЗ рдмреНрд▓реВрдЯреВрде рдХреЗ рд╕рд╛рде рдбреЗрдЯрд╛ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдкрд░ рдХреЛрдИ рд╕рд╛рдорд╛рдиреНрдп рдФрд░ рдЖрдзреБрдирд┐рдХ рд▓реЗрдЦ рдирд╣реАрдВ рд╣реИред рдФрд░ рдореБрдЭреЗ рдпрд╣ рд╕рдордЭрдиреЗ рдХреЗ рд▓рд┐рдП рдЯрд┐рдВрдХрд░ рдХрд░рдирд╛ рдкрдбрд╝рд╛ рдХрд┐ рдХрд╣рд╛рдВ рд╕реЗ рд╢реБрд░реВ рдХрд░реЗрдВ рдФрд░ рдХреИрд╕реЗ рдХрд░реЗрдВред рдФрд░ рддрд╛рдХрд┐ рд▓реЛрдЧ рдореЗрд░реА рдЧрд▓рддрд┐рдпреЛрдВ рдХреЛ рди рджреЛрд╣рд░рд╛рдПрдВ, рдпрд╣рд╛рдБ рдПрдХ рдЫреЛрдЯрд╛ рдирд┐рдмрдВрдз рд╣реИ рдХрд┐ рдпрд╣ рдХреИрд╕реЗ рдХрд░рдирд╛ рд╣реИред



рд▓реЗрдХрд┐рди рдкрд╣рд▓реЗ, рдкреИрдереЛрд╕ рд╢рдмреНрджреЛрдВ рдХреЗ рдПрдХ рдЬреЛрдбрд╝реЗ, рдЬрд┐рд╕рдХреЗ рд▓рд┐рдП рдпрд╣ рдЖрдо рддреМрд░ рдкрд░ рдЖрд╡рд╢реНрдпрдХ рд╣реИред рдЖрдзреБрдирд┐рдХ рджреБрдирд┐рдпрд╛ рддреЗрдЬреА рд╕реЗ рдЪреАрдЬреЛрдВ рдХреА рдЗрдВрдЯрд░рдиреЗрдЯ рдХреА рд╡рд┐рдЪрд╛рд░рдзрд╛рд░рд╛ рдореЗрдВ рдбреВрдмреА рд╣реБрдИ рд╣реИред рдЕрдм рд╕рдм рдХреБрдЫ рдПрдХ рджреВрд╕рд░реЗ рдХреЗ рд╕рд╛рде рдмрд╛рддрдЪреАрдд рдХрд░рддрд╛ рд╣реИ, рдПрдХ рд╕реНрдЯреЛрд╡ рдХреЗ рд╕рд╛рде рдПрдХ рд░реЗрдлреНрд░рд┐рдЬрд░реЗрдЯрд░, рдПрдХ рд╡реИрдХреНрдпреВрдо рдХреНрд▓реАрдирд░ рдХреЗ рд╕рд╛рде рдПрдХ рд▓реЛрд╣рд╛, рдЖрджрд┐ред Apple рд╡реЙрдЪ рдореЗрдВ ECG рдХреЗ рдХрд╛рд░рдг рдмрд╣реБрдд рд╢реЛрд░ рдордЪрд╛рдпрд╛ рдЧрдпрд╛ рдерд╛, рд▓реЗрдХрд┐рди рдЖрдзреБрдирд┐рдХ рд░рдХреНрддрдЪрд╛рдк рдореЙрдирд┐рдЯрд░, рд╣реГрджрдп рдЧрддрд┐ рдореЙрдирд┐рдЯрд░, рдерд░реНрдорд╛рдореАрдЯрд░ рд▓рдВрдмреЗ рд╕рдордп рддрдХ рдмреНрд▓реВрдЯреВрде рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдбреЗрдЯрд╛ рд╕рдВрдЪрд╛рд░рд┐рдд рдХрд░рдиреЗ рдореЗрдВ рд╕рдХреНрд╖рдо рд░рд╣реЗ рд╣реИрдВред рдФрд░ рдпрд╣ рд╕рдм рдХрд┐рд╕реА рди рдХрд┐рд╕реА рдПрдХрд▓ рдиреЗрдЯрд╡рд░реНрдХ рд╕реЗ рдЬреБрдбрд╝рд╛ рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдПред рдФрд░ рдЗрд╕ рдиреЗрдЯрд╡рд░реНрдХ рдореЗрдВ рдореБрдЦреНрдп рддрддреНрд╡ рд╣реИ, рдЬреЛ рдХреЛрдИ рднреА рдХрд╣ рд╕рдХрддрд╛ рд╣реИ, рдПрдХ рдХрдВрдкреНрдпреВрдЯрд░ред рдЗрд╕ рд╕рдВрдмрдВрдз рдореЗрдВ, рдХрд╛рд░реНрдп рдмреНрд▓реВрдЯреВрде рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдПрдХ рд╡рд┐рд╢рд┐рд╖реНрдЯ рдбрд┐рд╡рд╛рдЗрд╕ рд╕реЗ рдбреЗрдЯрд╛ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЙрддреНрдкрдиреНрди рд╣реБрдЖред

рд╢реБрд░реВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдХреНрдпрд╛ рдерд╛ рдФрд░ рдПрдХ рд╕рдорд╛рдзрд╛рди рдХреЗ рд▓рд┐рдП рдЦреЛрдЬ рдХреЛ рддреЗрдЬ рдХрд░ рджрд┐рдпрд╛ред рдФрд░ рд╣рдорд╛рд░реЗ рдкрд╛рд╕ .net Core рдореЗрдВ рдПрдХ рдПрдкреНрд▓реАрдХреЗрд╢рди рд▓рд┐рдЦрд╛ рд╣реБрдЖ рдерд╛ред рдЖрд╡реЗрджрди рдХрд╛ рд╕рд╛рд░ рдХреНрдпрд╛ рдорд╣рддреНрд╡рд╣реАрди рд╣реИ, рд╕рд╛рджрдЧреА рдХреЗ рд▓рд┐рдП рд╣рдо рдорд╛рди рд▓реЗрдВрдЧреЗ рдХрд┐ рд╣рдорд╛рд░реЗ рдкрд╛рд╕ .Net рдХреЛрд░ рдкрд░ рдмрд╕ рдПрдХ рдХрдВрд╕реЛрд▓ рд╣реИред рдЦреИрд░, рдбрд┐рд╡рд╛рдЗрд╕ рдХреЛ рдПрди рдЕрдХреНрд╖рд░ рдХрд╣рд╛ рдЬрд╛рдПрдЧрд╛ ред

рд╕реА # рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдмреНрд▓реВрдЯреВрде рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рд╡рд╛рд▓реЗ рдХреБрдЫ рдЦреЛрдЬрдиреЗ рдХрд╛ рдкрд╣рд▓рд╛ рдкреНрд░рдпрд╛рд╕ 32 рдлрд╝реБрдЯ рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдореЗрдВ рд▓реЗ рдЬрд╛рдПрдЧрд╛ред

NuGet рдкреИрдХреЗрдЬ рдореЗрдВ, рдпрд╣ 32feet.NET рдХреА рддрд░рд╣ рд▓рдЧрддрд╛ рд╣реИред

рдФрд░ рд╡рд╣, рдЕрдкрдиреЗ рдирд╡реАрдирддрдо рдЙрддреНрдкрд╛рдж рд╕рдВрд╕реНрдХрд░рдг рдореЗрдВ, рдпрд╣рд╛рдВ рддрдХ тАЛтАЛрдХрд┐ рдмреНрд▓реВрдЯреВрде рдЙрдкрдХрд░рдгреЛрдВ рдХреЛ рднреА рдвреВрдВрдврддреА рд╣реИ, рд▓реЗрдХрд┐рди рдмреАрдПрд▓рдИ рдорд╛рдирдХ рдирд╣реАрдВ [рдЬреИрд╕рд╛ рдХрд┐ рдпрд╣ рдмрд╣реБрдд рдмрд╛рдж рдореЗрдВ рдирд┐рдХрд▓рд╛]ред рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рд╡рд╣реА рд╡рдирдкреНрд▓рд╕ 5 рдЯреА рдХреЛ рдХрд╛рдлреА рдЦреЛрдЬрд╛ рдЧрдпрд╛ рдерд╛, рд▓реЗрдХрд┐рди рдЖрд╡рд╢реНрдпрдХ рдбрд┐рд╡рд╛рдЗрд╕ рдПрди рдирд╣реАрдВ рд╣реИред рдЗрд╕рдХреЗ рд╕рдорд╛рдирд╛рдВрддрд░, рд▓реЗрдЦрдХ рдХреЗ рдЖрдзрд┐рдХрд╛рд░рд┐рдХ рдЙрддреНрддрд░ рдореЗрдВ рдкрд╛рдпрд╛ рдЧрдпрд╛ рдерд╛ рдХрд┐ рдЙрдирдХрд╛ рдкреБрд╕реНрддрдХрд╛рд▓рдп BLE рдХреЗ рд╕рд╛рде рд╕реИрджреНрдзрд╛рдВрддрд┐рдХ рд░реВрдк рд╕реЗ рдмрд╛рддрдЪреАрдд рдирд╣реАрдВ рдХрд░рддрд╛ рд╣реИ, рдФрд░ рдХреЛрд╢рд┐рд╢ рдХрд░рдиреЗ рдХрд╛ рдХреЛрдИ рдорддрд▓рдм рдирд╣реАрдВ рд╣реИред рд╣рд╛рд▓рд╛рдБрдХрд┐, InTheHand.Devices.Bluaxy on Github рдХрд╛ рдПрдХ рдкреНрд░рд╛рд░рдВрднрд┐рдХ рд╕рдВрд╕реНрдХрд░рдг рд╣реИ рдЬреЛ BLE рдХрд╛ рд╕рдорд░реНрдерди рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдП, рд╕рдм рдХреБрдЫ рдЗрддрдирд╛ рдмрджрд▓ рдЧрдпрд╛ рд╣реИ рдФрд░ рдЗрд╕рдореЗрдВ рдХреЛрдИ рджрд╕реНрддрд╛рд╡реЗрдЬрд╝реАрдХрд░рдг рдирд╣реАрдВ рд╣реИ рдХрд┐ рдпрд╣ 32feet.NET рджреНрд╡рд╛рд░рд╛ рд▓рд┐рдП рдЧрдП рд╡рд┐рдЪрд╛рд░реЛрдВ рдХреЗ рд╕рд╛рде рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреЛ рд╕рдВрдХрд▓рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рднреА рдХрд╛рдо рдирд╣реАрдВ рдХрд░рддрд╛ рд╣реИред

рдирдП рд╢реЛрдз рдиреЗ рдореБрдЭреЗ рдФрд░ рдЕрдзрд┐рдХ рдорд╛рдирдХ рд╕рдорд╛рдзрд╛рдиреЛрдВ рддрдХ рдкрд╣реБрдВрдЪрд╛рдпрд╛, рдЬрд┐рд╕рдХрд╛ рдирд╛рдо рд╣реИ рдпреВрдирд┐рд╡рд░реНрд╕рд▓ рд╡рд┐рдВрдбреЛрдЬ рдкреНрд▓реЗрдЯрдлреЙрд░реНрдо ( UWP )ред рдЗрд╕ рдкреНрд▓реЗрдЯрдлрд╝реЙрд░реНрдо рдХреЛ рд╡рд┐рдХрд╕рд┐рдд рдХрд░рдиреЗ рдореЗрдВ, Microsoft , рдмрд╣реБрдореБрдЦреА рдкреНрд░рддрд┐рднрд╛ рдХреЗ рд╡рд┐рдЪрд╛рд░ рдФрд░ рдПрдХ рдХрдВрдкреНрдпреВрдЯрд░ рдФрд░ рдлреЛрди рдХреЗ рд▓рд┐рдП рдПрдХ рдПрдХрд▓ рдЕрдиреБрдкреНрд░рдпреЛрдЧ рджреНрд╡рд╛рд░рд╛ рдЧрд▓реЗ рд▓рдЧрд╛ рд▓рд┐рдпрд╛, рдФрд░ рдмреНрд▓реВрдЯреВрде рдХреЗ рд╕рд╛рде рдмрд╛рддрдЪреАрдд рдХрд░рдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХреАред рдФрд░ рдпрд╣рд╛рдБ рд╕рдм рдХреБрдЫ рдареАрдХ рдХрд╛рдо рдХрд░рддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди ... рд╣рдорд╛рд░реЗ рдкрд╛рд╕ .Net Core рдкрд░ рдПрдХ рдкреНрд░реЛрдЬреЗрдХреНрдЯ рд╣реИ ... рдФрд░ рдЗрд╕рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдХреБрдЫ рднреА рдирд╣реАрдВ рдХрд┐рдпрд╛ рдЬрд╛рдирд╛ рд╣реИред

рдореБрдЭреЗ рддреБрд░рдВрдд рдпрд╣ рдХрд╣рдирд╛ рд╣реЛрдЧрд╛ рдХрд┐ рд╣рдо .W рдХреЛрд░ рдХреЗ рд╕рд╛рде UWP рдкреБрд╕реНрддрдХрд╛рд▓рдпреЛрдВ рдХреА рдмрд╛рддрдЪреАрдд рдХреЗ рд▓рд┐рдП рд╕рдорд╛рдзрд╛рди рдирд╣реАрдВ рдЦреЛрдЬ рд╕рдХреЗ рдФрд░ рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреЛ 4.7.1 рдкрд░ рд╕реНрд╡рд┐рдЪ рдХрд░рдирд╛ рдкрдбрд╝рд╛ ред рд▓рд╛рдн рдореБрд╢реНрдХрд┐рд▓ рдирд╣реАрдВ рд╣реИред рд╣рд╛рд▓рд╛рдВрдХрд┐ рд╡рд┐рдЪрд╛рд░ рдереЗ рдХрд┐ рдХреИрд╕реЗ рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреЛ .Net рдХреЛрд░ рдкрд░ рдЫреЛрдбрд╝ рджрд┐рдпрд╛ рдЬрд╛рдП рдФрд░ рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП рдирд╛рдорд┐рдд рдкрд╛рдЗрдк (рдирд╛рдо рдкрд╛рдЗрдк) рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдбреЗрдЯрд╛ рдЯреНрд░рд╛рдВрд╕рдлрд░ рдХреЗ рд╕рд╛рде рдПрдХ рдЕрд▓рдЧ рд╡рд┐рдВрдбреЛрдЬ рд╕реЗрд╡рд╛ рдмрдирд╛рдПрдВ рдпрд╛ рдбрдмреНрд▓реНрдпреВрд╕реАрдПрдл рд╕реЗрд╡рд╛ рдмрдврд╝рд╛рдПрдВ рдФрд░ рдЗрд╕рдХреЗ рд╕рд╛рде рдмрд╛рддрдЪреАрдд рд╕реНрдерд╛рдкрд┐рдд рдХрд░реЗрдВ, рд▓реЗрдХрд┐рди рд╣рдорд╛рд░реЗ рдорд╛рдорд▓реЗ рдореЗрдВ рдЗрд╕рдХрд╛ рдХреЛрдИ рдорддрд▓рдм рдирд╣реАрдВ рдерд╛ред



рддреЛ рдЕрдВрдд рдореЗрдВ рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рд╢реБрд░реБрдЖрдд рд╕реЗ рдкрд╣рд▓реЗ рд╣реИ:

  • 4.7.1 рдкрд░ рдкрд░рд┐рдпреЛрдЬрдирд╛ ред
  • Win10 рдХреЛ рд╕рдВрд╕реНрдХрд░рдг 10.0.17134 рдмрд┐рд▓реНрдб 17134 рдореЗрдВ рдЕрджреНрдпрддрди рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ ред

рдкрд╣рд▓реЗ рдЖрдкрдХреЛ рдкреБрд╕реНрддрдХрд╛рд▓рдпреЛрдВ рдХреЗ рдПрдХ рдЬреЛрдбрд╝реЗ рдХреЛ рдХрд▓рдо рд╕реЗ рдЬреЛрдбрд╝рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ, рдЕрд░реНрдерд╛рддреН

  • "рдпреВрдирд┐рд╡рд░реНрд╕рд▓ рд╡рд┐рдВрдбреЛрдЬ рдкреНрд▓реЗрдЯрдлреЙрд░реНрдо рд╕реЗ рд╡рд┐рдВрдбреЛрдЬ"
    C: \ Program Files (x86) \ Windows Kits \ 10 \ UnionMetadata \ 10.0.17134.0 \ Windows .winmd
  • ┬лSystem.Runtime.WindowsRuntime┬╗
    C: \ Program Files (x86) \ Reference Assemblies \ Microsoft \ Framework \ .NETCore \ v4.5 \ System.untime.WindowsRuntime.dll

рдФрд░ рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рд╕рдм рдХреБрдЫ рд╣реИ, рдлрд┐рд░ рдбрд┐рд╡рд╛рдЗрд╕ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдЦреБрдж рд╣реА рд╕рдорд╕реНрдпрд╛рдУрдВ рдХреЗ рдмрд┐рдирд╛ рдкреНрд░рд▓реЗрдЦрди рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдЬрд╛рддрд╛ рд╣реИред
рдмреНрд▓реВрдЯреВрде рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ BLE рдХреЗ рд╕рд╛рде рд╕рд╣рднрд╛рдЧрд┐рддрд╛ рдХрд░реЗрдВрд╡рд┐рдХрд╛рд╕ рд╡рд░реНрдЧ
рд▓реЗрдХрд┐рди рд╣рдореЗрдВ рдпрд╣ рдирд╣реАрдВ рднреВрд▓рдирд╛ рдЪрд╛рд╣рд┐рдП рдХрд┐ рдбрд┐рд╡рд╛рдЗрд╕ рдХреЗ рдПрдкреАрдЖрдИ рдХреЗ рдмрд┐рдирд╛, рдХреБрдЫ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╕рдордЭрджрд╛рд░ рдХрд╛рдо рдирд╣реАрдВ рдХрд░рддрд╛ рд╣реИред

рдбрд┐рд╡рд╛рдЗрд╕ рд╕реЗ рдбреЗрдЯрд╛ рдХреИрд╕реЗ рдкреНрд░рд╛рдкреНрдд рдХрд░реЗрдВ, рдЗрд╕ рдкрд░ рдореЗрд░рд╛ рдирдореВрдирд╛ рдХреЛрдб рд╣реИред

рдпрд╣ рдХреЛрдб рдпрд╣ рд╕реБрдирд┐рд╢реНрдЪрд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдбрд┐рдЬрд╝рд╛рдЗрди рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ рдХрд┐ рдЙрдкрдХрд░рдг рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдЬреЛрдбрд╝рд╛ рдЧрдпрд╛ рд╣реИ (рдЬреБрдбрд╝рд╛ рд╣реБрдЖ)ред

public class BluetoothObserver { BluetoothLEAdvertisementWatcher Watcher { get; set; } public void Start() { Watcher = new BluetoothLEAdvertisementWatcher() { ScanningMode = BluetoothLEScanningMode.Active }; Watcher.Received += Watcher_Received; Watcher.Stopped += Watcher_Stopped; Watcher.Start(); } private bool isFindDevice { get; set; } = false; private async void Watcher_Received(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args) { if (isFindDevice) return; if (args.Advertisement.LocalName.Contains("deviceName")) { isFindDevice = true; BluetoothLEDevice bluetoothLeDevice = await BluetoothLEDevice.FromBluetoothAddressAsync(args.BluetoothAddress); GattDeviceServicesResult result = await bluetoothLeDevice.GetGattServicesAsync(); if (result.Status == GattCommunicationStatus.Success) { var services = result.Services; foreach (var service in services) { if (!service.Uuid.ToString().StartsWith("serviceName")) { continue; } GattCharacteristicsResult characteristicsResult = await service.GetCharacteristicsAsync(); if (characteristicsResult.Status == GattCommunicationStatus.Success) { var characteristics = characteristicsResult.Characteristics; foreach (var characteristic in characteristics) { if (!characteristic.Uuid.ToString().StartsWith("characteristicName")) { continue; } GattCharacteristicProperties properties = characteristic.CharacteristicProperties; if (properties.HasFlag(GattCharacteristicProperties.Indicate)) { characteristic.ValueChanged += Characteristic_ValueChanged; GattWriteResult status = await characteristic.WriteClientCharacteristicConfigurationDescriptorWithResultAsync(GattClientCharacteristicConfigurationDescriptorValue.Indicate); return; } if (properties.HasFlag(GattCharacteristicProperties.Read)) { GattReadResult gattResult = await characteristic.ReadValueAsync(); if (gattResult.Status == GattCommunicationStatus.Success) { var reader = DataReader.FromBuffer(gattResult.Value); byte[] input = new byte[reader.UnconsumedBufferLength]; reader.ReadBytes(input); // input } } } } } } } } private void Characteristic_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args) { var reader = DataReader.FromBuffer(args.CharacteristicValue); byte[] input = new byte[reader.UnconsumedBufferLength]; reader.ReadBytes(input); // input } private void Watcher_Stopped(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementWatcherStoppedEventArgs args) { ; } } 

рдЖрдкрдХрд╛ рдзреНрдпрд╛рди рдХреЗ рд▓рд┐рдП рдзрдиреНрдпрд╡рд╛рджред

Source: https://habr.com/ru/post/hi423893/


All Articles