• Why Embedded Swift

    • 지금까지 Swift를 사용해서 많은 종류의 소프트웨어를 빌드할 수 있었다.
      • 모바일 앱, 데스크탑 앱, 서버 앱 등
    • 여기서는 새로운 영역인 임베디드 디바이스를 다룰 것이다.
      • 이미 우리 주변에 많이 있다. 스마트 전등, 온도계, 알람, 스마트 선풍기, 음악 장비, 전등 끈 등 프로그래밍 가능한 마이크로컨트롤러를 사용한 수많은 물건들이 이에 해당한다.
      • 취미 수준이던 진지하게 하던 Swift를 사용해서 이 디바이스에서 돌아가는 프로그램을 만들 수 있다.
    • Embedded Swift는 제한된 환경인 임베디드 디바이스를 위한 새로운 컴파일 모드다.
      • 역사적으로는 C나 C++이 사용되어왔다.
      • 이제는 Swift의 편의성, 안전성, 쉬운 사용성들을 임베디드 개발자들도 누릴 수 있다.
        • 임베디드 디바이스
        • 커널 수준 코드
        • 저수준 라이브러리
    • 이미 Secure Enclave 프로세서에서 사용하고 있다.
      • Swift의 메모리 안전성은 이러한 플랫폼에서 큰 유익을 준다.
    • Embedded Swift는 Swift 언어의 대부분의 기능을 가지는 서브셋이다.
      • 값타입, 참조 타입, 클로저, 옵셔널, 에러 처리, 제네릭 등등
    • 아직 실험적인 기능이다.
      • 한창 개발중이고 아직 안정화가 안됐다.
      • 써보려면 프리뷰 툴체인을 써야한다.
  • Showcase

    • HomeKit 기반 컬러 LED등

    • 준비물

      • HomeKit 설정
        • WiFi 네트워크
        • 같은 WiFi에 연결된 애플 장비
      • 프로그래밍 가능한 임베디드 장비
        • 예시에서는 ESP32C6 개발 보드
        • RISC-V 마이크로컨트롤러를 가지고 있다.
        • 컬러 LED도 붙어있다.
      • Mac 컴퓨터
        • USB 케이블로 보드와 연결한다
        • Embedded Swift로 HomeKit accessory를 구현해서 디바이스에 구울 것이다.
        • 디바이스는 WiFi와 HomeKit 네트워크에 접속되고, Home 앱에서 조정이 가능해진다.
    • 개발 환경

      • Neovim, CMake
        • LSP 지원으로 자동완성과 가이드를 제공받을 수 있다.
      • 서드파티 SDK
        • C로 작성된 SDK
        • SDK를 바꾸기는 힘들기 떄문에 브릿징 헤더를 통해서 사용
      • 빌드 설정
        • 벤더의 SDK와 빌드시스템을 기반으로 Swift만 얹는다.
    • 시작 템플릿 코드로 빌드가 되는지 확인

      @_cdecl("app_main")
      func app_main() {
        print("🏎️   Hello, Embedded Swift!")
      }
      
    • 확인했으면 SDK코드를 사용해서 프로그래밍

      @_cdecl("app_main")
      func app_main() {
        print("🏎️   Hello, Embedded Swift!")
        var config = led_driver_get_config()
        let handle = led_driver_init(&config)
        led_driver_set_hue(handle, 240) // blue
        led_driver_set_saturation(handle, 100) // 100%
        led_driver_set_brightness(handle, 80) // 80%
        led_driver_set_power(handle, true)  
      }
      
    • C API를 직접 호출하지 않도록 래핑

      final class LED {
      	var enabled: Bool = true {
      		didSet {
      			led_driver_set_power(handle, enabled)
      		}
      	}
      	
      	var brightness: Int = 100 {
      		didSet {
      			brightness = max(0, min(100, brightness))
      			led_driver_set_brightness(handle, UInt8(brightness))
      		}
      	}
      	
      	// ...
      }
      
      let led = LED()
      
      @_cdecl("app_main")
      func app_main() {
        print("🏎️   Hello, Embedded Swift!")
      
        led.color = .red
        led.brightness = 80
      
        while true {
          sleep(1)
          led.enabled = !led.enabled
          if led.enabled {
            led.color = .hueSaturation(Int.random(in: 0 ..< 360), 100)
          }
        }
      }
      
    • HomeKit 액세서리를 구현하기 위해서 Matter 프로토콜을 사용할 것이다.

      • 스마트 홈 액세서리를 구현하기 위한 열린 표준
      • WWDC 21 “Add support for Matter in your smart home app” 세션에서 자세히 다뤘다.
      • C++ API인데, 이 역시 상호 운용을 통해서 기반을 그대로 쓸 수 있다.
      • device discovery, commisioning, Wi-Fi joining 등의 기능을 제공
      • HomeKit에서 네이티브로 Matter 악세사리를 지원하기 떄문에 자동으로 HomeKit에서 동작하게 된다.
    • Matter 프로토콜 구현

      • 클로저를 콜백으로 쓰고 있다.
        • C처럼 함수 포인터나 타입 없는 컨텍스트 인자를 쓰지 않아도 된다.
      • 연관값을 가진 enum과 패턴 매칭을 통해서 복잡한 명령도 간단하게 해석할 수 있다.
      let led = LED()
      
      @_cdecl("app_main")
      func app_main() {
        print("🏎️   Hello, Embedded Swift!")
        
        // (1) Matter 루트 노드 만들기
        // 루트 노드는 악세사리 자체를 의미한다.
        let rootNode = Matter.Node()
        rootNode.identifyHandler = {
          print("identify")
        }
        
        // (2) 엔드포인트 구성
        // 여기서는 컬러 LED가 된다.
        // 상태를 가지며(색상, 밝기 등) 명령을 받을 수 있다(불 키고 끄기 등)
        let lightEndpoint = Matter.ExtendedColorLight(node: rootNode)
        lightEndpoint.configuration = .default
        lightEndpoint.eventHandler = { event in
          print("lightEndpoint.eventHandler:")
          print(event.attribute)
          print(event.value)
      
          switch event.attribute {
          case .onOff:
            led.enabled = (event.value == 1)
          
          case .levelControl:
            led.brightness = Int(Float(event.value) / 255.0 * 100.0)
          
          case .colorControl(.currentHue):
            let newHue = Int(Float(event.value) / 255.0 * 360.0)
            led.color = .hueSaturation(newHue, led.color.saturation)
          
          case .colorControl(.currentSaturation):
            let newSaturation = Int(Float(event.value) / 255.0 * 100.0)
            led.color = .hueSaturation(led.color.hue, newSaturation)
          
          case .colorControl(.colorTemperatureMireds):
            let kelvins = 1_000_000 / event.value
            led.color = .temperature(kelvins)
          
          default:
            break
          }
        }
        
        // (3) 엔드포인트를 노드에 추가
        rootNode.addEndpoint(lightEndpoint)
        
        // (4) 노드를 애플리케이션에 제공하고 앱을 시작
        let app = Matter.Application()
        app.eventHandler = { event in
          print(event.type)
        }
        app.rootNode = rootNode
        app.start()
        
      }
      
    • 전체 샘플은 깃헙에서 확인 가능

  • How Embedded Swift differs

    • 임베디드 환경의 제한

      • 작고 단순한 바이너리를 필요로 한다.
      • 메모리, 저장소, CPU 성능도 매우 제한적이다.
    • 이 제한에 맞추기 위해서 특정 기능들은 허용하지 않는다.

      • ex. 리플렉션

        • 타입의 자식을 보기 위해서는 타입의 메타데이터 레코드를 봐야한다.

        • 여기에는 필드 이름, 오프셋, 타입 정보 등이 있다. 타입 정보는 다시 메타데이터 레코드를 가진다.

        • 이 레코드들이 다 합쳐지면 코드사이즈가 크게 늘어난다.

          let mirror = Mirror(reflecting: s)
          mirror.children.forEach { … }
          
          struct MyStruct {
            var count: Int
            var name: String
          }
          

          스크린샷 2024-06-18 오후 11.35.00.png

      • 위와 같은 이유로 Metatype과 any 타입도 사용이 불가능하다.

      • 하지만 대부분은 사용이 가능하다.

        스크린샷 2024-06-18 오후 11.36.25.png

    • Embedded Swift는 엄격한 서브셋이지, 변종이나 방언이 아니다.

      • 모든 Embedded Swift 코드는 full Swift에서도 돌아간다.
    • 사용할 수 없는 기능을 사용하면 에러가 난다.

      protocol Countable {
        var count: Int { get }
      }
      
      func count(countable: any Countable) {
        print(countable.count) // Cannot use a value of protocol type in embedded Swift
      }
      
    • any 보다는 some을 쓸 것

      • 제네릭은 컴파일러가 함수 호출을 특수화해서 런타임 지원이 필요없게 한다.
      protocol Countable {
        var count: Int { get }
      }
      
      func count(countable: some Countable) {
        print(countable.count)
      }
      
  • Explore more

    • Swift Evolution 프로세스를 통해서 Vision문서가 채택되어 공개 되어있다.
      • 여기서 고수준 설계, 요구사항, 접근 방법등을 볼 수 있고 컴파일 모드와 언어 서브셋도 잘 설명해준다.
    • 사용해보고 싶으면 사용자 매뉴얼을 참조하는 것을 권장한다.
    • 예제 프로젝트
      • ARM 혹은 RISC-V 기반 여러 임베디드 디바이스를 다룬다.
      • 인기있는 개발 보드나 Playdate 게임 콘솔 등을 다루고 있다.
      • 여러 빌드 시스템 사용법이나 통합 옵션등을 제공하므로 다른 프로젝트의 템플릿 역할도 할 수 있다.
    • 종종 저수준 하드웨어 레지스터에 접근할 필요가 있다.
    • Swift 포럼에 Embedded 서브카테고리가 추가되었다.
    • 현재는 ARM과 RISC-V를 32bit와 64bit 모두 지원한다.