Individální projekty MPOA

Mikroprocesory s architekturou ARM

Uživatelské nástroje

Nástroje pro tento web


2017:pool-ctrl

Zadání

Navrhněte modul pro ovládání činnosti filtračního a vyhřívacího systému bazénu. Použijte vývojovou desku s ethernetovým rozhraním (např. FRDM-K64F). Vytvořte jednoduchou webovou aplikaci, která umožní konfiguraci a řízení systému.


Úvod

Cieľom tohoto projektu je umožniť vzdialené ovládanie zariadení bazéna bez potreby návštevy technickej miestnosti. Taktiež je požadované automatické riadenie činnosti čerpadla a filtrácie na základe nastavených časov. Keďže je použitý modul K64F, ktorý obsahuje veľké množstvo vstupne-výstupných portov, do budúcnosti je možné tento projekt ľahko upraviť na využitie riadenia iných častí domácnosti, prípadne je možné pridať ďalšie senzory a možnosti pripojenia a ovládania (WiFi, displej, atď…).


Vývojový hardware

Ako vývojový HW boli použité nasledujúce moduly. FRDM-K64F ako hlavná riadiaca doska, CY7C68013A na debugovanie I2C zbernice a vlastný modul obsahujúci spínanie zariadení (čerpadlo, generátor ozónu, svetlo) pomocou relé a zároveň integrujúci obvod reálneho času (RTC).

  • NXP Freedom Development Board FRDM-K64F
  • LCSOFT CY7C68013A Mini Board
  • Prídavná doska s relé a RTC

