Управляем светодиодом через интернет с использованием RaspberryPi

В наше время обычная вещь, подключённая к интернету, начинает становиться обыденностью. Даже появилось понятие — «интернет вещей» (Internet of Things, IoT). Но как подступиться к этому своеобразному интернету новичку — не всегда понятно, потому что хотя статей по данной теме много, но каждому хочется, чтобы статья была простой для воспроизведения и чтобы в ней разбиралось что-то очень близкое и приятное для читателя.

Поэтому попробуем подключить к интернету самое простое — светодиод, взятый из сломанной оптической мыши. Будем через страницу в интернете включать и выключать светодиод, управлять частотой его мерцаний.

Для нашего эксперимента потребуется


  • Интернет через ethernet кабель
  • Raspberry Pi
  • Сломанная оптическая мышка, провода, паяльник
  • Доступ к сайту на vps с правами рута


Собираем схему


Схема подключения светодиода к интернету будет следующая:



Итак, соберём все детали на столе и спаяем нашу простую схемку. Из мышки достаём светодиод и резистор в 220 Ом. Резистор нужен для ограничения тока, он мелкий и его еле можно разглядеть на конце провода. Для соединения проводов с GPIO использую коннекторы, которые выпросил в сервисе, ремонтирующем компьютеры.



Подготавливаем софт


На сервере и «малинке» должен стоять node.js и npm (node package manager). Устанавливаем по данной инструкции.

Для того, чтобы node.js смог работать с GPIO, требуется установить модуль rpi-gpio. А для соединения с сервером RPi потребуется socket.io-client. Устанавливаются пакеты командой sudo npm install rpi-gpio и sudo npm install socket.io-client.

Скрипт для node.js на Raspberry Pi:
var socket = require('socket.io-client')('vpssite.domain:3141');
var gpio = require('rpi-gpio');
var fs = require('fs');

// hack due to error
fs.exists = require('path').exists;
var async = require('async');

// pin GPIO4
var pin = 7;

// current fps
var piFps = 0;
var currentValue = false;
var timemanager;


var set0 = function(err, results) {
    if (err)
        console.log(err);
    console.log('Pin ' + pin + ' closed');
    directWrite(pin, false, function() {
        clearTimeout(timemanager);
    });
};

var blinkexec = function() {
    delayedWrite(7, true, function() {
        delayedWrite(7, false, blinkexec)
    });
};

var blink = function(err, results) {
    if (err)
        console.log(err);
    console.log('Pin ' + pin + ' blinking');
    blinkexec();
};


function directWrite(pin, value, callback) {
    return gpio.write(pin, value, callback);

}
function delayedWrite(pin, value, callback) {
    var delay = Math.round(1000 / piFps / 2);
    
    clearTimeout(timemanager);
    timemanager = setTimeout(function() {
        directWrite(pin, value, callback);
    }, delay);
}


var release = function() {
    console.log('Writes complete, pause then unexport pins');
    setTimeout(function() {
        gpio.destroy(function() {
            console.log('Closed pins, now exit');
            return process.exit(0);
        });
    }, 500);
};


socket.on('connect', function() {
    console.log('connected');
    
    socket.on('setfps', function(data) {
        console.log(data);
        
        if (data.fps > 0) {
            piFps = data.fps;
            gpio.setup(pin, gpio.DIR_OUT, blink);
        } else {
            gpio.setup(pin, gpio.DIR_OUT, set0);
        }
    });
    
    socket.on('disconnect', function() {
        console.log('disconnect');
        release();
    });
});




Следующий код на сервере создаёт сервер на порту 3141 и принимает команду setfps и рассылает её дальше всем браузерам и Raspberry Pi.

Скрипт для node.js на vps
var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);

var allClients = [];
var count = 0;
var fpsPi = 0;

server.listen(3141);

server.on('error', function(e) {
    if (e.code == 'EADDRINUSE') {
        console.log('Address in use, exit...');
        process.exit();
    }
});

app.get('/', function (req, res) {
  res.send('Fps is ' + fpsPi);
  console.log('requested / - show ' + fpsPi);
});

function getDate() {
    var datas = new Date();
    return datas.getHours() + ':' + datas.getMinutes() + ':' + datas.getSeconds() + '.' + datas.getMilliseconds()
}

function consolelog(msg) {
    console.log(getDate() + ' ' + msg);
}

io.on('connection', function (socket) {
    io.emit('setfps', {fps: fpsPi});
    
    // browser subscribes to listen to the station 
    socket.on('subscribe', function(data) {
        socket.json.emit('subscribed', {fps: fpsPi});
        io.emit('setfps', {fps: fpsPi});
    });
    
    // disconnect on error. Browser will reconnect
    socket.on('error', function() {
        socket.disconnect();
    });
    
    // client disconnects
    socket.on('disconnect', function() {
        consolelog('Client disconnected.');
    });
    
    // save to RPi and browsers new fps
    socket.on('setfps', function(fps){
        fpsPi = fps;
        consolelog('setfps ' + fpsPi);
        io.emit('setfps', {fps: fpsPi});
    });
});




Запуск работы


Запускаем скрипты следующим образом. На RaspberryPi — с правами рута: sudo nodejs led.js, а на сервере — просто добавляем в кронтаб * * * * * cd /var/www/apps; node server.js >> cron_rpi.log, это не так красиво, зато всегда сервер будет запущен и можем о нём забыть.

