Espruino:Примеры/Расширение возможностей Espruino 2 – создание новой версии класса Graphics

Материал из Онлайн справочника
Перейти к навигацииПерейти к поиску

Перевод: Максим Кузьмин
Проверка/Оформление/Редактирование: Мякишев Е.А.


Расширение возможностей Espruino 2 – создание новой версии класса Graphics[1]

Во-первых, вы можете заглянуть в это руководство, в котором объясняется, как создавать собственные простые расширения.

Итак, у Espruino уже есть встроенный класс Graphics, поддерживающий большое количество функций. Он поддерживает если не все, то точно большинство LCD-экранов. И зачем же тогда создавать новый класс? Хороший вопрос. Давайте просуммируем:

  • В старом классе Graphics нет даже простых анимаций.
  • Один из распространённых стандартов на рынке – это WS2812 вроде матриц 8х8 или круглых 5-кольцевых светильников.
  • Хорошая отправная точка в изучении чего-либо – это попытаться это что-то сделать.
  • Гибкость класса Graphics также означает наличие сложных операций.
Для информации

Необходимые условия: Перед тем, как начать, у вас должно быть всё необходимое для разработки и компиляции собственной версии. Более подробно об этом читайте на GitHub.

Итак, чтобы создать новую версию Graphics, вначале нам нужно проделать следующее:

  • Название. Название нового класса не должно совпадать с названиями уже существующих классов, но по нему должно быть понятно, за что отвечает этот класс. Давайте назовём его LedMatrix.
  • Файлы. Мы как минимум должны создать 2 файла исходного кода (.c и .h). Давайте назовём их по тому же принципу, что и в Espruino – jswrap_LedMatrix.h и jswrap_LedMatrix.c.
  • Заголовочный файл. Мы должны начать с информации о лицензии и авторских правах, как это обычно делается в C. Далее добавляем пару общих строчек:
#ifndef TARGETS_ESP8266_JSWRAP_LEDMATRIX_H_
#define TARGETS_ESP8266_JSWRAP_LEDMATRIX_H_
#include "jsvar.h"
.......
.......
#endif

Рекомендуем подключить jsvar.h при помощи директивы #include. Это одна из самых главных частей Espruino, и вам понадобится более чем 90% от неё.

Файл с исходным кодом

Опять же, информация об авторских правах и лицензии должна быть вверху. Новички в C (вроде меня) часто забывают подключить заголовочный файл.

#include "jswrap_LedMatrix.h"

Позже мы добавим и другие #include, специфичные для нашего нового класса.

Задаём локальные данные

В данный момент в этом файле нет ничего для нашего класса, так что давайте начнём со структуры для общих данных.

typedef struct {
  int width,height,cells,cellsLength;
  int red,green,blue;
} PACKED_FLAGS JsLedMatrixData;

В width и height задаётся размер матрицы, а уже затем на их основе рассчитываются cells и cellsLength. И давайте уже добавим информацию для цветов:

typedef struct JsLedMatrix {
  JsVar *LedMatrixVar;
  JsLedMatrixData data;
} PACKED_FLAGS JsLedMatrix;

Итак, вместе с шаблоном для самого JavaScript-объекта у нас также появилась структура для внутренних данных. Разумеется, есть и другие способы для достижения аналогичного результата. Лично мне оказалось полезным разделение между внутренними данными и JavaScript-данными.

Управление внутренними данными

Мы уже создали структуру для внутренних данных. Теперь нам надо инициализировать эти данные и добавить часто используемые функции для обработки этих данных.

Добавляем дополнительные #include

Мы уже подключили jsvar.h, теперь давайте подключим ещё несколько:

#include "jsparse.h"
#include "jswrap_arraybuffer.h"
#include "jswrap_espruino.h"

Если вы хотите знать, какие функции были добавлены, то посмотрите это сами в исходном коде. Их список слишком большой для этого руководства.

Инициализируем внутренние данные

