Showing posts with label Bash. Show all posts
Showing posts with label Bash. Show all posts

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 ծրագիրը։

* * *

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

Sunday, April 7, 2013

Bash: Հայկական ժողովրդական հեքիաթներ

Վերջերս հայտնաբերեցի մի հղում՝ http://serials.flib.sci.am/openreader/, որը տանում է ՀՀ ԳԱԱ ինչ-որ բաժնի կողմից սկանավորված գրքերի բավականին մեծ պաշարի։ Ցուցակում ինձ հետաքրքրեցին «Հայկական Ժողովրդական Հեքիաթներ»-ի ակադեմիական հրատարակության 15 հատորները։ Դես ու դեն քչփորելուց հետո պարզեցի, որ չեմ կարողանալու հատորները ներբեռնել ամբողջական ֆայլի տեսքով։ Միակ տարբերակը մնում էր էջ առ էջ ներբեռնելը։ Հետո պարզեցի, որ ամեն մի գրքի համար առանձնացված է մի պանակ, իսկ այդ պանակում գրքի էջերը եռանիշ թվերով համարակալված JPG ֆայլեր են։ Օրինակ, առաջին հատորի էջերի պանակը հետևյալն է․
http://serials.flib.sci.am/openreader/jox_heqiatner_1/book/
Մյուս հատորների համար պարզապես 1-15 միջակայքում փոխվում է «jox_heqiatner_» պանակի համարը։

Գրեցի մի սկրիպտ, որի արգումենտում պետք է տալ, թե որ հատորն եմ ուզում ներբեռնել և այն կստեղծի առանձին պանակ ու դրա մեջ կներբեռնի տրված հատորի էջերի բոլոր JPG ֆայլերը։ Սկրիպտի սկզբում նախ ստուգում եմ, որ տրված լինի միայն մեկ արգումենտ և այդ արգումենտը լինի ամբողջ թիվ։
#!/bin/bash

