рдпреВ рдЧреЛрдирд╛ рд▓рд╡ рдлреНрд░рдВрдЯреЗрдВрдб рд╕рдореНрдореЗрд▓рди рдореЗрдВ рдЪрд░реНрдЪрд╛ рдХрд┐рдП рдЧрдП рд╡рд┐рд╖рдпреЛрдВ рдкрд░ рд╣рдо рдЪрд░реНрдЪрд╛ рдХрд░рдирд╛ рдЬрд╛рд░реА рд░рдЦрддреЗ рд╣реИрдВред рдпрд╣ рд▓реЗрдЦ рдорд┐рд╢реЗрд▓рд╛ рд▓реЗрд╣рд░ рдХреЗ рдПрдХ рд╡реНрдпрд╛рдЦреНрдпрд╛рди рд╕реЗ рдкреНрд░реЗрд░рд┐рдд рд╣реИред рдЬрдм рддрдХ рд╕реНрд▓рд╛рдЗрдб рд╣реИрдВ рддрдм рддрдХ рд╕рдореНрдореЗрд▓рди рд╡реАрдбрд┐рдпреЛ рдЗрд╕ рд╕рдкреНрддрд╛рд╣ рдЙрдкрд▓рдмреНрдз рд░рд╣реЗрдВрдЧреЗред ( рдкрд╣рд▓реЗ рд╕реЗ рдЙрдкрд▓рдмреНрдз рд╡реАрдбрд┐рдпреЛ )

рдорд╛рдЗрдХреЗрд▓рд╛ рд▓реЗрд╣рд░ рдиреЗ рд╡рд╛рдЗрдмреНрд░реЗрдЯрд░ рдХреЛ рд╡реЗрдм рдПрдкреАрдЖрдИ, рдЕрд░реНрдерд╛рдд рд╡реЗрдм рдмреНрд▓реВрдЯреВрде рдПрдкреАрдЖрдИ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдмреНрд░рд╛рдЙрдЬрд╝рд░ рд╕реЗ рдЬреЛрдбрд╝рд╛ред рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдФрд░ рд╡рд╛рдЗрдмреНрд░реЗрдЯрд░ рдХреЗ рдмреАрдЪ рдкреНрд░реЛрд╕реЗрдиреНрдирд┐рдлрд╝рд░ рдЯреНрд░реИрдлрд╝рд┐рдХ, рдЙрд╕рдиреЗ рдкрд╛рдпрд╛ рдХрд┐ рднреЗрдЬреА рдЧрдИ рдХрдорд╛рдВрдб рдмрд╣реБрдд рд╕рд░рд▓ рд╣реИрдВ, рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП: vibrate: 5
ред рдлрд┐рд░, рдЙрд╕реЗ рдПрдХ рд╡реАрдбрд┐рдпреЛ рд╕реЗ рдХрд░рд╛рд╣рдиреЗ рдХреА рдЖрд╡рд╛рдЬрд╝ рдХреЛ рдХрдВрдкрди рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╕рд┐рдЦрд╛рдпрд╛ рдЬреЛ рдЙрд╕реЗ рдЗрдВрдЯрд░рдиреЗрдЯ рдкрд░ рдорд┐рд▓ рд╕рдХрддрд╛ рдерд╛, рдЙрд╕рдиреЗ рдЕрдкрдиреЗ рд▓рдХреНрд╖реНрдп рд╣рд╛рд╕рд┐рд▓ рдХрд┐рдП :)
рдореЗрд░реЗ рдкрд╛рд╕ рдРрд╕реЗ рдЦрд┐рд▓реМрдиреЗ рдирд╣реАрдВ рд╣реИрдВ рдФрд░ рдбрд┐рдЬрд╝рд╛рдЗрди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХрд╛ рдЗрд░рд╛рджрд╛ рдирд╣реАрдВ рд╣реИ, рд▓реЗрдХрд┐рди рдПрдХ рдкреЛрд▓рд░ H10 рд╣реГрджрдп рдЧрддрд┐ рдореЙрдирд┐рдЯрд░ рд╣реИ рдЬреЛ рдбреЗрдЯрд╛ рд╕рдВрдЪрд╛рд░рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдмреНрд▓реВрдЯреВрде рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реИред рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ, рдореИрдВрдиреЗ "рдЗрд╕реЗ рдХреНрд░реИрдХ рдХрд░рдиреЗ" рдХрд╛ рдлреИрд╕рд▓рд╛ рдХрд┐рдпрд╛ред
рдХреЛрдИ рд╣реИрдХрд┐рдВрдЧ рдирд╣реАрдВ
рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рдпрд╣ рд╕рдордЭрдиреЗ рдпреЛрдЧреНрдп рд╣реИ рдХрд┐ рдбрд┐рд╡рд╛рдЗрд╕ рдХреЛ рдмреНрд░рд╛рдЙрдЬрд╝рд░ рд╕реЗ рдХреИрд╕реЗ рдХрдиреЗрдХреНрдЯ рдХрд┐рдпрд╛ рдЬрд╛рдП? Google рдпрд╛ рдпрд╛рдВрдбреЗрдХреНрд╕, рдЖрдкрдХреЗ рдЭреБрдХрд╛рд╡ рдХреЗ рдЖрдзрд╛рд░ рдкрд░: Web Bluetooth API
, рдФрд░ рдкрд╣рд▓реЗ рд▓рд┐рдВрдХ рдкрд░ рд╣рдо рдЗрд╕ рд╡рд┐рд╖рдп рдкрд░ рдПрдХ рд▓реЗрдЦ рджреЗрдЦрддреЗ рд╣реИрдВред
рджреБрд░реНрднрд╛рдЧреНрдп рд╕реЗ, рд╕рдм рдХреБрдЫ рдмрд╣реБрдд рд╕рд░рд▓ рд╣реИ рдФрд░ рдЕрдЧрд░ рдЖрдк рдирд╣реАрдВ рдЪрд╛рд╣рддреЗ рдХрд┐ рдПрдХ рдЙрдкрдХрд░рдг рдХреЛ рдХреБрдЫ рднреЗрдЬрдирд╛ рдирд╣реАрдВ рд╣реИ, рддреЛ рдЙрд╕реЗ рд╕реВрдБрдШрдиреЗ рдХреЗ рд▓рд┐рдП рдХреБрдЫ рднреА рдирд╣реАрдВ рд╣реИред рдПрдХ рд╣реА рд▓реЗрдЦ рдореЗрдВ рджрд┐рд▓ рдХреА рджрд░ рдкрд░ рдирдЬрд╝рд░ рд░рдЦрдиреЗ рд╕реЗ рдЬреБрдбрд╝рд╛ рдПрдХ рдкреНрд░рджрд░реНрд╢рди рднреА рд╣реИред

