Dawid Liberda
Published © GPL3+

Walle - Home Assistant

Home automation system with built-in Alexa assistant and wireless controllers for light and central heating? It sounds great, doesn't it?

AdvancedFull instructions providedOver 6 days7,682

Things used in this project

Hardware components

Raspberry Pi 3 Model B
Raspberry Pi 3 Model B
×1
Laptop LCD Screen with controller board
×1
eGalax USB Touch Screen Panel
×1
Arduino Pro Mini 328 - 5V/16MHz
SparkFun Arduino Pro Mini 328 - 5V/16MHz
×2
SparkFun XBee Explorer USB
SparkFun XBee Explorer USB
×1
SparkFun XBee Explorer Regulated
SparkFun XBee Explorer Regulated
×2
RobotGeek Relay
RobotGeek Relay
×2
DHT11 Temperature & Humidity Sensor (4 pins)
DHT11 Temperature & Humidity Sensor (4 pins)
×1
Converter 230V -> 5V
×2
USB Keyboard and USB Mouse
×1

Story

Read more

Schematics

Walle Controller - The Light - PCB1

Walle Controller - The Light - Schematic

Walle Controller - The Light - PCB2

Walle Controller - Central Heating System - PCB1

Walle Controller - Central Heating System - Schematic

Walle Controller - Central Heating System - PCB2

Code

MainScreen.js

JavaScript
import React from 'react';

import DateTime from './DateTime';
import Events from './Events';
import Weather from './Weather';
import News from './News';
import Home from './Home';

class MainScreen extends React.Component {    
    render() {
        return(
            <div>
                <DateTime />
                <Events />
                <Weather />
                <News />
                <Home />
            </div>
        )
    }
}

export default MainScreen

DateTime.js

JavaScript
import React from 'react';
import backDateTime from './back/backDateTime';

class DateTime extends React.Component {
    constructor(props) {
        super(props);
        
        this.dateTime = new backDateTime();
        
        this.state = {
            time: this.dateTime.getTime(),
            date: this.dateTime.getDate()
        };
    }
    
    componentDidMount() {
        this.timerID = setInterval(
            () => this.tick(),
            1000
        );
    }
    
    componentWillUnmount() {
        this.dateTime.destroy();
    }
    
    tick() {
        this.dateTime.destroy();
        this.dateTime = new backDateTime();
        
        this.setState({
            time: this.dateTime.getTime(),
            date: this.dateTime.getDate()
        });
    }
    
    render() {
        return (
            <div className='dateContainer'>
                <div className='clock'>            
                    <div className='clock1'>{this.state.time.hours}:{this.state.time.minutes}</div>
                    <div className='clock2'>
                        <div>{this.state.time.seconds}</div>
                        <div>{this.state.time.divide}</div>
                    </div>
                </div>
                <div className='date'>
                    <div className='dayOfWeek'>{this.state.date.weekday}</div>
                    <div className='month'>{this.state.date.monthname} {this.state.date.day} <sup>{this.state.date.ordinal}</sup></div>
                </div>
            </div>
       )
    }
}

export default DateTime

backDateTime.js

JavaScript
function pad(num, size) { 
    return ('000000000' + num).substr(-size); 
}

function nth(day) {
    if (day > 3 && day < 21)
        return 'th';
    
    switch (day % 10) {
        case 1:
            return 'st';
        case 2:  
            return 'nd';
        case 3:  
            return 'rd';
        default: 
            return 'th';
    }
} 

class backDateTime {
    constructor() {
        this.date = new Date();
        
        this.dateInterval = setInterval(() => {
            this.date = new Date();
        }, 1000);
        
        this.format = 'en-us';
    }
    
    destroy() {
        clearInterval(this.dateInterval);
    }
    
    getTime() {
        // TO DO: check settings for a clock type (12/24)
        
        let time = {};
        
        time.hours = this.date.toLocaleTimeString(this.format, {hour12: false, hour: '2-digit'});
        time.minutes = pad(this.date.getMinutes(), 2);
        time.seconds = pad(this.date.getSeconds(), 2);
        //time.divide = this.date.toLocaleTimeString(this.format, {hour12: true, hour: '2-digit'}).slice(2);
        time.divide = '';
        time.full = time.hours + ':' + time.minutes
        
        return time;
    }
    
    getDate() {
        // TO DO: check settings for a date arrangement
        
        let date = {};
        
        date.day = this.date.getDate();
        date.year = this.date.getFullYear();
        date.month = this.date.getMonth();
        date.ordinal = nth(this.date.getDate());
        date.weekday = this.date.toLocaleDateString(this.format, {weekday: 'long'});
        date.monthname = this.date.toLocaleDateString(this.format, {month: 'long'});
        date.full = date.day + date.ordinal + ' ' + date.monthname;
        
        return date;
    }
}

export default backDateTime;

calendar.js

JavaScript
fullscreen
import React from 'react';
import ReactDOM from 'react-dom';
import MainScreen from '../main/MainScreen';
import backDateTime from '../main/back/backDateTime';
import backCalendar from '../main/back/backCalendar';