Инициализация JsLedMatrix выполняется следующим образом:

static inline void LedMatrixStructInit(JsLedMatrix *lm){
  lm->data.width = 8;
  lm->data.height = 8;
  lm->data.cells = 64;
  lm->data.cellsLength = 192;
  lm->data.red = 32;
  lm->data.green = 32;
  lm->data.blue = 32;
}

Значение cells можно рассчитать – это width x height, а значение cellsLength – это cells x 3, потому что на каждый пиксель в WS2812 приходится по 3 байта.

Сохраняем внутренние данные и подключаемся к JavaScript-переменной

void LedMatrixSetVar(JsLedMatrix *lm){
  JsVar *dataname = jsvFindChildFromString(lm->LedMatrixVar,JS_HIDDEN_CHAR_STR"lm",true);
  JsVar *data = jsvSkipName(dataname);
  if (!data) {
    data = jsvNewStringOfLength(sizeof(JsLedMatrixData));
    jsvSetValueOfName(dataname, data);
  }
  jsvUnLock(dataname);
  assert(data);
  jsvSetString(data, (char*)&lm->data, sizeof(JsLedMatrixData));
  jsvUnLock(data);
}

В этом фрагменте кода мы сохраняем внутренние данные и подключаем их к JavaScript-переменной. По правде говоря, я не могу детально объяснить, как работает этот код, но всё же попробую. Сначала создаём указатель на dataname, а также указатель на сами данные. Если данных нет, нам придётся их создать. Никогда не забывайте про следующую строчку, jsvUnlock важен. В противном случае появится утечка памяти, что рано или поздно приведёт к ошибке. Теперь мы подключаем внутренние данные к JavaScript-переменной и последнее, но не менее важное – ещё раз используем jsvUnlock.

Считываем внутренние данные и подготавливаем их к использованию

bool LedMatrixGetFromVar(JsLedMatrix *lm, JsVar *parent){
  lm->LedMatrixVar = parent;
  JsVar *data = jsvObjectGetChild(parent,JS_HIDDEN_CHAR_STR"lm",0);
  assert(data);
  if(data){
    jsvGetString(data, (char*)&lm->data, sizeof(JsLedMatrixData)+1);
    jsvUnLock(data);
    return true;
  } else
    return false;
}

Опять же, попробую всё объяснить, но получится, возможно, сумбурно, так что заранее извините. Вначале берём parent, что обычно является JavaScript-переменной и считываем подключенные внутренние данные. Если данных нет, возвращаем false, в противном случае считываем данные и подключаемся к parent.

Инициализируем внутренние данные и создаём буфер

JsVar *jswrap_LedMatrix_createArrayBuffer(int width,int height){
  JsVar *parent = jspNewObject(0,"LedMatrix");
  JsLedMatrix lm;
  LedMatrixStructInit(&lm);
  lm.LedMatrixVar = parent;
  lm.data.width = width;
  lm.data.height = height;
  lm.data.cells = width * height;
  lm.data.cellsLength = width * height * 3;
  LedMatrixSetVar(&lm);
  JsVar *buf = jswrap_arraybuffer_constructor(lm.data.cellsLength);
  jsvUnLock2(jsvAddNamedChild(lm.LedMatrixVar,buf,"buffer"),buf);
  return parent;
}

Мы уже создали эту функцию выше, теперь давайте добавим в неё мяса и костей. Это наша отправная точка для создания LedMatrix. Итак, мы:

  • Создаём класс при помощи jsvNewObject.
  • Считываем внутренние данные.
  • Присваиваем размер массива.
  • Сохраняем изменённые данные.
  • Создаём буфер, который также подключен к нашей JavaScript-переменной.
  • Снимаем блокировку с данных и возвращаем класс в JavaScript.

Создаём описание для класса LedMatrix

