Flutter에서 MiBand4의 현재걸음수를 가져오기

써드파티 앱에서 샤오미 미밴드(MiBand4)에 집계된 현재걸음수를 알아내려면 어떻게 해야 할까요? 오늘은 그 방법에 대해 정리해보고자 합니다. 방법은 간단합니다. 일단 flutter_blue_plus 라이브러리를 dependencies에 정의해주세요.

그런 다음 flutter_blue_plus 인스턴스와 변수들을 초기화 합니다.

static const String MI_BAND_NAME = "Mi Smart Band 4";
static const String UUID_SERVICE_MIBAND =
      "0000fee0-0000-1000-8000-00805f9b34fb";
static const String UUID_CHARACTERISTIC_REALTIME_STEPS =
      "00000007-0000-3512-2118-0009af100700";
FlutterBluePlus flutterBlue = FlutterBluePlus.instance;
BluetoothDevice? miDevice;
BluetoothCharacteristic? stepChr;

그리고 나서 연결하려는 블루투스 디바이스를 찾습니다. 저 같은 경우엔 디바이스 네임을 특정해서 하드코딩된 방식으로 찾았지만, 보통의 경우 별도의 선택 UI를 만들어서 연결합니다.

// 이미 연결되어 있으면 먼저 연결을 해제합니다
List<BluetoothDevice> devices = await flutterBlue.connectedDevices;
if (devices.length > 0) {
  for (BluetoothDevice d in devices) {
    if (d.name == MI_BAND_NAME) {
      d.disconnect();
    }
  }
}
// 이제 다시 연결합니다. 
flutterBlue.startScan(timeout: Duration(seconds: 4));
var subs = flutterBlue.scanResults.listen((results) async {
  for (ScanResult r in results) {
    if (r.device.name == MI_BAND_NAME) {
      miDevice = r.device;
      flutterBlue.stopScan();
      await miDevice!.connect(timeout: Duration(seconds: 4), autoConnect: false);
    }
  }
});

이제 현재걸음수를 얻기 위해 필요한 블루투스특성을 얻어옵니다.

List<BluetoothService> services = await miDevice!.discoverServices();
for (BluetoothService s in services) {
    if (s.uuid.toString() == UUID_SERVICE_MIBAND) {
        for (BluetoothCharacteristic c in basicService!.characteristics) {
            if (c.uuid.toString() == UUID_CHARACTERISTIC_REALTIME_STEPS) {
              stepChr = c;
            }
        }
    }
}

마지막으로 해당특성을 이용해서 현재 걸음수를 얻어와 봅시다. 여기서 중요한 점은 돌아오는 바이트스트림의 1,2번째에 숫자가 패키징되어서 저장된다는 점 입니다.

await stepChr!.setNotifyValue(true);
stepChr!.value.listen((data) {
    if (data.length > 2) {
        int steps = ((((data[1] & 255) | ((data[2] & 255) << 8))));
        print("Current Step: ${steps}");
    }      
});

심박수 정보도 얻어오고 싶은데 아직은 연구가 필요한 상황입니다. 추후에 정리되면 게시하겠습니다. 그 이후에 음악재생하는 방법도 게시해보고자 합니다.

주소를 PNU 코드로 변환해봅시다

PNU(필지고유번호)라는 것이 있습니다. 우리가 살고 있는 곳의 주소를 숫자로만 이루어진 코드로 표현하는 방식 인데요. 정부에서 제공하는 GIS OPEN API를 사용하다보면 이 코드를 이용해야 하는 경우가 많습니다.

예를들어 아래와 같은 데이터가 그러한데요.

국토교통부_토지이용계획정보서비스

내가 살고 있는 곳이 주거지역인지 상업지역인지 등의 토지용도를 알아내려고 할 때 요청값으로 PNU코드를 사용해야 합니다. 위 링크의 REST Request Parameter를 봐보시면 알겠지만, 요청변수에 지번주소는 존재하지 않아요. 지번주소를 PNU 코드로 변환해서 보내줘야 결과를 얻을 수 있습니다.

그런데 이 PNU 코드는 아래와 같은 규칙으로 정해집니다.

광역시도코드(2자리) + 시/군/구 코드(3자리) + 읍/면/동 코드(3자리) + 
리 코드(2자리) + 토지/임야 코드(1자리) + 
본번 코드(4자리) + 부번 코드(4자리)