function Card(props) {
    return (
        <div className='card'>
            <div today={props.today}><span>{props.day}</span></div>
            <div className='eventsCard'>
                <div>{(props.event1) ? ` ${props.event1}` : ''}</div>
                <div>{(props.event2) ? ` ${props.event2}` : ''}</div>
                <div>{(props.event3) ? ` ${props.event3}` : ''}</div>
            </div>
        </div>
    )
}

class Calendar extends React.Component {
    constructor(props) {
        super(props);
        
        this.dateTime = new backDateTime();
        this.backCalendar = new backCalendar();
        
        this.state = {
            time: this.dateTime.getTime(),
            year: this.dateTime.getDate().year,
            month: this.dateTime.getDate().month,
            day: this.dateTime.getDate().day,
            events: []
        };
        
        this.backCalendar.getMonthEvents(this.state.year, this.state.month+1).then(res => {
            this.setState({
                events: res
            });
        });
        
        this.clickForward = this.clickForward.bind(this);
        this.clickBackward = this.clickBackward.bind(this);
        
        this.backToMainScreen = this.backToMainScreen.bind(this);
    }
    
    backToMainScreen() {
        ReactDOM.unmountComponentAtNode(document.getElementById('root'));
        ReactDOM.render(<MainScreen />, document.getElementById('root'));
    }
    
    componentDidMount() {
        this.timerDateTime = setInterval(
            () => this.tick(),
            1000
        );
    }
    
    tick() {
        this.dateTime.destroy();
        this.dateTime = new backDateTime();
        
        this.setState({
            time: this.dateTime.getTime(),
            day: this.dateTime.getDate().day 
        });
    }
    
    componentWillUnmount() {
        this.dateTime.destroy();
        clearInterval(this.timerDateTime);
    }
    
    clickForward() {
        this.setState(prevState => ({
            year: (prevState.month < 11) ? prevState.year : prevState.year+1,
            month: (prevState.month < 11) ? prevState.month+1 : 0,
            events: []
        }));
        
        setTimeout(() => {
            this.backCalendar.getMonthEvents(this.state.year, this.state.month+1).then(res => {
                this.setState({
                    events: res
                });
            });
        }, 1000);
    }
    
    clickBackward() {
        this.setState(prevState => ({
            year: (prevState.month > 0) ? prevState.year : prevState.year-1,
            month: (prevState.month > 0) ? prevState.month-1 : 11,
            events: []
        }));
        
        setTimeout(() => {
            this.backCalendar.getMonthEvents(this.state.year, this.state.month+1).then(res => {
                this.setState({
                    events: res
                });
            });
        }, 1000);
    }
    
    days() {
        let tab = [];
        
        for(let i=1; i<=this.monthLength(this.state.month, this.state.year); i++)
            tab[i] = i;
            
        return tab;
    }
    
    monthLength(m, y) {
        return new Date(y, m+1, 0).getDate();
    }
    
    monthGap(m, y) {
        let gap = new Date(y, m, 1).getDay()-1;
        return (gap >= 0) ? gap : 6;
    }
    
    monthName(month) {
        let months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
        return months[month];
    }
    
    getSpecificEvent(day, event) {
        if (this.state.events[day-1] !== undefined)
            if (this.state.events[day-1].events !== undefined)
                if (this.state.events[day-1].events[event-1] !== undefined)
                    return this.state.events[day-1].events[event-1].summary;
                else
                    return '';
            else
                return '';
        else
            return '';
    }
    
    render() {
        return (
            <div className='calendar'>
                <div className='menu' onClick={this.backToMainScreen}><i className='material-icons'>arrow_back</i><span>Back</span></div>
                <div className='head'>
                    <div className='year'>{this.state.year}</div>
                    <div className='day'>{this.state.day}</div>
                    <div className='hour'>{this.state.time.full}</div>
                </div>
                <div className="actual">
                    <button type='button' onClick={this.clickBackward}>Backward</button>
                    <div>{this.monthName(this.state.month)}</div>
                    <button type='button' onClick={this.clickForward}>Forward</button>
                </div>
                <div className='calendarGrid'>
                    <div className='daysOfWeek'>
                        <div>Mon</div>
                        <div>Tue</div>
                        <div>Wed</div>
                        <div>Thu</div>
                        <div>Fri</div>
                        <div>Sat</div>
                        <div>Sun</div>
                    </div>
                    {[...Array(this.monthGap(this.state.month,this.state.year))].map((e,i) => <Card day='&nbsp;' key={e} />)}
                    {this.days().map((v,i) => <Card day={v} today={(v === this.state.day && new Date().getMonth() === this.state.month && new Date().getFullYear() === this.state.year) ? 'y' : ''} event1={this.getSpecificEvent(v, 1)} event2={this.getSpecificEvent(v, 2)} event3={this.getSpecificEvent(v, 3)} key={`${this.state.month}_${v}`} /> )}
                </div>
            </div>
        )
    }
}

export default Calendar

Events.js

JavaScript
import React from 'react';
import ReactDOM from 'react-dom';
import Calendar from '../fullscreen/calendar';
import backCalendar from './back/backCalendar';