Итак, мы создали простой каркас для нашего нового класса. Теперь давайте добавим информацию о синтаксисе и структуре JavaScript-команд. Эти описания добавляются в файл с исходным кодом как комментарии. В течение компиляции и компоновки эта информация будет использоваться для создания дополнительных строчек в файле под названием «jswrapper.c». Помните, что он создаётся автоматически и используется во время компиляции. Вы не найдёте его в дереве исходного кода.

Создаём класс

Во-первых, создаём наш класс:

/*JSON{
  "type" : "class",
  "class" : "LedMatrix"
}
A class to support some simple drawings and animations (rotate and shift).
For a LED Matrix like the 8*8 WS2812 board.

Класс, поддерживающий простую графику и анимации
(вращение и смещение).
Предназначен для светодиодных матриц вроде 8х8 панелей WS2812.
*/

В этом простом описании одна часть написана с использованием синтаксиса JSON. Она будет использоваться на внутреннем уровне для создания объектов. Таким образом, нам нужно будет следовать правилам использования формата JSON. Вторая часть – это просто текст, который будет использоваться для создания описания в справочнике Espruino. Поскольку мы создаём свой собственный класс, делать описание необязательно, но если им решит воспользоваться кто-то другой, ему будет проще разобраться, что к чему.

Инициализируем класс

Теперь, когда наш новый класс создан, давайте зададим, как он будет собираться. Начнём с инициализации и возвращения указателя на буфер:

/*JSON{
  "type" : "staticmethod",
  "class" : "LedMatrix",
  "name" : "createArrayBuffer",
  "generate" : "jswrap_LedMatrix_createArrayBuffer",
  "params" :[
    ["width","int32","width"],
    ["height","int32","height"]
  ],
  "return" : ["JsVar","new LedMatrix object"],
  "return_object" : "LedMatrix"
}

Создаёт объект LEDMatrix с Arraybuffer для светодиодной матрицы.

Тестировался с матрицей WS2812 8x8. Но другие тоже должны работать.

Creates LEDMatrix object with an Arraybuffer for a LED-Matrix.
This is tested with 8*8 WS2812 matrix. Others should work.
*/
JsVar *jswrap_LedMatrix_createArrayBuffer(int width,int height){
...
...
}

Итак, тут у нас появились новые данные. Давайте разберём, что это такое:

  • type – предоставляет информацию о том, как создаётся обёртка для этой команды. Обычно мы используем staticmethod или method. Есть и много других типов, но давайте начнём с самого простого.
  • class – это название класса, к которому относится этот метод.
  • name – это (сюрприз, сюрприз) название метода.
  • generate – это название C-функции, вызываемой из JavaScript-команды.
  • params – это массив с параметрами, которые надо описать (в нашем случае – размер внутреннего буфера). Сначала идёт название, потом тип данных языка C, а затем описание.
  • _return_object_ – мы возвращаем объект класса, и название – LedMatrix.
  • return – в этом случае мы возвращаем переменную типа JsVar. Это главный конструкт языка Espruino, описывающий то, как хранятся данные и обрабатываются переменные.

Следующие строчки – это описание для справочника и, наконец, тело C-функции. Оно должно совпадать с названием в generate (но многие об этом забывают – это распространённая ошибка).

Простые операции с внутренними данными и графическим буфером

С первой функцией всё очень просто – она задаёт цвет для рисующих функций.

/*JSON{
  "type" : "method",
  "class" : "LedMatrix",
  "name" : "setColorHSB",
  "generate" : "jswrap_LedMatrix_setColorHSB",
  "params"   : [
    ["hue","float","hue"],
    ["sat","float","saturation"],
    ["bri","float","brightness"]
 ]   
}

Задаёт цвет для рисующих функций на основе тона, насыщенности и яркости.

Значения варьируются в диапазоне от 0 до 1.

Sets color for following drawings.
Based on hue, saturation and brightness.
Values are from 0 to 1.
*/

