PC 프로그램과 블록 공작소 연동

PC 프로그램과 블록 공작소 연동

이 문서는 2023년 3월 14일에 작성되었습니다.

PC 프로그램에서 블록 공작소를 연동하는 방법을 설명합니다.

블록 공작소란?

코디니 사이트에는 블록 공작소가 있습니다. 블록 공작소에서 아래와 같이 설명하고 있습니다.

블록 공작소란?

블록을 블록코딩으로 만들 수 있다면 재미있지 않을까요? 블록 공작소는 사용자가 블록코딩으로 자신만의 블록을 만드는 기능입니다. 기본으로 제공되는 블록만으로는 부족했던 분들은, 직접 블록을 만들어서 블록 코딩을 할 수 있습니다.

모든 사용자가 자신만의 블록을 만들 수 있습니다. 그리고 교육 업체도 만들수 있습니다.

  • 사용자는 코디니에서 제작한 많은 블록들을 이용합니다.
  • 그리고 다른 사용자나 업체가 블록 공작소를 이용해서 제작한 블록을 이용할 수도 있습니다.

PC 프로그램과 블록코딩

  • 교구 업체는 PC 프로그램에 하드웨어를 제어하는 여러 함수들을 작성하고,
  • 그 함수를 호출하는 블록을 제작함으로써 블록코딩으로 하드웨어를 제어하는 기능을 만들 수 있습니다.
  • 자체 블록을 제작하는 법, 즉 블록 공작소를 사용하는 방법은 이 문서에서 설명하지 않습니다. 블록 공작소 의 문서를 확인해주세요.
  • 여기에서는 블록 공작소에서 만든 블록이 PC 프로그램의 함수를 호출하는 방법에 대해서 설명합니다.

블록과 함수

  • 블록 공작소에서 만드는 블록은 PC 프로그램에서 작성한 하드웨어 함수를 호출합니다.
  • 하나의 블록은 반드시 한 개의 함수를 호출합니다. 하나의 블록에서 두 개의 함수를 호출할 수 없습니다.
  • 블록과 함수는 일대일 대응인 셈입니다. 그래서 블록을 함수처럼 생각해도 의미가 통합니다.

요약하면 블록=함수입니다.

이제, 블록을 함수로 생각해보겠습니다.

함수는 리턴값의 유무에 따라, 2가지 유형으로 분류할 수 있습니다.

  1. 리턴값이 있는 함수
  2. 리턴값이 없는 함수(void 함수)

아래는 wiseXboard 하드웨어 함수들 중 analogRead()digitalWrite() 두 개의 함수를 발췌했습니다.

  • analogRead()는 리턴값이 있는 함수
  • digitalWrite()는 리턴값이 없는 함수의 예입니다.
// file: src/custom/hw/wiseXboard/WiseXboardControl.ts

/**
 * 엑스보드 하드웨어 제어
 */
export class WiseXboardControl extends AbstractHwConrtol implements IWiseXboardControl {
    // ... 생략

    /**
     * pin번호에서 아날로그 값을 읽는 함수
     * @param ctx context
     * @param pin 핀번호
     * @returns 주어진 핀번호의 값
     */
    analogRead = async (ctx: any, pin: number): Promise<number> => {
        const values = await this.readNext_(ctx)
        const v = values[pin - 1]
        return v ?? 0
    }

    /**
     * 주어진 pin에 디지털 값을 쓰는 함수
     * @param ctx context
     * @param pin 핀번호
     * @param value 핀에 write할 값
     */
    digitalWrite = async (ctx: any, pin: number, value: number): Promise<void> => {
        value = value > 0 ? 1 : 0

        const pkt = [chr('X'), chr('R'), 2, 0, 0, 0, 0, 0, chr('S')]
        pkt[2 + pin] = value
        await this.write_(ctx, pkt)
    }
}

const chr = (ch: string): number => ch.charCodeAt(0)