class Events extends React.Component {
    constructor(props) {
        super(props);
        
        this.backCalendar = new backCalendar();
        
        this.state = {
            upcoming: []
        };
        
        this.backCalendar.getUpcomingEvents().then(res => {
            this.setState({
                upcoming: (res.quantity) ? res.events : []
            });
        });
        
        this.goToCalendarComponent = this.goToCalendarComponent.bind(this);
    }
    
    goToCalendarComponent() {
        ReactDOM.unmountComponentAtNode(document.getElementById('root'));
        ReactDOM.render(<Calendar />, document.getElementById('root'));
    }
    
    getSummaryOfEvent(event) {
        if (this.state.upcoming !== undefined)
            if (this.state.upcoming[event] !== undefined)
                return this.state.upcoming[event].summary;
            else
                return '';
        else
            return '';
    }
    
    getStartOfEvent(event) {
        if (this.state.upcoming !== undefined)
            if (this.state.upcoming[event] !== undefined)
                if (this.state.upcoming[event].start !== undefined)
                    if (this.state.upcoming[event].start.time !== undefined)
                        return this.state.upcoming[event].start.time.hours + ':' + this.state.upcoming[event].start.time.minutes;
                    else
                        return '';
                else
                    return '';
            else
                return '';
        else
            return '';
    }
    
    getEndOfEvent(event) {
        if (this.state.upcoming !== undefined)
            if (this.state.upcoming[event] !== undefined)
                if (this.state.upcoming[event].end !== undefined)
                    if (this.state.upcoming[event].end.time !== undefined)
                        return this.state.upcoming[event].end.time.hours + ':' + this.state.upcoming[event].end.time.minutes;
                    else
                        return '';
                else
                    return '';
            else
                return '';
        else
            return '';
    }
    
    render() {
        return(
            <div className='events' onClick={this.goToCalendarComponent}>
                <div>Upcoming events</div>
                <div className='eventsList'>
                    {this.state.upcoming.map((e, i) =>
                        <div key={i}>{this.getStartOfEvent(i)} - {this.getEndOfEvent(i)} {this.getSummaryOfEvent(i)}</div>
                    )}
                </div>
            </div>
        )
    }
}

export default Events

backCalendar.js

JavaScript
class backCalendar {    
    readClientSecret() {
        return new Promise(resolve => {
            let fs = window.require('fs');
            
            fs.readFile('/walle/client_secret.json', (err, content) => {
                if (err) {
                    console.log('Error loading client secret file');
                    console.log(err);
                    return false;
                }
            
                resolve(JSON.parse(content));
            });
        });
    }
    
    readAccessToken() {
        return new Promise(resolve => {
            let fs = window.require('fs');
            
            fs.readFile('/walle/access_token.json', (err, content) => {
                if (err) {
                    console.log('Error loading access token file');
                    console.log(err);
                    return false;
                }
            
                resolve(JSON.parse(content));
            });
        });
    }
    
    async getUpcomingEvents() {
        let client_secret = await this.readClientSecret();
        let access_token = await this.readAccessToken();
        
        let date = new Date();
        
        let events = await this.getSpecificEvents(client_secret, access_token, date.getFullYear(), date.getMonth()+1, date.getDate(), 'primary', false, 3);
        
        return events;
    }
    
    async getMonthEvents(year, month) {
        Date.prototype.monthDays = function() {
            var d = new Date(this.getFullYear(), this.getMonth()+1, 0);
            return d.getDate();
        }
        
        let client_secret = await this.readClientSecret();
        let access_token = await this.readAccessToken();
        
        let date = new Date(year, month-1);
        let monthDays = date.monthDays();
        
        let events = [];
        
        for (var i=1; i<=monthDays; i++) {
            let dayEvents = await this.getSpecificEvents(client_secret, access_token, year, month, i, 'primary', true, 3);
            events.push(dayEvents);
        }
        
        return events;
    }
    
    async getDayEvents(year, month, day) {
        let client_secret = await this.readClientSecret();
        let access_token = await this.readAccessToken();
        
        let events = await this.getSpecificEvents(client_secret, access_token, year, month, day, 'primary', true, 100);
        
        return events;
    }
    
    async getSpecificEvents(credentials, token, year, month, day, calendarId, allDay, resultsLimit=20) {
        var google = window.require('googleapis');
        var googleAuth = window.require('google-auth-library');
        
        let clientSecret = credentials.installed.client_secret;
        let clientId = credentials.installed.client_id;
        let redirectUrl = credentials.installed.redirect_uris[0];
        
        let auth = new googleAuth();
        let oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUrl);
        oauth2Client.credentials = token;
        