На странице своего сайта включаем код для слайдера jquery-ui и socket.io с нашего сервера. При получении сигнала от node.js-сервера слайдер выставляет текущее значение fps и наоборот — при передвижении слайдера мы на сервер отсылаем новое значение fps, которое сервер затем рассылает всем клиентам в браузеры и на Raspberry Pi.

Код, размещаемый на странице сайта
<script src="http://vpssite.domain:3141/socket.io/socket.io.js"></script>

<script>
    function setSlided(val) {
        if (val == 0) {
            $('#freq').html('Выключен');
            $('#freq2').hide();
        }
        else {
            $('#freq').html(val);
            $('#freq2').show();
        }
    }

    $(function() {
        // соединяемся с nodejs на сервере
        var socket = io.connect('http://bk-it.ru:3141');
        socket.emit('subscribe');

        // при ответе сервера выставляем текущую fps
        socket.on('subscribed', function(data) {
            if (!data.error) {
                setSlided(data.fps);
                $("#loading").hide();
                
                $("#slider").slider({
                    min: 0,
                    max: 20,
                    value: data.fps,
                    slide: function(event, ui) {
                        $("#slider").slider({ disabled: true });
                        socket.emit('setfps', ui.value);
                        //setSlided(ui.value);
                    }
                });
            }
        });
        socket.on('setfps', function(data) {
            if (!data.error) {
                setSlided(data.fps);
                $("#slider").slider({value: data.fps});
                $("#slider").slider({ disabled: false});
            }
        });
    });
</script>

<h1>Светодиод, подключённый к интернету</h1>
<link rel="stylesheet" href="//code.jquery.com/ui/1.11.2/themes/smoothness/jquery-ui.css">
<script src="//code.jquery.com/jquery-1.10.2.js"></script>
<script src="//code.jquery.com/ui/1.11.2/jquery-ui.js"></script>

<div class="panel panel-info">
    <div class="panel-heading">Управляйте светодиодом через сайт</div>
    <div class="panel-body">
        <div class="row">
            <div class="col-md-6">
                <div  style="height:40px;position:relative;top:12px;">
                    <div id="slider"><div id="loading" style="position:relative;top:-4px;">Соединяемся <img src="/img/loading.gif" alt=""></div></div>
                </div>
            </div>
            <div class="col-md-6">
                <div class="well-sm">Частота мерцания <b id="freq">Выключен</b> <span id="freq2" style="display:none;">раз/сек</span></div>
            </div>
        </div>
    </div>
    <div class="panel-footer">
        Не так часто встречаете светодиод, подключённый к интернету?
    </div>
</div>



Результаты работы


А вот и видео, описывающее, что получилось:



Вот, собственно, и всё, светодиод подключен к интернету. Теперь с любого устройства с интернетом мы можем управлять его мерцанием. Простор для дальнейшего творчества большой. Можно к выводам подключить модуль-реле и кнопкой на своём сайте включать и выключать любое оборудование. Модулей для Raspberry Pi продаётся очень много, поэтому полёт фантазии почти не ограничен, особенно если в наличии восторженный восьмилетний почитатель электроники.

Полезные ссылки, помогшие эксперименту


Документация Socket.io;
Документация Node.js;
Пакет rpi-gpio;
Документация на jquery-ui слайдер;
Распиновка GPIO на Raspberry Pi;
Модуль respawn для автоматического запуска nodejs-приложения (но я выбрал crontab).

Код можете взять из rpi-led на гитхабе.
Метки:
  • +2
  • 15,7k
  • 9
Поделиться публикацией
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 9
  • –1
    Сервер под рутом вызвал у меня легкий нервный тик.
    • 0
      В смысле? Чтобы на rpi получить доступ к GPIO, требуются рутовые права, иначе будет что-то вроде RuntimeError: No access to /dev/mem. Try running as root!. Либо я просто не знаю, как иначе достучаться до GPIO малинки.
      • 0
        Это была питоновская ошибка, а в node { [Error: EACCES, open '/sys/class/gpio/export'] errno: 3, code: 'EACCES', path: '/sys/class/gpio/export' } Если знаете, как сделать без sudo, сообщите, пожалуйста. А в мануале к применённому пакету стоит как раз это требование «Firstly, make make sure you are running your application as root or with sudo, else the Raspberry Pi will not let you output to the GPIO.»
        • 0
          На стэковерфлоу предлагают abyz.co.uk/rpi/pigpio/python.html
          • 0
            Демон под sudo, который занимается обращением к gpio, а скрипт при этом может без прав обращаться к демону? Что ж, хорошее решение.
            • 0
              Демон еще умеет генерировать ШИМ, причем авторы уверяют, что точность — одна микросекунда.
    • –1
      Спасибо за статью! Как раз пытаюсь сделать нечто подобное, очень полезно :)
      • 0
        На мой взгляд, в статье не раскрыта тема обеспечения безопасного доступа к такому устройству. Шуточки с печатью 9000 репродукций Малевича могут показаться шалостью, если зловред доберется до такого управления и устроит например потоп, открыв кран в ванной, подключенный к Сети аналогичным образом.
        • +1
          Напомнило

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.