Monday, July 9, 2018

IoT: Էլ-փոստով ղեկավարվող լուսավորություն

Ինչ-որ ժամանակ առաջ պիտի գնայինք գյուղ և Յերեվանի տանը մի քանի օր մարդ չէր լինելու։ Մտքովս անցավ մտածել մի սարքավորում, որը հնարավորություն կտա գյուղից, ինչ-որ եղանակով միացնել տան լույսերը (կամ մի այլ սարք)։ Ինտերնետում բավականին քչփորելով գտա մի քանի եղանակներ, որոնք օգտագործում էին օժանդակ ցանցային ծառայություններ։ Վերջապես, համադրելով մի քանի գաղափարներ, կառուցեցի ստորև նկարագրված սարքա-ծրագրային համակարգը։

Աշխատանքի մեխանիզմն այսպիսինն է. Յերեվանի տանը դրած համակարգիչը, օգտագործում եմ Raspberry Pi 1 Model B Rev. 2, ամեն երկու (կամ 1, կամ 5 և այլն) րոպեն մեկ ստոգում է հատուկ այդ նպատակի համար ստեղծված էլ-փոստը։ Հենց որ ստացվում է նոր նամակ՝ ստուգում նամակի վերնագիրը, որում գրված է կոնկրետ առաջադրանքը, օրինակ, «LIGHT ON» կամ «LIGHT OFF»։ Որպեսզի որևէ օտար մարդ չկարողանա համակարգչին առաջադրանք տալ (նամակ ուղարկել), ստուգվում է նաև ուղարկողի հասցեն (միամիտ ու անհուսալի պաշտպանություն է. կարելի է ու պետք է կատարելագործել)։ Եթե ամեն ինչ սպասվածի պես է, ապա համակարգչի միացված ռելեյի միջոցով, օգտագործում եմ KY-019 մոդուլը, միացվում կամ անջատվում է էլեկտրական սարքը։

Էլ֊փոստը կարդալու համար օգտագործում եմ getmail֊ը, իսկ cron֊ը օգտագործում եմ getmail֊ը երկու րոպեն մեկ աշխատեցնելու համար։ getmail֊ը տեղադրել եմ սովորական եղանակով․

$ sudo apt install -y getmail4

Տեղադրելուց հետո այն պետք է կարգավորել այնպես, որ կարդա իմ էլ֊փոստը։ Դրա համար $HOME պանակում ստեղծում եմ .getmail պանակը, իսկ դրա մեջ էլ getmailrc ֆայլը։ Վերջինս էլ հենց getmail֊ի կարգավորումների ֆայլն է։ Ինձ մոտ այն հետևյալ տեքսի է․

[retriever]
type = SimpleIMAPSSLRetriever
server = imap.yandex.com
port = 993
username = __իմ էլ֊փոստի անունը__
password = __իմ էլ֊փոստի գաղտնաբառը__

[options]
read_all = false
delivered_to = false
received = false

[destination]
type = MDA_external
path = ~/Projects/a5/readanddo.sh

retriever բլոկում getmail֊ը կարգավորվում է կոնկրետ փոստարկղի համար։ Կարծում եմ, որ այդ բլոկի պարամետրերը բացատրելու կարիք չկա․ դրանց անուններն ամեն ինչ ասում են իրենց մասին։

options բլոկի read_all = false պարամետրը նշանակում է, որ պետք չէ ամեն անգամ սերվերից կարդալ բոլոր նամակները, այլ կարդալ միայն նորերը։ Եթե delivered_to և received պարամետրերը դրված են true, ապա ստացված նամակի վերնագրին (header) ավելացվում են համապատասխանաբար «Delivered To:» և «Received:» դաշտերը (սրանց իմաստը չեմ հասկանում, պարզապես false եմ դրել ավելորդություններից խուսափելու համար)։

Ամենակարևորն իմ աշխատանքում destination բլոկն է։ Սրա պարամետրերով են որոշվում, թե ինչ պետք է անել փոստարկղի սերվերից ներբեռնված նամակների հետ։ Իմ դեպքում type = MDA_external պարամետրն ասում է, որ նամակները պետք է մշակվեն արտաքին (ոչ ներդրված) MDA ― mail delivery application ծրագրով։ Ամեն անգամ, հենց որ getmail֊ը սերվերից նոր նամակ է կարդում, այն ուղղարկում է path պարամետրով տրված ծրագր (կամ սկրիպտի) ստանդարտ ներմուծման հոսքին։

Ես գրել եմ readanddo.sh սկրիպտը, որը ստուգում է նամակի «From:» և «Subject:» դաշտերը։ Եթե դրանցում գրված են սպավող արժեքները՝ «From:» դաշտում հրամաններ ուղարկող էլ֊փոստի հասցեն, իսկ «Subject:» դաշտում՝ կոնկրետ հրամանը, ապա Raspberry Pi֊ի GPIO֊ին ուղղարկվում է համապատասխան ազդանշանը։

#!/bin/bash

operation=''
commander=''

while read line
do
    if [[ ${line} =~ ^Subject: ]]
    then
        if [[ ${line} =~ DO:LIGHT:ON ]]
        then
            operation="LIGHT:ON"
        elif [[ ${line} =~ DO:LIGHT:OFF ]]
        then
            operation="LIGHT:OFF"
        fi
    fi

    if [[ ${line} =~ ^From: ]]
    then
        if [[ ${line} =~ __իմ էլ֊փոստի հասցեն__ ]]
        then
            commander=${line}
        fi
    fi
done



if [ -z ${commander} ]
then
    exit 0
fi

if [ -z ${operation} ]
then
    exit 0