        return new Promise(resolve => {
            var calendar = google.calendar('v3');
            
            let date = new Date();
            let timezone = -1 * date.getTimezoneOffset();
            
            var specific_start;
            
            if (allDay)
                specific_start = new Date(year, month-1, day, 0, 0+timezone, 0);
            else
                specific_start = new Date(year, month-1, day, date.getHours(), date.getMinutes()+timezone, date.getSeconds());
        
            var specific_end = new Date(year, month-1, day, 23, 59, 59);
            
            if (calendarId !== 'primary')
                calendarId = encodeURIComponent(calendarId);
            
            calendar.events.list({
                auth: oauth2Client,
                calendarId: 'primary'
            }, {
                qs: {
                    timeMin: specific_start.toISOString(),
                    timeMax: specific_end.toISOString(),
                    maxResults: 100,
                    singleEvents: true,
                    orderBy: 'startTime'
                }
            }, (err, response) => {
                if (err) {
                    console.log('Error reading events from Google Calendar:');
                    console.log(err);
                    return false;
                }
                
                let events = response.items;
                
                let calendarEvents = {};
                
                calendarEvents.requestYear = year;
                calendarEvents.requestMonth = month;
                calendarEvents.requestDay = day;
                calendarEvents.quantity = events.length;
                
                if (!calendarEvents.quantity) {
                    resolve(calendarEvents);
                    return;
                }
                
                calendarEvents.events = [];
                
                for (var i=0; i<calendarEvents.quantity; i++) {
                    calendarEvents.events.push({
                        summary: events[i].summary,
                        creator_email: events[i].creator.email,
                        creator_name: events[i].creator.displayName,
                        start: {
                            date: {
                                year: events[i].start.dateTime.substr(0, 4),
                                month: events[i].start.dateTime.substr(5, 2),
                                day: events[i].start.dateTime.substr(8, 2)
                            },
                            time: {
                                hours: events[i].start.dateTime.substr(11, 2),
                                minutes: events[i].start.dateTime.substr(14, 2),
                                seconds: events[i].start.dateTime.substr(17, 2)
                            }
                        },
                        end: {
                            date: {
                                year: events[i].end.dateTime.substr(0, 4),
                                month: events[i].end.dateTime.substr(5, 2),
                                day: events[i].end.dateTime.substr(8, 2)
                            },
                            time: {
                                hours: events[i].end.dateTime.substr(11, 2),
                                minutes: events[i].end.dateTime.substr(14, 2),
                                seconds: events[i].end.dateTime.substr(17, 2)
                            }
                        }
                    });
                }
                
                resolve(calendarEvents);
            });
        });
    }
}

export default backCalendar;

weather.js

JavaScript
fullscreen
import React from 'react';
import ReactDOM from 'react-dom';
import MainScreen from '../main/MainScreen';
import backWeather from '../main/back/backWeather';
import backDateTime from '../main/back/backDateTime';

class WeatherFS extends React.Component {
    constructor(props) {
        super(props);
        
        this.weather = new backWeather();
        this.dateTime = new backDateTime();
        
        this.state = {
            today: {img: './icons/weather/mostly_cloudy.svg'},
            forecast: [],
            hourly: [],
            date: this.dateTime.getDate(),
            time: this.dateTime.getTime()
        }
        
        this.weather.getToday().then(res => {
           this.setState({
               today: res
           }); 
        });
        
        this.weather.getForecast().then(res => {
            this.setState({
                forecast: res
            });
        });
        
        this.weather.getHourly().then(res => {
            this.setState({
                hourly: res
            });
        });
        
        this.backToMainScreen = this.backToMainScreen.bind(this);
    }
    
    backToMainScreen() {
        ReactDOM.unmountComponentAtNode(document.getElementById('root'));
        ReactDOM.render(<MainScreen />, document.getElementById('root'));
    }
    
    componentDidMount() {
        this.timerDateTime = setInterval(() => {
            this.dateTime.destroy();
            this.dateTime = new backDateTime();
            
            this.setState({
                date: this.dateTime.getDate(),
                time: this.dateTime.getTime()
            });
        }, 1000);
        
        this.timerWeather = setInterval(() => {
            this.weather = new backWeather();
            
            this.weather.getToday().then(res => {
               this.setState({
                   today: res
               });
            });

            this.weather.getForecast().then(res => {
                this.setState({
                    forecast: res
                });
            });

            this.weather.getHourly().then(res => {
                this.setState({
                    hourly: res
                });
            });
        }, 1000*5)
    }
    
    componentWillUnmount() {
        clearInterval(this.timerDateTime);
        clearInterval(this.timerWeather);

        this.dateTime.destroy();
    }
    
    render() {
        return (
            <div className='weatherFS' onClick={this.backToMainScreen}>
                <div className='menu'><i className='material-icons'>arrow_back</i><span>Back</span></div>
                <div className='today'>
                    <div className='city'>{this.state.today.loc_city}</div>
                    <div className='todayDate'>{this.state.date.weekday},{' '}{this.state.date.full} {this.state.time.full}</div>
                    <div className='todayCond'><img className='icon' src={require(`${this.state.today.img}`)} alt='icon' /><span className='temp'>{this.state.today.temp} {this.state.today.temp_unit}</span></div>
                    <div className='wCondition'>{this.state.today.condition}</div>
                    <div className='feel'>Feels like {this.state.today.feelslike} {this.state.today.temp_unit}</div>
                </div>
                <div className='hourly'>
                    {this.state.hourly.map((e,i)=>{
                        return (
                            <div className={`hourly${i}`} key={e.time.slice(0,2)}>
                                <div>{e.time}</div>
                                <img className='icon' src={require(`${e.condition_img}`)} alt='icon' />
                                <div>{e.temp}{e.temp_unit}</div>
                            </div>
                        )
                    })} 
                </div>
                <div className='extended'>
                    <div>
                        <span>Humidity</span>
                        <div className='value'>{this.state.today.humidity}</div>
                    </div>
                    <div>
                        <span>Wind</span>
                        <div className='value'>{this.state.today.wind} {this.state.today.wind_unit}</div>
                    </div>
                    <div>
                        <span>UV Index</span>
                        <div className='value'>0</div>
                    </div>
                    <div>
                        <span>Pressure</span>
                        <div className='value'>{this.state.today.pressure} {this.state.today.pressure_unit}</div>
                    </div>
                </div>
                <div className='forecast'>             
                    {this.state.forecast.map((e,i)=>{
                        return (
                            <div className={`forecast${i}`} key={e.weekday.slice(0,3)}>
                                <div className='weekday'>{e.weekday}</div>
                                <img className='icon' src={require(`${e.condition_img}`)} alt='icon' />
                                <div className='humid'>{e.humidity_ave}</div>
                                <div className='temp_high'><i className='material-icons'>keyboard_arrow_up</i>{e.temp_high} {this.state.today.temp_unit}</div>
                                <div className='temp_low'><i className='material-icons'>keyboard_arrow_down</i>{e.temp_low} {this.state.today.temp_unit}</div>
                            </div>
                        )
                    })}
                </div>
            </div>
        )
    }
}