계속해서 리턴값을 설명하고 있는 이유는

  • 리턴값의 유무에 따라
    • 블록이 호출하는 함수를 작성하는 방법이 다르기 때문입니다.
    • 그리고 이 문서는 블록이 호출하는 함수를 작성하는 방법을 설명하는 것이 목적입니다.
리턴값의 유무리턴값의 유무
  • 리턴값이 있는 함수를 호출하는 블록들은 모두 왼쪽이 돌출된 형태입니다.
  • 그래서 블록의 모양만 봐도, 리턴값의 유무를 판단할 수 있습니다.

아래 그림에서 어떤 블록들이 리턴값이 있는지 확인해보세요. 왼쪽이 돌출된 형태의 블록들은 모두 리턴값이 있습니다.

5,6번만 리턴값이 있는 함수를 호출합니다5,6번만 리턴값이 있는 함수를 호출합니다

제너레이터

용어 정의 : 제너레이터란?

  • 블록이 자바스크립트 함수를 호출하게 만드는 것은, 블록의 입장에서 자바스크립트 소스 코드를 생성(generation)하는 것입니다. 블록을 이용하여 소스코드를 생성해주는 로직을, 블록의 입장에서는 제너레이터라고 부릅니다.
  • 특별히 자바스크립트 소스코드를 생성한다면, 자바스크립트 제너레이터라고 부르고
  • 파이썬 소스코드를 생성한다면, 파이썬 제너레이터라고 부릅니다.
  • PHP, Dart 등의 다른 프로그래밍 언어들도 가능합니다.
  • 다만, 코디니는 현재 자바스크립트만 지원하고 있습니다.

문서의 아래에서는 하드웨어 함수를 호출하는 로직 부분을 제너레이터라고 부르겠습니다. 그리고 별도의 언급이 없다면 제너레이터자바스크립트 제너레이터를 의미합니다.

제너레이터 작성법

이제 제너레이터를 작성하는 방법을 설명드리겠습니다. 아래 그림은 블록 공작소에서 블록을 정의하는 화면입니다.

블록 공작소 UI블록 공작소 UI

위의 그림에서

  • 1번은 블록을 정의하는 영역입니다. 원하는 모양의 블록을 만들기 위해, 사용자가 드래그해서 조립합니다.
  • 교구업체는 1번 영역을 작성해야 합니다.
  • 2, 3, 4번은 1번이 변경될 때마다 자동으로 생성되는 영역입니다.
  • 자동으로 생성되는 2, 3, 4번은 당연히 편집이 불가능합니다.

4번 영역을 주목해주세요. 4번이 바로 제너레이터 영역입니다. 우리는 지금 제너레이터 부분에 하드웨어 함수를 호출하는 코드를 작성해야 합니다. 예를 들면, digitalWrite() 같은 함수를 호출하는 것입니다.

다만, 위의 그림에서는 자동으로 생성되는 영역이라서 편집이 불가능합니다. 편집하려면, 그림의 윗부분에 있는 저장 버튼을 클릭하면 됩니다. 저장버튼을 클릭하면 팝업이 뜨는데, 팝업에서 코드를 작성할 수 있습니다.

블록 공작소 제너레이터블록 공작소 제너레이터

6번은 자동으로 생성된 코드입니다. 참고하여 5번을 작성하시면 됩니다.

권장사항

  • 본인이 선호하는 편집기에서 작성하시고, 위의 팝업에서는 붙여넣기만 하는 것이 편리합니다.
  • 각각의 블록들에 대해, generator 소스코드를 개발자분이 별도로 보관해주세요.
  • 예를 들면, 위의 예시는 wise_digital_write.js 파일에 보관하여, 실수로 삭제하는 등의 사고에 대응하시기 바랍니다.

유형1 : 리턴값이 없는 제너레이터

리턴값이 없는 블록리턴값이 없는 블록
블록 사용 예시블록 사용 예시

