export const writeCharacteristic = (text) => (dispatch, getState, DeviceManager) => {
const state = getState();
let buffer = str2ab(text)
let packetsize = 20;
let offset = 0;
let packetlength = packetsize;
do {
if (offset + packetsize > buffer.length) {
packetlength = buffer.length;
} else {
packetlength = offset + packetsize;
}
let packet = buffer.slice(offset, packetlength);
console.log("packet: ", packet)
let base64packet = Base64.btoa(String.fromCharCode.apply(null, packet));
state.BLE_Reducer.connectedDevice.writeCharacteristicWithoutResponseForService('0000FFE0-0000-1000-8000-00805F9B34FB', '0000FFE1-0000-1000-8000-00805F9B34FB', base64packet)
offset += packetsize;
} while (offset < buffer.length)
}
const str2ab = (str) => {
console.log("string to send: ", str)
let bufView = new Uint8Array(str.length);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return bufView;
}
먼저 str2ab 부터 살펴보자. 먼저, Uint8Array는 view 객체(Typed Array)로, 인자로 들어온 문자열을 8bit 단위로 참조할 수 있게한다. 상기 코드로 보자면, 'on\n'이라는 문자열이 들어와 길이 3, 즉 24 비트 크기의 view 객체가 생성되고, for 문을 돌면서 'on\n'의 아스키 코드, 즉 111, 110, 10 이 bufView 배열에 들어가는 것이다. 과정은 알겠지만, 아직 "왜 이렇게 하는데?" 라는 질문에는 답을 할 수 없다.
[모던JS: 심화] 바이너리 데이터와 파일 (velog.io)
writeCharacteristic에서는, 한번에 보낼 packetsize를 20으로 끊어놓고, 만약 전송할 문자열의 길이가 20보다 작다면 그 문자열 길이 그대로, 크다면 20으로 끊어서 연결된 ble device에 전송하고 있다.
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
const Base64 = {
btoa: (input = '') => {
let str = input;
let output = '';
for (let block = 0, charCode, i = 0, map = chars;
str.charAt(i | 0) || (map = '=', i % 1);
output += map.charAt(63 & block >> 8 - i % 1 * 8)) {
charCode = str.charCodeAt(i += 3/4);
if (charCode > 0xFF) {
throw new Error("'btoa' failed: The string to be encoded contains characters outside of the Latin1 range.");
}
block = block << 8 | charCode;
}
return output;
},
atob: (input = '') => {
let str = input.replace(/=+$/, '');
let output = '';
if (str.length % 4 == 1) {
throw new Error("'atob' failed: The string to be decoded is not correctly encoded.");
}
for (let bc = 0, bs = 0, buffer, i = 0;
buffer = str.charAt(i++);
~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
) {
buffer = chars.indexOf(buffer);
}
return output;
}
};
export default Base64;
위 코드는 base64.js이다. 먼저 한줄 한줄 끊어서 천천히 살펴봐야겠다... 그 전에, base 64에 대한 기본적인 내용을 알고 가야한다. base64를 통신에서 사용하는 이유는 다름아닌 통신 중 데이터 손실을 체크하고 방지하기 위함이란다. 예전에 임베디드 강의에서 들었던 패리티 비트가 생각나는 부분이다.
알고 나면 아무것도 아닌 Base64의 정체 - YouTube
for 문에서 부터 벌써 머리가 아파온다. 루프 조건을 보면 || 로 묶여있는데, 여기선 JS에서의 단축평가를 떠올릴 수 있겠다.
[자바스크립트] 논리연산자(&&, ||) 단축평가 (tistory.com)
아무튼 첫번째로 검사하는 내용을 보면 str.charAt(i | 0) 이다. charAt은 문자열 index에 있는 문자를 반환하는데, index를 i | 0 으로 줬다. | 는 비트연산자로, i | 0 === i 이므로 결국 i번째에 문자가 존재하면, 이라고 해석 가능하겠다. 오른쪽 조건은 (map = '=', i % 1) 인데, 쉼표연산자가 쓰였다. 왼쪽부터 시작해서 맨 오른쪽 값을 평가하는 연산이다. 여기서는 map에 '='를 할당하고 i가 정수인지 아닌지 확인하는 거라고 보면 되겠다. i가 정수면 0으로 falsy 일 것이고, 정수가 아니면 0이 아니므로 truthy 일 것이다.
반복할 때마다, output에는 어떤 값을 더해준다. 63 & block >> 8 - i % 1 * 8 을 먼저 해석해보자. 링크는 연산자의 우선순위이다.
연산자 우선순위 - JavaScript | MDN (mozilla.org)
우선순위는 %,* 그리고 - 그리고 >> 그리고 & 이다. 비트연산자가 제일 느리다. 따라서 위 식은 63&(block-(8-((i%1)*8))) 로 정리 가능하다. 63은 64에서 1을 뺀 값으로, 0b0111111 이다.
또 아랫줄엔 charCode에 charCodeAt으로 str의 i += 3/4 번째 문자의 아스키 코드값을 할당한다. 방금 실험해봤는데 charCodeAt에 실수를 넣어줘도 자동으로 버림을 해서 정수로 인식한다. 예를 들면 0.75를 넣으면 0번째 문자를 가져오는 식으로. charCode가 0xFF, 즉 16진수 FF 보다 크다면 (15*16^0+15*16^1 = 255, 0b11111111) error를 발생시킨다. 이후 block을 8과 charCode를 or 비트연산을 한 값을 8비트 만큼 왼쪽 쉬프트 하고 block에 재할당한다.
위 과정을 내가 넣은 값인 'on\n'으로 시뮬레이션 해보자.
조건확인
str.charAt(0) || (확인 안함) //좌항이 truthy 여서 우항은 실행되지 않고 넘어간다.
실행
charCode = str.charCodeAt(0) // 111 = 'o'
if ... // 여긴 아스키코드 표 안에 없는값을 넣었을때 에러를 띄우기 위해 있는 조건문이다.
block = block << 8 | (0b1101111) // block = 0 | (0b1101111) = 111
output += map.charAt(63 & 111 >> 8 - 0.75*8)
0b1101111 >> 2
=> 0b11011 = 27
0b0111111 |
0b0011011
=>0b0011011 = 27 = 25+2
output += map.charAt(27) // output = 'b'
...
위와 같이 chars 문자열로 맵핑해서 인코딩을 하는 것이다. 정확히는 base 64 표라고 하면 되겠다.
암호화를 이렇게 하는 구체적인 이유만큼은... 정말 궁금하지만 넘어가도록 하자... 복호화인 atob 역시 더이상 알아보지 않고 넘어가도록 한다.
계속해서, 'on\n'을 아스키코드로 변환해 담은 배열을 packet으로 잘라 fromCharCode.apply하여 문자열로 반환받고, base64.js에서 인코딩을 해서 다시 반환해준다. 아두이노는 받은 문자열을 \n 단위로 잘라 읽는다.
'BABIL_PROJECT > BLE' 카테고리의 다른 글
BLE (0) | 2022.06.18 |
---|---|
BLE_SCAN 액션 수정 (main/index.js) (0) | 2022.04.11 |
BLE_SCAN 액션 수정 (babilScan.js) (0) | 2022.04.10 |
React 와 Redux 불변성 (Immutability in React and Redux) (0) | 2022.03.31 |
BLE-PLX (0) | 2022.01.29 |