В языке Espruino количество аргументов в функции ограничено. Таким образом, перед использованием цвета нам надо его задать. Ещё один вариант – это передать полномочия объекту, в котором цвет задаётся с помощью 3 значений вместо 1 значения для каждого цвета. Возможно, мы расскажем об этом в каком-нибудь будущем руководстве.

Итак, нам нужно добавить JSON-описание того, как эта функция будет использоваться в JavaScript. Там есть новые типы параметров. Значения задаются в виде чисел с плавающей точкой в диапазоне между 0 и 1.

void jswrap_LedMatrix_setColorHSB(JsVar *parent, JsVarFloat hue, JsVarFloat sat, JsVarFloat bri){
  JsLedMatrix lm;
  if(!LedMatrixGetFromVar(&lm,parent)) return ;
  JsVarInt JsVarrgb = jswrap_espruino_HSBtoRGB(hue,sat,bri);
  int rgb = (int)JsVarrgb; lm.data.red = (char)rgb;
  rgb = (rgb >> 8); lm.data.green = (char)rgb;
  rgb = (rgb >> 8); lm.data.blue = (char)rgb;
  LedMatrixSetVar(&lm);
}

На внутреннем уровне для значений с плавающей точкой используется специальный тип JsVar под названием JsVarFloat. Мы берём *parent (указатель на класс) и считываем внутренние данные оттуда. HSB-значения конвертируются в RGB-значения в диапазоне между 0 и 255 и сохраняются во внутренние данные. В конце мы фиксируем эти изменения, и готово.

Ещё одна часто используемая функция – это заполнение буфера одним цветом, т.е. заливка.

/*JSON{
 "type"     : "method",
 "class"    : "LedMatrix",
 "name"     : "fill",
 "generate" : "jswrap_LedMatrix_fill"  
}
Заполняет всю матрицу одним цветом.

Fills whole matrix with same color.
*/

Цвет для заливки задаётся командой, которую мы разобрали выше. Так что никаких параметров указывать не надо.

void jswrap_LedMatrix_fill(JsVar *parent){
  JsLedMatrix lm;
  if(!LedMatrixGetFromVar(&lm,parent)) return ;
  JsVar *buf = jsvObjectGetChild(parent,"buffer",false);
  LedMatrix_fill(buf,lm);
  jsvUnLock(buf);
}

Для заливки нам понадобятся внутренние данные вроде ширины, высоты и самого буфера. Итак, сначала нам нужны внутренние данные, а затем указатель на буфер. Вся заливка разбита на две функции, и первая из них более общая и близка к JavaScript-коду.

void LedMatrix_fill(JsVar *buf,JsLedMatrix lm){
  JsvArrayBufferIterator it;
  jsvArrayBufferIteratorNew(&it,buf,0);
  for(int i = 0; i < lm.data.cells; i++){
    jsvArrayBufferIteratorSetByteValue(&it,(char)lm.data.green);
    jsvArrayBufferIteratorNext(&it);
    jsvArrayBufferIteratorSetByteValue(&it,(char)lm.data.red);
    jsvArrayBufferIteratorNext(&it);
    jsvArrayBufferIteratorSetByteValue(&it,(char)lm.data.blue);
    jsvArrayBufferIteratorNext(&it);
  }
  jsvArrayBufferIteratorFree(&it);
}

Главную работу делает вторая функция. Буфер хранится в специфическом Espruino-буфере. Для прохода через этот буфер используются специальные итераторы. Кроме того, здесь используется специальная функция для того, чтобы задать данные и перейти от одного элемента массива к другому. В конце не забывайте освободить память.

Массивы – использование в аргументах и возвращение из функции

В разделах выше мы уже использовали аргументы в функциях. Иногда в аргументах полезно использовать массивы – чтобы отправить в одном аргументе больше одного значения. Кроме того, если вы отправляете данные в одном направлении, иногда есть смысл вернуть данные в виде массива. В этом разделе мы расскажем, как сделать и то, и другое.

Массив в аргументе