fi


gpio -g mode 4 out

if [ ${operation} = "LIGHT:ON" ]
then
    gpio -g write 4 1
    exit 0
elif [ ${operation} = "LIGHT:OFF" ]
then
    gpio -g write 4 0
    exit 0
fi

Ռելեյի KY-019 մոդուլն ունի երեք մուտքային ոտիկներ․ «+», «-» և «S»։ «+»-ը միացնում եմ Raspberry Pi-ի 2֊րդ GPIO֊ին՝ 5v, «-»-ը միացնում եմ 6֊րդ GPIO֊ին՝ GND, իսկ «S»֊ը, որը ղեկավարող ազդանշանն է, միացնում եմ 7-րդ GPIO֊ին (ֆիզիկական համարակալմամբ 7֊րդը BCM համարակալմամբ 4֊րդն է)։

Երբ readanddo.sh սկրիպտը համոզվում է, որ հրամանն ուղարկվել է նախապես որոշված հասցեից, և հրամանի ֆորմատն էլ նախապես որոշվածներից մեկն է, RPi֊ի 4֊րդ GPIO֊ի (BCM համարակալմամբ) ուղղությունը դարձնում է «out».

gpio -g mode 4 out
և այդ GPIO֊ի արժեքը դնում է 0 կամ 1.
gpio -g write 4 1
gpio -g write 4 0

Մնում է միայն սահմանել cron֊ի առաջադրանք, որը երկու րոպեն մեկ կգործարկի getmail ծրագիրը։

* * *

Դժվար թե սա կիրառելի լինի իրական կյանքում։ Կարծում եմ, որ կան տանը մարդու ներկայության իմիտացիայի ավելի լավ միջոցներ։

Friday, July 6, 2018

JavaScript: mapcar-ի ևս մի իրականացման մասին

Չեմ հիշում, թե ինչի համար, բայց ինձ պետք էր JavaScript ծրագրում օգտագործել Common Lisp-ի mapcar ֆունկցիայի պես մի ֆունկցիա։ Մի քիչ դեսուդեն քչփորելուց հետո գտա սա. https://www.npmjs.com/package/mapcar։ Իրականացումից շատ բան չհասկացա ու դրա համար էլ որոշեցի գրել ավելի պարզ տարբերակը։
Ահա այն՝ մանրամասն մեկնաբանություններով.
//
// Ֆայլի անունը. mapcar.js
//

//
// Ֆունկցիայի անունը որոշեցի թողնել նույնը, ինչ որ
// Common Lisp լեզվում է՝ mapcar։
//
// mapcar ֆունկցիան սպասում է մեկ և ավելի արգումենտներ։
// Դրանցից առաջինը կիրառվող ֆունկցիան է, մյուսները՝ վեկտորներ են։
//
var mapcar = function( func, ...args ) {
    // համոզվել, որ առաջին արգումենտը ֆունկցիա է
    if( 'function' !== typeof func ) {
        throw 'mapcar-ի առաջին արգումենտը ֆունկցիա չէ։'
    }

    // համոզվել, որ երկրորդ և հաջորդ արգումենտներում վեկտորներ են.
    if( !args.every(Array.isArray) ) {
        throw 'Ոչ բոլոր արգումենտներն են վեկտոր տիպի։'
    }

    // համոզվել, որ ֆունկցիայի պարամետրերի քանակն ու mapcar-ին
    // տրված արգումենտների քանակները նույնն են
    if( func.length != args.length ) {
        throw 'Ֆունկցիայի պարամետրերի քանակն ու վեկտորների քանակը տարբեր են։'
    }

    // mapcar ֆունկցիայի կիրառման արդյունքը վեկտոր է
    let result = []

    // եթե վեկտորների երկարությունները տարբեր են, ապա mapcar—ի
    // արդյունքը ստացվելու է դրանցից ամենակարճի չափով
    const lengths = args.map((e) => e.length)
    const reslen = Math.min.apply(null, lengths)

    // ցիկլը կատարելով վեկտորներից ամենակարճի տարրերի քանակով...
    for( let i = 0; i < reslen; ++i )  {
        // վերցնել բոլոր վեկտորների i-րդ տարրերը, ...
        const atu = args.map((ev) => ev[i])
        // ֆունկցիան կիրառել դրանց նկատմամբ, ...
        const ri = func.apply(null, atu)
        // արդյունքն ավելացնել result վեկտորում
        result.push(ri)
    }

    // վերադարձնել կառուցված արդյունքը
    return result
}

// տրամադրել այս ֆունկցիան արտաքին աշխարհին
module.exports.mapcar = mapcar

  1. every մեթոդը true է վերադարձնում միայն այն դեպքում, երբ զանգվածի բոլոր տարրերը բավարարում են տրված պրեդիկատին։
  2. map մեթոդը վերադարձնում է զանգված բոլոր տարրերի նկատմամբ տրված ֆունկցիայի կիրառումների արդյունքում ստացված արժեքների վեկտորը։
  3. apply մեթոդը հնարավորություն է տալիս ֆունկցիան կանչել արգումենտների վեկտորով։ Օրինակ, եթե սահմանված է var f = function(x, y, z) { ... } , ապա -ը կարելի է օգտագործել այսպես. f.apply(null, [1, 2, 3])։ Հարմար է այն դեպքում, երբ կանչի արգումենտները դինամիկ են ձևավորվում։


Հիշեցի. mapcar-ն ինձ պետք էր zip-ի նման մի ֆունկցիա իրականացնելու համար։
var zip = function(x, y) {
    return mapcar((a, b) => [a, b], x, y)
}