예를들어 서울특별시 중랑구 상봉동 126-39을 PNU 코드로 바꿔보면요

  • 광역시도 코드는 서울이니까 : 11
  • 시군구 코드는 중랑구니까 : 260
  • 읍면동 코드는 상봉동이니까 : 102
  • 리 코드는 없으니까 : 00
  • 토지니까 : 1 (‘산’ 이 붙는 주소의 경우 임야라서 2가 붙습니다)
  • 본번은 4자리여야 하니까 앞에 0을 붙여서 : 0126
  • 부번도 4자리여야 하니까 앞에 00을 붙여서 : 0039
  • 이렇게 해서 전체코드는 : 1126010200101260039 입니다.

주소를 PNU 코드로 입력하려면 적어도 리까지는 맵타입이던지 Nested Object 데이터가 있어야 하겠죠? 그때 쓰면 좋을 시료 파일을 공유해봅니다. 2022년 4월 버젼이에요. 4만줄이 넘는 파일인데 중간에 “폐지”라고 쓰여져 있는 부분은 사용안하는 부분 입니다.

"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."

PNU 코드를 이용해서 토지이용계획 확인하기

지번주소를 PNU 코드로 바꾸는 것에 관한 내용은 아래 링크 참고해주세요.

주소를 PNU 코드로 변환해봅시다

오늘은 PNU 코드를 이용해서 국토교통부 토지이용계획정보서비스 API에 요청을 보내고 해당 필지의 토지이용계획정보를 얻어와 보겠습니다.

국토교통부_토지이용계획정보서비스

토지이용계획은 굉장히 다양한 정보를 담고 있습니다. 예를들어 해당 필지가 상업지역이면서 대공방어협조구역 일 수도 있고 재정비촉진지구 일 수 도 있어요. 즉, API 정보를 보내면 필드가 1개만 있는게 아니라 배열로 반환된다는 점 입니다.

아래는 저희 동네에 있는 어떤 시설의 결과 값을 API로 요청해서 받은것 입니다. JSON 내용을 봐보면 배열에 담겨 있는 것을 확인할 수 있죠? 이점 참고해서 데이터를 가공하거나 사용하시면 되겠습니다.