export default WeatherFS

Weather.js

JavaScript
import React from 'react';
import ReactDOM from 'react-dom';
import WeatherFS from '../fullscreen/weather';
import backWeather from './back/backWeather';

class Weather extends React.Component {
    constructor(props) {
        super(props);
        
        this.state = {
            today: {img: './icons/weather/mostly_cloudy.svg'},
            day1: {condition_img: './icons/weather/mostly_cloudy.svg'},
            day2: {condition_img: './icons/weather/mostly_cloudy.svg'},
            day3: {condition_img: './icons/weather/mostly_cloudy.svg'}
        }
        
        this.weather = new backWeather();
        
        this.weather.getToday().then(res => {
            this.setState({
                today: res
            });
        });
        
        this.weather.getForecast().then(res => {
            this.setState({
                day1: res[1],
                day2: res[2],
                day3: res[3]
            });
        });
        
        this.goToWeatherComponent = this.goToWeatherComponent.bind(this);
    }
    
    goToWeatherComponent() {
        ReactDOM.unmountComponentAtNode(document.getElementById('root'));
        ReactDOM.render(<WeatherFS />, document.getElementById('root'));
    }
    
    componentDidMount() {
        this.timerWeather = setInterval(() => {
            this.weather = new backWeather();
            
            this.weather.getToday().then(res => {
                this.setState({
                    today: res
                });
            });
            
            this.weather.getForecast().then(res => {
                this.setState({
                    day1: res[1],
                    day2: res[2],
                    day3: res[3]
                });
            });
        }, 1000*5)
    }
    
    componentWillUnmount() {
        clearInterval(this.timerWeather);
    }
    
    getObservationDate() {
        if (this.state.today !== undefined)
            if (this.state.today.observation !== undefined)
                return this.state.today.observation.date.full;
            else
                return '';
        else
            return '';
    }
    
    getObservationTime() {
        if (this.state.today !== undefined)
            if (this.state.today.observation !== undefined)
                return this.state.today.observation.time.hours + ':' + this.state.today.observation.time.minutes;
            else
                return '';
        else
            return '';
    }
    
    render() {
        return(
            <div className='weather' onClick={this.goToWeatherComponent}>
                <div className='today'>
                    <div className='left'>
                        <img className='icon' src={require(`${this.state.today.img}`)} alt='icon' />
                        <div className='temp'>{this.state.today.temp} {this.state.today.temp_unit}</div>
                    </div>
                    <div className='right'>
                        <div className='condition'>{this.state.today.condition}</div>
                        <div className='feels'>Feels like {this.state.today.feelslike} {this.state.today.temp_unit}</div>
                        <div className='wind'>Wind {this.state.today.wind} {this.state.today.wind_unit}</div>
                    </div>
                </div>
                <div className='fore'>
                    <div className='day1'>
                        <div className='weekday'>{this.state.day1.weekday}</div>
                        <img className='icon' src={require(`${this.state.day1.condition_img}`)} alt='icon'/>
                        <div className='temp'>{this.state.day1.temp_high} {this.state.day1.temp_unit}</div>
                    </div>
                    <div className='day2'>
                        <div className='weekday'>{this.state.day2.weekday}</div>
                        <img className='icon' src={require(`${this.state.day2.condition_img}`)} alt='icon'/>
                        <div className='temp'>{this.state.day2.temp_high} {this.state.day2.temp_unit}</div>
                    </div>
                    <div className='day3'>
                        <div className='weekday'>{this.state.day3.weekday}</div>
                        <img className='icon' src={require(`${this.state.day3.condition_img}`)} alt='icon'/>
                        <div className='temp'>{this.state.day3.temp_high} {this.state.day3.temp_unit}</div>
                    </div>
                </div>
                <div className='location'>
                    <i className='material-icons'>location_on</i><span>{this.state.today.loc_city}, {this.state.today.loc_country}</span>
                </div>
                <div className='update'>
                    Last updated on {this.getObservationDate()} at {this.getObservationTime()}
                </div>
            </div>
        )
    }
}

