433 МГц НС-12: Arduino, ESP-8266, Iskra JS. Считаем CRC8 с испуга.

Тема в разделе "Глядите, что я сделал", создана пользователем ИгорьК, 14 сен 2016.

  1. ИгорьК

    ИгорьК Гуру

    Приобрел в Ампеке первую тестовую партию HC-12 - удлиннннннннителей UART на 433МГц в количестве 5 шт.
    На зависть всем обладателям nRF24L01+ он сразу заработал, "из коробки".
    То есть, просто воткнул в UART двух железок и они без плясок установили связь: двустороннюю UART связь на растоянии дачного домика по диагонали. Устойчиво. Ой, как устойчиво. Связь есть!
    В общем, песня.

    О сабже HC-12 уже есть заметки и повторять их нет смысла.
    Есть даже кино от нашего зверского коллеги:


    Лучше пару слов о недостатке, который является продолжением достоинств: канал связи никак не закрыт.
    Для "поиграться" с роботом, который весело катается по комнате пока не сядет батарейка - это не проблема. Однако, если вы решите применять эти модули в автоматизации жилища, незаметно вы можете получить серьезные проблемы.
    Модули имеют настройки по частоте (127 шагов) и несколько скоростей обмена.
    Безусловно, сдвиг этих настроек из сосотяния "по умолчанию" в определенной мере решит проблему.
    Вместе с тем, есть ряд ограничений. Двигать частоты сильно не следует, поскольку вы выйдете из разрешенного диапазона и можете нажить неприятности.
    Двигать настройки скорости приема-передачи тоже особо не стоит: чем выше скорость - тем меньше дальность.
    Повторюсь, для единичного устройства все это не играет роли.
    А вот если создавать сеть со многими устройствами и надолго, то когда-нибудь обязательно найдется школота с желанием навести порядок в вашем доме.
    А модуль - хорош. Есть спящий режим, энергию практически не расходует. Например, можно взять какой-нибудь мелкий МК, аккумулятор и затолкать все это считать расход воды, а информацию передавать раз в сутки.
    В общем, штука очень полезная, наряду с Nextion.

    Мое предложение (для вашей критики, и особенно моего преданного почитателя) таково: будем работать с форматом json и прибавлять к нему маленький хвостик.

    (JSON не слишком распространен на стороне Arduino, а вот JavaScript (Iskra JS & etc.) и Lua(ESP-8266) имеют отличную поддержку такого явления. У меня 3/4 устройств работают именно на этом железе. Также общий обработчик событий OpenHab принимает json "на ура". Уверен, что если вы серьезно пойдете по пути автоматизации дома, переход к скриптовым языкам неизбежен.)

    На стороне приема (и решения что делать дальше) будем проверять правильность хвостика. Если угадали - обрабатывать, если нет - мимо ушей.

    son.jpg
    Ниже предлагаю скетч для Ардуино, который кодирует и декодирует данные типа String.
    При кодировании String {"t":22.3} возвращается то, что на картинке выше.
    При проверке пришедшей из UART информации типа {"t":22.3}148#65 возвращается {"t":22.3} если 65 верное crc или пустой стринг если в данных ошибка.
    Таким образом решается проблема не только злонамеренных действий, но и ошибок передачи.
    Код проверяет данные и на наличие скобок {} и решетки #, что уменьшает вероятность поступления всякого бреда.
    В итоге, мы расплачиваемся минус 10% памяти Uno.
    В коде имеется таблица. При нападении паранойи, вы имеете уникальную возможность поменять местами в ней циферки, и я уже не смогу вам навредить. Желающие могут преобразовать скетч в библиотеку и выложить его для всеобщей пользы.
    Код (C++):
    #define UART_MAX_LENGTH 20  // Максимальная длина сообщений по UART
    String  inputString = "";    // Сюда ловим данные из UART
    boolean stringComplete = false;    // Флаг окончания приема
    boolean startInput = false;    // Потому что работаем с JSON - ловим его начало в виде '{'
    uint8_t next;    // Считаем приходящие симолы

    // const uint8_t crc_table[] PROGMEM = { // Так можно затолкать в PROGMEM.
    static const uint8_t crc_table[] = {  // Таблица crc
        0x00,0x5e,0xbc,0xE2,0x61,0x3F,0xDD,0x83,
        0xC2,0x9C,0x7E,0x20,0xA3,0xFD,0x1F,0x41,
        0x9D,0xC3,0x21,0x7F,0xFC,0xA2,0x40,0x1E,
        0x5F,0x01,0xE3,0xBD,0x3E,0x60,0x82,0xDC,
        0x23,0x7D,0x9F,0xC1,0x42,0x1C,0xFE,0xA0,
        0xE1,0xBF,0x5D,0x03,0x80,0xDE,0x3C,0x62,
        0xBE,0xE0,0x02,0x5C,0xDF,0x81,0x63,0x3D,
        0x7C,0x22,0xC0,0x9E,0x1D,0x43,0xA1,0xFF,
        0x46,0x18,0xFA,0xA4,0x27,0x79,0x9B,0xC5,
        0x84,0xDA,0x38,0x66,0xE5,0xBB,0x59,0x07,
        0xDB,0x85,0x67,0x39,0xBA,0xE4,0x06,0x58,
        0x19,0x47,0xA5,0xFB,0x78,0x26,0xC4,0x9A,
        0x65,0x3B,0xD9,0x87,0x04,0x5A,0xB8,0xE6,
        0xA7,0xF9,0x1B,0x45,0xC6,0x98,0x7A,0x24,
        0xF8,0xA6,0x44,0x1A,0x99,0xC7,0x25,0x7B,
        0x3A,0x64,0x86,0xD8,0x5B,0x05,0xE7,0xB9,
        0x8C,0xD2,0x30,0x6E,0xED,0xB3,0x51,0x0F,
        0x4E,0x10,0xF2,0xAC,0x2F,0x71,0x93,0xCD,
        0x11,0x4F,0xAD,0xF3,0x70,0x2E,0xCC,0x92,
        0xD3,0x8D,0x6F,0x31,0xB2,0xEC,0x0E,0x50,
        0xAF,0xF1,0x13,0x4D,0xCE,0x90,0x72,0x2C,
        0x6D,0x33,0xD1,0x8F,0x0C,0x52,0xB0,0xEE,
        0x32,0x6C,0x8E,0xD0,0x53,0x0D,0xEF,0xB1,
        0xF0,0xAE,0x4C,0x12,0x91,0xCF,0x2D,0x73,
        0xCA,0x94,0x76,0x28,0xAB,0xF5,0x17,0x49,
        0x08,0x56,0xB4,0xEA,0x69,0x37,0xD5,0x8B,
        0x57,0x09,0xEB,0xB5,0x36,0x68,0x8A,0xD4,
        0x95,0xCB,0x29,0x77,0xF4,0xAA,0x48,0x16,
        0xE9,0xB7,0x55,0x0B,0x88,0xD6,0x34,0x6A,
        0x2B,0x75,0x97,0xC9,0x4A,0x14,0xF6,0xA8,
        0x74,0x2A,0xC8,0x96,0x15,0x4B,0xA9,0xF7,
        0xB6,0xE8,0x0A,0x54,0xD7,0x89,0x6B,0x35
    };

    uint8_t crc8(String str){       // Подсчет crc8 для String
        uint8_t ln = str.length();  // Длина вошедшего String
        uint8_t input[ln];          // Массив char длины Стринга
        for(uint8_t i = 0; i < ln; i++ ){ // Наполняем массив чарами
            input[i] = str.charAt(i);
        }
        uint8_t *addr = input; // Ловим начало адреса массива
        uint8_t crc = 0; // Назначаем переменную crc
        while(ln--){    // Пока не переберем массив
            crc = crc_table[*addr++ ^ crc]; // получаем значение crc
        }
        return crc;
    }
    String encode(String dat){ // Кодируем данные типа {"t":22.3}
        String r = (String)random(0, 255);
        // {"t":22.3} => 148 = 65
        //r = "148";
        dat = dat + r + "#"; // Добавляем случайную цифру и разделитель #
        uint8_t cr = crc8(dat); // Получаем crc
        dat = dat + (String) cr; // Добавляем crc в стринг
        return dat;
    }

    String decode(String dat){ // Декодируем данные формата {"t":22.3}148#65

        uint8_t place = dat.indexOf("#") +1; // Находим разделитель # и делим на
        if (!place) { // Нет разделителя
            return "";
        }
        String  fpart = dat.substring(0, place); // две части. Первая сначала и включая #
        String  spart = dat.substring(place);    // Вторая - последня цифра
        uint8_t cr = crc8(fpart);    // Вычисляем crc для первой части

        if(cr == (uint8_t)spart.toInt() ){ // если crc равно второй части
        place = dat.indexOf("}") +1;   // находим место закрывающей скобки '}'
            return dat.substring(0,place); // и возвращаем "чистый" json.
        }
        else {  // Иначе возвращаем ничего
            return "";
        }
    }


    void setup() {
        Serial.begin(9600);
    }

    void loop() {
        if (stringComplete) {
            // Ниже закомментировано самое главное - функции
            // закодирования и раскодирования  json
            // Закодируйте {"t":22.3} через сериал порт
            // или раскодируйте {"t":22.3}148#65 через него же
            // String cod = decode(inputString);
            // String cod = encode(inputString);
            // Serial.print(cod);

            Serial.print(inputString);
            inputString = "";
            stringComplete = false;
        }
    }

    void serialEvent() { // Здесь ловим данные из UART

        while (Serial.available()) {
            char inChar = (char)Serial.read();   // Ловим побайтно
            // Только если нашли '{' - начало json
            if((inChar == '{')&&(startInput == false)){ // Разрешаем дальнейшие действия
                startInput = true;
            }
            if(startInput){ // Найдена скобка - работаем
                if (inChar != '\n') { // Пока не конец строки - складываем и считаем сколько сложили
                    inputString += inChar;
                    next++;
                }
                else { // Конец строки - оповещаем loop об этом
                    next = 0;
                    startInput = false;
                    stringComplete = true;
                }

                if(next > UART_MAX_LENGTH) { // Или если много буков - все сбрасываем
                    next = 0;
                    inputString = "";
                    startInput = false;
                }
            }
        }
    }

    Если указанное выше некошерно - вам сюда.
     

    Вложения:

    • HC12_1.jpg
      HC12_1.jpg
      Размер файла:
      48,5 КБ
      Просмотров:
      849
    Последнее редактирование: 8 ноя 2016
    Rapidshe, Пушной звер, alp69 и ещё 1-му нравится это.
  2. ИгорьК

    ИгорьК Гуру

    Теперь посмотрим соединение модуля с Iskra JS. Для нее я сам наваял библиотечку. Обзовите скрипт как-нибудь и пользуйтесь.
    Код (Javascript):

    var Coding = function(){
    this.crc8_table = [
      0x00,0x5E,0xBC,0xE2,0x61,0x3F,0xDD,0x83,
      0xC2,0x9C,0x7E,0x20,0xA3,0xFD,0x1F,0x41,
      0x9D,0xC3,0x21,0x7F,0xFC,0xA2,0x40,0x1E,
      0x5F,0x01,0xE3,0xBD,0x3E,0x60,0x82,0xDC,
      0x23,0x7D,0x9F,0xC1,0x42,0x1C,0xFE,0xA0,
      0xE1,0xBF,0x5D,0x03,0x80,0xDE,0x3C,0x62,
      0xBE,0xE0,0x02,0x5C,0xDF,0x81,0x63,0x3D,
      0x7C,0x22,0xC0,0x9E,0x1D,0x43,0xA1,0xFF,
      0x46,0x18,0xFA,0xA4,0x27,0x79,0x9B,0xC5,
      0x84,0xDA,0x38,0x66,0xE5,0xBB,0x59,0x07,
      0xDB,0x85,0x67,0x39,0xBA,0xE4,0x06,0x58,
      0x19,0x47,0xA5,0xFB,0x78,0x26,0xC4,0x9A,
      0x65,0x3B,0xD9,0x87,0x04,0x5A,0xB8,0xE6,
      0xA7,0xF9,0x1B,0x45,0xC6,0x98,0x7A,0x24,
      0xF8,0xA6,0x44,0x1A,0x99,0xC7,0x25,0x7B,
      0x3A,0x64,0x86,0xD8,0x5B,0x05,0xE7,0xB9,
      0x8C,0xD2,0x30,0x6E,0xED,0xB3,0x51,0x0F,
      0x4E,0x10,0xF2,0xAC,0x2F,0x71,0x93,0xCD,
      0x11,0x4F,0xAD,0xF3,0x70,0x2E,0xCC,0x92,
      0xD3,0x8D,0x6F,0x31,0xB2,0xEC,0x0E,0x50,
      0xAF,0xF1,0x13,0x4D,0xCE,0x90,0x72,0x2C,
      0x6D,0x33,0xD1,0x8F,0x0C,0x52,0xB0,0xEE,
      0x32,0x6C,0x8E,0xD0,0x53,0x0D,0xEF,0xB1,
      0xF0,0xAE,0x4C,0x12,0x91,0xCF,0x2D,0x73,
      0xCA,0x94,0x76,0x28,0xAB,0xF5,0x17,0x49,
      0x08,0x56,0xB4,0xEA,0x69,0x37,0xD5,0x8B,
      0x57,0x09,0xEB,0xB5,0x36,0x68,0x8A,0xD4,
      0x95,0xCB,0x29,0x77,0xF4,0xAA,0x48,0x16,
      0xE9,0xB7,0x55,0x0B,0x88,0xD6,0x34,0x6A,
      0x2B,0x75,0x97,0xC9,0x4A,0x14,0xF6,0xA8,
      0x74,0x2A,0xC8,0x96,0x15,0x4B,0xA9,0xF7,
      0xB6,0xE8,0x0A,0x54,0xD7,0x89,0x6B,0x35,
      ];
    };
    Coding.prototype.get_crc8 = function(data) {
    function expl(dt){
    var m = [];
    for(var i = 0; i < dt.length; i++) {
    m.push((dt[i]).charCodeAt(0));
    }
    return m;
    }
    var dat = (expl(data));
    var crc = 0;
    for(var i = 0; i < dat.length; i++) {
    var dig = dat[i];
    crc = this.crc8_table[dig ^ crc];
    }
    return crc;
    };

    Coding.prototype.decode = function(data){
    var place = data.indexOf("#");
    if (place < 8) {
    return 'NoCRC:#';
    }
    var dat = data.substring(0, place+1);
    var crcstr = data.substring(data.indexOf("#")+1);
    if (crcstr.length > 3) {
    return 'NoCRC:long';
    }

    var crc = parseInt(crcstr, 10);
    var control = this.get_crc8(dat);
    if(crc == control){
    //console.log('Good!');
    var dt = data.substring(0, data.indexOf("}")+1);
    return dt;

    }
    else {
    //console.log('Bad!');
    return 'NoCRC';
    }
    };
    Coding.prototype.encode = function(data){
    var first = (Math.random()*255).toFixed(0);
    var part = data+first+'#';
    var end = this.get_crc8(part).toFixed(0);
    var all = part+end;
    return all;
    };

    exports.connect = function() {
      return new Coding();
    };

     

    Вот здесь начальство обещало в Искре заделать шифрование. Вещь это классная, хотел сначала подождать. Однако умный дом, он, ведь, состит из всего подряд. И, получается, надо сделать что-то кроссплатформенное.
    Открытые данные в формате json, полагаю, особой тайной не являются. Придется терепеть возможность соседей узнать, например, температуру у вас в подвале.
    Однако возможность татей включить вам обогреватель, подсмотрев команду, стоит исключить.
     
    Последнее редактирование: 22 сен 2016
  3. ИгорьК

    ИгорьК Гуру

    И теперь о ESP-8266. (Здесь Lua. ArduinoIDEпоклонники смотрят первый пост.)

    Код (Lua):
    local M = {}
    M.math = math
    M.crc8_table = {
        0x00,0x5E,0xBC,0xE2,0x61,0x3F,0xDD,0x83,
        0xC2,0x9C,0x7E,0x20,0xA3,0xFD,0x1F,0x41,
        0x9D,0xC3,0x21,0x7F,0xFC,0xA2,0x40,0x1E,
        0x5F,0x01,0xE3,0xBD,0x3E,0x60,0x82,0xDC,
        0x23,0x7D,0x9F,0xC1,0x42,0x1C,0xFE,0xA0,
        0xE1,0xBF,0x5D,0x03,0x80,0xDE,0x3C,0x62,
        0xBE,0xE0,0x02,0x5C,0xDF,0x81,0x63,0x3D,
        0x7C,0x22,0xC0,0x9E,0x1D,0x43,0xA1,0xFF,
        0x46,0x18,0xFA,0xA4,0x27,0x79,0x9B,0xC5,
        0x84,0xDA,0x38,0x66,0xE5,0xBB,0x59,0x07,
        0xDB,0x85,0x67,0x39,0xBA,0xE4,0x06,0x58,
        0x19,0x47,0xA5,0xFB,0x78,0x26,0xC4,0x9A,
        0x65,0x3B,0xD9,0x87,0x04,0x5A,0xB8,0xE6,
        0xA7,0xF9,0x1B,0x45,0xC6,0x98,0x7A,0x24,
        0xF8,0xA6,0x44,0x1A,0x99,0xC7,0x25,0x7B,
        0x3A,0x64,0x86,0xD8,0x5B,0x05,0xE7,0xB9,
        0x8C,0xD2,0x30,0x6E,0xED,0xB3,0x51,0x0F,
        0x4E,0x10,0xF2,0xAC,0x2F,0x71,0x93,0xCD,
        0x11,0x4F,0xAD,0xF3,0x70,0x2E,0xCC,0x92,
        0xD3,0x8D,0x6F,0x31,0xB2,0xEC,0x0E,0x50,
        0xAF,0xF1,0x13,0x4D,0xCE,0x90,0x72,0x2C,
        0x6D,0x33,0xD1,0x8F,0x0C,0x52,0xB0,0xEE,
        0x32,0x6C,0x8E,0xD0,0x53,0x0D,0xEF,0xB1,
        0xF0,0xAE,0x4C,0x12,0x91,0xCF,0x2D,0x73,
        0xCA,0x94,0x76,0x28,0xAB,0xF5,0x17,0x49,
        0x08,0x56,0xB4,0xEA,0x69,0x37,0xD5,0x8B,
        0x57,0x09,0xEB,0xB5,0x36,0x68,0x8A,0xD4,
        0x95,0xCB,0x29,0x77,0xF4,0xAA,0x48,0x16,
        0xE9,0xB7,0x55,0x0B,0x88,0xD6,0x34,0x6A,
        0x2B,0x75,0x97,0xC9,0x4A,0x14,0xF6,0xA8,
        0x74,0x2A,0xC8,0x96,0x15,0x4B,0xA9,0xF7,
        0xB6,0xE8,0x0A,0x54,0xD7,0x89,0x6B,0x35,
    };
         M.get_crc8 = function(data)
            function expl(dt)
                local m = {}
                for i = 1, #dt  do
                    m[i] = string.byte(string.sub(dt,i,i))
                end
                return m
            end

            local dat = (expl(data))
            local crc = 0

            for i = 1, #dat do
                local dig = dat[i]
                crc = M.crc8_table[(bit.bxor(dig, crc))+1]
            end
            return crc
        end

        M.decode = function(data)
            local dat = data:sub(1, data:find("#")) or "Not#"
            local plc = data:find("#") or -1
            local crc = tonumber(data:sub(plc+1))
            local control = M.get_crc8(dat)
            if crc == control then
                -- print('Good!')
                local pl = data:find("}") or data:find("#")
                local dt = data:sub(1, pl)
                return dt;
            else
               -- print('Bad!')
                return 'NoCRC'
            end
        end

        M.encode = function(data)
            local first
            first = tostring(M.math.random(0,255))
            local part = data..first..'#'
            local fin = M.get_crc8(part)
            local all = part..fin
            return all
        end

    return M
     
     
    Последнее редактирование: 20 сен 2016
  4. Tomasina

    Tomasina Сушитель лампочек Модератор

    получается можно реализовать беспроводную загрузку скетча?
     
  5. ИгорьК

    ИгорьК Гуру

    Если умеете это делать по UART - то да.
     
  6. alp69

    alp69 Форумчанин

    Было бы очень интересно.
     
  7. Tomasina

    Tomasina Сушитель лампочек Модератор

    Практически всегда по UART и делаю (использую в основном ProMini, там только этот интерфейс выведен).
    Если решить вопрос с DTR (чтобы ресет ручками не жмакать), то вообще ляпота. Как вариант - при получении по UART определенной последовательности сразу делать программный hard reset.
     
  8. а чем easytransfer не устроил?
     
  9. ИгорьК

    ИгорьК Гуру

    А кто ему учил?
     
  10. в конце.
    можно же также и с HC12 работать, передавая структуру.

     
  11. ИгорьК

    ИгорьК Гуру

    Не стал бы.
    1. Открыто передавыаемая информация все таки должна иметь седства защиты.
    2. Не кроссплатформенно. В даннном сучае информация будет обрабатываться одинаково на Ардуино и на JS. И на Lua тоже.
    3. JSON выбран как обрабатываемая везде структура. Однако его простота для открытой передачи - его недостаток. Поэтому и нужен crc.
     
  12. ИгорьК

    ИгорьК Гуру

    Сегодня сделал проверку crc8 для ESP-8266.
    В первых трех постах - три кода для разных систем, которые могут взаимодействовать в рамках умного дома. Например:
    0001HC12.jpg

    Вне зависимости от используемого железа, каждый код обрабатывает одни и те же данные одинаково.
     
    Последнее редактирование: 21 сен 2016
  13. сложно представить насколько НС-12 безумные модули, просто меняем пружинку из комплекта на медный одножильный провод длиной 173мм и получаем работу на пол километра со скоростью 9600 в практически любых условиях, стабильно километр в примой видимости.
     
    Tomasina и ИгорьК нравится это.
  14. ИгорьК

    ИгорьК Гуру

    фигасе
     
  15. решил проверить будет ли связь до гаража, до него пол километра разной застройки и зарослей, поставил один на окно, второй блин ловит внутри гаража, причем весьма уверенно.
     
    ИгорьК нравится это.
  16. alp69

    alp69 Форумчанин

    Жду свои модули из Китая. В планах модернизация сети автоматизации. На приведенной структурной схеме один момент кажется не рациональным. А именно связка малины и есп, к которой подключен hc-12.
    В связи с этим вопрос. Есть ли у кого опыт заталкивания данных от устройств в брокер на малине через hc-12. Другими словами - в шлюзе, на который указывает красная стрелка "Lua", исключить ESP, подавая данные через hc-12 сразу в малину.
    Или проще реализовать "диспетчера" на модуле ESP?
     
  17. ИгорьК

    ИгорьК Гуру

    Я бы затолкал... Если бы малина не была в другом месте. НС-12 работает у меня на даче, а малина с OpenHab - в городе.
     
  18. ИгорьК

    ИгорьК Гуру

    Почитайте, это мой опыт заталкивания другого UART-модуля. Разницы не вижу.
     
    alp69 нравится это.
  19. Rapidshe

    Rapidshe Нуб

    не подскажите, как использовать прогмен? просто раскомментирование одной строки закомментирование следующей дает плохой результат) crc всегда равно 8 и на стороне приемника часто проблема возникает.

    Помню что из прогмена надо вызывать инфу по особенному. попробовал вспомнить как - ардуина аш перезагружается на этом куске кода))
     
    Последнее редактирование: 27 апр 2017
  20. ИгорьК

    ИгорьК Гуру

    Идите на arduino.cc и ищите - я находил. Там слегка синтаксис изменился.