리턴값이 없는 제너레이터가 더 간단하므로 먼저 살펴보겠습니다. 위에서 살펴본 wise_digital_write 블록은 리턴값이 없는 제너레이터입니다.

제너레이터제너레이터
  • 사용자가 선택한 드롭다운 필드 PIN의 값을 가져오기 위해 block.getFieldValue('PIN')를 호출하고 있습니다.
  • 사용자가 입력한 값 VALUE를 가져오기 위해, Blockly.JavaScript.valueToCode(...)를 호출하고 있습니다.
  • PINVALUE는 하드웨어 함수의 파라미터로 전송할 것입니다.
  • 코드 아래줄의 wiseXboard.digitalWritewiseXboard 하드웨어의 digitalWrite() 함수를 호출하는 것을 의미합니다.
  • 그 뒤의 PINVALUEdigitalWrite() 함수의 인자로 전달되는 값입니다.

제너레이터 코드

1. 제너레이터

위의 제너레이터 코드를 보기 좋게 정리하면 아래와 같습니다.

Blockly.JavaScript['wise_digital_write'] = function (block) {
    const pin = parseInt(block.getFieldValue('PIN'))
    const value = Blockly.JavaScript.valueToCode(block, 'VALUE', Blockly.JavaScript.ORDER_ATOMIC)
    const code = `await makerKit.hw.run(routineContext, 'wiseXboard.digitalWrite', ${pin}, ${value});\n`
    return code
}

2. 제너레이터가 생성한 자바스크립트

제너레이터가 생성한 코드의 예시는 아래와 같습니다.

await makerKit.hw.run(routineContext, 'wiseXboard.digitalWrite', 1, 0);
  • 생성된 자바스크립트는 표현식(expression)이 아닌 문장(statement)인 점을 주목해야 합니다. 그래서 끝에 세미콜론이 붙어 있습니다. 문장이므로 실행을 할 뿐입니다. 반면에 나중에 살펴볼 리턴 값이 있는 함수 호출에서는 표현식 형태로 생성하게 됩니다. 표현식에서는 세미콜론을 붙이면 안되겠지요?
  • 그리고 세미콜론 뒤에 개행문자(\n)도 포함되어 있습니다. 문장이므로 보기 좋게 코드가 생성되도록 개행문자를 포함하였습니다. 표현식에서는 보통 개행문자를 넣지 않습니다.
  • 앞에 await가 붙어 있는 것을 볼 수 있습니다. await에 의해 하드웨어 함수가 리턴할 때까지 기다릴 수 있습니다. 그래서 블록코딩 사용자가 블록을 나열한 순서로 하드웨어 함수가 호출될 수 있습니다.

3. 호출되는 하드웨어 함수

하드웨어 함수는 처음에 살펴봤지만, 여기에 한번 더 적겠습니다. 위의 호출에 대해 PC 프로그램에 작성한 아래의 하드웨어 함수가 호출됩니다.

// file: src/custom/hw/wiseXboard/WiseXboardControl.ts

/**
 * 엑스보드 하드웨어 제어
 */
export class WiseXboardControl extends AbstractHwConrtol implements IWiseXboardControl {
    // ... 생략

    /**
     * 주어진 pin에 디지털 값을 쓰는 함수
     * @param ctx context
     * @param pin 핀번호
     * @param value 핀에 write할 값
     */
    digitalWrite = async (ctx: any, pin: number, value: number): Promise<void> => {
        value = value > 0 ? 1 : 0

        const pkt = [chr('X'), chr('R'), 2, 0, 0, 0, 0, 0, chr('S')]
        pkt[2 + pin] = value
        await this.write_(ctx, pkt)
    }
}

유형2 : 리턴값이 있는 제너레이터

이제 마지막으로 리턴값이 있는 블록의 제너레이터를 살펴보겠습니다.

리턴값이 있는 블록리턴값이 있는 블록
블록 사용 예시블록 사용 예시

위 그림의 블록은 리턴값이 있는 제너레이터입니다.