{{ :2017:xbarto78:final_photo.jpg?direct&600 |


Obvodové zapojenie

K modulu FRDM-K64F bol navrhnutá externá doska obsahujúca relé a obvod reálneho času (RTC), ktorý komunikuje s procesorom pomocou I2C zbernice. Zapojenie a plošný spoj je vyobrazený nižšie. Pull-up rezistory na I2C dátovej zbernici nie sú použité z dôvodu, že ich obsahuje priamo doska FRDM-K64F, na ktorej je na túto zbernicu pripojený akcelerometer. Bolo preto potrebné vyvarovať sa použitiu rovnakých adries zariadení. Obvod reálneho času používa 5V úrovne, zatiaľ čo K64F 3.3V úrovne, a preto by ani nemohli byť pull-up rezistory použité a zapojené medzi 5V napájaciu vetvu a I2C zbernice. Obvod reálneho času DS1307 avšak bezproblémovo funguje aj v takomto zapojení. Čas je zálohovaný pomocou 3V líthiovej batérie, ktorá v prípade výpadku napájania udrží aktuálny čas až po dobu cca 10 rokov.

Schéma zapojenia

Obraz plošného spoja

Zoznam súčiastok

Part     Value          Device       Package  
BATT1    CR2032V        CR2032V      CR2032V  
C1       100n           C-KER_0805   0805     
D1       1N4148.        1N4148.      SOD-80   
D2       1N4148.        1N4148.      SOD-80   
D3       1N4148.        1N4148.      SOD-80   
IO1      DS1337+        DS1337+      DIL8     
JP2                     PINHD-2X10   2X10     
K1       CZM_5/3        CZM_5/3      CZM_5/3  
K2       CZM_5/3        CZM_5/3      CZM_5/3  
K3       CZM_5/3        CZM_5/3      CZM_5/3  
PAD_+5V  +5V            PAD_1---     PAD_1--- 
R3       1k             R_0805       0805     
R4       1k             R_0805       0805     
R5       1k             R_0805       0805     
RE1      E3206S         E3206S       E3206S   
RE2      E3206S         E3206S       E3206S   
RE3      E3206S         E3206S       E3206S   
T1       BC847          BC847        SOT23    
T2       BC847          BC847        SOT23    
T3       BC847          BC847        SOT23    
XTAL1    32k768         CRYSTALTC26V TC26V
  

Software

Softwarové riešenie je možné rozdeliť na 2 časti - klientskú a serverovú. Klientská časť obsahuje samotnú web stránku v HTML a javascripte. Stránka obsahuje použitý Boostrap toolkit, ktorý pomáha zabezpečiť responzívnosť webu. Na interaktivitu so serverom je použitý Javasript. Serverová časť sa skladá z jednoduchého webového servera schopného obsluhovať požiadavky GET a POST. Pomocou požiadavok POST sa následne vykonávajú všetky dostupné príkazy, ktoré museli byť ručne vytvorené a následne sa v kóde spracovávajú.

Stránka sa načítava z microSD karty na ktorú sú umiestnené súbory index.html a api.js. Taktiež je tu možné umiestniť jQuery a Bootstrap javascript knižnice, avšak načítavanie stránky by kvôli nízkemu výkonu serveru zabralo značne dlhú dobu, preto sú tieto nalinkované na externé servery. Na SD karte sa nachádzajú aj textové súbory obsahujúce zapísaný aktuálny stav ovládaných zariadení a intervaly automatickej prevádzky. Preto po výpadku napájania, prípadne reštarte zariadenia nabehne zariadenie do pôvodného stavu, resp. v prípade automatického režimu do stavu definovaného intervalmi.

Web stránka - HTML kód Stránka sa skladá z jednoduchého HTML kódu doplneného o Bootstrap položky. Zabezpečenie prístupu nebolo implementované kvôli využívaniu web stránky čisto na lokálnej sieti.

<!DOCTYPE html>
<html lang="en">
    <head>
	<title>Swimmingpool controller</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
        <script src="api.js"></script>
    </head>
 
    <body>
        <div class="container">
            <button type="button" class="btn btn-primary"><h1>Swimmingpool controller</h1></button>
            <div class="row" id="last_update_time"></div>			
            <div class="row" id="light_control">
                <div class="col-xs-12">
                    <h1><span name="state" class="label label-default">Light:</span></h1>
                    <ul class="nav nav-pills">
                        <li name="on" class="active"><a data-toggle="pill" onclick="trigger('light', 'on'); return false;">ON</a></li>
                        <li name="off"><a data-toggle="pill" onclick="trigger('light', 'off'); return false;"=>OFF</a></li>
                    </ul>
                </div>
            </div>
 
            <div class="row" id="pump_control">
                <div class="col-xs-12">
                    <h1><span name="state" class="label label-default">Pump:</span></h1>
                    <ul class="nav nav-pills">
                        <li name="on"><a data-toggle="pill" onclick="trigger('pump', 'on'); return false;">ON</a></li>
                        <li name="off"><a data-toggle="pill" onclick="trigger('pump', 'off'); return false;"=>OFF</a></li>
                        <li name="auto" class="active"><a data-toggle="pill" onclick="trigger('pump', 'auto'); return false;">AUTO</a></li>
                    </ul>
                </div>
            </div>
 
            <div class="row" id="ozone_control">
                <div class="col-xs-12">
                    <h1><span name="state" class="label label-default">Ozone:</span></h1>
                    <ul class="nav nav-pills">
                        <li name="on"><a data-toggle="pill" onclick="trigger('ozone', 'on'); return false;">ON</a></li>
                        <li name="off"><a data-toggle="pill" onclick="trigger('ozone', 'off'); return false;"=>OFF</a></li>
                        <li name="auto" class="active"><a data-toggle="pill" onclick="trigger('ozone', 'auto'); return false;">AUTO</a></li>
                    </ul>
                </div>
            </div>
			<p>&nbsp;</p>
            <div class="row">
                <div class="col-xs-12 col-sm-6" id="pump_intervals">
                    <h1><span class="label label-default">Pump ON intervals:</span></h1>
                    <div class="table-responsive">
                        <table class="table table-striped">
                            <thead>
                                <tr>
                                    <th>Start Hour</th>
                                    <th>Start Minute</th>
                                    <th>End Hour</th>
                                    <th>End Minute</th>
                                    <th></th>
                                </tr>
                            </thead>
                            <tbody>
                            </tbody>
                        </table>
                    </div>
                    <button type="button" class="btn btn-success" onclick="add_interval('pump');">Add <span class="glyphicon glyphicon-plus"></span></button>
                    <button type="button" class="btn btn-default" onclick="update_intervals('pump', 'set');">Update</button>
                </div>
 
                <div class="col-xs-12 col-sm-6" id="ozone_intervals">
                    <h1><span class="label label-default">Ozone ON intervals:</span></h1>
                    <div class="table-responsive">
                        <table class="table table-striped">
                            <thead>
                                <tr>
                                    <th>Start Hour</th>
                                    <th>Start Minute</th>
                                    <th>End Hour</th>
                                    <th>End Minute</th>
                                    <th></th>
                                </tr>
                            </thead>
                            <tbody>
                            </tbody>
                        </table>
                    </div>
                    <button type="button" class="btn btn-success" onclick="add_interval('ozone');">Add <span class="glyphicon glyphicon-plus"></span></button>
                    <button type="button" class="btn btn-default" onclick="update_intervals('ozone', 'set');">Update</button>
                </div>
            </div>
			<p>&nbsp;</p>
			<p>&nbsp;</p>
            <div class="row">
                <div class="form-group">
                    <div class="col-xs-6 col-md-2">
                        <label for="sel_year">Year:</label>
                        <select class="form-control" id="sel_year">
                            <option value="18">2018</option>
                            <option value="19">2019</option>
                            .
                            .
                            .
                        </select>
                    </div> 
                    <div class="col-xs-6 col-md-2">
                        <label for="sel_month">Month:</label>
                        <select class="form-control" id="sel_month">
                            <option value="1">1</option>
                            <option value="2">2</option>
                            <option value="3">3</option>
                            <option value="4">4</option>
                            .
                            .
                            .
                        </select>
                    </div> 
                    <div class="col-xs-6 col-md-1">
                        <label for="sel_date">Day:</label>
                        <select class="form-control" id="sel_date">
                            <option value="1">1</option>
                            <option value="2">2</option>
                            <option value="3">3</option>
                            .
                            .
                            .
                        </select>
                    </div> 
                    <div class="col-xs-6 col-md-2">
                        <label for="sel_day">Day of week:</label>
                        <select class="form-control" id="sel_day">
                            <option value="1">Monday</option>
                            <option value="2">Tuesday</option>
                            <option value="3">Wednesday</option>
                            <option value="4">Thursday</option>
                            <option value="5">Friday</option>
                            <option value="6">Saturday</option>
                            <option value="7">Sunday</option>
                        </select>
                    </div> 
                    <div class="col-xs-6 col-md-1">
                        <label for="sel_hour">Hours:</label>
                        <select class="form-control" id="sel_hour">
                            <option value="0">0</option>
                            <option value="1">1</option>
                            <option value="2">2</option>
                            .
                            .
                            .
                        </select>
                    </div> 
                    <div class="col-xs-6 col-md-1">
                        <label for="sel_min">Minutes:</label>
                        <select class="form-control" id="sel_min">
                            <option value="0">0</option>
                            <option value="1">1</option>
                            <option value="2">2</option>
                            .
                            .
                            .
                        </select>
                    </div> 
                    <div class="col-xs-6 col-md-1">
                        <label for="sel_sec">Seconds:</label>
                        <select class="form-control" id="sel_sec">
                            <option value="0">0</option>
                            <option value="1">1</option>
                            <option value="2">2</option>
                            .
                            .
                            .
                        </select>
                    </div>
				</div>
				<div class="row">
					&nbsp;<button type="button" class="btn btn-primary" onclick="set_clock();">Set clock</button>
				</div>					
            </div> 
        </div>
 
    </body>
</html>

Web stránka - Javascript kód

Keďže server nie je práve výkonný a pre dané použitie to ani nie je potrebné, javascript kód zabezpečuje aktualizovanie údajov na stránke každých 20 sekúnd. Nízky výkon servera spôsobuje občasné neobslúženie requestu webového klienta, preto bol do Javascriptu vytvorený timeout, kedy sa daný request príkaz znova odošle, ak predchádzajúce odoslanie do 2 sekúnd zlyhalo. Toto spôsobuje síce pomalšie aktualizovanie jednotlivých položiek stránky, avšak zabezpečí to 100% načítanie všetkých hodnôt.

var update_interval = 20; //update interval (sec) of refreshing actual light, pump and ozone state
var error_resend_timeout = 2000; // timeout (ms) for resending request if previous request failed 
 
/*    Change status of output on webpage  */
function set_status(controller, new_status) {
    if (new_status == 0) {
        $("#" + controller + "_control li[name='off']").addClass('active').siblings().removeClass('active');
        console.log("changing " + controller + " to off");
    } else if (new_status == 1) {
        $("#" + controller + "_control li[name='on']").addClass('active').siblings().removeClass('active');
        console.log("changing " + controller + " to on");
    } else if (new_status == 2) {
        $("#" + controller + "_control li[name='auto']").addClass('active').siblings().removeClass('active');
        console.log("changing " + controller + " to auto");
    }
}
 
/*    Change state of output   */
function set_state(controller, new_state) {
    if (new_state == 0) {
        $("#" + controller + "_control span[name='state']").addClass('label-danger').removeClass('label-default label-success');
        console.log("changing " + controller + " state to stop");
    } else if (new_state == 1) {
        $("#" + controller + "_control span[name='state']").addClass('label-success').removeClass('label-default label-danger');
        console.log("changing " + controller + " state to play");
    }
}
 
/*    Update actual time on website   */
function update_time(tmv) {
    var d = new Date();
    // ds1307 uses year in range 0 - 99
    d.setFullYear(tmv.year + 2000);
    // ds1307 uses month in range 1 - 12
    d.setMonth(tmv.month - 1);
    d.setDate(tmv.date);
    d.setHours(tmv.hour);
    d.setMinutes(tmv.min);
    d.setSeconds(tmv.sec);
    $("#last_update_time").html("Last update: " + d.toLocaleString());
}
 
/*    Send post with actual time to server    */
function set_clock_ajax(data_string) {
    $.ajax({
        url: "/set_clock",
        type: "POST",
        data: data_string,
        contentType: "text/plain",
        dataType: "json",
        success: function(result) {
            update_time(result.time);
			$("#sel_year").val(tmv.year);
			$("#sel_month").val(tmv.month);
			$("#sel_date").val(tmv.date);
			$("#sel_day").val(tmv.day);
			$("#sel_hour").val(tmv.hour);
			$("#sel_min").val(tmv.min);
			$("#sel_sec").val(tmv.sec);
        },
        error: function (request, status, error) {
            console.log(request);
            console.log(status);
            console.log(error);
            setTimeout(function() {set_clock_ajax(data_string);}, error_resend_timeout);
        },
    });
}
 
/*    Parse time to string    */
function set_clock() {
    var year = $("#sel_year").val().toString();
    var month = $("#sel_month").val().toString();
    var date = $("#sel_date").val().toString();
    var day = $("#sel_day").val().toString();
    var hour = $("#sel_hour").val().toString();
    var min = $("#sel_min").val().toString();
    var sec = $("#sel_sec").val().toString();
    var data_string = year + " " + month + " " + date + " " + day + " " + hour + " " + min + " " + sec;
    set_clock_ajax(data_string);
}
 
/*    Set status, state and update time via AJAX    */
function trigger_ajax(controller, action) {
    $.ajax({
        url: "/" + controller + "_" + action,
        type: "POST",
        dataType: "json",
        success: function(result) {
            set_status(controller, result.status);
            set_state(controller, result.state);
            update_time(result.time);
        },
        error: function (request, status, error) {
            console.log(request);
            console.log(status);
            console.log(error);
            setTimeout(function() {trigger_ajax(controller, action);}, error_resend_timeout);
        },
    });
}
 
/*     Trigger action    */
function trigger(controller, action) {
    console.log(controller + "_" + action);
    trigger_ajax(controller, action);
}
 
/*     Update status of all controlled devices    */
function update_all() {
    trigger("light", "status");
    trigger("pump", "status");
    trigger("ozone", "status");
}
 
/*     Maximal number of intervals in table   */
var max_intervals = {
    "pump": 10,
    "ozone": 10,
};
var intervals_count = {
    "pump": 0,
    "ozone": 0,
};
 
/*    Function for adding new interval    */
function add_interval(ctrl) {
    console.log("adding " + ctrl + " interval");
    var hour_spinbox = '<input type="number" min="0" max="23" value="0" style="max-width: 50px;" required>';
    var min_spinbox = '<input type="number" min="0" max="59" value="0" style="max-width: 50px;" required>';
 
    if (intervals_count[ctrl] < max_intervals[ctrl]) { //max input box allowed
        $("#" + ctrl + "_intervals tbody").append(
                '<tr id="' + ctrl + '_interval' + intervals_count[ctrl] + '"><th name="start_hour">' + hour_spinbox + '</th><th name="start_minute">' + min_spinbox +
                '</th><th name="end_hour">' + hour_spinbox + '</th><th name="end_minute">' + min_spinbox +
                '</th><th><button type="button" class="btn btn-danger"><span class="glyphicon glyphicon-minus"></span></button></th></tr>');
        intervals_count[ctrl]++;
        var tr_to_remove = $("#" + ctrl + "_intervals tbody tr:last");
        $(tr_to_remove).find("button").click(function () {
            console.log("removing " + ctrl + " interval");
            $(tr_to_remove).remove();
            intervals_count[ctrl]--;
        });
    }
    return tr_to_remove;
}
 
/*    Set parameters of interval - start hour+min and end hour+min    */
function set_interval(interval, values) {
    $(interval).find("[name=start_hour] input").val(values["start_hour"]);
    $(interval).find("[name=start_minute] input").val(values["start_minute"]);
    $(interval).find("[name=end_hour] input").val(values["end_hour"]);
    $(interval).find("[name=end_minute] input").val(values["end_minute"]);
}
 
/*    Set all intervals    */
function set_intervals(ctrl, intervals, tmv) {
    for (var i = 0; i < intervals.length; i++) {
        var interval = add_interval(ctrl);
        set_interval(interval, intervals[i]);
    }
    update_time(tmv);
}
 
/*    Clears interval table    */
function clear_intervals(ctrl) {
    $("#" + ctrl + "_intervals tbody").empty();
    intervals_count[ctrl] = 0;
}
 
/*     Post interval parameters to server via AJAX    */
function update_intervals_ajax(ctrl, act, data_values) {
    $.ajax({
        url: "/" + ctrl + "_" + act + "_intervals",
        type: "POST",
        data: data_values,
        contentType: "text/plain",
        dataType: "json",
        success: function(result) {
            console.log("set new values for " + ctrl + " intervals");
            clear_intervals(ctrl);
            set_intervals(ctrl, result["intervals"], result["time"]);
            update_all();
        },
        error: function (request, status, error) {
            console.log(request);
            console.log(status);
            console.log(error);
            setTimeout(function() {update_intervals_ajax(ctrl, act, data_values);}, error_resend_timeout);
        },
    });
}
 
/*    Update intervals on webpage via data from webserver    */
function update_intervals(ctrl, act) {
    console.log("update " + ctrl + " intervals");
    var data_values = "";
    $("#" + ctrl + "_intervals tbody tr").each(function(index) {
        var start_hour = $(this).find("[name=start_hour] input").val().toString();
        var start_minute = $(this).find("[name=start_minute] input").val().toString();
        var end_hour = $(this).find("[name=end_hour] input").val().toString();
        var end_minute = $(this).find("[name=end_minute] input").val().toString();
        data_values += start_hour + " ";
        data_values += start_minute + " ";
        data_values += end_hour + " ";
        data_values += end_minute;
        data_values += "\n";
    });
 
    console.log(data_values);
 
    update_intervals_ajax(ctrl, act, data_values);
}
 
update_all();
setInterval(update_all, update_interval * 1000); 
update_intervals("pump", "get");
update_intervals("ozone", "get");

Serverová časť

Zo serverovej časti sú v dokumentácii zverejnené iba niektoré zaujímavé časti kódu. Prevzaté knižnice pre ovládanie RTC DS1307 a čítanie/zápis na SD kartu, ďalej prevzatý funkčný základný kód web servera je možné nájsť na odkazoch priložených na konci dokumentácie. Kompletný kód tohoto projektu pre FRDM-K64F je na nasledujúcom odkaze: https://os.mbed.com/users/zelatina/code/projekt-mpoa-final/

/*    Set clock on DS1307 RTC device, print set and current time to serial console    */
void set_clock_cmd(std::string payload)
{
    int ret;
    struct tm_ds1307 tmv;
    std::stringstream ss(payload);
 
    ss >> tmv.year;
    ss >> tmv.month;
    ss >> tmv.date;
    ss >> tmv.day;
    ss >> tmv.hour;
    ss >> tmv.min;
    ss >> tmv.sec;    
 
    ret = my1307.settime(tmv.sec, tmv.min, tmv.hour, tmv.day, tmv.date, tmv.month, tmv.year);
    if (ret != 0) {
        uart.printf("Error: could not set time!\n");
    } else {
        uart.printf("Time set successfully: %d/%d/%d (%s) %d:%d:%d\n", tmv.year + 2000, tmv.month, tmv.date, day_to_str(tmv.day).c_str(), tmv.hour, tmv.min, tmv.sec);
    }
 
    ret = my1307.gettime(&tmv.sec, &tmv.min, &tmv.hour, &tmv.day, &tmv.date, &tmv.month, &tmv.year);
    if (ret != 0) {
        uart.printf("Error: could not get time!\n");
    } else {
        uart.printf("Current time got successfully: %d/%d/%d (%s) %d:%d:%d\n", tmv.year + 2000, tmv.month, tmv.date, day_to_str(tmv.day).c_str(), tmv.hour, tmv.min, tmv.sec);
    }
 
    std::stringstream ss_response;
    ss_response << "{\"time\": " << jsonify_time(tmv) << "}";
    std::string response = ss_response.str();
    client.send((char *)response.c_str(), strlen(response.c_str()));
}

Ukážka komunikácie na I2C zbernici pri získavaní nového času

/*    While loop for web server and automatic control thread    */ 
    while (true) {
        uart.printf("\nWait for new connection...\r\n");
        server.accept(client);
        client.set_blocking(false, 1500); // Timeout after (1.5)s
 
        uart.printf("Connection from: %s\r\n", client.get_address());
        while (true) {
            int n = client.receive(buffer, sizeof(buffer));
            if (n <= 0) break;
            uart.printf("Recieved Data: %d\r\n\r\n%.*s\r\n",n,n,buffer);
            if (n >= HTTPD_MAX_REQ_LENGTH + 1) {
                sprintf(httpHeader,"HTTP/1.1 413 Request Entity Too Large \r\nContent-Type: text\r\nConnection: Close\r\n\r\n");
                client.send(httpHeader,strlen(httpHeader));
                client.send(buffer,n);
                break;
            } else {
                buffer[n]=0;
            }
            if (!strncmp(buffer, "GET ", 4)) {
                uristr = buffer + 4;
                eou = strstr(uristr, " ");
                if (eou == NULL) {
                    sprintf(httpHeader,"HTTP/1.1 400 Bad Request \r\nContent-Type: text\r\nConnection: Close\r\n\r\n");
                    client.send(httpHeader,strlen(httpHeader));
                    client.send(buffer,n);
                } else {
                    *eou = 0;
                    get_file(uristr);
                }
            } else if (!strncmp(buffer, "POST ", 5)) {   /* Handling of own AJAX-like POST commands */
                uristr = buffer + 5;
                eou = strstr(uristr, " ");
                char *header_end = strstr(uristr, "\r\n\r\n");
                if (eou == NULL || header_end == NULL) {
                    sprintf(httpHeader,"HTTP/1.1 400 Bad Request \r\nContent-Type: text\r\nConnection: Close\r\n\r\n");
                    client.send(httpHeader,strlen(httpHeader));
                    client.send(buffer,n);
                } else {
                    *eou = 0;
                    char *payload_ptr = header_end + strlen("\r\n\r\n");
                    int payload_size = n - (payload_ptr - buffer);
                    /* +1 for terminating character (null byte - '\0' */
                    char tmp_payload[payload_size + 1];
                    memset(tmp_payload, 0, payload_size + 1);
                    memcpy(tmp_payload, payload_ptr, payload_size);
                    std::string payload_str = std::string(tmp_payload);
                    uart.printf("Payload: %s\n", tmp_payload); // print actual string sent in HTTP header
 
                    handle_post_cmd(uristr, payload_str);
                }
            }
        }
 
        client.close();
 
 
        auto_control_thread(NULL); // calling of automatic control thread (each 20sec) 
    }
/*    Handling (running) HTTP POST commands from web client    */
void run_post_cmd(char *cmd, std::string payload)
{
    if (strcmp("/light_on", cmd) == 0) {
        trigger(LIGHT, ON);
    } else if (strcmp("/light_off", cmd) == 0) {
        trigger(LIGHT, OFF);
    } else if (strcmp("/light_status", cmd) == 0) {
        trigger(LIGHT, STATUS);
    } else if (strcmp("/pump_on", cmd) == 0) {
        trigger(PUMP, ON);
    } else if (strcmp("/pump_off", cmd) == 0) {
        trigger(PUMP, OFF);
    } else if (strcmp("/pump_auto", cmd) == 0) {
        trigger(PUMP, AUTO);
    } else if (strcmp("/pump_status", cmd) == 0) {
        trigger(PUMP, STATUS);
    } else if (strcmp("/ozone_on", cmd) == 0) {
        trigger(OZONE, ON);
    } else if (strcmp("/ozone_off", cmd) == 0) {
        trigger(OZONE, OFF);
    } else if (strcmp("/ozone_auto", cmd) == 0) {
        trigger(OZONE, AUTO);
    } else if (strcmp("/ozone_status", cmd) == 0) {
        trigger(OZONE, STATUS);
    } else if (strcmp("/set_clock", cmd) == 0) {
        set_clock_cmd(payload);
    } else if (strcmp("/pump_get_intervals", cmd) == 0) {
        handle_intervals(PUMP, payload, false);
    } else if (strcmp("/pump_set_intervals", cmd) == 0) {
        handle_intervals(PUMP, payload, true);
    } else if (strcmp("/ozone_get_intervals", cmd) == 0) {
        handle_intervals(OZONE, payload, false);
    } else if (strcmp("/ozone_set_intervals", cmd) == 0) {
        handle_intervals(OZONE, payload, true);
    }
}
 
/*    Sending header to web client    */
void handle_post_cmd(char *cmd, std::string payload)
{
    uart.printf("Sending: header\n");
    sprintf(httpHeader,"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nConnection: Close\r\n\r\n");
    client.send(httpHeader,strlen(httpHeader));
 
    run_post_cmd(cmd, payload);
    uart.printf("done\n");
}

Ukážka - výpis na sériovú zbernicu pri komunikácii web serveru s klientom

Connection from: 192.168.1.7
Recieved Data: 458

POST /light_status HTTP/1.1
Host: 192.168.1.5
Connection: keep-alive
Content-Length: 0
Accept: application/json, text/javascript, */*; q=0.01
Origin: http://192.168.1.5
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36
DNT: 1
Referer: http://192.168.1.5/index.html
Accept-Encoding: gzip, deflate
Accept-Language: cs,sk;q=0.9,en;q=0.8


Payload: 
Sending: header.
Current time got successfully: 2018/1/14 (Sunday) 15:56:18
Done sending header.
Checking and executing auto intervals.
Current time got successfully: 2018/1/14 (Sunday) 15:56:20

Wait for new connection...
/*    Condition, if time value is in interval    */
bool is_in_interval(int sh, int sm, int eh, int em, int hour, int min)
{
    if ((sh < hour && hour < eh) ||
            (sh <= hour && sm <= min && (hour < eh || (hour <= eh && min <= em))) ||
            (sh < hour && hour <= eh && min <= em)) {
        return true;
    } else {
        return false;
    }
}
 
/*    Manual and auto check and trigger, using condition "is_in_interval"    */
void check_and_trigger(enum controllers ctrl, int hour, int min)
{
    if (read_mode(ctrl).compare("auto") != 0) {
        /* Not auto mode, return now */
        return;
    }
 
    bool power_on = false;
    uart.printf("Checking auto for %d\n", ctrl);
 
    std::vector<struct interval> intervals = read_intervals(ctrl);
    for (std::vector<struct interval>::iterator it = intervals.begin(); it != intervals.end(); it++) {
        int sh = it->start_hour;
        int sm = it->start_min;
        int eh = it->end_hour;
        int em = it->end_min;
 
 
        if ((sh < eh) || (sh == eh && sm <= em)) {
            /*
             * Timeline:
             * -----------------------------------
             *      ^                     ^
             *     start                 end
             */
            if (is_in_interval(sh, sm, eh, em, hour, min)) {
                power_on = true;
                break;
            }
        } else {
            /*
             * Timeline:
             * -----------------------------------
             *      ^                     ^
             *     end                   start
             */
            if (!is_in_interval(eh, em, sh, sm, hour, min)) {
                power_on = true;
                break;
            }
        }
    }
    /* Switching power for pump and ozone, depending on state detected from interval or from manual state */ 
    if (power_on) {
        write_state(ctrl, STATE_ON);
    } else {
        write_state(ctrl, STATE_OFF);
    }
}
 
/*    Thread running automatic control mode    */
void auto_control_thread(void const *arg)
{
    while (true) {
        uart.printf("Checking and executing auto intervals.\n");
 
        struct tm_ds1307 tmv;
        int ret;
 
        ret = my1307.gettime(&tmv.sec, &tmv.min, &tmv.hour, &tmv.day, &tmv.date, &tmv.month, &tmv.year);
        if (ret != 0) {
            uart.printf("Error: could not get time!\n");
        } else {
            uart.printf("Current time got successfully: %d/%d/%d (%s) %d:%d:%d\n", tmv.year + 2000, tmv.month, tmv.date, day_to_str(tmv.day).c_str(), tmv.hour, tmv.min, tmv.sec);
        }    
 
        check_and_trigger(PUMP, tmv.hour, tmv.min);
        check_and_trigger(OZONE, tmv.hour, tmv.min);
 
        break;
    }
}

Demonštračné video

Na demonštračnom videu sú predvedené funkcie zariadenia, nastavenie hodín, automatický a manuálny režim. Ako si pozornejší určite všimnú, ovládacia automatika riadenia ozónu je zapnutá, avšak podľa intervalu a aktuálneho času by generátor ozónu nemal byť zapnutý. Je to spôsobené vytvorením nového intervalu bez zaslania na server pomocou tlačítka „Update“. Automatika preto beží na pôvodne nastavené intervaly uložené na SD karte. V prípade čerpadla (Pump) bol ukážkový interval odoslaný na server a preto ovládacia automatika funguje správne.


Záver

Zadaný projekt bol dokončený do plne funkčného stavu. Funguje ako manuálne, tak aj automatické ovládanie. Automatika funguje podľa zadaných intervalov, nezávisle pre bazénové čerpadlo a pre generátor ozónu. Riadenie je zabezpečované na základe aktuálneho času, ktorý je uložený v obvode reálneho času (RTC) DS1307, tento obvod je zálohovaný, takže aj po výpadku napájania je čas stále aktuálny. Zálohované sú aj aktuálne stavy výstupov a časové intervaly pre automatickú prevádzku. Stránka je kvôli použitiu toolkitu Bootstrap responzívna a dá sa pohodlne ovládať či už na PC alebo aj na mobilných zariadeniach.

Jediný problém spôsobuje malý výkon zariadenia a z toho vyplývajúce problémy pri pripojení viacerých zariadení. Toto avšak bolo testované iba kvôli vyladeniu softwéru, reálne bude k serveru pristupovať naraz iba jedno zariadenie, čo tento server bez väčších problémov zvláda. Väčšinu problémov spôsobených dlhou odozvou sa podarilo vyriešiť opakovaným posielaním requestov a externým načítavaním jQuery a Bootstrap knižníc.

Do kódu pre K64F bolo pôvodne riadenie automatickej obsluhy implementované pomocou paralelného threadu a RTOS, avšak nepodarilo sa toto riešenie správne odladiť. Pravdepodobne chyby v knižnici pre SD kartu spôsobovali pri čítaní/ zápise a použití paralelného threadu náhodné zamrznutie procesora. Preto je táto časť kódu momentálne volaná v nekonečnej slučke popri čakaní HTTP servera na prístup.

Absentuje zabezpečenie prístupu na web stránku, čo v prípade použitia v lokálnej sieti nevadí. Pre použitie s prístupom z vonkajšej siete nie je problém do HTML kódu vložiť jednoduchý blok kódu zabezpečujúci overenie užívateľa pomocou hesla.


Odkazy na použité zdroje, prílohy

Základ webového serveru - https://os.mbed.com/teams/FRDM-K64F-Code-Share/code/HTTP_SD_Server_K64F/

Knižnica pre RTC DS1307 - https://os.mbed.com/users/harrypowers/code/DS1307/docs/c3e4da8feb10/classDS1307.html

Boostrap na web stránke - https://www.w3schools.com/bootstrap/

jQuery na web stránke - https://jquery.com/download/

Podklady pre výrobu DPS, HTML, Javascript kód, Mbed binárka - Komprimovaný súbor zip

Vlastný Mbed repozitár - https://os.mbed.com/users/zelatina/code/projekt-mpoa-final/

2017/pool-ctrl.txt · Poslední úprava: 2018/01/14 19:28 autor: Adam Bartoš