"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."
{
  "landUses": {
    "field": [
      {
        "regstrSeCode": "1",
        "pnu": "1126010200100830008",
        "lastUpdtDt": "2022-07-14",
        "manageNo": "15000001126020000000UQA01X0001001",
        "ldCode": "1126010200",
        "ldCodeNm": "서울특별시 중랑구 상봉동",
        "mnnmSlno": "83-8",
        "regstrSeCodeNm": "토지대장",
        "cnflcAt": "1",
        "cnflcAtNm": "포함",
        "prposAreaDstrcCode": "UQA01X",
        "prposAreaDstrcCodeNm": "도시지역",
        "registDt": "2017-09-05"
      },
      {
        "regstrSeCode": "1",
        "pnu": "1126010200100830008",
        "lastUpdtDt": "2022-07-14",
        "manageNo": "15000001126020000000UQA2200017017",
        "ldCode": "1126010200",
        "ldCodeNm": "서울특별시 중랑구 상봉동",
        "mnnmSlno": "83-8",
        "regstrSeCodeNm": "토지대장",
        "cnflcAt": "1",
        "cnflcAtNm": "포함",
        "prposAreaDstrcCode": "UQA220",
        "prposAreaDstrcCodeNm": "일반상업지역",
        "registDt": "2017-09-05"
      },
      {
        "regstrSeCode": "1",
        "pnu": "1126010200100830008",
        "lastUpdtDt": "2022-07-14",
        "manageNo": "15000001126020090001UBA1000001001",
        "ldCode": "1126010200",
        "ldCodeNm": "서울특별시 중랑구 상봉동",
        "mnnmSlno": "83-8",
        "regstrSeCodeNm": "토지대장",
        "cnflcAt": "1",
        "cnflcAtNm": "포함",
        "prposAreaDstrcCode": "UBA100",
        "prposAreaDstrcCodeNm": "과밀억제권역",
        "registDt": "2017-09-05"
      },
      {
        "regstrSeCode": "1",
        "pnu": "1126010200100830008",
        "lastUpdtDt": "2022-07-14",
        "manageNo": "15800001126020192019UNE2000001003",
        "ldCode": "1126010200",
        "ldCodeNm": "서울특별시 중랑구 상봉동",
        "mnnmSlno": "83-8",
        "regstrSeCodeNm": "토지대장",
        "cnflcAt": "1",
        "cnflcAtNm": "포함",
        "prposAreaDstrcCode": "UNE200",
        "prposAreaDstrcCodeNm": "대공방어협조구역",
        "registDt": "2019-07-01"
      },
      {
        "regstrSeCode": "1",
        "pnu": "1126010200100830008",
        "lastUpdtDt": "2022-07-14",
        "manageNo": "30600001126020110009UMZ1000001001",
        "ldCode": "1126010200",
        "ldCodeNm": "서울특별시 중랑구 상봉동",
        "mnnmSlno": "83-8",
        "regstrSeCodeNm": "토지대장",
        "cnflcAt": "1",
        "cnflcAtNm": "포함",
        "prposAreaDstrcCode": "UMZ100",
        "prposAreaDstrcCodeNm": "가축사육제한구역",
        "registDt": "2017-09-05"
      },
      {
        "regstrSeCode": "1",
        "pnu": "1126010200100830008",
        "lastUpdtDt": "2022-07-14",
        "manageNo": "30600001126020190027ZA00130001001",
        "ldCode": "1126010200",
        "ldCodeNm": "서울특별시 중랑구 상봉동",
        "mnnmSlno": "83-8",
        "regstrSeCodeNm": "토지대장",
        "cnflcAt": "2",
        "cnflcAtNm": "저촉",
        "prposAreaDstrcCode": "ZA0013",
        "prposAreaDstrcCodeNm": "건축선",
        "registDt": "2019-11-21"
      },
      {
        "regstrSeCode": "1",
        "pnu": "1126010200100830008",
        "lastUpdtDt": "2022-07-14",
        "manageNo": "61100001126020150099UDA1000003001",
        "ldCode": "1126010200",
        "ldCodeNm": "서울특별시 중랑구 상봉동",
        "mnnmSlno": "83-8",
        "regstrSeCodeNm": "토지대장",
        "cnflcAt": "1",
        "cnflcAtNm": "포함",
        "prposAreaDstrcCode": "UDA100",
        "prposAreaDstrcCodeNm": "재정비촉진지구",
        "registDt": "2017-09-05"
      },
      {
        "regstrSeCode": "1",
        "pnu": "1126010200100830008",
        "lastUpdtDt": "2022-07-14",
        "manageNo": "61100001126020170174UQS1160001001",
        "ldCode": "1126010200",
        "ldCodeNm": "서울특별시 중랑구 상봉동",
        "mnnmSlno": "83-8",
        "regstrSeCodeNm": "토지대장",
        "cnflcAt": "2",
        "cnflcAtNm": "저촉",
        "prposAreaDstrcCode": "UQS116",
        "prposAreaDstrcCodeNm": "대로3류(폭 25m~30m)",
        "registDt": "2017-09-05"
      },
      {
        "regstrSeCode": "1",
        "pnu": "1126010200100830008",
        "lastUpdtDt": "2022-07-14",
        "manageNo": "61100001126020170402UDA1000001001",
        "ldCode": "1126010200",
        "ldCodeNm": "서울특별시 중랑구 상봉동",
        "mnnmSlno": "83-8",
        "regstrSeCodeNm": "토지대장",
        "cnflcAt": "1",
        "cnflcAtNm": "포함",
        "prposAreaDstrcCode": "UDA100",
        "prposAreaDstrcCodeNm": "재정비촉진지구",
        "registDt": "2017-12-06"
      },
      {
        "regstrSeCode": "1",
        "pnu": "1126010200100830008",
        "lastUpdtDt": "2022-07-14",
        "manageNo": "61100001126020170402UQQ3000001001",
        "ldCode": "1126010200",
        "ldCodeNm": "서울특별시 중랑구 상봉동",
        "mnnmSlno": "83-8",
        "regstrSeCodeNm": "토지대장",
        "cnflcAt": "1",
        "cnflcAtNm": "포함",
        "prposAreaDstrcCode": "UQQ300",
        "prposAreaDstrcCodeNm": "지구단위계획구역",
        "registDt": "2017-12-06"
      }
    ],
    "totalCount": "10",
    "numOfRows": "10",
    "pageNo": "1",
    "resultCode": null,
    "resultMsg": null
  }
}