export default Weather

backWeather.js

JavaScript
function pad(num, size) { 
    return ('000000000' + num).substr(-size); 
}

function nth(day) {
    if (day > 3 && day < 21)
        return 'th';
    
    switch (day % 10) {
        case 1:
            return 'st';
        case 2:  
            return 'nd';
        case 3:  
            return 'rd';
        default: 
            return 'th';
    }
}

function monthname(day) {
    let months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
    return months[day-1];
}

function convertTo24Hour(time) {
    var hours = parseInt(time.substr(0, 2));

    if(time.indexOf('AM') !== -1 && hours === 12)
        time = time.replace('12', '0');

    if(time.indexOf('PM') !== -1 && hours < 12)
        time = time.replace(hours, (hours + 12));

    return time.replace(/(AM|PM)/, '').trim();
}

class backWeather {
    constructor() {
        this.apiKey = 'your_api_key';
        this.zmw = '00000.375.12660';
    }
    
    getConditionImg(condition) {
        return './icons/weather/mostly_cloudy.svg';
    }
    
    async getToday() {        
        let body = await new Promise(resolve => {
            fetch('http://api.wunderground.com/api/' + this.apiKey + '/conditions/q/zmw:' + this.zmw + '.json')
                .then(res => {
                    resolve(res.json());
                })
        });
        
        let today = {};
        
        today.loc_city = body.current_observation.display_location.city;
        today.loc_state = body.current_observation.display_location.state;
        today.loc_country = body.current_observation.display_location.state_name;
        
        // TO DO: CONVERT UTC TO LOCAL TIME
        
        let date = new Date(body.current_observation.observation_epoch*1000);
        
        today.observation = {};
        
        today.observation.time = {};
        today.observation.time.hours = date.toLocaleTimeString('en-us', {hour12: false, hour: '2-digit'});
        today.observation.time.minutes = pad(date.getMinutes(), 2);
        today.observation.time.seconds = pad(date.getSeconds(), 2);
        today.observation.time.divide = date.toLocaleTimeString('en-us', {hour12: true, hour: '2-digit'}).slice(2);
        
        today.observation.date = {};
        today.observation.date.day = date.getDate();
        today.observation.date.ordinal = nth(today.observation.date.day);
        today.observation.date.weekday = date.toLocaleDateString('en-us', {weekday: 'long'});
        today.observation.date.monthname = monthname(date.getMonth()+1);
        today.observation.date.full = today.observation.date.day + today.observation.date.ordinal + ' ' + today.observation.date.monthname;
        
        today.temp = Math.round(body.current_observation.temp_c);
        today.feelslike = Math.round(body.current_observation.feelslike_c);
        today.temp_unit = 'C';
        today.humidity = body.current_observation.relative_humidity;
        today.wind = body.current_observation.wind_kph;
        today.wind_unit = 'kph';
        today.wind_dir = body.current_observation.wind_dir;
        today.pressure = body.current_observation.pressure_mb;
        today.pressure_unit = 'hPa';
        
        today.condition = body.current_observation.weather;
        today.img = this.getConditionImg(today.condition);
        
        body = await new Promise(resolve => {
            fetch('http://api.wunderground.com/api/' + this.apiKey + '/forecast/q/zmw:' + this.zmw + '.json')
                .then(res => {
                    resolve(res.json());
                })
        });
        
        today.temp_high = body.forecast.simpleforecast.forecastday[0].high.celsius;
        today.temp_low = body.forecast.simpleforecast.forecastday[0].low.celsius;
        
        return today;
    }
    
    async getHourly() {        
        let body = await new Promise(resolve => {
            fetch('http://api.wunderground.com/api/' + this.apiKey + '/hourly/q/zmw:' + this.zmw + '.json')
                .then(res => {
                    resolve(res.json());
                })
        });
        
        let hourly = [];
        
        for (var i=0; i<6; i++) {
            hourly.push({
                'time': convertTo24Hour(body.hourly_forecast[i].FCTTIME.civil),
                'temp': body.hourly_forecast[i].temp.metric,
                'temp_unit': 'C',
                'condition': body.hourly_forecast[i].condition,
                'condition_img': this.getConditionImg(body.hourly_forecast[i].condition)
            });
        }
        
        return hourly;
    }
    
    async getForecast() {        
        let body = await new Promise(resolve => {
            fetch('http://api.wunderground.com/api/' + this.apiKey + '/forecast10day/q/zmw:' + this.zmw + '.json')
                .then(res => {
                    resolve(res.json());
                })
        });
        
        let forecast = [];
        
        for (var i=0; i<9; i++) {
            forecast.push({
                'weekday': body.forecast.simpleforecast.forecastday[i].date.weekday,
                'temp_high': body.forecast.simpleforecast.forecastday[i].high.celsius,
                'temp_low': body.forecast.simpleforecast.forecastday[i].low.celsius,
                'temp_unit': 'C',
                'condition': body.forecast.simpleforecast.forecastday[i].conditions,
                'condition_img': this.getConditionImg(body.forecast.simpleforecast.forecastday[i].conditions),
                'wind_ave': body.forecast.simpleforecast.forecastday[i].avewind.kph,
                'wind_dir': body.forecast.simpleforecast.forecastday[i].avewind.dir,
                'wind_unit': 'kph',
                'humidity_ave': body.forecast.simpleforecast.forecastday[i].avehumidity + '%',
                'pop': body.forecast.simpleforecast.forecastday[i].pop,
                'rain_day': body.forecast.simpleforecast.forecastday[i].qpf_allday.mm,
                'rain_unit': 'mm',
                'snow_day': body.forecast.simpleforecast.forecastday[i].snow_allday.mm,
                'snow_unit': 'mm'
            });
        }
        
        return forecast;
    }
}