У класса LedCircle, предназначенного для 5-кольцевых светильников и который мы разберём ниже, есть внутреннее описание размера каждого кольца (то есть количество светодиодов в каждом кольце). Но это описание может не совпадать с реальным положением дел в зависимости от того, как подключены кольца этого светильника. То есть кольца можно подключить начиная с внутреннего кольца или начиная с внешнего кольца: если начать с внешнего, это будет выглядеть как [24,16,12,8,1], а если с внутреннего – [1,8,12,16,24]. Давайте создадим простую функцию, которая как раз и будет определять этот порядок. И в нашем примере ниже соответствующий класс уже готов и называется lc:

lc.circleSize([1,8,12,16,24]);

Чтобы понять, как это программируется, давайте начнём с определения в заголовочном файле:

void jswrap_LedCircle_circleSize(JsVar *parent, JsVar *circleSizeArr);

То есть у нас только один аргумент, чтобы задать количество светодиодов для всех 5 колец. Теперь давайте взглянем на JSON-описание обёртки в C-файле:

/*JSON{
  "type" : "method",
  "class" : "LedCircle",
  "name" : "circleSize",
  "generate" : "jswrap_LedCircle_circleSize",
  "params"   : [
    ["circleSizeArr","JsVar","circleSizes"]
 ]   
}
Задаёт количество светодиодов для каждого кольца.
Sets number of Leds for each circle.
*/

Чтобы скопировать эти данные во внутреннее описание, в коде ниже используется простой цикл и итератор массивов Espruino.

void jswrap_LedCircle_circleSize(JsVar *parent, JsVar *circleSizeArr){
  JsLedCircle lm; JsvIterator itcs; int i;
  if(!LedCircleGetFromVar(&lm,parent)) return ;
  jsvIteratorNew(&itcs, circleSizeArr);
  i = 0;
  while (jsvIteratorHasElement(&itcs)) {
    lm.data.circleSize[i] = (int)jsvIteratorGetIntegerValue(&itcs);
    jsvIteratorNext(&itcs);
    i++;
  }
  jsvIteratorFree(&itcs);
  LedCircleSetVar(&lm);
}

Итератор также поддерживает аргументы с плавающей точкой. Загляните в «jsvariterator.h» – там ещё немало других полезных функций.

Возвращение массива

Иногда вам может понадобиться лишь часть буфера, чтобы внести в неё какие-нибудь изменения. Самое простое решение здесь – это вернуть массив, который можно использовать для внесения изменений, а затем сохранить его обратно. На этот раз мы возьмём класс LedMatrix, с помощью которого прочтём значение одного пикселя. Оно будет возвращено в виде массива из 3 байтов. Точнее, то, что мы вернём, будет объектом типа ArrayBuffer. Давайте начнём с заголовочного файла, как и выше:

JsVar *jswrap_LedMatrix_getPixel(JsVar *parent, int row, int column);

Думаю, здесь всё понятно, так что давайте сразу перейдём к следующему шагу – JSON-описанию функции.

/*JSON{
  "type" : "method",
  "class" : "LedMatrix",
  "name" : "getPixel",
  "generate" : "jswrap_LedMatrix_getPixel",
  "params" : [
    ["row","int","row"],
    ["column","int","column"]
  ],
  "return" : ["JsVar","pixelBuffer"] 
}
Возвращает буфер с цветом пикселя, 
заданного при помощи ряда и столбца.

Returns a buffer with color of pixel defined by row and column
*/

Как видите, возвращаемый параметр – это JsVar, а не ArrayBuffer. JsVar – это одна из главнейших конструкций в языке Espruino. У её использования очень много преимуществ, её поддерживают много функций и, кроме того, благодаря ей вам не нужно волноваться об излишнем расходовании памяти.

Следующий и самый последний шаг – это сам код:

JsVar *LedMatrix_getPixel(JsVar *buf,JsLedMatrix lm, int row,int column){
  JsvArrayBufferIterator it;int p;
  p = (row * lm.data.width + column) *3;
  jsvArrayBufferIteratorNew(&it,buf,p);
  char *ptr = 0;
  JsVar *bufa = jsvNewArrayBufferWithPtr(3,&ptr);
  JsvArrayBufferIterator itd;int pd;
  jsvArrayBufferIteratorNew(&itd,bufa,0);
  for(int i = 0; i < 3; i++){
    jsvArrayBufferIteratorSetByteValue(&itd,(char)jsvArrayBufferIteratorGetIntegerValue(&it));
    jsvArrayBufferIteratorNext(&it);
    jsvArrayBufferIteratorNext(&itd);
  }
  jsvArrayBufferIteratorFree(&it);
  jsvArrayBufferIteratorFree(&itd);
  return bufa;
}

Здесь интересно посмотреть на то, как итератор проходит через массив. Выше мы уже затрагивали тему итераторов, так что теперь давайте сфокусируемся на более общих вещах. Во-первых, создаём итератор, указывающий на внутренний буфер пикселей. Далее создаём новый массив размером в 3 байта для возвращения значений. Копируем из буфера пикселей в буфер возврата, очищаем память от использованных итераторов и возвращаем буфер возврата. Готово!

В следующем примере мы разберём, как использовать объекты в аргументах.

Объекты в аргументах функций

Итак, мы разобрались, как использовать массивы, теперь давайте шагнём чуть дальше и разберёмся, как использовать объекты.

Объект в аргументе

Есть как минимум одна хорошая причина для использования объектов – в Espruino есть ограничение на количество аргументов. Если мы, например, захотим задать цвет какого-нибудь пикселя, нам для этого понадобятся 5 аргументов: координаты X и Y, и значения R, G и B для цвета. Для Espruino это слишком много. Помните, что речь об использовании JavaScript на очень маленьких устройствах, из-за чего приходится иметь дело с ограничениями. Но, к счастью, у нас есть JsVar, и JsVar также может быть объектом. Мы можем вызвать setPixel() вот так:

lc.setPixel({x:2,y:3},{red:88,green:12,blue:40});

Таким образом, мы без труда поместили в объект данные о координатах и цвете. Для лучшего понимания координаты были помещены в один объект, а цвет – в другой. Если все данные надо поместить в один объект, то без проблем. В коде-примере ниже мы воспользуемся только данными о цвете, чтобы сделать объяснение максимально коротким и лаконичным.

Итак, вызов функции с объектом будет выглядеть вот так:

lc.init({red:1,green:30,blue:3});

В заголовочном файле это будет выглядеть вот так:

void jswrap_LedCircle_init(JsVar *parent, JsVar *options);

Описание для обёртки такое же простое, как и для массивов:

/*JSON{
  "type" : "method",
  "class" : "LedCircle",
  "name" : "init",
  "generate" : "jswrap_LedCircle_init",
  "params"   : [
    ["options","JsVar","options"]
 ]   
}

Инициализирует цвет на основе JavaScript-объекта.

Initializes color based on Javascript object.
*/
Сам код чуть сложнее  нам нужно создать описание того, как управлять опциями:
void jswrap_LedCircle_init(JsVar *parent, JsVar *options){
  JsLedCircle lm;
  if(!LedCircleGetFromVar(&lm,parent)) return ;
  jsvConfigObject configs[] = {
      {"red", JSV_INTEGER, &lm.data.red},
      {"green", JSV_INTEGER, &lm.data.green},
      {"blue", JSV_INTEGER, &lm.data.blue}
  };
  jsvReadConfigObject(options, configs, sizeof(configs) / sizeof(jsvConfigObject));
  LedCircleSetVar(&lm);
}

Возвращение объекта

Теперь, когда мы знаем, как использовать объекты в аргументах, давайте двинемся немного в другом направлении. Допустим, нам нужно аналогичном образом прочесть информацию о цвете.