рдЗрд╕рдиреЗ рдореБрдЭреЗ рдмреЗрддрд╣рд╛рд╢рд╛ рд╣рддреЛрддреНрд╕рд╛рд╣рд┐рдд рдХрд┐рдпрд╛, рдпрд╣рд╛рдБ рддрдХ рдХрд┐ рд╕реЛрд░реНрд╕ рдХреЛрдб рднреАред рдХреНрдпрд╛ рд╕рдордп рд╣реЛ рдЧрдпрд╛ рд╣реИ?
рд╣рдо рдбрд┐рд╡рд╛рдЗрд╕ рдХрдиреЗрдХреНрдЯ рдХрд░рддреЗ рд╣реИрдВ
рдЖрдЗрдП рд╡рд┐рд╢рд┐рд╖реНрдЯ рдорд╛рд░реНрдХрдЕрдк рдХреЗ рд╕рд╛рде index.html
рдмрдирд╛рдПрдВ:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> </body> </html>
рдЪреВрдВрдХрд┐ рдЪреАрдиреА рдХрд╛рд░реНрдпрд╢рд╛рд▓рд╛рдУрдВ рдореЗрдВ рдореЗрд░реА рдкреНрд░рдорд╛рдгрд┐рдд рд╣реГрджрдп рдЧрддрд┐ рдореЙрдирд┐рдЯрд░ рдбрд┐рд╡рд╛рдЗрд╕ рдЬрд╛рд▓реА рдереА, рд▓реЗрдХрд┐рди рдорд╛рдирдХреЛрдВ рдХреЗ рдЕрдиреБрдкрд╛рд▓рди рдореЗрдВ, рдЗрд╕рдХрд╛ рдХрдиреЗрдХреНрд╢рди рдФрд░ рдЙрдкрдпреЛрдЧ рдХрд┐рд╕реА рднреА рдХрдард┐рдирд╛рдЗрдпреЛрдВ рдХрд╛ рдХрд╛рд░рдг рдирд╣реАрдВ рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдПред рдРрд╕реА рдмрд╛рдд рд╣реИ - рдЬреЗрдиреЗрд░рд┐рдХ рдПрдЯреНрд░реАрдмреНрдпреВрдЯреНрд╕ (GATT) ред рдореИрдВ рдмрд╣реБрдд рд╡рд┐рд╕реНрддрд╛рд░ рдореЗрдВ рдирд╣реАрдВ рдЧрдпрд╛ рдерд╛, рд▓реЗрдХрд┐рди рдЕрдЧрд░ рдпрд╣ рд╕рд░рд▓ рд╣реИ, рддреЛ рдпрд╣ рдПрдХ рдкреНрд░рдХрд╛рд░ рдХрд╛ рд╡рд┐рдирд┐рд░реНрджреЗрд╢ рд╣реИ рдЬреЛ рдмреНрд▓реВрдЯреВрде рдбрд┐рд╡рд╛рдЗрд╕ рдХрд╛ рдкрд╛рд▓рди рдХрд░рддрд╛ рд╣реИред рдЧреИрдЯ рдЙрдирдХреЗ рдЧреБрдгреЛрдВ рдФрд░ рдЗрдВрдЯрд░реИрдХреНрд╢рди рдХрд╛ рд╡рд░реНрдгрди рдХрд░рддрд╛ рд╣реИред рд╣рдорд╛рд░реЗ рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдХреЗ рд▓рд┐рдП, рдпрд╣ рд╣рдо рд╕рднреА рдХреЛ рдкрддрд╛ рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдПред рд╣рдорд╛рд░реЗ рд▓рд┐рдП рдПрдХ рдЙрдкрдпреЛрдЧреА рд▓рд┐рдВрдХ рднреА рд╕реЗрд╡рд╛рдУрдВ рдХреА рдПрдХ рд╕реВрдЪреА рд╣реИ (рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рдЙрдкрдХрд░рдг)ред рддрдм рдореБрдЭреЗ рд╣рд╛рд░реНрдЯ рд░реЗрдЯ рд╕рд░реНрд╡рд┐рд╕ (org.bluaxy.service.heart_rate) рдорд┐рд▓реА, рдЬреЛ рдХрд┐ рд╣рдорд╛рд░реА рдЬрд░реВрд░рдд рдХреА рддрд░рд╣ рд▓рдЧрддреА рд╣реИред
рдбрд┐рд╡рд╛рдЗрд╕ рдХреЛ рдмреНрд░рд╛рдЙрдЬрд╝рд░ рд╕реЗ рдХрдиреЗрдХреНрдЯ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЛ рд╕рдЪреЗрдд рд░реВрдк рд╕реЗ UI рдХреЗ рд╕рд╛рде рдЗрдВрдЯрд░реИрдХреНрдЯ рдХрд░рдирд╛ рд╣реЛрдЧрд╛ред рд╣рд╛рдВ-рд╣рд╛рдВ, рдирд┐рд╢реНрдЪрд┐рдд рд░реВрдк рд╕реЗ, рд╕реБрд░рдХреНрд╖рд╛, рдЗрд╕ рдмрд╛рдд рдкрд░ рд╡рд┐рдЪрд╛рд░ рдХрд░рддреЗ рд╣реБрдП рдХрд┐ рдЬрд┐рдо рдореЗрдВ рдкреНрд░рд╡реЗрд╢ рдХрд░рддреЗ рд╕рдордп, рдореЗрд░реЗ рджрд┐рд▓ рдХреА рджрд░ рдкрд░ рдЪреБрдкрдЪрд╛рдк рд╕рдм рдХреБрдЫ рдЬреЛ рдореИрдВ рдЪрд╛рд╣рддрд╛ рд╣реВрдВ, рд╕реЗ рдЬреЛрдбрд╝рддрд╛ рд╣реИ (рдПрдХ рд╕рдордп рдореИрдВ рдЗрд╕рд╕реЗ рдЖрд╢реНрдЪрд░реНрдпрдЪрдХрд┐рдд рдерд╛)ред рдмреНрд░рд╛рдЙрдЬрд╝рд░ рдбреЗрд╡рд▓рдкрд░реНрд╕ рдХреЗ рд▓рд┐рдП рдирд┐рд╢реНрдЪрд┐рдд рд░реВрдк рд╕реЗ рдзрдиреНрдпрд╡рд╛рдж, рд▓реЗрдХрд┐рди рдХреНрдпреЛрдВ?! рдУрд╣ рдареАрдХ рд╣реИ, рдпрд╣ рдореБрд╢реНрдХрд┐рд▓ рдирд╣реАрдВ рд╣реИ рдФрд░ рдЗрддрдирд╛ рдШреГрдгрд┐рдд рдирд╣реАрдВ рд╣реИред
рдЖрдЗрдП <body>
рдХреЗ рдкреГрд╖реНрда рдореЗрдВ рдкреЗрдЬ рдкрд░ рдПрдХ рдмрдЯрди рдФрд░ рдПрдХ рд╣реИрдВрдбрд▓рд░ рдЬреЛрдбрд╝реЗрдВ:
<button id="pair">Pair device</button> <script> window.onload = () => { const button = document.getElementById('pair') button.addEventListener('pointerup', function(event) { </script>
рдЬреИрд╕рд╛ рдХрд┐ рдЖрдк рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВ, рдЕрднреА рддрдХ рдХреЛрдИ рд╡реАрдпреВ рдирд╣реАрдВ рд╣реИ рдЬрд┐рд╕реЗ рдореИрдВрдиреЗ рд╢реАрд░реНрд╖рдХ рд╕реЗ рджреЗрдЦрддреЗ рд╣реБрдП рд╡рд╛рджрд╛ рдХрд┐рдпрд╛ рдерд╛ред рд▓реЗрдХрд┐рди рдореИрдВ рдЦреБрдж рд╕рдм рдХреБрдЫ рдирд╣реАрдВ рдЬрд╛рдирддрд╛ рдФрд░ рд░рд╛рд╕реНрддреЗ рдореЗрдВ рдПрдХ рд▓реЗрдЦ рд▓рд┐рдЦ рд░рд╣рд╛ рд╣реВрдВред рддреЛ рд╣рдо рдЗрд╕ рддрд░рд╣ рд╕реЗ рдХреНрдпрд╛ рдХрд░ рд░рд╣реЗ рд╣реИрдВ :)
рдбрд┐рд╡рд╛рдЗрд╕ рдХреЛ рдХрдиреЗрдХреНрдЯ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдореЗрдВ navigator.bluetooth.requestDevice
рдЙрдкрдпреЛрдЧ рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдПред рдпрд╣ рд╡рд┐рдзрд┐ рдлрд╝рд┐рд▓реНрдЯрд░ рдХреА рдПрдХ рд╕рд░рдгреА рд╕реНрд╡реАрдХрд╛рд░ рдХрд░ рд╕рдХрддреА рд╣реИред рдЪреВрдВрдХрд┐ рд╣рдорд╛рд░рд╛ рдЖрд╡реЗрджрди рдХреЗрд╡рд▓ рд╣реГрджрдп рдЧрддрд┐ рдореЙрдирд┐рдЯрд░ рдХреЗ рд╕рд╛рде рд╕рдмрд╕реЗ рдЕрдзрд┐рдХ рднрд╛рдЧ рдХреЗ рд▓рд┐рдП рдХрд╛рдо рдХрд░реЗрдЧрд╛, рд╣рдо рдЙрдирдХреЗ рджреНрд╡рд╛рд░рд╛ рдлрд╝рд┐рд▓реНрдЯрд░ рдХрд░реЗрдВрдЧреЗ:
navigator.bluetooth.requestDevice({ filters: [{ services: ['heart_rate'] }] })
рдмреНрд░рд╛рдЙрдЬрд╝рд░ рдореЗрдВ HTML рдлрд╝рд╛рдЗрд▓ рдЦреЛрд▓реЗрдВ рдпрд╛ browser-sync
рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВ:
browser-sync start --server --files ./
рдореИрдВрдиреЗ рд╣реГрджрдп рдЧрддрд┐ рдореЙрдирд┐рдЯрд░ рдкрд╣рдирд╛ рд╣реИ рдФрд░ рдХреБрдЫ рд╕реЗрдХрдВрдб рдХреЗ рдмрд╛рдж рдХреНрд░реЛрдо рдиреЗ рдкрд╛рдпрд╛:

рд╣рдореЗрдВ рдЬрд┐рд╕ рдбрд┐рд╡рд╛рдЗрд╕ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ, рдЙрд╕рдХреЗ рдмрд╛рдж рд╣рдореЗрдВ рдЙрд╕рд╕реЗ рдбреЗрдЯрд╛ рдкрдврд╝рдирд╛ рдЖрд╡рд╢реНрдпрдХ рд╣реИред рдРрд╕рд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдЗрд╕реЗ GATT рд╕рд░реНрд╡рд░ рд╕реЗ рдХрдиреЗрдХреНрдЯ рдХрд░реЗрдВ:
navigator.bluetooth.requestDevice({ filters: [{ services: ['heart_rate'] }] }) .then((device) => { return device.gatt.connect(); })
рдЬреЛ рдбреЗрдЯрд╛ рд╣рдо рдкрдврд╝рдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВ, рд╡рд╣ рд╕реЗрд╡рд╛ рд╡рд┐рд╢реЗрд╖рддрд╛рдУрдВ рдореЗрдВ рд╣реИред рджрд┐рд▓ рдХреА рджрд░ рдкрд░ рдирдЬрд╝рд░ рд░рдЦрдиреЗ рдХреА рдХреЗрд╡рд▓ 3 рд╡рд┐рд╢реЗрд╖рддрд╛рдПрдВ рд╣реИрдВ, рдФрд░ рд╣рдо org.bluaxy.characteristic.heart_rate_memment рдореЗрдВ рд░реБрдЪрд┐ рд░рдЦрддреЗ рд╣реИрдВ
рдЗрд╕ рд╡рд┐рд╢реЗрд╖рддрд╛ рдкрд░ рд╡рд┐рдЪрд╛рд░ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдореЗрдВ рдореБрдЦреНрдп рд╕реЗрд╡рд╛ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред рдИрдорд╛рдирджрд╛рд░реА рд╕реЗ, рдореИрдВ рдХреНрдпреЛрдВ рдирд╣реАрдВ рдЬрд╛рдирддрд╛ред рд╢рд╛рдпрдж рдХреБрдЫ рдЙрдкрдХрд░рдгреЛрдВ рдореЗрдВ рдХрдИ рдЙрдк рд╕реЗрд╡рд╛рдПрдБ рд╣реИрдВред рдлрд┐рд░ рдПрдХ рдкреНрд░рд╢рдВрд╕рд╛рдкрддреНрд░ рдкреНрд░рд╛рдкреНрдд рдХрд░реЗрдВ рдФрд░ рд╕реВрдЪрдирд╛рдУрдВ рдХреЗ рд▓рд┐рдП рд╕рд╛рдЗрди рдЕрдк рдХрд░реЗрдВред
.then(server => { return server.getPrimaryService('heart_rate'); }) .then(service => { return service.getCharacteristic('heart_rate_measurement'); }) .then(characteristic => characteristic.startNotifications()) .then(characteristic => { characteristic.addEventListener( 'characteristicvaluechanged', handleCharacteristicValueChanged ); }) .catch(error => { console.log(error); }); function handleCharacteristicValueChanged(event) { var value = event.target.value; console.log(parseValue(value)); }
parseValue
рдлрд╝рдВрдХреНрд╢рди рд╣реИ рдЬреЛ рдбреЗрдЯрд╛ рдкрд╛рд░реНрд╕ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рдбреЗрдЯрд╛ рд╡рд┐рдирд┐рд░реНрджреЗрд╢ рдпрд╣рд╛рдВ рдкрд╛рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ - org.bluaxy.characteristic.heart_rate_measurement ред рд╣рдо рдЗрд╕ рдлрд╝рдВрдХреНрд╢рди рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рд╡рд┐рд╕реНрддрд╛рд░ рд╕реЗ рдирд╣реАрдВ рдмрддрд╛рдПрдВрдЧреЗ, рд╡рд╣рд╛рдВ рд╕рдм рдХреБрдЫ рд╕рд╣реА рд╣реИред
parseValue parseValue = (value) => { // Chrome 50+ DataView. value = value.buffer ? value : new DataView(value); let flags = value.getUint8(0); // let rate16Bits = flags & 0x1; let result = {}; let index = 1; // if (rate16Bits) { result.heartRate = value.getUint16(index, true); index += 2; } else { result.heartRate = value.getUint8(index); index += 1; } // RR let rrIntervalPresent = flags & 0x10; if (rrIntervalPresent) { let rrIntervals = []; for (; index + 1 < value.byteLength; index += 2) { rrIntervals.push(value.getUint16(index, true)); } result.rrIntervals = rrIntervals; } return result; }
рдпрд╣рд╛рдБ рд╕реЗ рд▓рд┐рдпрд╛ рдЧрдпрд╛: heartRateSensor.js
рдФрд░ рдЗрд╕рд▓рд┐рдП, рдХрдВрд╕реЛрд▓ рдореЗрдВ рд╣рдореЗрдВ рд╡рд╣ рдбреЗрдЯрд╛ рджрд┐рдЦрд╛рдИ рджреЗрддрд╛ рд╣реИ рдЬрд┐рд╕рдХреА рд╣рдореЗрдВ рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред рд╣реГрджрдп рдЧрддрд┐ рдХреЗ рдЕрд▓рд╛рд╡рд╛, рдореЗрд░реЗ рд╣реГрджрдп рдХреА рджрд░ рдХреА рдирд┐рдЧрд░рд╛рдиреА рдЖрд░рдЖрд░ рдЕрдВрддрд░рд╛рд▓ рднреА рджрд░реНрд╢рд╛рддреА рд╣реИред рдореИрдВ рдХрднреА рдирд╣реАрдВ рдЖрдпрд╛ рдХрд┐ рдЙрдиреНрд╣реЗрдВ рдХреИрд╕реЗ рдЗрд╕реНрддреЗрдорд╛рд▓ рдХрд░рдирд╛ рд╣реИ, рдпрд╣ рдЖрдкрдХрд╛ рд╣реЛрдорд╡рд░реНрдХ рд╣реИ :)
рдкреВрд░реНрдг рдкреГрд╖реНрда рдХреЛрдб <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <button id="pair">Pair device</button> <script> window.onload = () => { const button = document.getElementById('pair') parseValue = (value) => { // Chrome 50+ DataView. value = value.buffer ? value : new DataView(value); let flags = value.getUint8(0); // let rate16Bits = flags & 0x1; let result = {}; let index = 1; // if (rate16Bits) { result.heartRate = value.getUint16(index, true); index += 2; } else { result.heartRate = value.getUint8(index); index += 1; } // RR let rrIntervalPresent = flags & 0x10; if (rrIntervalPresent) { let rrIntervals = []; for (; index + 1 < value.byteLength; index += 2) { rrIntervals.push(value.getUint16(index, true)); } result.rrIntervals = rrIntervals; } return result; } button.addEventListener('pointerup', function(event) { navigator.bluetooth.requestDevice({ filters: [{ services: ['heart_rate'] }] }) .then((device) => { return device.gatt.connect(); }) .then(server => { return server.getPrimaryService('heart_rate'); }) .then(service => { return service.getCharacteristic('heart_rate_measurement'); }) .then(characteristic => characteristic.startNotifications()) .then(characteristic => { characteristic.addEventListener('characteristicvaluechanged', handleCharacteristicValueChanged); }) .catch(error => { console.log(error); }); function handleCharacteristicValueChanged(event) { var value = event.target.value; console.log(parseValue(value)); // See https://github.com/WebBluetoothCG/demos/blob/gh-pages/heart-rate-sensor/heartRateSensor.js } }); } </script> </body> </html>
рдбрд┐рдЬрд╝рд╛рдЗрди
рдЕрдЧрд▓рд╛ рдХрджрдо рдЖрд╡реЗрджрди рдХреЗ рдбрд┐рдЬрд╛рдЗрди рдкрд░ рд╡рд┐рдЪрд╛рд░ рдХрд░рдирд╛ рд╣реИред рдУрд╣, рдирд┐рд╢реНрдЪрд┐рдд рд░реВрдк рд╕реЗ, рдПрдХ рд▓реЗрдЦ рдЬреЛ рдкрд╣рд▓реА рдирдЬрд╝рд░ рдореЗрдВ рд╕рд░рд▓ рд╣реИ, рдПрдХ рдЧреИрд░-рддреБрдЪреНрдЫ рдХрд╛рд░реНрдп рдореЗрдВ рдмрджрд▓ рдЬрд╛рддрд╛ рд╣реИред рдореИрдВ рд╕рднреА рдкреНрд░рдХрд╛рд░ рдХреЗ рдкрд╛рдереЛрд╕ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдирд╛ рдЪрд╛рд╣рддрд╛ рд╣реВрдВ рдФрд░ рдореЗрд░реЗ рд╕рд┐рд░ рдореЗрдВ рдЙрди рд▓реЗрдЦреЛрдВ рдХреА рдХрддрд╛рд░ рд╣реИ рдЬрд┐рдиреНрд╣реЗрдВ рдореБрдЭреЗ рд╕реАрдПрд╕рдПрд╕ рдЧреНрд░рд┐рдб рдкрд░ рдкрдврд╝рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ flexbox рдФрд░ рдЬреЗрдПрд╕ (рдкрд▓реНрд╕ рдПрдиреАрдореЗрд╢рди рд╕реНрдерд┐рд░ рдирд╣реАрдВ рд╣реИ) рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рд╕реАрдПрд╕рдПрд╕ рдПрдиреАрдореЗрд╢рди рдЬреЛрдбрд╝рддреЛрдбрд╝ред
рд╕реНрдХреЗрдЪ
рдореБрдЭреЗ рд╕реБрдВрджрд░ рдбрд┐рдЬрд╛рдЗрди рдкрд╕рдВрдж рд╣реИ, рд▓реЗрдХрд┐рди рдбрд┐рдЬрд╛рдЗрдирд░ рдореЗрд░реЗ рд╕рд╛рде рдРрд╕рд╛ рд╣реИред
рдореЗрд░реЗ рдкрд╛рд╕ рдлрд╝реЛрдЯреЛрд╢реЙрдк рдирд╣реАрдВ рд╣реИ, рд╣рдо рдХрд┐рд╕реА рддрд░рд╣ рд╕реЗ рдмрд╛рд╣рд░ рдирд┐рдХрд▓реЗрдВрдЧреЗред
рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рдЪрд▓реЛ Vue-cli рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдПрдХ рдирдпрд╛ Vue.js рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдмрдирд╛рдПрдВ
vue create heart-rate
рдореИрдВрдиреЗ рдореИрдиреНрдпреБрдЕрд▓ рд╕реЗрдЯрд┐рдВрдЧреНрд╕ рдХреЛ рдЪреБрдирд╛ рдФрд░ рдкрд╣рд▓рд╛ рд╕реЗрдЯрд┐рдВрдЧ рдкреЗрдЬ рдЗрд╕ рддрд░рд╣ рджрд┐рдЦрддрд╛ рд╣реИ:

рдЕрдЧрд▓рд╛, рдЕрдкрдиреЗ рд▓рд┐рдП рдЪреБрдиреЗрдВ, рд▓реЗрдХрд┐рди рдореЗрд░реЗ рдкрд╛рд╕ рдПрдХ рдПрдпрд░рдмреАрдПрдирдмреА, рдЬреЗрд╕реНрдЯ рдФрд░ рд╕реИрд╕ рдХреЙрдиреНрдлрд┐рдЧ рд╣реИред
рдореИрдВрдиреЗ рд╡реЗрд╕ рдмреЛрд╕ рд╕реАрдПрд╕рдПрд╕ рдЧреНрд░рд┐рдб рдЯреНрдпреВрдЯреЛрд░рд┐рдпрд▓ рдХреЗ рдЖрдзреЗ рд╣рд┐рд╕реНрд╕реЗ рдХреЛ рджреЗрдЦрд╛, рдореИрдВ рд╕рд▓рд╛рд╣ рджреЗрддрд╛ рд╣реВрдВ рдХрд┐ рд╡реЗ рд╕реНрд╡рддрдВрддреНрд░ рд╣реИрдВред
рдпрд╣ рдкреНрд░рд╛рд░рдВрднрд┐рдХ рд▓реЗрдЖрдЙрдЯ рдХрд░рдиреЗ рдХрд╛ рд╕рдордп рд╣реИред рд╣рдо рдХрд┐рд╕реА рднреА рд╕реАрдПрд╕рдПрд╕ рдлреНрд░реЗрдорд╡рд░реНрдХ рдХрд╛ рдЙрдкрдпреЛрдЧ рдирд╣реАрдВ рдХрд░реЗрдВрдЧреЗ, рд╣рдорд╛рд░реЗ рдЕрдкрдиреЗ рд╕рднреАред рдмреЗрд╢рдХ, рд╣рдо рд╕рдорд░реНрдерди рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдирд╣реАрдВ рд╕реЛрдЪрддреЗ рд╣реИрдВред
рдЙрд▓реНрд▓реВ рдХрд╛ рдЬрд╛рджреВ
рдФрд░ рдЗрд╕рд▓рд┐рдП, рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рд╣рдо рдЕрдкрдиреЗ layout
рдХреЛ рдкрд░рд┐рднрд╛рд╖рд┐рдд рдХрд░рддреЗ рд╣реИрдВред рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ, рдЖрд╡реЗрджрди рджреЛ рднрд╛рдЧреЛрдВ рд╕реЗ рдорд┐рд▓рдХрд░ рдмрдиреЗрдЧрд╛ред рд╣рдо рдЙрдиреНрд╣реЗрдВ рдХрд╣реЗрдВрдЧреЗ рдХрд┐ - first
рдФрд░ second
ред рдкрд╣рд▓реЗ рднрд╛рдЧ рдореЗрдВ рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдПрдХ рд╕рдВрдЦреНрдпрд╛рддреНрдордХ рдкреНрд░рддрд┐рдирд┐рдзрд┐рддреНрд╡ рд╣реЛрдЧрд╛ (рдкреНрд░рддрд┐ рдорд┐рдирдЯ рдзрдбрд╝рдХрддрд╛ рд╣реИ), рджреВрд╕рд░реЗ рдЧреНрд░рд╛рдл рдореЗрдВред
рдореИрдВрдиреЗ рдпрд╣рд╛рдВ рд╕реЗ рд░рдВрдЧ рдпреЛрдЬрдирд╛ рдЪреБрд░рд╛рдиреЗ рдХрд╛ рдлреИрд╕рд▓рд╛ рдХрд┐рдпрд╛ред

рдпрджрд┐ рдЖрдкрдиреЗ рдкрд╣рд▓реЗ рд╕реЗ рдРрд╕рд╛ рдирд╣реАрдВ рдХрд┐рдпрд╛ рд╣реИ рддреЛ рд╣рдо рдЕрдкрдирд╛ Vue рдПрдкреНрд▓рд┐рдХреЗрд╢рди рд▓реЙрдиреНрдЪ рдХрд░рддреЗ рд╣реИрдВ:
npm run serve
рдЙрдкрдХрд░рдг рд╕реНрд╡рдпрдВ рдмреНрд░рд╛рдЙрдЬрд╝рд░ рдХреЛ рдЦреЛрд▓ рджреЗрдЧрд╛ (рдпрд╛ рдирд╣реАрдВ), рдПрдХ рдЧрд░реНрдо рдкреБрдирдГ рд▓реЛрдб рд╣реИ рдФрд░ рдмрд╛рд╣рд░реА рдкрд░реАрдХреНрд╖рдг рдХреЗ рд▓рд┐рдП рдПрдХ рд▓рд┐рдВрдХ рд╣реИред рдореИрдВрдиреЗ рддреБрд░рдВрдд рдЕрдкрдиреЗ рдмрдЧрд▓ рдореЗрдВ рдПрдХ рдореЛрдмрд╛рдЗрд▓ рдлреЛрди рд░рдЦ рджрд┐рдпрд╛, рдХреНрдпреЛрдВрдХрд┐ рд╣рдо рдореЛрдмрд╛рдЗрд▓ рдХреЗ рдкрд╣рд▓реЗ рдбрд┐рдЬрд╛рдЗрди рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рд╕реЛрдЪ рд░рд╣реЗ рд╣реИрдВред рджреБрд░реНрднрд╛рдЧреНрдп рд╕реЗ, рдореИрдВрдиреЗ рдЯреЗрдореНрдкрд▓реЗрдЯ рдореЗрдВ рдПрдХ PWA рдЬреЛрдбрд╝рд╛, рдФрд░ рдореЛрдмрд╛рдЗрд▓ рдлреЛрди рдкрд░, рдмреНрд░рд╛рдЙрдЬрд╝рд░ рдмрдВрдж рд╣реЛрдиреЗ рдкрд░ рдХреИрд╢ рдХреЛ рд╕рд╛рдл рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдРрд╕рд╛ рд╣реЛрддрд╛ рд╣реИ, рдФрд░ рд╕рд╣реЗрдЬрдиреЗ рдХреЗ рд▓рд┐рдП рдареАрдХ рдЕрдкрдбреЗрдЯ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред рд╕рд╛рдорд╛рдиреНрдп рддреМрд░ рдкрд░, рдПрдХ рд╕рдордЭ рд╕реЗ рдмрд╛рд╣рд░ рдХрд╛ рдХреНрд╖рдг рдЬрд┐рд╕рдХреЗ рд╕рд╛рде рдореБрдЭреЗ рд╕рдордЭ рдирд╣реАрдВ рдЖрдпрд╛ред
рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, utils.js
рдЬреЛрдбрд╝реЗрдВ, рдкрд╛рд░реНрд╕рд┐рдВрдЧ рдорд╛рдиреЛрдВ рдХреЗ рд╣рдорд╛рд░реЗ рдХрд╛рд░реНрдп рдХреЗ рд╕рд╛рде, рдЗрд╕реЗ рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдореЗрдВ рдПрд╕реНрд▓рд┐рдВрдЯ рдХреЗ рддрд╣рдд рдереЛрдбрд╝рд╛ рд╕рд╛ рд░рд┐рдлреИрдХреНрдЯ рдХрд░рддреЗ рд╣реБрдПред
utils.js export const parseHeartRateValues = (data) => { // Chrome 50+ DataView. const value = data.buffer ? data : new DataView(data); const flags = value.getUint8(0); // const rate16Bits = flags & 0x1; const result = {}; let index = 1; // if (rate16Bits) { result.heartRate = value.getUint16(index, true); index += 2; } else { result.heartRate = value.getUint8(index); index += 1; } // RR const rrIntervalPresent = flags & 0x10; if (rrIntervalPresent) { const rrIntervals = []; for (; index + 1 < value.byteLength; index += 2) { rrIntervals.push(value.getUint16(index, true)); } result.rrIntervals = rrIntervals; } return result; }; export default { parseHeartRateValues, };
рддрдм рд╣рдо HelloWolrd.vue
рд╕реЗ рд╕рднреА рдЕрдирд╛рд╡рд╢реНрдпрдХ рдХреЛ рд╣рдЯрд╛ рджреЗрддреЗ рд╣реИрдВ, HelloWolrd.vue
рдирд╛рдо рдмрджрд▓рдХрд░ HelloWolrd.vue
рджрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рдпрд╣ рдШрдЯрдХ рдкреНрд░рддрд┐ рдорд┐рдирдЯ рдмреАрдЯреНрд╕ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЬрд┐рдореНрдореЗрджрд╛рд░ рд╣реЛрдЧрд╛ред
<template> <div> <span>{{value}}</span> </div> </template> <script> export default { name: 'HeartRate', props: { </script> // SCSS <style scoped lang="scss"> @import '../styles/mixins'; div { @include heart-rate-gradient; font-size: var(--heart-font-size); // } </style>
рдЪрд╛рд░реНрдЯ рдХреЗ рд▓рд┐рдП HeartRateChart.vue
рдмрдирд╛рдПрдВ:
// HeartRateChart.vue <template> <div> chart </div> </template> <script> export default { name: 'HeartRateChart', props: { values: { type: Array, default: () => [], . . }, }, }; </script>
App.vue
рдЕрдкрдбреЗрдЯ рдХрд░ App.vue
:
App.vue <template> <div class=app> <div class=heart-rate-wrapper> <HeartRate v-if=heartRate :value=heartRate /> <i v-else class="fas fa-heartbeat"></i> <div> <button v-if=!heartRate class=blue>Click to start</button> </div> </div> <div class=heart-rate-chart-wrapper> <HeartRateChart :values=heartRateData /> </div> </div> </template> <script> import HeartRate from './components/HeartRate.vue'; import HeartRateChart from './components/HeartRateChart.vue'; import { parseHeartRateValues } from './utils'; export default { name: 'app', components: { HeartRate, HeartRateChart, }, data: () => ({ heartRate: 0, heartRateData: [], }), methods: { handleCharacteristicValueChanged(e) { this.heartRate = parseHeartRateValues(e.target.value).heartRate; }, }, }; </script> <style lang="scss"> @import './styles/mixins'; html, body { margin: 0px; } :root { // COLORS
рдФрд░ рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ mixins.scss
, рдЬрдмрдХрд┐ рдХреЗрд╡рд▓ рдПрдХ рдорд┐рд╢реНрд░рдг рд╣реИ рдЬреЛ рдЖрдЗрдХрди рдХреЗ рд░рдВрдЧ рдХреЗ рд▓рд┐рдП рдЬрд┐рдореНрдореЗрджрд╛рд░ рд╣реИ рдФрд░ рдкрд╛рда рдкреНрд░рддрд┐ рдорд┐рдирдЯ рдмреАрдЯреНрд╕ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рддрд╛ рд╣реИред
@mixin heart-rate-gradient { background: -webkit-linear-gradient(
рдпрд╣ рдЗрд╕ рддрд░рд╣ рдирд┐рдХрд▓рд╛:
рджрд┐рд▓рдЪрд╕реНрдк рдмрд┐рдВрджреБрдУрдВ рдореЗрдВ рд╕реЗ - рджреЗрд╢реА рд╕реАрдПрд╕рдПрд╕ рдЪрд░ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдПрд╕рд╕реАрдПрд╕рдПрд╕ рд╕реЗ рдорд┐рд╢реНрд░рдгред
рдкреВрд░рд╛ рдкреГрд╖реНрда CSS Grid
:
display: grid; grid-gap: 1rem; height: 100vh; grid-template-rows: 1fr 1fr; grid-template-areas: "first" "second";
flexbox
рддрд░рд╣, рдореВрд▓ рдХрдВрдЯреЗрдирд░ рдореЗрдВ рдХрд┐рд╕реА рдкреНрд░рдХрд╛рд░ рдХрд╛ display
рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдПред рдЗрд╕ рдорд╛рдорд▓реЗ рдореЗрдВ, рдпрд╣ grid
ред
grid-gap
- columns
рдФрд░ rows
рдмреАрдЪ рдПрдХ рдкреНрд░рдХрд╛рд░ рдХрд╛ рд╕реНрдерд╛рдиред
height: 100vh
- рдкреВрд░реЗ viewport
рдХреА рдКрдБрдЪрд╛рдИ, рдпрд╣ рдЖрд╡рд╢реНрдпрдХ рд╣реИ рдХрд┐ height: 100vh
(рд╣рдорд╛рд░реЗ рдЖрд╡реЗрджрди рдХреЗ 2 рднрд╛рдЧ)ред
grid-template-rows
- рд╣рдорд╛рд░реЗ рдЯреЗрдореНрдкрд▓реЗрдЯ рдХреЛ рдкрд░рд┐рднрд╛рд╖рд┐рдд рдХрд░реЗрдВ, fr
рдПрдХ рдЪреАрдиреА рдЗрдХрд╛рдИ рд╣реИ рдЬреЛ grid-gap
рдФрд░ рдЕрдиреНрдп рдЧреБрдгреЛрдВ рдХреЛ рдзреНрдпрд╛рди рдореЗрдВ рд░рдЦрддреА рд╣реИ рдЬреЛ рдЖрдХрд╛рд░ рдХреЛ рдкреНрд░рднрд╛рд╡рд┐рдд рдХрд░рддреЗ рд╣реИрдВред
grid-template-areas
- рд╣рдорд╛рд░реЗ рдЙрджрд╛рд╣рд░рдг рдореЗрдВ, рд╕рд┐рд░реНрдл рд╢рдмреНрджрд╛рд░реНрдеред
Chrome рдиреЗ рдЕрднреА рддрдХ CSS рдЧреНрд░рд┐рдб рдХреЗ рдирд┐рд░реАрдХреНрд╖рдг рдХреЗ рд▓рд┐рдП рд╕рд╛рдорд╛рдиреНрдп рдЙрдкрдХрд░рдг рдирд╣реАрдВ рджрд┐рдП рд╣реИрдВ:

рдЙрд╕реА рд╕рдордп рдордл рдореЗрдВ:

рдЕрдм рд╣рдореЗрдВ рдПрдХ рдмрдЯрди рдХреНрд▓рд┐рдХ рд╣реИрдВрдбрд▓рд░ рдЬреЛрдбрд╝рдиреЗ рдХреА рдЬрд░реВрд░рдд рд╣реИ, рдЬреИрд╕рд╛ рд╣рдордиреЗ рдкрд╣рд▓реЗ рдХрд┐рдпрд╛ рдерд╛ред
рдПрдХ рд╣реИрдВрдбрд▓рд░ рдЬреЛрдбрд╝реЗрдВ:
// App.vue <button v-if=!heartRate @click=onClick class=blue>Click to start</button>
// Methods: {} onClick() { navigator.bluetooth.requestDevice({ filters: [{ services: ['heart_rate'] }], }) .then(device => device.gatt.connect()) .then(server => server.getPrimaryService('heart_rate')) .then(service => service.getCharacteristic('heart_rate_measurement')) .then(characteristic => characteristic.startNotifications()) .then(characteristic => characteristic.addEventListener('characteristicvaluechanged', this.handleCharacteristicValueChanged.bind(this))) .catch(error => console.log(error)); },
рдордд рднреВрд▓реЛ рдХрд┐ рдпрд╣ рдХреЗрд╡рд▓ рдХреНрд░реЛрдо рдореЗрдВ рдХрд╛рдо рдХрд░рддрд╛ рд╣реИ рдФрд░ рдХреЗрд╡рд▓ рдПрдВрдбреНрд░реЙрдЗрдб рдкрд░ рдХреНрд░реЛрдо рдореЗрдВ :)
рдЗрд╕рдХреЗ рдмрд╛рдж, рдПрдХ рдЪрд╛рд░реНрдЯ рдЬреЛрдбрд╝реЗрдВ, рд╣рдо Chart.js
рд▓рд┐рдП Chart.js
рдФрд░ рдПрдХ рдЖрд╡рд░рдг рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВрдЧреЗ
npm install vue-chartjs chart.js
рдзреНрд░реБрд╡реАрдп рдореЗрдВ 5 рдкреНрд░рд╢рд┐рдХреНрд╖рдг рдХреНрд╖реЗрддреНрд░ рд╣реИрдВ ред рдЗрд╕рд▓рд┐рдП, рд╣рдореЗрдВ рдХрд┐рд╕реА рддрд░рд╣ рдЗрди рдХреНрд╖реЗрддреНрд░реЛрдВ рдХреЗ рдмреАрдЪ рдЕрдВрддрд░ рдХрд░рдиреЗ рдФрд░ / рдпрд╛ рдЙрдиреНрд╣реЗрдВ рд╕реНрдЯреЛрд░ рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА heartRateData
ред рд╕реМрдВрджрд░реНрдпрд╢рд╛рд╕реНрддреНрд░ рдХреЗ рд▓рд┐рдП, рд╣рдо рдлреЙрд░реНрдо рдХрд╛ рдбрд┐рдлрд╝реЙрд▓реНрдЯ рдорд╛рди рдмрдирд╛рддреЗ рд╣реИрдВ:
heartRateData: [[], [], [], [], [], []],
рд╣рдо 5 рдХреНрд╖реЗрддреНрд░реЛрдВ рдХреЗ рдЕрдиреБрд╕рд╛рд░ рдореВрд▓реНрдпреЛрдВ рдХреЛ рдмрд┐рдЦреЗрд░реЗрдВрдЧреЗ:
pushData(index, value) { this.heartRateData[index].push({ x: Date.now(), y: value }); this.heartRateData = [...this.heartRateData]; }, handleCharacteristicValueChanged(e) { const value = parseHeartRateValues(e.target.value).heartRate; this.heartRate = value; switch (value) { case value > 104 && value < 114: this.pushData(1, value); break; case value > 114 && value < 133: this.pushData(2, value); break; case value > 133 && value < 152: this.pushData(3, value); break; case value > 152 && value < 172: this.pushData(4, value); break; case value > 172: this.pushData(5, value); break; default: this.pushData(0, value); } },
Vue.js ChartJS рдирд┐рдореНрдирд╛рдиреБрд╕рд╛рд░ рдЙрдкрдпреЛрдЧ рдХрд┐рдП рдЬрд╛рддреЗ рд╣реИрдВ:
// Example.js import { Bar } from 'vue-chartjs' export default { extends: Bar, mounted () { this.renderChart({ labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], datasets: [ { label: 'GitHub Commits', backgroundColor: '#f87979', data: [40, 20, 12, 39, 10, 40, 39, 80, 40, 20, 12, 11] } ] }) } }
рдЖрдк рдЖрд╡рд╢реНрдпрдХ рдЪрд╛рд░реНрдЯ рд╢реИрд▓реА рдХрд╛ рдЖрдпрд╛рдд рдХрд░рддреЗ рд╣реИрдВ, рдЕрдкрдиреЗ рдШрдЯрдХ рдХрд╛ рд╡рд┐рд╕реНрддрд╛рд░ рдХрд░рддреЗ рд╣реИрдВ, рдФрд░ рдЗрд╕ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗред рдЪрд╛рд░реНрдЯ рдЪрд╛рд░реНрдЯ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рддреЗ рд╣реИрдВред
рд╣рдорд╛рд░реЗ рдорд╛рдорд▓реЗ рдореЗрдВ, рдирдП рдбреЗрдЯрд╛ рдХреЗ рдЖрддреЗ рд╣реА рд╢реЗрдбреНрдпреВрд▓ рдХреЛ рдЕрдкрдбреЗрдЯ рдХрд░рдирд╛ рдЖрд╡рд╢реНрдпрдХ рд╣реИ, рдЗрд╕рд▓рд┐рдП рд╣рдо рдбрд┐рд╕реНрдкреНрд▓реЗ рдХреЛ рдПрдХ рдЕрд▓рдЧ updateChart
рд╡рд┐рдзрд┐ рдореЗрдВ updateChart
рдФрд░ рдЗрд╕реЗ mounted
рдкрд░ рдХреЙрд▓ рдХрд░рддреЗ рд╣реИрдВ рдФрд░ values
рдирд┐рдЧрд░рд╛рдиреА рдХреЗ рд▓рд┐рдП рдШрдбрд╝реА рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ values
:
HeartRateChart.vue <script> import { Scatter } from 'vue-chartjs'; export default { extends: Scatter, name: 'HeartRateChart', props: { values: { type: Array, default: () => [[], [], [], [], [], []], }, }, watch: { values() { this.updateChart(); }, }, mounted() { this.updateChart(); }, methods: { updateChart() { this.renderChart({ datasets: [ { label: 'Chilling', data: this.values[0], backgroundColor: '#4f775c', borderColor: '#4f775c', showLine: true, fill: false, }, { label: 'Very light', data: this.values[1], backgroundColor: '#465f9b', borderColor: '#465f9b', showLine: true, fill: false, }, { label: 'Light', data: this.values[2], backgroundColor: '#4e4491', borderColor: '#4e4491', showLine: true, fill: false, }, { label: 'Moderate', data: this.values[3], backgroundColor: '#6f2499', borderColor: '#6f2499', showLine: true, fill: false, }, { label: 'Hard', data: this.values[4], backgroundColor: '#823e62', borderColor: '#823e62', showLine: true, fill: false, }, { label: 'Maximum', data: this.values[5], backgroundColor: '#8a426f', borderColor: '#8a426f', showLine: true, fill: false, }, ], }, { animation: false, responsive: true, maintainAspectRatio: false, elements: { point: { radius: 0, }, }, scales: { xAxes: [{ display: false, }], yAxes: [{ ticks: { beginAtZero: true, fontColor: '#394365', }, gridLines: { color: '#2a334e', }, }], }, }); }, }, }; </script>
рд╣рдорд╛рд░рд╛ рдЖрд╡реЗрджрди рддреИрдпрд╛рд░ рд╣реИред рд▓реЗрдХрд┐рди, рддрд╛рдХрд┐ рд╕реНрдХреНрд░реАрди рдХреЗ рд╕рд╛рдордиреЗ рдХреВрдж рди рдЬрд╛рдПрдВ рдФрд░ рдЦреБрдж рдХреЛ 5 рд╡реЗрдВ рд╕реНрддрд░ рдкрд░ рд▓рд╛рдПрдВ, рдЖрдЗрдП рдПрдХ рдмрдЯрди рдЬреЛрдбрд╝реЗрдВ рдЬреЛ рд╣рдорд╛рд░реЗ рд▓рд┐рдП рд╕рднреА 5 рд╕реНрддрд░реЛрдВ рдХреЗ рд▓рд┐рдП рдпрд╛рджреГрдЪреНрдЫрд┐рдХ рдбреЗрдЯрд╛ рдЙрддреНрдкрдиреНрди рдХрд░реЗрдЧрд╛:
// App.vue <div> <button v-if=!heartRate @click=onClickTest class=blue>Test dataset</button> </div> ... import data from './__mock__/data'; ... onClickTest() { this.heartRateData = [ data(300, 60, 100), data(300, 104, 114), data(300, 133, 152), data(300, 152, 172), data(300, 172, 190), ]; this.heartRate = 73; },
// __mock__/date.js const getRandomIntInclusive = (min, max) => Math.floor(Math.random() * ((Math.floor(max) - Math.ceil(min)) + 1)) + Math.ceil(min); export default (count, from, to) => { const array = []; for (let i = 0; i < count; i += 1) { array.push({ y: getRandomIntInclusive(from, to), x: i }); } return array; };
рдкрд░рд┐рдгрд╛рдо:

рдирд┐рд╖реНрдХрд░реНрд╖
рд╡реЗрдм рдмреНрд▓реВрдЯреВрде рдПрдкреАрдЖрдИ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдирд╛ рдмрд╣реБрдд рд╕рд░рд▓ рд╣реИред рдмрд┐рдЯрд╡рд╛рдЗрдЬрд╝ рдСрдкрд░реЗрдЯрд░реЛрдВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдбреЗрдЯрд╛ рдкрдврд╝рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдХреЗ рд╕рд╛рде рдХреБрдЫ рдХреНрд╖рдг рд╣реИрдВ, рд▓реЗрдХрд┐рди рдпрд╣ рдПрдХ рд╡рд┐рд╢рд┐рд╖реНрдЯ рдХреНрд╖реЗрддреНрд░ рд╣реИред рдордВрддреНрд░рд╛рд▓рдпреЛрдВ рдореЗрдВ рд╕реЗ, рдирд┐рд╢реНрдЪрд┐рдд рд░реВрдк рд╕реЗ рд╕рдорд░реНрдерди рд╣реИред рдлрд┐рд▓рд╣рд╛рд▓ рдпрд╣ рдХреЗрд╡рд▓ рдХреНрд░реЛрдо рд╣реИ, рдФрд░ рдореЛрдмрд╛рдЗрд▓ рдлреЛрди рдХреНрд░реЛрдо рдкрд░ рдФрд░ рдХреЗрд╡рд▓ рдПрдВрдбреНрд░реЙрдЗрдб рдкрд░ред

рдЧреАрдереВрдм рд╕реВрддреНрд░
рдбреЗрдореЛ