export default backWeather;

Home.js

JavaScript
import React from 'react';

import backHome from './back/backHome';

class Home extends React.Component {
    constructor() {
        super();
        
        this.backHome = new backHome();
        
        this.state = {
            light: 'false',
            centralHeating: 'false',
            currentTemp: 0,
            targetTemp: 0
        };
        
        this.switchLight = this.switchLight.bind(this);
        this.switchCentralHeating = this.switchCentralHeating.bind(this);
    }
    
    componentDidMount() {
        this.timerLightStatus = setInterval(
            () => this.checkLight(),
            1000
        );
        
        this.timerCentralHeatingStatus = setInterval(
            () => this.checkCentralHeating(),
            1000
        );
        
        this.timerCurrentTemp = setInterval(
            () => this.getCurrentTemp(),
            1000
        );
        
        this.timerTargetTemp = setInterval(
            () => this.getTargetTemp(),
            1000
        );
    }
    
    componentWillUnmount() {
        clearInterval(this.timerLightStatus);
        clearInterval(this.timerCentralHeatingStatus);
        clearInterval(this.timerCurrentTemp);
        clearInterval(this.timerTargetTemp);
    }
    
    checkLight() {
        this.backHome.checkLight().then(res => {
            this.setState({
               light: res 
            });
        });
    }

    checkCentralHeating() {
        this.backHome.checkCentralHeating().then(res => {
            this.setState({
               centralHeating: res 
            });
        });
    }
    
    getCurrentTemp() {
        this.backHome.readCurrentTemp().then(res => {
            this.setState({
                currentTemp: res 
            });
        });
    }
    
    getTargetTemp() {
        this.backHome.readTargetTemp().then(res => {
            this.setState({
                targetTemp: res 
            });
        });
    }
    
    switchLight() {
        if (this.state.light === 'false')
            this.backHome.lightOn().then(res => {
                if (res === true)
                    this.setState({
                         light: 'true'
                    });
            });
        else if (this.state.light === 'true')
            this.backHome.lightOff().then(res => {
                if (res === true)
                    this.setState({
                         light: 'false'
                    });
            });
    }
    
    switchCentralHeating() {
        if (this.state.centralHeating === 'false')
            this.backHome.centralHeatingOn().then(res => {
                if (res === true)
                    this.setState({
                         centralHeating: 'true'
                    });
            });
        else if (this.state.centralHeating === 'true')
            this.backHome.centralHeatingOff().then(res => {
                if (res === true)
                    this.setState({
                         centralHeating: 'false'
                    });
            });
    }
    
    render() {
        return(
            <div className='home'>
                <div>Home automation</div>
                <div className='lights' bg={this.state.light} onClick={this.switchLight}><div><i className="material-icons">lightbulb_outline</i></div><div>Light</div></div>
                <div className='heating'>
                    <div className='status'>
                        Automatic control
                        <div className='temps'>
                            <div className='current'>Current temp: {this.state.currentTemp} C</div>
                            <div className='target'>Target temp: {this.state.targetTemp} C</div>
                        </div>
                    </div>
                    <div className='heatingBtn' bg={this.state.centralHeating} onClick={this.switchCentralHeating}>
                        <i className="material-icons">whatshot</i>
                        <div>Central Heating</div>
                    </div>
                </div>
            </div>
        )
    }
}

export default Home

backHome.js

JavaScript
class backHome {
    constructor() {
        this.serial = '/dev/ttyUSB0';
    }
    
    connectWithController(message, serial=this.serial) {
        return new Promise(resolve => {
            let PythonShell = window.require('python-shell');

            let options = {
                mode: 'text',
                pythonPath: '/usb/bin/python',
                scriptPath: '/walle/',
                args: [serial, message]
            };

            PythonShell.run('walle_controller.py', options, (err, results) => {
                if (err) throw err;
                resolve(results[0]);
            });
        });
    }
    
    saveStatusInFile(file, status) {
        let fs = window.require('fs');
        
        fs.writeFile(file, status, (err) => {
            if (err) {
                console.log('Error writing status to file ' + file);
                return false;
            }
        })
    }
    
    readStatusFromFile(file) {
        return new Promise(resolve => {
            let fs = window.require('fs');
            
            fs.readFile(file, (err, content) => {
                if (err) {
                    console.log('Error reading status from file ' + file);
                    return false;
                }
            
                resolve(content.toString());
            });
        });
    }
    
    async checkLight() {
        let body = await this.readStatusFromFile('/walle/lightStatus.txt');
        
        return body;
    }
    