lc.getColor();

В заголовочном файле мы добавляем лишь одну маленькую строчку:

JsVar *jswrap_LedCircle_getColor(JsVar *parent);

Следующий шаг – это, как и обычно, JSON-описание команды:

/*JSON{
  "type" : "method",
  "class" : "LedCircle",
  "name" : "getColor",
  "generate" : "jswrap_LedCircle_getColor",
  "return" : ["JsVar","color_Object"]   
}

Возвращает текущие настройки цвета.

Returns actual color setting.
*/

Мы уже много об этом писали в разделах выше, так что ничего нового тут нет. Так что переходим сразу к исходному коду:

JsVar *jswrap_LedCircle_getColor(JsVar *parent){
  JsLedCircle lm;
  if(!LedCircleGetFromVar(&lm,parent)) return false;
  JsVar *obj = jsvNewObject();
  jsvObjectSetChildAndUnLock(obj, "red", jsvNewFromInteger(lm.data.red));
  jsvObjectSetChildAndUnLock(obj, "green", jsvNewFromInteger(lm.data.green));
  jsvObjectSetChildAndUnLock(obj, "blue", jsvNewFromInteger(lm.data.blue));
  return obj;
}

Названия функций здесь должны говорить сами за себя. Если простыми словами, мы создаём несколько дочерних элементов, привязанных к объекту – по одному для каждого цвета – и возвращаем объект. Готово. Разблокировка данных уже встроена в другие функции, так что об этом беспокоиться не нужно.

Целое число или строка, проверка аргументов

Одно из важнейших преимуществ JavaScript – это типы переменных. Переменная может быть строкой, целым числом, функцией, числом с плавающей точкой и так далее. В языке Espruino есть общая структура для переменных, которая называется JsVar. Этот конструкт содержит всю информацию о текущем типе переменной. Теперь представьте, что вам нужна функция, поддерживающая аргументы в виде целых чисел или строки, чтобы её можно было использовать для считывания информации по индексу или названию. В таком случае мы могли бы создать две функции вроде getDataByInt() и getDataByName(). Но есть вариант получше – создать общую функцию вроде getData(), которая была бы разделена внутри и действовала бы исходя из «скормленного» ей аргумента. Сначала давайте зададим эту функцию в заголовочном файле:

JsVar *jswrap_flash_getData(JsVar *dataVar);

Как видите, тип переменной – это JsVar. Таким образом, мы не ограничены каким-то одним типом данных. Давайте взглянем на определение функции для Espruino в исходном коде:

/*JSON{
  "type" : "staticmethod",
  "class" : "myClass",
  "name" : "getData",
  "generate" : "jswrap_myClass_getData",
  "params" : [
    ["dataVar","JsVar","Id(integer) or name(string)"]
  ],
  "return" : ["JsVar","myClass string"],
}
*/

Определение для JavaScript в заголовочном файле выглядит похожим образом. Аргумент имеет тип данных JsVar. Но не забудьте поменять название класса на название своего класса. Теперь нам надо переключиться между типом аргумента – целым числом или строкой.

int i;
if(jsvIsInt(dataVar)){
  i = callHandleInt(dataVar)
}
else{
  i = callHandleString(dataVar);
}
callYourFunction(i);

Во-первых, давайте предположим, что нам нужен указатель на массив, а не на dataVar. Поэтому сначала мы проверим, является ли dataVar целым числом, а затем создадим указатель. Если dataVar не будет целым числом, то мы будем считать, что это строка. Чтобы улучшить стиль кода, проверку на строку лучше делать при помощи функции jsvIsString(). Ещё больше функций типа jsvIs... можно найти в файле «src/jsvar.h» – выберите там ту, что соответствует вашим нуждам.

Генерирование событий

