Skip to content
isdnetworks
Go back

도구가 못 다루는 경우가 나왔다

센서 값을 읽는 도구를 만들어 두고 잘 쓰고 있었다. 새 장비를 붙이니 안 됐다.

Table of contents

Open Table of contents

도구의 전제

읽는 코드는 이렇게 돼 있었다.

struct sensor_data {
    uint16_t temp;
    uint16_t humid;
};

int read_sensor(int fd, struct sensor_data *out) {
    uint8_t buf[4];
    if (i2c_read(fd, 0x00, buf, 4) < 0) return -1;
    out->temp  = (buf[0] << 8) | buf[1];
    out->humid = (buf[2] << 8) | buf[3];
    return 0;
}

i2c_read 로 4바이트를 읽어 temphumid 로 나눈다. 그때까지 붙어 있던 센서가 다 그랬다.

새 센서는 6바이트를 준다. 기압이 하나 더 들어 있어서 이 코드로는 못 읽는다.

그 자리에 조건을 넣으려다 멈췄다

당장 고치는 방법은 조건 하나를 넣는 것이었다.

if (model == MODEL_NEW) {
    /* 6바이트를 읽는다 */
}

이렇게 하면 지금은 되는데 다음 센서가 또 다르면 조건이 하나 더 늘어난다.

이미 센서가 네 종류였고 더 늘 것이 확실해서 조건을 하나씩 넣다 보면 read_sensor 가 곧 조건문 덩어리가 된다.

무엇이 다른지 적어 봤다

붙어 있는 센서들의 차이를 표로 적었다.

모델   주소   길이   항목            바이트 순서
A      0x00   4      온도,습도       big
B      0x00   4      온도,습도       big
C      0x02   6      온도,습도,기압  big
D      0x00   8      온도,습도,조도,기압  little

다른 것이 넷이었는데 읽기 시작 주소와 전체 길이와 항목 구성과 바이트 순서였다.

넷이 조합되면 경우의 수가 금방 늘어나서 조건문으로 다루기에는 가지가 너무 많았다.

표로 만들었다

다른 점을 데이터로 옮겼다.

struct field_def {
    const char *name;
    uint8_t offset;
    uint8_t size;
    int     scale;
};

struct sensor_def {
    const char *model;
    uint8_t reg;
    uint8_t len;
    int little_endian;
    const struct field_def *fields;
    int nfields;
};

static const struct field_def fields_c[] = {
    { "temp",  0, 2, 10 },
    { "humid", 2, 2, 10 },
    { "press", 4, 2, 1  },
};

static const struct sensor_def sensors[] = {
    { "A", 0x00, 4, 0, fields_ab, 2 },
    { "C", 0x02, 6, 0, fields_c,  3 },
    { "D", 0x00, 8, 1, fields_d,  4 },
};

sensor_def 가 모델 하나를 기술하고 field_def 가 그 안의 항목 하나를 기술한다. 그러면 읽는 코드는 한 벌이면 된다.

int read_sensor(int fd, const struct sensor_def *def, struct reading *out) {
    uint8_t buf[16];
    if (def->len > sizeof(buf)) return -1;
    if (i2c_read(fd, def->reg, buf, def->len) < 0) return -1;
    for (int i = 0; i < def->nfields; i++) {
        const struct field_def *f = &def->fields[i];
        uint32_t raw = pack(buf + f->offset, f->size, def->little_endian);
        out->values[i] = (double)raw / f->scale;
        out->names[i]  = f->name;
    }
    out->n = def->nfields;
    return 0;
}

def 를 따라 offsetsize 만큼 잘라 scale 로 나눈다. 새 센서가 오면 sensors 에 한 줄을 넣고 코드는 안 건드린다.

검증 — 표가 맞는지 확인

표로 옮기면 걸리는 점이 하나 있는데 잘못 적어도 컴파일러가 아무 말을 안 한다.

static int validate_defs(void) {
    for (size_t i = 0; i < ARRAY_SIZE(sensors); i++) {
        const struct sensor_def *d = &sensors[i];
        for (int j = 0; j < d->nfields; j++) {
            if (d->fields[j].offset + d->fields[j].size > d->len) {
                fprintf(stderr, "%s: %s 가 길이를 넘는다\n", d->model, d->fields[j].name);
                return -1;
            }
        }
    }
    return 0;
}

offsetsize 의 합이 len 을 넘으면 그 자리에서 멈춘다. 시작할 때 validate_defs 를 불러 표가 어긋나면 아예 안 뜨게 했다.

조건문에는 컴파일러가 걸어 주는 것이 조금이라도 있는데 표에는 없으므로 그 자리를 확인 코드로 메워야 했다.

저장하는 쪽도 넓혀야 했다

항목 수가 고정이 아니게 되니 앞쪽만 고쳐서는 끝이 아니었다.

/* 전에는 컬럼이 고정이었다 */
influx_write("sensor,dev=%s temp=%.1f,humid=%.1f", dev, t, h);

temphumid 가 형식 문자열에 박혀 있었다. 항목 이름을 표에서 들고 있으니 그것으로 줄을 만들게 고쳤다.

char line[512];
int n = snprintf(line, sizeof(line), "sensor,dev=%s ", dev);
for (int i = 0; i < r->n; i++) {
    n += snprintf(line + n, sizeof(line) - n, "%s%s=%.2f",
                  i ? "," : "", r->names[i], r->values[i]);
}

r->names 를 돌면서 이름과 값을 붙인다. 항목이 늘면 줄이 길어지므로 snprintf 의 남은 자리를 매번 계산해 넘침을 막았다.

앞이 유연해지면 뒤도 같이 넓혀야 하는데 한쪽만 고치면 새 항목이 읽히고도 저장되지 않는다.

결과 — 두 달 뒤의 추가

두 달 뒤에 센서 두 종류가 더 들어왔다.

{ "E", 0x10, 4, 1, fields_e, 2 },
{ "F", 0x00, 12, 0, fields_f, 6 },

sensors 에 두 줄을 넣고 끝났는데 조건문 방식이었으면 코드를 두 번 더 고치고 그때마다 앞의 조건을 다시 읽어야 했다.

못 다루는 경우가 하나 나왔을 때 넓힌 것이 그다음 것들까지 받아 냈다. 처음 한 번은 조건문보다 품이 더 들었지만 두 번째부터 그 값을 했다.

판단 기준 — 넓히지 말아야 할 자리

같은 방식으로 다른 곳도 넓히려다 멈춘 것이 있다. 통신 규격을 다루는 부분이었다.

지금 쓰는 규격이 하나뿐이고 다른 것이 들어올 계획도 없는데 표로 만들면 코드가 길어지고 읽는 사람이 다른 것도 있는지 찾게 된다.

센서는 이미 네 종류였고 더 늘 것이 확실한데 통신 규격은 하나였고 늘 이유가 없었다.

같은 방식이 한쪽에는 맞고 한쪽에는 안 맞았다. 넓힐지 말지는 지금 몇 가지인지와 앞으로 늘 것인지로 갈랐다.

정리


Share this post on:

Previous Post
배경 절의 숫자들
Next Post
하나만 멈추고 나머지는 계속 돌았다