    async lightOn() {
        let body = await this.connectWithController('M1H');
        
        if (body === 'True') {
            this.saveStatusInFile('/walle/lightStatus.txt', 'true');
            return true;
        }
        else if (body === 'False') {
            this.saveStatusInFile('/walle/lightStatus.txt', 'false');
            return false;
        }
    }
    
    async lightOff() {
        let body = await this.connectWithController('M1L');
        
        if (body === 'True') {
            this.saveStatusInFile('/walle/lightStatus.txt', 'false');
            return true;
        }
        else if (body === 'False') {
            this.saveStatusInFile('/walle/lightStatus.txt', 'true');
            return false;
        }
    }
    
    async checkCentralHeating() {
        let body = await this.readStatusFromFile('/walle/centralHeatingStatus.txt');
        
        return body;
    }
    
    async centralHeatingOn() {
        let body = await this.connectWithController('M2H');
        
        if (body === 'True') {
            this.saveStatusInFile('/walle/centralHeatingStatus.txt', 'true');
            return true;
        }
        else if (body === 'False') {
            this.saveStatusInFile('/walle/centralHeatingStatus.txt', 'false');
            return false;
        }
    }
    
    async centralHeatingOff() {
        let body = await this.connectWithController('M2L');
        
        if (body === 'True') {
            this.saveStatusInFile('/walle/centralHeatingStatus.txt', 'false');
            return true;
        }
        else if (body === 'False') {
            this.saveStatusInFile('/walle/centralHeatingStatus.txt', 'true');
            return false;
        }
    }
    
    async readCurrentTemp() {
        let body = await new Promise(resolve => {
            let fs = window.require('fs');
            
            fs.readFile('/walle/currentTemp.txt', (err, content) => {
                if (err) {
                    console.log('Error reading status from file currentTemp.txt');
                    return false;
                }
            
                resolve(parseInt(content.toString()));
            });
        });
        
        return body;
    }
    
    async readTargetTemp() {
        let body = await new Promise(resolve => {
            let fs = window.require('fs');
            
            fs.readFile('/walle/targetTemp.txt', (err, content) => {
                if (err) {
                    console.log('Error reading status from file targetTemp.txt');
                    return false;
                }
            
                resolve(parseInt(content.toString()));
            });
        });
        
        return body;
    }
}

export default backHome;

News.js

JavaScript
import React from 'react';
import backNews from './back/backNews';

class News extends React.Component {
    constructor(props) {
        super(props);
        
        this.state = {
            news: {}
        };
        
        this.news = new backNews();
        
        this.news.getLatest().then(res => {
            this.setState({
                news: res
            });
        });
    }
    
    componentDidMount() {
        this.timerNews = setInterval(() => {
            this.news = new backNews();
            
            this.news.getLatest().then(res => {
                this.setState({
                    news: res
                });
            });
        }, 1000*5)
    }
    
    componentWillUnmount() {
        clearInterval(this.timerNews);
    }
    
    render() {
        return(
            <div className='news'>
                <div className='source'>{this.state.news.source} latest news</div>
                <div className='title'>{this.state.news.title}</div>
                <div className='description'>{this.state.news.desc}</div>
            </div>
        )
    }
}

export default News

backNews.js

JavaScript
function nth(day) {
    if (day > 3 && day < 21)
        return 'th';
    
    switch (day % 10) {
        case 1:
            return 'st';
        case 2:  
            return 'nd';
        case 3:  
            return 'rd';
        default: 
            return 'th';
    }
}

function monthname(day) {
    let months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
    return months[day-1];
}

class backNews {
    constructor() {
        this.source = 'the-verge';
        this.apiKey = 'your_api_key';
    }
    
    getSourceOfNews(source) {
        if (source === 'the-verge')
            return 'The Verge';
    }
    
    async getLatest() {        
        let body = await new Promise(resolve => {
            fetch('https://newsapi.org/v1/articles?source=' + this.source + '&sortBy=latest&apiKey=' + this.apiKey)
                .then(res => {
                    resolve(res.json());
                })
        });
        
        let latest = {};

        let art_num = 9;
        
        latest.source = this.getSourceOfNews(this.source);
        latest.author = body.articles[art_num].author;
        latest.title = body.articles[art_num].title;
        latest.desc = body.articles[art_num].description;
        latest.url = body.articles[art_num].url;
        
        // TO DO: CONVERT UTC TO LOCAL TIME
        latest.publishedAt = {};
        latest.publishedAt.day = body.articles[art_num].publishedAt.substr(8, 2);
        latest.publishedAt.ordinal = nth(latest.publishedAt.day);
        latest.publishedAt.monthname = monthname(body.articles[art_num].publishedAt.substr(5, 2));
        latest.publishedAt.year = body.articles[art_num].publishedAt.substr(0, 4);
        latest.publishedAt.hours = body.articles[art_num].publishedAt.substr(11, 2);
        latest.publishedAt.minutes = body.articles[art_num].publishedAt.substr(14, 2);
        latest.publishedAt.seconds = body.articles[art_num].publishedAt.substr(17, 2);
        
        return latest;
    }
}

export default backNews;

Credits

Dawid Liberda

Dawid Liberda

1 project • 3 followers
👨‍💻CEO & Founder of Walle, early-stage startup 🎓Computer Science student at AGH UST

Comments