if [[ $# -ne 1 ]]
then 
  echo Enter book number, an integer between 1 and 15.
  exit 1
fi

if ! [[ $1 =~ ^[0-9]+$ ]]
then
  echo Argument is not an integer.
  exit 2
fi
Հետո ձևավորում եմ երկու հասցե՝ bookurl - էջերի հասցեն, և destdir - ներբեռնված ֆայլերի հասցեն։ Եթե արդեն գոյություն ունի destdir պանակը, ապա այն ջնջում եմ ու ստեղծում եմ նորը։
bookurl="http://serials.flib.sci.am/openreader/jox_heqiatner_$1/book/"
destdir=$(pwd)$(printf "/T-%.3d/" $1)

rm -rf ${destdir}
mkdir ${destdir}
Էջերը հերթականությամբ ներբեռնելու համար page փոփոխականը արժեքավորում եմ 1 սկզբնական արժեքով․ այն ցույց է տալու հերթական էջը։ Հետո կազմակերպում եմ անվերջ ցիկլ, որի մարմնում wget ծրագրով բեռնում եմ հերթական ֆայլը։ Ցիկլի կատարում ընդհատվում է այն ժամանակ, երբ wget ծրագիրը չի կարողանում բեռնել ֆայլը և վերադարձնում է զրոյից տարբեր ավարտի կոդ։
page=1
while true
do
  # էջի համարը դարձնել եռանի
  num=$(printf "%.3d.jpg" ${page})
  # բեռնել հերթական էջը
  wget -q ${bookurl}${num} -O ${destdir}${num}
  # ստուգել wget-ի ավարտի կոդը
  if [[ $? -ne 0 ]]
  then
    # ընդհատել ցիկլը
    echo Done
    # ջնջել զրոյական ֆայլը
    rm -rf ${destdir}${num}
    break
  fi
  echo -n ${page} ' '
  # հաշվել հաջորդ էջի համարը
  page=$(expr ${page} + 1)
done
Այսքանը։ Հիմա արդեն մնում է ստացված էջերը միացնել իրար ու կազմել ամբողջական գրքի ֆայլը։

Friday, March 29, 2013

Այլ ծրագրի աշխատեցնելը Java ծրագրից

Ինչ որ մի անգամ ինձ պետք էր անել այնպես որ օգտագործողի գրաֆիկական ինտերֆեյսում (GUI) անընդհատ ռեժիմով արտածվեն մեկ այլ պրոցեսում աշխատեցրած ծրագրի արտածման ստանդարտ հոսքի (stdout) և սխալների ստանդարտ հոսքի (stderr) հաղորդագրությունները։ Այս գրառման մեջ ես պարզեցրած օրինակով ցույց կտամ, թե ինչպես դա արեցի։

1. NetBeans աշխատանքային միջավայրում ստեղծենք ScriptRunner անունով պրոյեկտը և նրա լռելությամբ ստեղծված scriptrunner փաթեթում ավելացնենք JFrame դասից ժառանգված MainWindow անունով պատուհանը։

2. Պատուհանի վրա ավելացնենք արտաքին սկրիպտի ճանապարհը նշելու, այն աշխատեցնելու և արդյունքներն արտածելու հետևյալ երեք օբյեկտները։
  • txtScript : JTextField - սկրիպտի կամ հրամանի ճանապարհը նշելու դաշտ,
  • btnRun : JButton - աշխատանքը սկսելու կոճակ,
  • areaResults : JTextArea - արդյունքներն արտածելու տիրույթ:
Դասավորենք օբյեկտներն այնպես, որ նրանք ունենան ստորև բերված նկարի տեսքը։

3. "Run" կոճակի կոնտեքստային մենյուից ընտրենք Events->Action->actionPerformed գործողությունը, որի հետևանքով պատուհանի կոդի հատվածում կստեղծվի btnRunActionPerformed անունով մեթոդի դատարկ մարմինը։ Այս մեթոդը կանչվելու է, երբ սեղմենք "Run" կոճակը։
private void btnRunActionPerformed(java.awt.event.ActionEvent evt) { 
  //
}


Արտաքին ծրագիրն աշխատեցնում եմ Process դասի օգնությամբ, որը հնարավորություն է տալիս իր getInputStream մեթոդից ստեղծել կարդալու հոսք և կարդալ արտաքին ծրագրի արտածման ու սխալների ստանդարտ հոսքերի արտածումները։ Օրինա, եթե պետք է Java ծրագրից աշխատեցնել ls ծրագիրը, ապա պետք է նախ ProcessBuilder դասի կոնստրուկտորին տալ աշխատեցվելիք ծրագրի անունը, ապա կանչելով նրա start մեթոդը, ստեղծել նոր պրոցես։
Process ps = (new ProcessBuilder("ls")).start();
InputStream is = ps.getInputStream();
Պրոցեսի տված հաղորդագրությունները սինխրոն կարդալու համար ստեղծել եմ մի thread, որը կարդում է պրոցեսի արտածման հոսքի հետ կապված ընթերցման հոսքի պարունակությունը և արտածում է areaResults դաշտում։

Ահա btnRunActionPerformed մեթոդի իրականացումը։
private void btnRunActionPerformed(java.awt.event.ActionEvent evt) {                                       
  try {
    // պրոցեսի ստեղծում
    ProcessBuilder pb = new ProcessBuilder(txtScript.getText());
    // սխալների հոսքն ուղղել stdout-ի վրա
    pb.redirectErrorStream(true); 
    // պրոցեսի գործարկում
    Process procFlow = pb.start();
    
    // վերցնել պրոցեսի արտածման հոսքը
    InputStream inp = procFlow.getInputStream();
    final BufferedReader reader = 
            new BufferedReader(new InputStreamReader(inp));

    // thread-ի ստեղծում
    Thread outputThread = new Thread() {
      @Override
      public void run() 
      {
        try {
          // ապահովել reader-ը,
          synchronized(reader) {
            String text = "";
            // քանի դեռ կարդալու բան կա, կարդալ
            while(null != (text = reader.readLine())) {
              // տեքստը կցել areaResults-ի պարունակության պոչից
              areaResults.append(text + "\n");
              // կուրսորը տեղափոխել տեքստի վերջը
              areaResults.setCaretPosition(areaResults.getText().length());
            }
          }
        }
        catch (Exception e) {}
      }
    };
    
    // կարդացող thread-ի գործարկում
    outputThread.start();
  }
  catch(IOException ex) {
    JOptionPane.showMessageDialog(this, ex.getMessage(), 
               "Error", JOptionPane.ERROR_MESSAGE);
  } 
}

* * *
Ծրագիրը փորձարկելու համար ստեղծել եմ Bash-ի մի սխրիպտ, որը երկու վայրկյան ընդմիջումներով հաղորդագրություններ է արտածում արտածման ստանդարտ հոսքի վրա։
#!/bin/bash

for m in 1 2 3 4 5 6 7 8 9 10 11 12
do
  echo Message number: $m
  sleep 2
done
Ծրագիրն աշխատեցնելուց հետո txtScript դաշտում ներմուում եմ սկրիպտի ճանապարհը ու սեղմում եմ Run կոճակը։ Արդյունքն այն է, ինչ ես սպասում էի։