제너레이터제너레이터
  • 사용자가 선택한 드롭다운 필드 PIN의 값을 가져오기 위해 block.getFieldValue('PIN')를 호출하고 있습니다.
  • block.getFieldValue('PIN')가 리턴하는 값은 문자열 타입입니다. PIN 번호이므로 숫자로 변환해야 하는데, parseInt()대신 + 연산자로 변환해보았습니다. 이렇게 작성된 코드들도 있으므로, 여기에서는 예시로 사용해보았습니다.
  • PIN은 하드웨어 함수의 파라미터로 전송할 것입니다.
  • 코드 아래줄의 wiseXboard.analogReadwiseXboard 하드웨어의 analogRead() 함수를 호출하는 것을 의미합니다.
  • 그 뒤의 PINanalogRead() 함수의 인자로 전달되는 값입니다.

제너레이터 코드

1. 제너레이터

위의 제너레이터 코드를 보기 좋게 정리하면 아래와 같습니다.

Blockly.JavaScript['wise_analog_read'] = function (block) {
    const pin = block.getFieldValue('PIN') ?? ''
    const pinNum = +pin.replace(/\D+/g, '')
    const code = `await makerKit.hw.run(routineContext, 'wiseXboard.analogRead', ${pinNum})`
    return [code, Blockly.JavaScript.ORDER_AWAIT]
}

2. 제너레이터가 생성한 자바스크립트

제너레이터가 생성한 코드의 예시는 아래와 같습니다.

await makerKit.hw.run(routineContext, 'wiseXboard.analogRead', 1, 0)
  • 생성된 자바스크립트는 문장(statement)이 아닌 표현식(expression)인 점을 주목해야 합니다.
  • 함수의 리턴값을 표현하는 표현식인 셈입니다. 그래서 문장(statement)에는 포함되었던 세미콜론과 개행문자가 없습니다.
  • 이렇게 생성된 표현식은 다른 표현식과 사칙연산을 할 수도 있고, if문의 조건으로 사용할 수도 있습니다. 그래서 연산자 우선순위를 지정할 필요가 있습니다. 하드웨어를 호출하는 함수는 모두 await를 붙이므로 연산자의 우선순위는 Blockly.JavaScript.ORDER_AWAIT로 고정하시면 됩니다.
  • 비동기 함수 호출을 표현식으로 쓸 수 있는 것이 매력적이네요.

3. 호출되는 하드웨어 함수

하드웨어 함수는 처음에 살펴봤지만, 여기에 한번 더 적겠습니다. 위의 호출에 대해 PC 프로그램에 작성한 아래의 하드웨어 함수가 호출됩니다.

// file: src/custom/hw/wiseXboard/WiseXboardControl.ts

/**
 * 엑스보드 하드웨어 제어
 */
export class WiseXboardControl extends AbstractHwConrtol implements IWiseXboardControl {
    // ... 생략

    /**
     * pin번호에서 아날로그 값을 읽는 함수
     * @param ctx context
     * @param pin 핀번호
     * @returns 주어진 핀번호의 값
     */
    analogRead = async (ctx: any, pin: number): Promise<number> => {
        const values = await this.readNext_(ctx)
        const v = values[pin - 1]
        return v ?? 0
    }
}

다음 단계

지금까지 블록에서 하드웨어 함수를 호출하기 위해 블록공작소의 제너레이터 작성법을 살펴보았습니다. 만들어진 블록은 블록공작소 플레이그라운드에서 테스트 한 후에 배포할 수 있습니다. 자세한 내용은 블록 공작소 사이트에서 확인해주세요.

단, 배포하기 직전에 코디니의 승인이 필요합니다. 메일로 문의하시면 작성한 코드를 검토한 후에, 승인합니다. 검토 시간은 일주일 정도 소요됩니다.

승인절차승인절차

승인이 된 후에, 블록 공작소에서 배포하시면 일반 사용자에게 노출됩니다.