События часто используются в JavaScript. В вашем браузере они носят названия вроде onClick, onChange и так далее. В языке Espruino тоже есть события – их, например, очень много в коде, связанном с HTTP. В этом разделе я расскажу, как создать свои собственные события, а если точнее, мы создадим так называемый «триггер Шмитта». Более подробно о нём моно почитать на «Википедии». Если вкратце, у триггера Шмитта есть аналоговый ввод и двоичный вывод. На внутреннем уровне это 2-позиционный компаратор (система сравнения сигналов). Если сигнал на вводе больше, чем HIGH, вывод изменится на True, а если меньше, чем LOW, то на False. Если входной сигнал находится в диапазоне между HIGH и LOW, изменения не произойдёт. В большинстве случаев важно определить изменения только на выводе. Давайте начнём с JavaScript-кода, который будем вызывать в самой программе.

var st = new SchmittTrigger(3,5);
st.on("change",function(state{console.log(state);});
st.set(4); // значение между LOW и HIGH, изменения нет
st.set(8); // значение > HIGH, меняем на «true»
st.set(4);
st.set(2);

В консоли должно появиться 2 строчки: одна для переключения на true, а вторая – на false.

В этом разделе речь идёт о событиях, но если вы хотите посмотреть исходный код целиком, то это можно сделать на GitHub. Как обычно, давайте начнём с заголовочного файла:

JsVar *jswrap_SchmittTrigger_constructor(JsVarFloat low,JsVarFloat high);
bool jswrap_SchmittTrigger_set(JsVar *parent,JsVarFloat value);

Тут на самом деле ничего нового нет (кроме разве что конструктора, но на него можно взглянуть в исходном коде) – обо всём этом уже рассказывалось в разделах выше. Далее давайте посмотрим, как выглядит описание для JavaScript:

/*JSON{
  "type"  : "class",
  "class" : "SchmittTrigger"
}
Это встроенный класс для триггера Шмитта.
This is the built-in class for Schmitt Trigger.
*/
/*JSON{
  "type" : "event",
  "class" : "SchmittTrigger",
  "name" : "onChanged"
}
Это событие будет вызываться при изменении статуса триггера Шмитта.
This event is called when status of Schmitt Trigger changes.
*/

Сначала нам надо задать класс, а уже после этого – само событие. Обе строчки в основном используются для создания документации. Здесь нет определений для двух строчек из заголовочного файла выше. Более подробно о них читайте на GitHub. Функции для генерации события ищите в «jswrap_SchmittTrigger_set».

if(value < st.data.low){
  st.data.status = false;
  emitOnChange(parent,false);
}

Здесь важна строчка, где мы генерируем событие изменения. Теперь нам осталось взглянуть на то, что находится внутри emitOnChange():

void emitOnChange(JsVar *parent,bool status){
  JsVar *args[1];
  args[0] = jsvNewFromBool(status);
  JsVar *eventName = jsvNewFromString(JS_EVENT_PREFIX"change");
  JsVar *callback = jsvSkipNameAndUnLock(jsvFindChildFromVar(parent, eventName, 0));
  jsvUnLock(eventName);
  if (callback) jsiQueueEvents(parent, callback, args, 1);
  jsvUnLock(callback);
  jsvUnLockMany(1, args);
}

Этот фрагмент кода выглядит поинтереснее, так что давайте пробежимся по всем строчкам. Сначала мы создаём массив JsVar и присваиваем значение статуса. Размер массива – это 1, потому что мы возвращаем только значение статуса. Далее мы создаём eventName и в конце получаем лишь строку "#onchange". Далее смотрим, есть ли у нас код, заданный для события изменения? Далее нужда в eventName отпадает, так что память, выделенную под JsVar, можно освободить. Если callback – это не undefined, то это значит, что что-то было задано, поэтому ставим событие в очередь во внутреннем списке. И готово, освобождаем память, выделенную под функцию обратного вызова и массив аргументов. Всё остальное для вызова очереди (в том числе для однократного её вызова и так далее), в Espruino уже есть.

См.также

Внешние ссылки