├── README.md ├── blynk ├── config.js └── index.js ├── esp8266 └── WeatherHub │ ├── Common.cpp │ ├── Common.h │ ├── DisplayBase.cpp │ ├── DisplayBase.h │ ├── DisplayConfig.h │ ├── DisplayEntity.cpp │ ├── DisplayEntity.h │ ├── DisplayLCDI2C.cpp │ ├── DisplayLCDI2C.h │ ├── DisplayOLED.cpp │ ├── DisplayOLED.h │ ├── DisplayOLEDFont.h │ ├── JsonConfig.cpp │ ├── JsonConfig.h │ ├── SensorAM2301.cpp │ ├── SensorAM2301.h │ ├── SensorBH1750.cpp │ ├── SensorBH1750.h │ ├── SensorBase.cpp │ ├── SensorBase.h │ ├── SensorDHT11.cpp │ ├── SensorDHT11.h │ ├── SensorDHT22.cpp │ ├── SensorDHT22.h │ ├── SensorEntity.cpp │ ├── SensorEntity.h │ ├── SensorOutputData.cpp │ ├── SensorOutputData.h │ ├── SensorSHT21.cpp │ ├── SensorSHT21.h │ ├── WeatherHub.ino │ ├── WebServer.cpp │ └── WebServer.h ├── nodejs ├── commandHelper.js ├── config.js ├── databaseHelper.js ├── modules │ ├── moduleGateway.js │ ├── moduleGeneric.js │ ├── modulePlug.js │ ├── moduleTemperature.js │ └── modulesFactory.js ├── package-lock.json ├── package.json ├── sensor.js └── serverHelper.js ├── sql ├── ModuleSensor.sql ├── SensorData.sql ├── WeatherData.sql ├── WeatherModule.sql ├── WeatherSensor.sql └── WeatherUser.sql ├── www ├── add.php ├── aqara.php ├── charts.php ├── datas.php ├── dbconfig.php ├── emailConfig.php ├── favicon.ico ├── fonts │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ ├── fontawesome-webfont.woff2 │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 ├── images │ ├── bullet-black-alt.png │ ├── bullet-green-alt.png │ ├── bullet-orange-alt.png │ ├── bullet-purple-alt.png │ ├── bullet-red-alt.png │ ├── bullet-yellow-alt.png │ └── home-icon.png ├── include │ ├── common.php │ ├── header.php │ ├── highcharts.php │ └── menu.php ├── index.php ├── lib │ ├── PHPMailer │ │ ├── PHPMailerAutoload.php │ │ ├── class.phpmailer.php │ │ ├── class.phpmaileroauth.php │ │ ├── class.phpmaileroauthgoogle.php │ │ ├── class.pop3.php │ │ ├── class.smtp.php │ │ └── get_oauth_token.php │ └── password.php ├── login.php ├── logout.php ├── queryData.php ├── queryUserData.php ├── register.php ├── requester.php ├── restorePassword.php ├── scripts │ ├── CommonUtil.js │ ├── FormatUtil.js │ ├── bootstrap.js │ ├── bootstrap.min.js │ ├── chartsController.js │ ├── datasController.js │ ├── dateFormat.js │ ├── dateFormat.min.js │ ├── indexController.js │ ├── jquery.dropdown.min.js │ ├── kalmanFilter.js │ ├── loginController.js │ ├── queryHelper.js │ ├── registerController.js │ ├── restoreController.js │ ├── setupController.js │ └── userController.js ├── setup.php ├── siteConfig.php ├── styles │ ├── awesome-bootstrap-checkbox.css │ ├── bootstrap-theme.css │ ├── bootstrap-theme.css.map │ ├── bootstrap-theme.min.css │ ├── bootstrap-theme.min.css.map │ ├── bootstrap.css │ ├── bootstrap.css.map │ ├── bootstrap.min.css │ ├── bootstrap.min.css.map │ ├── font-awesome.css │ ├── jquery.dropdown.min.css │ └── styles.css ├── updateModuleData.php ├── updateSensorData.php └── user.php └── xiaomi ├── config.js ├── package.json └── sensor.js /README.md: -------------------------------------------------------------------------------- 1 | # Домашняя метеостанция на esp8266 2 | В репозитории представлен код для создания и развертывания метеостанции (далее МС), основанной на модуле esp8266. 3 | 4 | Возможности: 5 | - получение данных о температуре/влажности/давлении с различных датчиков (список представлен ниже); 6 | - отправка полученных данных на указанный сервер для обработки/сохранения/отображения; 7 | - отображение данных с датчиков в веб-приложении 8 | 9 | МС, помимо получения данных с собственных модулей, позволяет получать и отображать данные с температурных датчиков производства Xiaomi/Aqara. Для этого требуется наличие шлюза Xiaomi и локального сервера, запущенного в той же сети, в которой работает шлюз. 10 | 11 | Код МС разбит по нескольким папкам: 12 | - esp8266 - собственно код прошивки модуля 13 | - nodejs - код для работы с шлюзом Xiaomi/Aqara для получения данных с температурных сенсоров 14 | - sql - скрипты создания таблиц БД для веб-приложения 15 | - www - код веб-приложения 16 | 17 | Список поддерживаемых датчиков: 18 | - DHT22, DHT11 - https://github.com/adafruit/DHT-sensor-library 19 | - BMP180 - https://github.com/sparkfun/BMP180_Breakout 20 | - BME280 - https://github.com/sparkfun/SparkFun_BME280_Arduino_Library 21 | - SHT21 - 22 | - BH1750 - https://github.com/claws/BH1750 23 | 24 | Список поддерживаемых дисплеев: 25 | - LCD 1602, LCD 2004 - https://github.com/agnunez/ESP8266-I2C-LCD1602 26 | - OLED SSD1306 - https://github.com/squix78/esp8266-oled-ssd1306 27 | 28 | Веб-приложение для развертывания и работы требует связку php/mysql, позволяет работать как в однопользовательском режиме (например в локальной домашней сети, где не нужна авторизация), так и на удаленном сервере с поддержкой множества пользователей с обязательным вводом имени-пароля для просмотра информации. 29 | 30 | Для прошивки модулей esp8266 используется Arduino IDE, так же требуется установка следующих библиотек: 31 | - ArduinoJson - https://github.com/bblanchon/ArduinoJson 32 | 33 | Локальный сервер для получения данных с датчиков Xiaomi/Aqara поднят на RPi, с установленным пакетом nodejs. 34 | 35 | На шлюзе Xiaomi включен режим разработчика. 36 | -------------------------------------------------------------------------------- /blynk/config.js: -------------------------------------------------------------------------------- 1 | var config = { 2 | blynkUrl: "https://192.168.1.212:9443/b2d5d60492ec4348847dea2c0e4d30fa", 3 | serverPort: 9898, 4 | multicastAddress: '224.0.0.50', 5 | multicastPort: 4321, 6 | sensorDelay: 30, 7 | htSensorIds: [20046, 52585, 59408], 8 | htSensorPins: [["V6", "V7"], ["V8", "V9"], ["V10", "V11"]] 9 | }; 10 | 11 | module.exports = config; -------------------------------------------------------------------------------- /blynk/index.js: -------------------------------------------------------------------------------- 1 | var request = require("request"); 2 | var config = require('./config'); 3 | 4 | const dgram = require('dgram'); 5 | const serverPort = config.serverPort; 6 | const serverSocket = dgram.createSocket('udp4'); 7 | const multicastAddress = config.multicastAddress; 8 | const multicastPort = config.multicastPort; 9 | const sensorDelay = config.sensorDelay; 10 | var htSensorIds = config.htSensorIds; 11 | var htSensorPins = config.htSensorPins; 12 | 13 | var sidToAddress = {}; 14 | var sidToPort = {}; 15 | var gatewayAddress; 16 | 17 | function sendSensorData(sensorId, temperature, humidity, gatewayAddress) { 18 | var htSensorIndex = -1; 19 | for (var i = 0; i < htSensorIds.length; i++) { 20 | var htSensorId = htSensorIds[i]; 21 | if (htSensorId == sensorId) { 22 | var sensorPins = htSensorPins[i]; 23 | updatePinValue(sensorPins[0], temperature); 24 | updatePinValue(sensorPins[1], humidity); 25 | } 26 | } 27 | } 28 | 29 | function updatePinValue(pin, value) { 30 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; 31 | request(config.blynkUrl + "/update/" + pin + "?value=" + value, 32 | function (error, response, body) { 33 | if (error) { 34 | console.log(error); 35 | } 36 | } 37 | ); 38 | } 39 | 40 | serverSocket.on('message', function (msg, rinfo) { 41 | 42 | console.log('Received \x1b[33m%s\x1b[0m (%d bytes) from client \x1b[36m%s:%d\x1b[0m.', msg, msg.length, rinfo.address, rinfo.port); 43 | var json; 44 | try { 45 | json = JSON.parse(msg); 46 | } 47 | catch (e) { 48 | console.log('\x1b[31mUnexpected message: %s\x1b[0m.', msg); 49 | return; 50 | } 51 | 52 | var cmd = json['cmd']; 53 | 54 | if (cmd === 'iam') { 55 | 56 | var address = json['ip']; 57 | var port = json['port']; 58 | 59 | gatewayAddress = address; 60 | 61 | var command = { 62 | cmd: "get_id_list" 63 | }; 64 | var cmdString = JSON.stringify(command); 65 | var message = new Buffer(cmdString); 66 | serverSocket.send(message, 0, cmdString.length, port, address); 67 | 68 | console.log('Requesting devices list...'); 69 | } 70 | else if (cmd === 'get_id_list_ack') { 71 | 72 | var data = JSON.parse(json['data']); 73 | console.log('Received devices list: %d device(s) connected.', data.length); 74 | for (var index in data) { 75 | var sid = data[index]; 76 | var command = { 77 | cmd: "read", 78 | sid: new String(sid) 79 | }; 80 | 81 | sidToAddress[sid] = rinfo.address; 82 | sidToPort[sid] = rinfo.port; 83 | 84 | var cmdString = JSON.stringify(command); 85 | var message = new Buffer(cmdString); 86 | serverSocket.send(message, 0, cmdString.length, rinfo.port, rinfo.address); 87 | 88 | console.log('Sending \x1b[33m%s\x1b[0m to \x1b[36m%s:%d\x1b[0m.', cmdString, rinfo.address, rinfo.port); 89 | } 90 | } 91 | else if (cmd === 'read_ack' || cmd === 'report' || cmd === 'heartbeat') { 92 | 93 | var model = json['model']; 94 | var data = JSON.parse(json['data']); 95 | 96 | if (model === 'sensor_ht') { 97 | var temperature = data['temperature'] ? data['temperature'] / 100.0 : 100; 98 | var humidity = data['humidity'] ? data['humidity'] / 100.0 : 0; 99 | var sensorId = json["short_id"]; 100 | 101 | console.log("Received data from sensor \x1b[31m%s\x1b[0m (sensorId: %s) data: temperature %d, humidity %d.", json['sid'], sensorId, temperature, humidity); 102 | 103 | sendSensorData(sensorId, temperature, humidity, gatewayAddress); 104 | 105 | console.log('Sending sensor data to \x1b[36m%s\x1b[0m.', config.addDataUrl); 106 | } 107 | } 108 | }); 109 | 110 | // err - Error object, https://nodejs.org/api/errors.html 111 | serverSocket.on('error', function (err) { 112 | console.log('Error, message - %s, stack - %s.', err.message, err.stack); 113 | }); 114 | 115 | serverSocket.on('listening', function () { 116 | console.log('Starting a UDP server, listening on port %d.', serverPort); 117 | serverSocket.addMembership(multicastAddress); 118 | }) 119 | 120 | console.log('Starting Aqara daemon...'); 121 | 122 | serverSocket.bind(serverPort); 123 | 124 | function sendWhois() { 125 | var command = { 126 | cmd: "whois" 127 | }; 128 | var cmdString = JSON.stringify(command); 129 | var message = new Buffer(cmdString); 130 | serverSocket.send(message, 0, cmdString.length, multicastPort, multicastAddress); 131 | 132 | console.log('Sending WhoIs request to a multicast address \x1b[36m%s:%d\x1b[0m.', multicastAddress, multicastPort); 133 | } 134 | 135 | sendWhois(); 136 | 137 | setInterval(function () { 138 | console.log('Requesting data...'); 139 | sendWhois(); 140 | }, sensorDelay * 1000); 141 | -------------------------------------------------------------------------------- /esp8266/WeatherHub/Common.cpp: -------------------------------------------------------------------------------- 1 | #include "Common.h" 2 | 3 | IPAddress stringToIp(String strIp) 4 | { 5 | String temp; 6 | IPAddress ip; 7 | 8 | int count = 0; 9 | for (int i = 0; i <= strIp.length(); i++) 10 | { 11 | if (strIp[i] != '.') 12 | { 13 | temp += strIp[i]; 14 | } 15 | else 16 | { 17 | if (count < 4) 18 | { 19 | ip[count] = atoi(temp.c_str()); 20 | temp = ""; 21 | count++; 22 | } 23 | } 24 | if (i == strIp.length()) 25 | { 26 | ip[count] = atoi(temp.c_str()); 27 | } 28 | } 29 | return ip; 30 | } 31 | 32 | bool isIPValid(const char * IP) 33 | { 34 | //limited size 35 | int internalcount = 0; 36 | int dotcount = 0; 37 | bool previouswasdot = false; 38 | char c; 39 | 40 | if (strlen(IP) > 15 || strlen(IP) == 0) 41 | { 42 | return false; 43 | } 44 | //cannot start with . 45 | if (IP[0] == '.') 46 | { 47 | return false; 48 | } 49 | 50 | //only letter and digit 51 | for (int i = 0; i < strlen(IP); i++) 52 | { 53 | c = IP[i]; 54 | if (isdigit(c)) 55 | { 56 | //only 3 digit at once 57 | internalcount++; 58 | previouswasdot = false; 59 | if (internalcount > 3) 60 | { 61 | return false; 62 | } 63 | } 64 | else if (c == '.') 65 | { 66 | //cannot have 2 dots side by side 67 | if (previouswasdot) 68 | { 69 | return false; 70 | } 71 | previouswasdot = true; 72 | internalcount = 0; 73 | dotcount++; 74 | }//if not a dot neither a digit it is wrong 75 | else 76 | { 77 | return false; 78 | } 79 | } 80 | //if not 3 dots then it is wrong 81 | if (dotcount != 3) 82 | { 83 | return false; 84 | } 85 | //cannot have the last dot as last char 86 | if (IP[strlen(IP) - 1] == '.') 87 | { 88 | return false; 89 | } 90 | return true; 91 | } 92 | 93 | String floatToString(float f, int valueType, int digits, int decimals) 94 | { 95 | if (isnan(f)) 96 | { 97 | return "-"; 98 | } 99 | if (valueType == VALUE_TEMP) 100 | { 101 | if (f > 70) return "-"; 102 | } 103 | if (valueType == VALUE_HUMIDITY) 104 | { 105 | if (f > 100) return "-"; 106 | } 107 | if (valueType == VALUE_ILLUMINATION) 108 | { 109 | if (f > 50000) return "-"; 110 | return String((int)f); 111 | } 112 | 113 | char c[10]; 114 | dtostrf(f, digits, decimals, c); 115 | return String(c); 116 | } 117 | 118 | String getUptimeData() 119 | { 120 | int highMillis = 0; 121 | int rollover = 0; 122 | 123 | //** Making Note of an expected rollover *****// 124 | if (millis() >= 3000000000) 125 | { 126 | highMillis = 1; 127 | } 128 | //** Making note of actual rollover **// 129 | if (millis() <= 100000 && highMillis == 1) 130 | { 131 | rollover++; 132 | highMillis = 0; 133 | } 134 | 135 | long secsUp = millis() / 1000; 136 | long second = secsUp % 60; 137 | long minute = (secsUp / 60) % 60; 138 | long hour = (secsUp / (60 * 60)) % 24; 139 | long day = (rollover * 50) + (secsUp / (60 * 60 * 24)); //First portion takes care of a rollover [around 50 days] 140 | 141 | char value_buff[120]; 142 | sprintf_P(value_buff, (const char *)F("%d day(s) %02d h %02d m"), day, hour, minute); 143 | return value_buff; 144 | } 145 | 146 | String getFreeMemory() 147 | { 148 | return String(ESP.getFreeHeap()); 149 | } 150 | 151 | String getIpString(IPAddress ip) 152 | { 153 | String ipStr = String(ip[0]) + '.' + String(ip[1]) + '.' + String(ip[2]) + '.' + String(ip[3]); 154 | return ipStr; 155 | } 156 | 157 | String getMacString() 158 | { 159 | uint8_t macData[6]; 160 | WiFi.macAddress(macData); 161 | 162 | char value_buff[20]; 163 | sprintf_P(value_buff, (const char *)F("%02x:%02x:%02x:%02x:%02x:%02x"), macData[0], macData[1], macData[2], macData[3], macData[4], macData[5]); 164 | return String(value_buff); 165 | } 166 | -------------------------------------------------------------------------------- /esp8266/WeatherHub/Common.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMON_H 2 | #define COMMON_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define VALUE_TEMP 0 9 | #define VALUE_HUMIDITY 1 10 | #define VALUE_PRESSURE 2 11 | #define VALUE_ILLUMINATION 3 12 | 13 | IPAddress stringToIp(String strIp); 14 | bool isIPValid(const char * IP); 15 | String floatToString(float f, int valueType, int digits = 4, int decimals = 1); 16 | String getUptimeData(); 17 | String getFreeMemory(); 18 | String getIpString(IPAddress ip); 19 | String getMacString(); 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /esp8266/WeatherHub/DisplayBase.cpp: -------------------------------------------------------------------------------- 1 | #include "DisplayBase.h" 2 | 3 | void DisplayBase::setup(DisplayConfig config) 4 | { 5 | // do nothing 6 | } 7 | 8 | void DisplayBase::clear() 9 | { 10 | // do nothing 11 | } 12 | 13 | void DisplayBase::printLine(String text, int row) 14 | { 15 | // do nothing 16 | } 17 | 18 | void DisplayBase::printData(SensorOutputData sensorData) 19 | { 20 | if (sensorData.hasTemperature) 21 | { 22 | String tempStr = floatToString(sensorData.temperature, VALUE_TEMP); 23 | Serial.println(String("T: " + tempStr)); 24 | } 25 | 26 | if (sensorData.hasHumidity) 27 | { 28 | String humidityStr = floatToString(sensorData.humidity, VALUE_HUMIDITY); 29 | Serial.println(String("H: " + humidityStr)); 30 | } 31 | 32 | if (sensorData.hasLightness) 33 | { 34 | String lightnessStr = floatToString(sensorData.lightness, VALUE_ILLUMINATION); 35 | Serial.println(String("L: " + lightnessStr)); 36 | } 37 | } -------------------------------------------------------------------------------- /esp8266/WeatherHub/DisplayBase.h: -------------------------------------------------------------------------------- 1 | #ifndef DisplayBase_H 2 | #define DisplayBase_H 3 | 4 | #include 5 | #include "SensorOutputData.h" 6 | #include "DisplayConfig.h" 7 | #include "Common.h" 8 | 9 | #define DISPLAY_BASE 0 10 | #define DISPLAY_LCD_I2C 1 11 | #define DISPLAY_OLED 2 12 | 13 | #define DISPLAY_OLED_ADDRESS 0x3c 14 | 15 | class DisplayBase 16 | { 17 | protected: 18 | bool printSensorTitle; 19 | 20 | public: 21 | virtual void setup(DisplayConfig config); 22 | virtual void clear(); 23 | virtual void printData(SensorOutputData sensorData); 24 | virtual void printLine(String text, int row); 25 | }; 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /esp8266/WeatherHub/DisplayConfig.h: -------------------------------------------------------------------------------- 1 | #ifndef DisplayConfig_H 2 | #define DisplayConfig_H 3 | 4 | class DisplayConfig 5 | { 6 | public: 7 | int address; 8 | int rows; 9 | int cols; 10 | int sda; 11 | int scl; 12 | bool printSensorTitle; // pring "S#:" at the line beginning or not 13 | }; 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /esp8266/WeatherHub/DisplayEntity.cpp: -------------------------------------------------------------------------------- 1 | #include "DisplayEntity.h" 2 | #include "DisplayLCDI2C.h" 3 | #include "DisplayOLED.h" 4 | 5 | DisplayEntity::DisplayEntity(int displayType) 6 | { 7 | this->displayType = displayType; 8 | 9 | if (displayType == DISPLAY_BASE) 10 | { 11 | this->display = new DisplayBase(); 12 | } 13 | if (displayType == DISPLAY_LCD_I2C) 14 | { 15 | this->display = new DisplayLCDI2C(); 16 | } 17 | if (displayType == DISPLAY_OLED) 18 | { 19 | this->display = new DisplayOLED(); 20 | } 21 | } 22 | 23 | void DisplayEntity::printData(SensorOutputData sensorData) 24 | { 25 | this->display->printData(sensorData); 26 | } 27 | 28 | void DisplayEntity::setup(DisplayConfig config) 29 | { 30 | this->display->setup(config); 31 | } 32 | 33 | void DisplayEntity::clear() 34 | { 35 | this->display->clear(); 36 | } 37 | 38 | void DisplayEntity::printLine(String text, int row) 39 | { 40 | this->display->printLine(text, row); 41 | } -------------------------------------------------------------------------------- /esp8266/WeatherHub/DisplayEntity.h: -------------------------------------------------------------------------------- 1 | #ifndef DisplayEntity_H 2 | #define DisplayEntity_H 3 | 4 | #include "DisplayBase.h" 5 | 6 | class DisplayEntity 7 | { 8 | int displayType; 9 | DisplayBase* display; 10 | 11 | public: 12 | DisplayEntity(int displayType); 13 | virtual void printData(SensorOutputData sensorData); 14 | virtual void setup(DisplayConfig config); 15 | virtual void clear(); 16 | virtual void printLine(String text, int row = 0); 17 | }; 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /esp8266/WeatherHub/DisplayLCDI2C.cpp: -------------------------------------------------------------------------------- 1 | #include "DisplayLCDI2C.h" 2 | 3 | byte termoIcon[8] = //icon for termometer 4 | { 5 | B00100, 6 | B01010, 7 | B01010, 8 | B01110, 9 | B01110, 10 | B11111, 11 | B11111, 12 | B01110 13 | }; 14 | 15 | byte hydroIcon[8] = //icon for water droplet 16 | { 17 | B00100, 18 | B00100, 19 | B01010, 20 | B01010, 21 | B10001, 22 | B10001, 23 | B10001, 24 | B01110, 25 | }; 26 | 27 | void DisplayLCDI2C::setup(DisplayConfig config) 28 | { 29 | this->display = new LiquidCrystal_I2C(config.address, config.cols, config.rows); 30 | this->display->begin(); 31 | this->display->backlight(); 32 | this->display->createChar(1, termoIcon); 33 | this->display->createChar(2, hydroIcon); 34 | 35 | this->printSensorTitle = config.printSensorTitle; 36 | } 37 | 38 | void DisplayLCDI2C::clear() 39 | { 40 | this->display->clear(); 41 | } 42 | 43 | void DisplayLCDI2C::printData(SensorOutputData sensorData) 44 | { 45 | DisplayBase::printData(sensorData); 46 | 47 | this->display->setCursor(0, sensorData.sensorOrder); 48 | 49 | if (this->printSensorTitle) 50 | { 51 | this->display->print("S"); 52 | this->display->print(sensorData.sensorOrder); 53 | this->display->print(": "); 54 | } 55 | 56 | this->display->print((char)1); 57 | this->display->print(" "); 58 | this->display->print(sensorData.temperature, 1); 59 | this->display->print((char)223); 60 | this->display->print(" "); 61 | 62 | this->display->print((char)2); 63 | this->display->print(" "); 64 | this->display->print(sensorData.humidity, 1); 65 | this->display->print("%"); 66 | } 67 | 68 | void DisplayLCDI2C::printLine(String text, int row) 69 | { 70 | while (text.length() < 20) 71 | { 72 | text += " "; 73 | } 74 | this->display->setCursor(0, row); 75 | this->display->print(text); 76 | } 77 | -------------------------------------------------------------------------------- /esp8266/WeatherHub/DisplayLCDI2C.h: -------------------------------------------------------------------------------- 1 | #ifndef DisplayLCDI2C_H 2 | #define DisplayLCDI2C_H 3 | 4 | #include "DisplayBase.h" 5 | #include 6 | 7 | class DisplayLCDI2C : public DisplayBase 8 | { 9 | LiquidCrystal_I2C* display; 10 | 11 | public: 12 | virtual void setup(DisplayConfig config); 13 | virtual void clear(); 14 | virtual void printData(SensorOutputData sensorData); 15 | virtual void printLine(String text, int row); 16 | }; 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /esp8266/WeatherHub/DisplayOLED.cpp: -------------------------------------------------------------------------------- 1 | #include "DisplayOLED.h" 2 | #include "DisplayOLEDFont.h" 3 | 4 | #define FONT_HEIGHT 14 5 | #define LINE_GAP 4 6 | #define SCREEN_HEIGHT 64 7 | #define SCREEN_WIDTH 128 8 | 9 | void DisplayOLED::setup(DisplayConfig config) 10 | { 11 | this->display = new SSD1306(config.address, config.sda, config.scl); 12 | this->display->init(); 13 | this->display->setTextAlignment(TEXT_ALIGN_LEFT); 14 | this->display->setFont(Monospaced_plain_14); 15 | 16 | this->printSensorTitle = config.printSensorTitle; 17 | } 18 | 19 | void DisplayOLED::clear() 20 | { 21 | this->display->clear(); 22 | } 23 | 24 | void DisplayOLED::printData(SensorOutputData sensorData) 25 | { 26 | DisplayBase::printData(sensorData); 27 | 28 | String text = ""; 29 | 30 | if (sensorData.hasTemperature) 31 | { 32 | text += "T "; 33 | if (isnan(sensorData.temperature)) 34 | { 35 | text += "- "; 36 | } 37 | else 38 | { 39 | text += floatToString(sensorData.temperature, VALUE_TEMP, 3, 1); 40 | text += "° "; 41 | } 42 | } 43 | 44 | if (sensorData.hasHumidity) 45 | { 46 | text += "H "; 47 | if (isnan(sensorData.humidity)) 48 | { 49 | text += "-"; 50 | } 51 | else 52 | { 53 | text += floatToString(sensorData.humidity, VALUE_HUMIDITY, 3, 1); 54 | text += "% "; 55 | } 56 | } 57 | 58 | if (sensorData.hasLightness) 59 | { 60 | text += "L: "; 61 | if (isnan(sensorData.lightness)) 62 | { 63 | text += "-"; 64 | } 65 | else 66 | { 67 | text += floatToString(sensorData.lightness, VALUE_ILLUMINATION); 68 | text += " lux"; 69 | } 70 | } 71 | 72 | this->printLine(text, sensorData.sensorOrder); 73 | } 74 | 75 | void DisplayOLED::printLine(String text, int row) 76 | { 77 | this->display->setColor(BLACK); 78 | this->display->fillRect(0, row * (FONT_HEIGHT + LINE_GAP), SCREEN_WIDTH, (FONT_HEIGHT + LINE_GAP)); 79 | this->display->setColor(WHITE); 80 | this->display->drawString(0, row * (FONT_HEIGHT + LINE_GAP), text); 81 | this->display->display(); 82 | } -------------------------------------------------------------------------------- /esp8266/WeatherHub/DisplayOLED.h: -------------------------------------------------------------------------------- 1 | #ifndef DisplayOLED_H 2 | #define DisplayOLED_H 3 | 4 | #include "DisplayBase.h" 5 | #include "SSD1306.h" 6 | 7 | class DisplayOLED : public DisplayBase 8 | { 9 | SSD1306* display; 10 | 11 | public: 12 | virtual void setup(DisplayConfig config); 13 | virtual void clear(); 14 | virtual void printData(SensorOutputData sensorData); 15 | virtual void printLine(String text, int row); 16 | }; 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /esp8266/WeatherHub/JsonConfig.cpp: -------------------------------------------------------------------------------- 1 | #include "JsonConfig.h" 2 | 3 | bool JsonConfig::printConfig() 4 | { 5 | Serial.println("\r\nConfig: printing"); 6 | 7 | Serial.print(F("module_id : ")); Serial.println(module_id); 8 | Serial.print(F("module_name : ")); Serial.println(module_name); 9 | Serial.print(F("sta_ssid : ")); Serial.println(sta_ssid); 10 | Serial.print(F("sta_pwd : ")); Serial.println(sta_pwd); 11 | 12 | Serial.print(F("static_ip_mode : ")); Serial.println(static_ip_mode); 13 | Serial.print(F("static_ip : ")); Serial.println(static_ip); 14 | Serial.print(F("static_gateway : ")); Serial.println(static_gateway); 15 | Serial.print(F("static_subnet : ")); Serial.println(static_subnet); 16 | 17 | Serial.print(F("get_data_delay : ")); Serial.println(get_data_delay); 18 | Serial.print(F("reboot_delay : ")); Serial.println(reboot_delay); 19 | 20 | Serial.print(F("add_data_url : ")); Serial.println(add_data_url); 21 | Serial.print(F("validation_code : ")); Serial.println(validation_code); 22 | 23 | Serial.println("\r\nConfig: printed"); 24 | 25 | return true; 26 | } 27 | 28 | bool JsonConfig::loadConfig() 29 | { 30 | Serial.println("\r\nConfig: loading"); 31 | 32 | File configFile = SPIFFS.open("/config.json", "r"); 33 | if (!configFile) 34 | { 35 | Serial.println("Config: failed to open config file for reading"); 36 | return false; 37 | } 38 | 39 | size_t size = configFile.size(); 40 | if (size > 2048) 41 | { 42 | Serial.println("Config: file size is too large"); 43 | SPIFFS.remove("/config.json"); 44 | saveConfig(); 45 | return false; 46 | } 47 | 48 | // Allocate a buffer to store contents of the file. 49 | std::unique_ptr buf(new char[size]); 50 | 51 | // We don't use String here because ArduinoJson library requires the input 52 | // buffer to be mutable. If you don't use ArduinoJson, you may as well 53 | // use configFile.readString instead. 54 | configFile.readBytes(buf.get(), size); 55 | 56 | StaticJsonBuffer<1024> jsonBuffer; 57 | JsonObject& json = jsonBuffer.parseObject(buf.get()); 58 | 59 | if (!json.success()) 60 | { 61 | Serial.println("Failed to parse config file"); 62 | SPIFFS.remove("/config.json"); 63 | saveConfig(); 64 | return false; 65 | } 66 | 67 | if (json.containsKey("module_id")) { const char* module_id_char = json["module_id"]; sprintf_P(module_id, ("%s"), module_id_char); } 68 | if (json.containsKey("module_name")) { const char* module_name_char = json["module_name"]; sprintf_P(module_name, ("%s"), module_name_char); } 69 | if (json.containsKey("sta_ssid")) { const char* sta_ssid_char = json["sta_ssid"]; sprintf_P(sta_ssid, ("%s"), sta_ssid_char); } 70 | if (json.containsKey("sta_pwd")) { const char* sta_pwd_char = json["sta_pwd"]; sprintf_P(sta_pwd, ("%s"), sta_pwd_char); } 71 | 72 | if (json.containsKey("static_ip_mode")) { const char* static_ip_mode_char = json["static_ip_mode"]; sprintf_P(static_ip_mode, ("%s"), static_ip_mode_char); } 73 | if (json.containsKey("static_ip")) { const char* static_ip_char = json["static_ip"]; sprintf_P(static_ip, ("%s"), static_ip_char); } 74 | if (json.containsKey("static_gateway")) { const char* static_gateway_char = json["static_gateway"]; sprintf_P(static_gateway, ("%s"), static_gateway_char); } 75 | if (json.containsKey("static_subnet")) { const char* static_subnet_char = json["static_subnet"]; sprintf_P(static_subnet, ("%s"), static_subnet_char); } 76 | 77 | if (json.containsKey("get_data_delay")) { const char* get_data_delay_char = json["get_data_delay"]; sprintf_P(get_data_delay, ("%s"), get_data_delay_char); } 78 | if (json.containsKey("reboot_delay")) { const char* reboot_delay_char = json["reboot_delay"]; sprintf_P(reboot_delay, ("%s"), reboot_delay_char); } 79 | 80 | if (json.containsKey("add_data_url")) { const char* add_data_url_char = json["add_data_url"]; sprintf_P(add_data_url, ("%s"), add_data_url_char); } 81 | if (json.containsKey("validation_code")) { const char* validation_code_char = json["validation_code"]; sprintf_P(validation_code, ("%s"), validation_code_char); } 82 | 83 | configFile.close(); 84 | 85 | Serial.println("Config: loaded"); 86 | 87 | return true; 88 | } 89 | 90 | bool JsonConfig::saveConfig() 91 | { 92 | Serial.println("\r\nConfig: saving"); 93 | 94 | StaticJsonBuffer<1024> jsonBuffer; 95 | JsonObject& json = jsonBuffer.createObject(); 96 | File configFile = SPIFFS.open("/config.json", "w"); 97 | if (!configFile) 98 | { 99 | Serial.println("Config: failed to open config file for writing"); 100 | return false; 101 | } 102 | 103 | json["module_id"] = module_id; 104 | json["module_name"] = module_name; 105 | json["sta_ssid"] = sta_ssid; 106 | json["sta_pwd"] = sta_pwd; 107 | 108 | json["static_ip_mode"] = static_ip_mode; 109 | json["static_ip"] = static_ip; 110 | json["static_gateway"] = static_gateway; 111 | json["static_subnet"] = static_subnet; 112 | 113 | json["get_data_delay"] = get_data_delay; 114 | json["reboot_delay"] = reboot_delay; 115 | 116 | json["add_data_url"] = add_data_url; 117 | json["validation_code"] = validation_code; 118 | 119 | json.printTo(configFile); 120 | configFile.close(); 121 | 122 | Serial.println("Config: saved"); 123 | 124 | return true; 125 | } 126 | 127 | -------------------------------------------------------------------------------- /esp8266/WeatherHub/JsonConfig.h: -------------------------------------------------------------------------------- 1 | #ifndef JSONCONFIG_H 2 | #define JSONCONFIG_H 3 | 4 | #include 5 | #include 6 | #include "FS.h" 7 | 8 | class JsonConfig 9 | { 10 | public: 11 | bool saveConfig(); 12 | bool loadConfig(); 13 | bool printConfig(); 14 | 15 | // numeric value between 1 and 1024 which will identify module 16 | char module_id[5] = "1"; 17 | // module name 18 | char module_name[32] = "Module-01"; 19 | // wifi network SSID 20 | char sta_ssid[32] = "Xiaomi_7C5A"; 21 | // wifi network password 22 | char sta_pwd[32] = "mi-alexpp"; 23 | 24 | // 0 - using static IP mode, 1 - dynamic IP mode 25 | char static_ip_mode[2] = "0"; 26 | // statis IP to set 27 | char static_ip[16] = "192.168.1.200"; 28 | // gateway to use 29 | char static_gateway[16] = "192.168.1.1"; 30 | // subnet mask to use 31 | char static_subnet[16] = "255.255.255.0"; 32 | 33 | // delay to retrieve data from sensors in seconds, max - 999 seconds 34 | char get_data_delay[5] = "10"; 35 | // delays in seconds before rebooting module, max - 999 seconds 36 | char reboot_delay[5] = "10"; 37 | 38 | // address to push module data 39 | char add_data_url[200] = "http://weatherhub.ru/add.php"; 40 | // validation code used to identify user's module - 16 alpha-digits 41 | char validation_code[17] = "0000000000000000"; 42 | 43 | private: 44 | }; 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /esp8266/WeatherHub/SensorAM2301.cpp: -------------------------------------------------------------------------------- 1 | #include "SensorAM2301.h" 2 | 3 | #define DHTTYPE DHT21 4 | 5 | void SensorAM2301::setup(int pin) 6 | { 7 | this->sensor = new DHT(pin, DHTTYPE); 8 | this->sensor->begin(); 9 | } 10 | 11 | -------------------------------------------------------------------------------- /esp8266/WeatherHub/SensorAM2301.h: -------------------------------------------------------------------------------- 1 | #ifndef SensorAM2301_H 2 | #define SensorAM2301_H 3 | 4 | #include "SensorDHT22.h" 5 | 6 | class SensorAM2301 : public SensorDHT22 7 | { 8 | public: 9 | virtual void setup(int pin); 10 | }; 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /esp8266/WeatherHub/SensorBH1750.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "SensorBH1750.h" 3 | 4 | // code from here: https://github.com/claws/BH1750 5 | 6 | #define BH1750_I2CADDR 0x23 7 | 8 | // No active state 9 | #define BH1750_POWER_DOWN 0x00 10 | 11 | // Wating for measurment command 12 | #define BH1750_POWER_ON 0x01 13 | 14 | // Reset data register value - not accepted in POWER_DOWN mode 15 | #define BH1750_RESET 0x07 16 | 17 | // Start measurement at 1lx resolution. Measurement time is approx 120ms. 18 | #define BH1750_CONTINUOUS_HIGH_RES_MODE 0x10 19 | 20 | // Start measurement at 0.5lx resolution. Measurement time is approx 120ms. 21 | #define BH1750_CONTINUOUS_HIGH_RES_MODE_2 0x11 22 | 23 | // Start measurement at 4lx resolution. Measurement time is approx 16ms. 24 | #define BH1750_CONTINUOUS_LOW_RES_MODE 0x13 25 | 26 | // Start measurement at 1lx resolution. Measurement time is approx 120ms. 27 | // Device is automatically set to Power Down after measurement. 28 | #define BH1750_ONE_TIME_HIGH_RES_MODE 0x20 29 | 30 | // Start measurement at 0.5lx resolution. Measurement time is approx 120ms. 31 | // Device is automatically set to Power Down after measurement. 32 | #define BH1750_ONE_TIME_HIGH_RES_MODE_2 0x21 33 | 34 | // Start measurement at 1lx resolution. Measurement time is approx 120ms. 35 | // Device is automatically set to Power Down after measurement. 36 | #define BH1750_ONE_TIME_LOW_RES_MODE 0x23 37 | 38 | // Define milliseconds delay for ESP8266 platform 39 | #if defined(ESP8266) 40 | 41 | #include 42 | #define _delay_ms(ms) delayMicroseconds((ms) * 1000) 43 | 44 | // Use _delay_ms from utils for AVR-based platforms 45 | #elif defined(__avr__) 46 | #include 47 | // Use Wiring's delay for compability with another platforms 48 | #else 49 | #define _delay_ms(ms) delay(ms) 50 | #endif 51 | 52 | 53 | // Legacy Wire.write() function fix 54 | #if (ARDUINO >= 100) 55 | #define __wire_write(d) Wire.write(d) 56 | #else 57 | #define __wire_write(d) Wire.send(d) 58 | #endif 59 | 60 | 61 | // Legacy Wire.read() function fix 62 | #if (ARDUINO >= 100) 63 | #define __wire_read() Wire.read() 64 | #else 65 | #define __wire_read() Wire.receive() 66 | #endif 67 | 68 | void SensorBH1750::setup(int pin) 69 | { 70 | Wire.begin(SDA_PIN, SCK_PIN); 71 | configure(BH1750_CONTINUOUS_HIGH_RES_MODE); 72 | } 73 | 74 | SensorOutputData SensorBH1750::getData() 75 | { 76 | uint16_t lightLevel = readLightLevel(false); 77 | 78 | SensorOutputData sensorData = SensorOutputData(); 79 | sensorData.lightness = lightLevel; 80 | sensorData.hasLightness = true; 81 | return sensorData; 82 | } 83 | 84 | uint16_t SensorBH1750::readLightLevel(bool maxWait) 85 | { 86 | // Measurment result will be stored here 87 | uint16_t level; 88 | 89 | // One-Time modes need to be re-applied after power-up. They have a maximum 90 | // measurement time and a typical measurement time. The maxWait argument 91 | // determines which measurement wait time is used when a one-time mode is 92 | // being used. The typical (shorter) measurement time is used by default and 93 | // if maxWait is set to True then the maximum measurement time will be used. 94 | // See data sheet pages 2, 5 and 7 for more details. 95 | switch (BH1750_MODE) { 96 | 97 | case BH1750_ONE_TIME_HIGH_RES_MODE: 98 | case BH1750_ONE_TIME_HIGH_RES_MODE_2: 99 | case BH1750_ONE_TIME_LOW_RES_MODE: 100 | 101 | // Send mode to sensor 102 | Wire.beginTransmission(BH1750_I2CADDR); 103 | __wire_write((uint8_t)BH1750_MODE); 104 | Wire.endTransmission(); 105 | 106 | // wait for measurement time 107 | if (BH1750_MODE == BH1750_ONE_TIME_LOW_RES_MODE) { 108 | maxWait ? _delay_ms(24) : _delay_ms(16); 109 | } 110 | else { 111 | maxWait ? _delay_ms(180) :_delay_ms(120); 112 | } 113 | 114 | break; 115 | } 116 | 117 | // Read two bytes from sensor 118 | Wire.requestFrom(BH1750_I2CADDR, 2); 119 | 120 | // Read two bytes, which are low and high parts of sensor value 121 | level = __wire_read(); 122 | level <<= 8; 123 | level |= __wire_read(); 124 | 125 | // Convert raw value to lux 126 | level /= 1.2; 127 | 128 | return level; 129 | } 130 | 131 | void SensorBH1750::configure(uint8_t mode) 132 | { 133 | // Check measurment mode is valid 134 | switch (mode) { 135 | 136 | case BH1750_CONTINUOUS_HIGH_RES_MODE: 137 | case BH1750_CONTINUOUS_HIGH_RES_MODE_2: 138 | case BH1750_CONTINUOUS_LOW_RES_MODE: 139 | case BH1750_ONE_TIME_HIGH_RES_MODE: 140 | case BH1750_ONE_TIME_HIGH_RES_MODE_2: 141 | case BH1750_ONE_TIME_LOW_RES_MODE: 142 | 143 | // Save mode so it can be restored when One-Time modes are used. 144 | BH1750_MODE = mode; 145 | 146 | // Send mode to sensor 147 | Wire.beginTransmission(BH1750_I2CADDR); 148 | __wire_write((uint8_t)BH1750_MODE); 149 | Wire.endTransmission(); 150 | 151 | // Wait a few moments to wake up 152 | _delay_ms(10); 153 | break; 154 | 155 | default: 156 | break; 157 | } 158 | 159 | } -------------------------------------------------------------------------------- /esp8266/WeatherHub/SensorBH1750.h: -------------------------------------------------------------------------------- 1 | #ifndef SensorBH1750_H 2 | #define SensorBH1750_H 3 | 4 | #include "SensorBase.h" 5 | 6 | class SensorBH1750 : public SensorBase 7 | { 8 | public: 9 | virtual void setup(int pin); 10 | virtual SensorOutputData getData(); 11 | 12 | private: 13 | uint16_t readLightLevel(bool maxWait = false); 14 | void configure(uint8_t mode); 15 | uint8_t BH1750_MODE; 16 | }; 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /esp8266/WeatherHub/SensorBase.cpp: -------------------------------------------------------------------------------- 1 | #include "SensorBase.h" 2 | 3 | void SensorBase::setup(int pin) 4 | { 5 | // do nothing 6 | } 7 | 8 | SensorOutputData SensorBase::getData() 9 | { 10 | return SensorOutputData(); 11 | } 12 | -------------------------------------------------------------------------------- /esp8266/WeatherHub/SensorBase.h: -------------------------------------------------------------------------------- 1 | #ifndef SensorBase_H 2 | #define SensorBase_H 3 | 4 | #include "SensorOutputData.h" 5 | 6 | #define SENSOR_BASE 0 7 | #define SENSOR_DHT22 1 8 | #define SENSOR_DHT21 2 9 | #define SENSOR_AM2301 2 10 | #define SENSOR_DHT11 3 11 | #define SENSOR_SHT21 4 12 | #define SENSOR_HTU21 4 13 | #define SENSOR_BH1750 5 14 | 15 | #define SDA_PIN D3 16 | #define SCK_PIN D5 17 | 18 | class SensorBase 19 | { 20 | public: 21 | virtual void setup(int pin); 22 | virtual SensorOutputData getData(); 23 | }; 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /esp8266/WeatherHub/SensorDHT11.cpp: -------------------------------------------------------------------------------- 1 | #include "SensorDHT11.h" 2 | 3 | #define DHTTYPE DHT11 4 | 5 | void SensorDHT11::setup(int pin) 6 | { 7 | this->sensor = new DHT(pin, DHTTYPE); 8 | this->sensor->begin(); 9 | } 10 | -------------------------------------------------------------------------------- /esp8266/WeatherHub/SensorDHT11.h: -------------------------------------------------------------------------------- 1 | #ifndef SensorDHT11_H 2 | #define SensorDHT11_H 3 | 4 | #include "SensorDHT22.h" 5 | 6 | class SensorDHT11 : public SensorDHT22 7 | { 8 | public: 9 | virtual void setup(int pin); 10 | }; 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /esp8266/WeatherHub/SensorDHT22.cpp: -------------------------------------------------------------------------------- 1 | #include "SensorDHT22.h" 2 | 3 | #define DHTTYPE DHT22 4 | 5 | void SensorDHT22::setup(int pin) 6 | { 7 | this->sensor = new DHT(pin, DHTTYPE); 8 | this->sensor->begin(); 9 | } 10 | 11 | SensorOutputData SensorDHT22::getData() 12 | { 13 | float h = this->sensor->readHumidity(); 14 | float t = this->sensor->readTemperature(); 15 | 16 | SensorOutputData sensorData = SensorOutputData(); 17 | sensorData.temperature = t; 18 | sensorData.hasTemperature = true; 19 | sensorData.humidity = h; 20 | sensorData.hasHumidity = true; 21 | 22 | return sensorData; 23 | } 24 | -------------------------------------------------------------------------------- /esp8266/WeatherHub/SensorDHT22.h: -------------------------------------------------------------------------------- 1 | #ifndef SensorDHT22_H 2 | #define SensorDHT22_H 3 | 4 | #include "SensorBase.h" 5 | #include 6 | 7 | class SensorDHT22 : public SensorBase 8 | { 9 | protected: 10 | DHT* sensor; 11 | public: 12 | virtual void setup(int pin); 13 | virtual SensorOutputData getData(); 14 | }; 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /esp8266/WeatherHub/SensorEntity.cpp: -------------------------------------------------------------------------------- 1 | #include "SensorEntity.h" 2 | #include "SensorDHT22.h" 3 | #include "SensorAM2301.h" 4 | #include "SensorDHT11.h" 5 | #include "SensorSHT21.h" 6 | #include "SensorBH1750.h" 7 | 8 | SensorEntity::SensorEntity(int sensorType) 9 | { 10 | if (sensorType == SENSOR_BASE) 11 | { 12 | this->sensor = new SensorBase(); 13 | } 14 | else if (sensorType == SENSOR_DHT22) 15 | { 16 | this->sensor = new SensorDHT22(); 17 | } 18 | else if (sensorType == SENSOR_DHT11) 19 | { 20 | this->sensor = new SensorDHT11(); 21 | } 22 | else if (sensorType == SENSOR_DHT21 || sensorType == SENSOR_AM2301) 23 | { 24 | this->sensor = new SensorAM2301(); 25 | } 26 | else if (sensorType == SENSOR_SHT21 || sensorType == SENSOR_HTU21) 27 | { 28 | this->sensor = new SensorSHT21(); 29 | } 30 | else if (sensorType == SENSOR_BH1750) 31 | { 32 | this->sensor = new SensorBH1750(); 33 | } 34 | } 35 | 36 | SensorOutputData SensorEntity::getData() 37 | { 38 | return this->sensor->getData(); 39 | } 40 | 41 | void SensorEntity::setup(int pin) 42 | { 43 | this->sensor->setup(pin); 44 | } 45 | -------------------------------------------------------------------------------- /esp8266/WeatherHub/SensorEntity.h: -------------------------------------------------------------------------------- 1 | #ifndef SensorEntity_H 2 | #define SensorEntity_H 3 | 4 | #include "SensorBase.h" 5 | 6 | class SensorEntity 7 | { 8 | SensorBase* sensor; 9 | public: 10 | SensorEntity(int sensorType); 11 | virtual SensorOutputData getData(); 12 | virtual void setup(int pin); 13 | }; 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /esp8266/WeatherHub/SensorOutputData.cpp: -------------------------------------------------------------------------------- 1 | #include "SensorOutputData.h" 2 | 3 | float SensorOutputData::getTempForJson(float value) 4 | { 5 | return (isnan(value) || value > 70) ? 0 : value; 6 | } 7 | 8 | float SensorOutputData::getHumidityForJson(float value) 9 | { 10 | return (isnan(value) || value > 100) ? 0 : value; 11 | } 12 | 13 | String SensorOutputData::getParamName(String param, int sensorCounter) 14 | { 15 | String paramName = ""; 16 | paramName += param; 17 | paramName += sensorCounter; 18 | return paramName; 19 | } 20 | 21 | void SensorOutputData::formatJson(JsonObject& json, int sensorCounter) 22 | { 23 | if (this->hasTemperature) 24 | { 25 | String paramName = getParamName("temperature", sensorCounter); 26 | json[paramName] = getTempForJson(this->temperature); 27 | } 28 | 29 | if (this->hasHumidity) 30 | { 31 | String paramName = getParamName("humidity", sensorCounter); 32 | json[paramName] = getHumidityForJson(this->humidity); 33 | } 34 | } -------------------------------------------------------------------------------- /esp8266/WeatherHub/SensorOutputData.h: -------------------------------------------------------------------------------- 1 | #ifndef SensorOutputData_H 2 | #define SensorOutputData_H 3 | 4 | #include 5 | #include 6 | 7 | class SensorOutputData 8 | { 9 | private: 10 | float getTempForJson(float value); 11 | float getHumidityForJson(float value); 12 | String getParamName(String param, int sensorCounter); 13 | 14 | public: 15 | float temperature; 16 | bool hasTemperature; 17 | 18 | float humidity; 19 | bool hasHumidity; 20 | 21 | float pressure; 22 | bool hasPressure; 23 | 24 | float lightness; 25 | bool hasLightness; 26 | 27 | int sensorOrder; 28 | 29 | void formatJson(JsonObject& json, int sensorCounter); 30 | }; 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /esp8266/WeatherHub/SensorSHT21.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "SensorSHT21.h" 3 | 4 | // code taken here: https://github.com/markbeee/SHT21 5 | 6 | #define SHT21_ADDRESS 0x40 //I2C address for the sensor 7 | #define TRIGGER_TEMP_MEASURE_NOHOLD 0xF3 8 | #define TRIGGER_HUMD_MEASURE_NOHOLD 0xF5 9 | 10 | void SensorSHT21::setup(int pin) 11 | { 12 | Wire.begin(SDA_PIN, SCK_PIN); 13 | } 14 | 15 | SensorOutputData SensorSHT21::getData() 16 | { 17 | float h = getHumidity(); 18 | float t = getTemperature(); 19 | 20 | SensorOutputData sensorData = SensorOutputData(); 21 | sensorData.temperature = t; 22 | sensorData.hasTemperature = true; 23 | sensorData.humidity = h; 24 | sensorData.hasHumidity = true; 25 | 26 | return sensorData; 27 | } 28 | 29 | uint16_t SensorSHT21::readSHT21(uint8_t command) 30 | { 31 | uint16_t result; 32 | 33 | Wire.beginTransmission(SHT21_ADDRESS); 34 | Wire.write(command); 35 | Wire.endTransmission(); 36 | delay(100); 37 | 38 | Wire.requestFrom(SHT21_ADDRESS, 3); 39 | while(Wire.available() < 3) { 40 | delay(1); 41 | } 42 | 43 | // return result 44 | result = ((Wire.read()) << 8); 45 | result += Wire.read(); 46 | result &= ~0x0003; // clear two low bits (status bits) 47 | return result; 48 | } 49 | 50 | float SensorSHT21::getHumidity() 51 | { 52 | return (-6.0 + 125.0 / 65536.0 * (float)(readSHT21(TRIGGER_HUMD_MEASURE_NOHOLD))); 53 | } 54 | 55 | float SensorSHT21::getTemperature() 56 | { 57 | return (-46.85 + 175.72 / 65536.0 * (float)(readSHT21(TRIGGER_TEMP_MEASURE_NOHOLD))); 58 | } 59 | -------------------------------------------------------------------------------- /esp8266/WeatherHub/SensorSHT21.h: -------------------------------------------------------------------------------- 1 | #ifndef SensorSHT21_H 2 | #define SensorSHT21_H 3 | 4 | #include "SensorBase.h" 5 | 6 | class SensorSHT21 : public SensorBase 7 | { 8 | public: 9 | virtual void setup(int pin); 10 | virtual SensorOutputData getData(); 11 | 12 | private: 13 | uint16_t readSHT21(uint8_t command); 14 | float getHumidity(); 15 | float getTemperature(); 16 | }; 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /esp8266/WeatherHub/WebServer.h: -------------------------------------------------------------------------------- 1 | #ifndef WebServer_H 2 | #define WebServer_H 3 | 4 | #include 5 | #include "JsonConfig.h" 6 | 7 | class WebServer 8 | { 9 | private: 10 | JsonConfig* config; 11 | ESP8266WebServer* webServer; 12 | 13 | void webRoot(); 14 | void webSetup(); 15 | void webReboot(); 16 | void webStyles(); 17 | void handleNotFound(); 18 | 19 | String renderParameterRow(String paramName, String paramId, String paramValue, int maxLength, bool isReadonly = false, bool isPassword = false); 20 | String renderTitle(String pageName, String moduleName); 21 | String renderAlert(String type, String text); 22 | String renderStyles(String styles); 23 | String renderMenu(String delay); 24 | 25 | public: 26 | typedef std::function TRebootFunction; 27 | void setup(ESP8266WebServer* webServer, JsonConfig* config, TRebootFunction rebootFunction); 28 | 29 | protected: 30 | TRebootFunction rebootFunction; 31 | }; 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /nodejs/commandHelper.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | var config = require('./config'); 3 | 4 | Date.prototype.Format = function(fmt) { 5 | var o = { 6 | "M+": this.getMonth() + 1, 7 | "d+": this.getDate(), 8 | "h+": this.getHours(), 9 | "m+": this.getMinutes(), 10 | "s+": this.getSeconds(), 11 | "q+": Math.floor((this.getMonth() + 3) / 3), 12 | "S": this.getMilliseconds() 13 | }; 14 | if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length)); 15 | for (var k in o) 16 | if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); 17 | return fmt; 18 | }; 19 | 20 | var commandHelper = function() { 21 | 22 | const iv = Buffer.from([0x17, 0x99, 0x6d, 0x09, 0x3d, 0x28, 0xdd, 0xb3, 0xba, 0x69, 0x5a, 0x2e, 0x6f, 0x58, 0x56, 0x2e]); 23 | var serverSocket = null; 24 | 25 | this.setServerSocket = function(socket) { 26 | serverSocket = socket; 27 | }; 28 | 29 | this.sendCommand = function(jsonObject, port, address) { 30 | var cmdString = JSON.stringify(jsonObject); 31 | var message = new Buffer(cmdString); 32 | serverSocket.send(message, 0, cmdString.length, port, address); 33 | console.log('Sending \x1b[33m%s\x1b[0m to \x1b[36m%s:%d\x1b[0m.', cmdString, address, port); 34 | }; 35 | 36 | this.sendWriteCommand = function(deviceSid, jsonObject, port, address, token) { 37 | var cipher = crypto.createCipheriv('aes-128-cbc', config.gatewayPassword, iv); 38 | var key = cipher.update(token, "ascii", "hex"); 39 | cipher.final('hex'); // Useless data, don't know why yet. 40 | var serialNumber = new Date().Format("yyyyMMddhhmmss"); 41 | 42 | jsonObject.data.key = key; 43 | var msgTag = 'write_' + deviceSid + "_t" + serialNumber; 44 | 45 | this.sendCommand(jsonObject, port, address); 46 | }; 47 | 48 | }; 49 | 50 | var helper = new commandHelper(); 51 | module.exports = helper; -------------------------------------------------------------------------------- /nodejs/config.js: -------------------------------------------------------------------------------- 1 | var config = { 2 | serverPort: 9898, 3 | multicastAddress: '224.0.0.50', 4 | multicastPort: 4321, 5 | sensorDelay: 60, 6 | databaseHost: "localhost", 7 | databaseUser: "phpmyadmin", 8 | databasePassword: "root", 9 | databaseBase: "homehub", 10 | gatewayPassword: "5uym4gcadgt4ph9f" 11 | }; 12 | 13 | module.exports = config; -------------------------------------------------------------------------------- /nodejs/databaseHelper.js: -------------------------------------------------------------------------------- 1 | var mysql = require('mysql'); 2 | 3 | var databaseHelper = function() { 4 | 5 | var connection; 6 | 7 | this.init = function(config) { 8 | connection = mysql.createConnection({ 9 | host: config.databaseHost, 10 | user: config.databaseUser, 11 | password: config.databasePassword, 12 | database: config.databaseBase 13 | }); 14 | 15 | connection.connect(function(err) { 16 | if (err) { 17 | throw err; 18 | } 19 | console.log("Database connected!"); 20 | }); 21 | }; 22 | 23 | this.updateHubData = function(cmd, model, sid, short_id, voltage) { 24 | // clear DB for old rows 25 | var deleteSql = "delete from HubData where sid = '" + sid + "'"; 26 | connection.query(deleteSql); 27 | 28 | var sql = "insert into HubData (cmd, model, sid, shortid, voltage) values (?)"; 29 | var hubValue = [cmd, model, sid, short_id, voltage]; 30 | connection.query(sql, [hubValue], function (err, result) { 31 | if (err) { 32 | throw err; 33 | } 34 | }); 35 | }; 36 | 37 | this.insertTemperatureHumidityData = function(sid, temperature, humidity) { 38 | var sensorSql = "insert into SensorData (sid, temperature, humidity) values (?)"; 39 | var sensorValue = [sid, temperature, humidity]; 40 | connection.query(sensorSql, [sensorValue]); 41 | }; 42 | 43 | this.insertStatusData = function(sid, status) { 44 | var sensorSql = "insert into SensorData (sid, status) values (?)"; 45 | var sensorValue = [sid, status]; 46 | connection.query(sensorSql, [sensorValue]); 47 | }; 48 | 49 | }; 50 | 51 | var helper = new databaseHelper(); 52 | module.exports = helper; -------------------------------------------------------------------------------- /nodejs/modules/moduleGateway.js: -------------------------------------------------------------------------------- 1 | var moduleGeneric = require('./moduleGeneric'); 2 | var util = require('util'); 3 | 4 | function moduleGateway() { 5 | moduleGeneric.apply(this, arguments); 6 | this.token = null; 7 | this.port = null; 8 | this.ip = null; 9 | } 10 | 11 | util.inherits(moduleGateway, moduleGeneric); 12 | 13 | moduleGateway.prototype.onHeartbeat = function(json) { 14 | this.token = this.getJsonParam(json, "token"); 15 | }; 16 | 17 | moduleGateway.prototype.onGetIdListAck = function(json) { 18 | this.token = this.getJsonParam(json, "token"); 19 | }; 20 | 21 | moduleGateway.prototype.onIam = function(json) { 22 | this.ip = json['ip']; 23 | this.port = json['port']; 24 | }; 25 | 26 | module.exports = moduleGateway; -------------------------------------------------------------------------------- /nodejs/modules/moduleGeneric.js: -------------------------------------------------------------------------------- 1 | function moduleGeneric(sid, model, databaseHelper) { 2 | this.databaseHelper = databaseHelper; 3 | this.sid = sid; 4 | this.model = model; 5 | this.events = []; 6 | } 7 | 8 | moduleGeneric.prototype.getJsonParam = function(json, paramName) { 9 | var param = json[paramName]; 10 | if (param === undefined || param == "undefined") { 11 | return null; 12 | } 13 | return param; 14 | }; 15 | 16 | moduleGeneric.prototype.setDatabaseHelper = function(helper) { 17 | this.databaseHelper = helper; 18 | }; 19 | 20 | moduleGeneric.prototype.onMessage = function(cmd, json) { 21 | var short_id = this.getJsonParam(json, "short_id"); 22 | var messageData = this.getJsonParam(json, "data"); 23 | var messageDataJson = messageData ? JSON.parse(messageData) : null; 24 | var voltage = messageDataJson ? messageDataJson['voltage'] : null; 25 | 26 | this.databaseHelper.updateHubData(cmd, this.model, this.sid, short_id, voltage); 27 | 28 | switch (cmd) { 29 | case "read_ack": 30 | this.onReadAck(json); 31 | break; 32 | case "get_id_list_ack": 33 | this.onGetIdListAck(json); 34 | break; 35 | case "iam": 36 | this.onIam(json); 37 | break; 38 | case "report": 39 | this.onReport(json); 40 | break; 41 | case "heartbeat": 42 | this.onHeartbeat(json); 43 | break; 44 | default: 45 | break; 46 | } 47 | }; 48 | 49 | moduleGeneric.prototype.onIam = function(json) { 50 | }; 51 | 52 | moduleGeneric.prototype.onReadAck = function(json) { 53 | }; 54 | 55 | moduleGeneric.prototype.onGetIdListAck = function(json) { 56 | }; 57 | 58 | moduleGeneric.prototype.onReport = function(json) { 59 | }; 60 | 61 | moduleGeneric.prototype.onHeartbeat = function(json) { 62 | }; 63 | 64 | module.exports = moduleGeneric; -------------------------------------------------------------------------------- /nodejs/modules/modulePlug.js: -------------------------------------------------------------------------------- 1 | var moduleGeneric = require('./moduleGeneric'); 2 | var util = require('util'); 3 | 4 | function modulePlug() { 5 | moduleGeneric.apply(this, arguments); 6 | this.events = [ 7 | { 8 | event: "on", 9 | data: {status: "on"} 10 | }, 11 | { 12 | event: "off", 13 | data: {status: "off"} 14 | } 15 | ]; 16 | } 17 | 18 | util.inherits(modulePlug, moduleGeneric); 19 | 20 | module.exports = modulePlug; -------------------------------------------------------------------------------- /nodejs/modules/moduleTemperature.js: -------------------------------------------------------------------------------- 1 | var moduleGeneric = require('./moduleGeneric'); 2 | var util = require('util'); 3 | 4 | function moduleTemperature() { 5 | moduleGeneric.apply(this, arguments); 6 | } 7 | 8 | util.inherits(moduleTemperature, moduleGeneric); 9 | 10 | moduleTemperature.prototype.onReadAck = function(json) { 11 | var data = JSON.parse(json['data']); 12 | var temperature = data['temperature'] ? data['temperature'] / 100.0 : 100; 13 | var humidity = data['humidity'] ? data['humidity'] / 100.0 : 0; 14 | 15 | this.databaseHelper.insertTemperatureHumidityData(this.sid, temperature, humidity); 16 | }; 17 | 18 | module.exports = moduleTemperature; -------------------------------------------------------------------------------- /nodejs/modules/modulesFactory.js: -------------------------------------------------------------------------------- 1 | var modulesFactory = function() { 2 | 3 | var modules = []; 4 | var databaseHelper; 5 | 6 | var getJsonParam = function(json, paramName) { 7 | var param = json[paramName]; 8 | if (param === undefined || param == "undefined") { 9 | return null; 10 | } 11 | return param; 12 | }; 13 | 14 | var getModuleClass = function(model) { 15 | switch (model) { 16 | case "gateway": 17 | return require('./moduleGateway'); 18 | case "plug": 19 | return require('./modulePlug'); 20 | case "sensor_ht": 21 | return require('./moduleTemperature'); 22 | case "motion": 23 | case "switch": 24 | case "magnet": 25 | case "sensor_wleak.aq1": 26 | case "sensor_switch.aq2": 27 | case "sensor_cube.aqgl01": 28 | case "86sw2": 29 | return require('./moduleGeneric'); 30 | default: 31 | return require('./moduleGeneric'); 32 | } 33 | }; 34 | 35 | var getModuleBySid = function(sid) { 36 | for (var i = 0; i < modules.length; i++) { 37 | var module = modules[i]; 38 | if (module.sid === sid) { 39 | return module; 40 | } 41 | } 42 | return null; 43 | }; 44 | 45 | var getFirstModuleByModel = function(model) { 46 | for (var i = 0; i < modules.length; i++) { 47 | var module = modules[i]; 48 | if (module.model === model) { 49 | return module; 50 | } 51 | } 52 | return null; 53 | }; 54 | 55 | this.setDatabaseHelper = function(helper) { 56 | databaseHelper = helper; 57 | }; 58 | 59 | this.onMessage = function(cmd, json) { 60 | var sid = getJsonParam(json, "sid"); 61 | var model = getJsonParam(json, "model"); 62 | 63 | var moduleObject = getModuleBySid(sid); 64 | if (moduleObject == null) { 65 | var moduleClass = getModuleClass(model); 66 | moduleObject = new moduleClass(sid, model, databaseHelper); 67 | modules.push(moduleObject); 68 | } 69 | 70 | modules.forEach(module => { 71 | if (module.sid === sid) { 72 | module.onMessage(cmd, json); 73 | } 74 | }); 75 | }; 76 | 77 | this.getGateway = function() { 78 | return getFirstModuleByModel("gateway"); 79 | }; 80 | 81 | this.getDevices = function() { 82 | var devices = []; 83 | modules.forEach(module => { 84 | devices.push({ 85 | sid: module.sid, 86 | model: module.model, 87 | events: module.events 88 | }); 89 | }); 90 | return devices; 91 | }; 92 | 93 | }; 94 | 95 | var factory = new modulesFactory(); 96 | module.exports = factory; -------------------------------------------------------------------------------- /nodejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "modulesapp", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "express": "^4.16.4", 6 | "mysql": "^2.16.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /nodejs/sensor.js: -------------------------------------------------------------------------------- 1 | const dgram = require('dgram'); 2 | var config = require('./config'); 3 | var databaseHelper = require('./databaseHelper'); 4 | var commandHelper = require('./commandHelper'); 5 | var serverHelper = require('./serverHelper'); 6 | var modulesFactory = require('./modules/modulesFactory'); 7 | 8 | const serverPort = config.serverPort; 9 | const serverSocket = dgram.createSocket('udp4'); 10 | const multicastAddress = config.multicastAddress; 11 | const multicastPort = config.multicastPort; 12 | const sensorDelay = config.sensorDelay; 13 | 14 | databaseHelper.init(config); 15 | modulesFactory.setDatabaseHelper(databaseHelper); 16 | commandHelper.setServerSocket(serverSocket); 17 | 18 | serverHelper.setModulesFactory(modulesFactory); 19 | serverHelper.setCommandHelper(commandHelper); 20 | 21 | serverSocket.on('message', function (msg, rinfo) { 22 | console.log('Received \x1b[33m%s\x1b[0m (%d bytes) from client \x1b[36m%s:%d\x1b[0m.', msg, msg.length, rinfo.address, rinfo.port); 23 | var json; 24 | try { 25 | json = JSON.parse(msg); 26 | } 27 | catch (e) { 28 | console.log('\x1b[31mUnexpected message: %s\x1b[0m.', msg); 29 | return; 30 | } 31 | 32 | var cmd = json['cmd']; 33 | modulesFactory.onMessage(cmd, json); 34 | 35 | if (cmd === 'iam') { 36 | var address = json['ip']; 37 | var port = json['port']; 38 | 39 | var command = { 40 | cmd: "get_id_list" 41 | }; 42 | console.log('Requesting devices list...'); 43 | commandHelper.sendCommand(command, port, address); 44 | } 45 | else if (cmd === 'get_id_list_ack') { 46 | var gatewaySid = json['sid']; 47 | var data = JSON.parse(json['data']); 48 | data.push(gatewaySid); 49 | console.log('Received devices list: %d device(s) connected.', data.length); 50 | for (var index in data) { 51 | var sid = data[index]; 52 | var command = { 53 | cmd: "read", 54 | sid: new String(sid) 55 | }; 56 | 57 | commandHelper.sendCommand(command, rinfo.port, rinfo.address); 58 | } 59 | } 60 | }); 61 | 62 | serverSocket.on('error', function (err) { 63 | console.log('Error, message - %s, stack - %s.', err.message, err.stack); 64 | }); 65 | 66 | serverSocket.on('listening', function () { 67 | console.log('Starting a UDP server, listening on port %d.', serverPort); 68 | serverSocket.addMembership(multicastAddress); 69 | }); 70 | 71 | console.log('Starting Aqara daemon...'); 72 | 73 | serverSocket.bind(serverPort); 74 | 75 | function sendWhois() { 76 | var command = { 77 | cmd: "whois" 78 | }; 79 | commandHelper.sendCommand(command, multicastPort, multicastAddress); 80 | } 81 | 82 | sendWhois(); 83 | 84 | setInterval(function () { 85 | console.log('Requesting data...'); 86 | sendWhois(); 87 | }, sensorDelay * 1000); 88 | -------------------------------------------------------------------------------- /nodejs/serverHelper.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | 3 | var serverHelper = function() { 4 | 5 | var modulesFactory; 6 | var commandHelper; 7 | 8 | this.setModulesFactory = function(factory) { 9 | modulesFactory = factory; 10 | }; 11 | 12 | this.setCommandHelper = function(helper) { 13 | commandHelper = helper; 14 | }; 15 | 16 | function getGateway() { 17 | return modulesFactory.getGateway(); 18 | } 19 | 20 | function init() { 21 | const app = express(); 22 | const port = 3000; 23 | 24 | app.get('/', (request, response) => { 25 | response.send('Hello from Express!'); 26 | }); 27 | 28 | app.get('/devices', (request, response) => { 29 | response.header('Content-Type', 'application/json'); 30 | response.send(modulesFactory.getDevices()); 31 | }); 32 | 33 | app.get('/color', (request, response) => { 34 | response.send('Calling color.'); 35 | var gateway = getGateway(); 36 | var command = { 37 | cmd: "write", 38 | model: "gateway", 39 | sid: gateway.sid, 40 | data: {rgb: 1677734911, illumination: 1200} 41 | }; 42 | commandHelper.sendWriteCommand(gateway.sid, command, gateway.port, gateway.ip, gateway.token); 43 | }); 44 | 45 | app.get('/sound', (request, response) => { 46 | response.send('Calling sound.'); 47 | var gateway = getGateway(); 48 | var command = { 49 | cmd: "write", 50 | model: "gateway", 51 | sid: gateway.sid, 52 | data: {mid: '8', volume: '5'} 53 | }; 54 | commandHelper.sendWriteCommand(gateway.sid, command, gateway.port, gateway.ip, gateway.token); 55 | }); 56 | 57 | app.get('/on', (request, response) => { 58 | response.send('Calling on.'); 59 | var deviceSid = "158d00024d89fb"; 60 | var gateway = getGateway(); 61 | var command = { 62 | cmd: "write", 63 | model: "plug", 64 | sid: deviceSid, 65 | data: {status: "on", key: ""} 66 | }; 67 | commandHelper.sendWriteCommand(deviceSid, command, gateway.port, gateway.ip, gateway.token); 68 | }); 69 | 70 | app.get('/off', (request, response) => { 71 | response.send('Calling off.'); 72 | var deviceSid = "158d00024d89fb"; 73 | var gateway = getGateway(); 74 | var command = { 75 | cmd: "write", 76 | model: "plug", 77 | sid: deviceSid, 78 | data: {status: "off", key: ""} 79 | }; 80 | commandHelper.sendWriteCommand(deviceSid, command, gateway.port, gateway.ip, gateway.token); 81 | }); 82 | 83 | app.listen(port, (err) => { 84 | if (err) { 85 | return console.log('Something bad happened: ', err); 86 | } 87 | console.log(`Server is listening on ${port}`); 88 | }); 89 | } 90 | 91 | init(); 92 | 93 | }; 94 | 95 | var helper = new serverHelper(); 96 | module.exports = helper; -------------------------------------------------------------------------------- /sql/ModuleSensor.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS ModuleSensor; 2 | 3 | CREATE TABLE IF NOT EXISTS ModuleSensor ( 4 | ID int(11) NOT NULL, 5 | ModuleID int(11) NOT NULL, 6 | SensorID int(11) NOT NULL, 7 | IsActive bit(1) DEFAULT NULL, 8 | Description text 9 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 10 | 11 | ALTER TABLE ModuleSensor 12 | ADD PRIMARY KEY (ID); 13 | 14 | ALTER TABLE ModuleSensor 15 | MODIFY ID int(11) NOT NULL AUTO_INCREMENT; 16 | -------------------------------------------------------------------------------- /sql/SensorData.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS SensorData; 2 | 3 | CREATE TABLE SensorData ( 4 | ID int(11) NOT NULL, 5 | UserID int(11) NOT NULL, 6 | SensorID int(11) NOT NULL, 7 | ChartVisibility bit(1) DEFAULT NULL, 8 | TableVisibility bit(1) DEFAULT NULL 9 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 10 | 11 | ALTER TABLE SensorData 12 | ADD PRIMARY KEY (ID); 13 | 14 | ALTER TABLE SensorData 15 | MODIFY ID int(11) NOT NULL AUTO_INCREMENT; -------------------------------------------------------------------------------- /sql/WeatherData.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS WeatherData; 2 | 3 | CREATE TABLE IF NOT EXISTS WeatherData ( 4 | ID int(11) NOT NULL, 5 | ModuleMAC varchar(50) NOT NULL, 6 | Temperature1 float DEFAULT NULL, 7 | Temperature2 float DEFAULT NULL, 8 | Temperature3 float DEFAULT NULL, 9 | Temperature4 float DEFAULT NULL, 10 | Humidity1 float DEFAULT NULL, 11 | Humidity2 float DEFAULT NULL, 12 | Humidity3 float DEFAULT NULL, 13 | Humidity4 float DEFAULT NULL, 14 | Pressure1 float DEFAULT NULL, 15 | Pressure2 float DEFAULT NULL, 16 | Pressure3 float DEFAULT NULL, 17 | Pressure4 float DEFAULT NULL, 18 | Illumination float DEFAULT NULL, 19 | CO2 float DEFAULT NULL, 20 | MeasuredDateTime timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP 21 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 22 | 23 | ALTER TABLE WeatherData 24 | ADD PRIMARY KEY (ID); 25 | 26 | ALTER TABLE WeatherData 27 | MODIFY ID int(11) NOT NULL AUTO_INCREMENT; 28 | -------------------------------------------------------------------------------- /sql/WeatherModule.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS WeatherModule; 2 | 3 | CREATE TABLE IF NOT EXISTS WeatherModule ( 4 | ID int(11) NOT NULL, 5 | ModuleID int(11) NOT NULL, 6 | ModuleName varchar(50) NOT NULL, 7 | MAC varchar(50) NOT NULL, 8 | IP varchar(15) NOT NULL, 9 | IsAqara bit(1) DEFAULT NULL, 10 | Description text, 11 | SensorDelay int(11) DEFAULT NULL, 12 | LastSeenDateTime timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 13 | IsActive bit(1) DEFAULT b'1', 14 | TableVisibility bit(1) DEFAULT b'1', 15 | ChartVisibility bit(1) DEFAULT b'1', 16 | ValidationCode varchar(16) DEFAULT NULL 17 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 18 | 19 | ALTER TABLE WeatherModule 20 | ADD PRIMARY KEY (ID); 21 | 22 | ALTER TABLE WeatherModule 23 | MODIFY ID int(11) NOT NULL AUTO_INCREMENT; 24 | -------------------------------------------------------------------------------- /sql/WeatherSensor.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS WeatherSensor; 2 | 3 | CREATE TABLE IF NOT EXISTS WeatherSensor ( 4 | ID int(11) NOT NULL, 5 | SensorName varchar(50) NOT NULL, 6 | Description varchar(50) DEFAULT NULL, 7 | Units varchar(50) DEFAULT NULL, 8 | ChartTitle varchar(50) DEFAULT NULL, 9 | SortOrder int(11) DEFAULT NULL 10 | ) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8; 11 | 12 | INSERT INTO `WeatherSensor`(`ID`, `SensorName`, `Description`, `Units`, `ChartTitle`, `SortOrder`) VALUES 13 | (1, 'Temperature1', 'Температура 1', '°C', 'Температура (°C)', 1), 14 | (2, 'Temperature2', 'Температура 2', '°C', 'Температура (°C)', 2), 15 | (3, 'Temperature3', 'Температура 3', '°C', 'Температура (°C)', 3), 16 | (4, 'Temperature4', 'Температура 4', '°C', 'Температура (°C)', 4), 17 | (5, 'Humidity1', 'Влажность 1', '%', 'Относительная влажность (%)', 5), 18 | (6, 'Humidity2', 'Влажность 2', '%', 'Относительная влажность (%)', 6), 19 | (7, 'Humidity3', 'Влажность 3', '%', 'Относительная влажность (%)', 7), 20 | (8, 'Humidity4', 'Влажность 4', '%', 'Относительная влажность (%)', 8), 21 | (9, 'Pressure1', 'Давление 1', 'mmHg', 'Атмосферное давление (mmHg)', 9), 22 | (10, 'Pressure2', 'Давление 2', 'mmHg', 'Атмосферное давление (mmHg)', 10), 23 | (11, 'Pressure3', 'Давление 3', 'mmHg', 'Атмосферное давление (mmHg)', 11), 24 | (12, 'Pressure4', 'Давление 4', 'mmHg', 'Атмосферное давление (mmHg)', 12), 25 | (13, 'Illumination', 'Освещенность', 'lx', 'Освещенность (lx)', 13), 26 | (14, 'CO2', 'Уровень CO2', 'ppm', 'Уровень CO2', 14); 27 | 28 | ALTER TABLE WeatherSensor 29 | ADD PRIMARY KEY (ID); 30 | 31 | ALTER TABLE WeatherSensor 32 | MODIFY ID int(11) NOT NULL AUTO_INCREMENT; -------------------------------------------------------------------------------- /sql/WeatherUser.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS WeatherUser; 2 | 3 | CREATE TABLE IF NOT EXISTS WeatherUser ( 4 | ID int(11) NOT NULL, 5 | UserName varchar(200) NOT NULL, 6 | Email varchar(200) NOT NULL, 7 | Password varchar(200) NOT NULL, 8 | LastLoginDateTime timestamp NULL DEFAULT NULL, 9 | CreatedDateTime timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 10 | VerifiedDateTime timestamp NULL DEFAULT NULL, 11 | IsActive bit(1) DEFAULT b'0', 12 | VerificationCode varchar(200) DEFAULT NULL 13 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 14 | 15 | ALTER TABLE WeatherUser 16 | ADD PRIMARY KEY (ID); 17 | 18 | ALTER TABLE WeatherUser 19 | MODIFY ID int(11) NOT NULL AUTO_INCREMENT; 20 | -------------------------------------------------------------------------------- /www/add.php: -------------------------------------------------------------------------------- 1 | 'Please use JSON to add data.' 28 | ); 29 | print json_encode($data); 30 | return; 31 | } 32 | 33 | //////////////////////////////////////////////////////////////////////////////////////////// 34 | 35 | $object = json_decode($input, true); 36 | 37 | $moduleId = (int)getParam($object, "moduleid"); 38 | $moduleName = getParam($object, "modulename"); 39 | $code = getParam($object, "code"); 40 | 41 | $ip = getParam($object, "ip"); 42 | $mac = getParam($object, "mac"); 43 | $delay = (int)getParam($object, "delay"); 44 | 45 | $isAqara = valueOrNull((int)getParam($object, "isaqara")); 46 | 47 | $temperature1 = valueOrNull((float)getParam($object, "temperature1")); 48 | $humidity1 = valueOrNull((float)getParam($object, "humidity1")); 49 | $pressure1 = valueOrNull((float)getParam($object, "pressure1")); 50 | 51 | $temperature2 = valueOrNull((float)getParam($object, "temperature2")); 52 | $humidity2 = valueOrNull((float)getParam($object, "humidity2")); 53 | $pressure2 = valueOrNull((float)getParam($object, "pressure2")); 54 | 55 | $temperature3 = valueOrNull((float)getParam($object, "temperature3")); 56 | $humidity3 = valueOrNull((float)getParam($object, "humidity3")); 57 | $pressure3 = valueOrNull((float)getParam($object, "pressure3")); 58 | 59 | $temperature4 = valueOrNull((float)getParam($object, "temperature4")); 60 | $humidity4 = valueOrNull((float)getParam($object, "humidity4")); 61 | $pressure4 = valueOrNull((float)getParam($object, "pressure4")); 62 | 63 | $illumination = valueOrNull((float)getParam($object, "illumination")); 64 | $co2level = valueOrNull((float)getParam($object, "co2")); 65 | 66 | //////////////////////////////////////////////////////////////////////////////////////////// 67 | 68 | $weatherData = (object) []; 69 | $weatherData->mac = $mac; 70 | $weatherData->temperature1 = $temperature1; 71 | $weatherData->temperature2 = $temperature2; 72 | $weatherData->temperature3 = $temperature3; 73 | $weatherData->temperature4 = $temperature4; 74 | $weatherData->humidity1 = $humidity1; 75 | $weatherData->humidity2 = $humidity2; 76 | $weatherData->humidity3 = $humidity3; 77 | $weatherData->humidity4 = $humidity4; 78 | $weatherData->pressure1 = $pressure1; 79 | $weatherData->pressure2 = $pressure2; 80 | $weatherData->pressure3 = $pressure3; 81 | $weatherData->pressure4 = $pressure4; 82 | $weatherData->illumination = $illumination; 83 | $weatherData->co2 = $co2level; 84 | 85 | //////////////////////////////////////////////////////////////////////////////////////////// 86 | 87 | $moduleData = (object) []; 88 | $moduleData->mac = $mac; 89 | $moduleData->ip = $ip; 90 | $moduleData->moduleName = $moduleName; 91 | $moduleData->moduleId = $moduleId; 92 | $moduleData->code = $code; 93 | $moduleData->delay = $delay; 94 | $moduleData->isAqara = $isAqara; 95 | 96 | //////////////////////////////////////////////////////////////////////////////////////////// 97 | 98 | $requester = new Requester; 99 | $id = $requester->addWeatherData($weatherData); 100 | $requester->updateModuleData($moduleData); 101 | 102 | //////////////////////////////////////////////////////////////////////////////////////////// 103 | 104 | $data = array( 105 | 'id' => $id, 106 | 'moduleid' => $moduleId, 107 | 'modulename' => $moduleName, 108 | 'temperature1' => $temperature1, 109 | 'temperature2' => $temperature2, 110 | 'temperature3' => $temperature3, 111 | 'temperature4' => $temperature4, 112 | 'humidity1' => $humidity1, 113 | 'humidity2' => $humidity2, 114 | 'humidity3' => $humidity3, 115 | 'humidity4' => $humidity4, 116 | 'pressure1' => $pressure1, 117 | 'pressure2' => $pressure2, 118 | 'pressure3' => $pressure3, 119 | 'pressure4' => $pressure4, 120 | 'illumination' => $illumination, 121 | 'co2' => $co2level, 122 | 'year' => (int)date('Y'), 123 | 'month' => (int)date('m'), 124 | 'day' => (int)date('d'), 125 | 'hour' => (int)date('H'), 126 | 'minute' => (int)date('i'), 127 | 'second' => (int)date('s') 128 | ); 129 | 130 | print json_encode($data); 131 | 132 | ?> -------------------------------------------------------------------------------- /www/aqara.php: -------------------------------------------------------------------------------- 1 | mac = $mac; 61 | $weatherData->temperature1 = $temperature1; 62 | $weatherData->temperature2 = $temperature2; 63 | $weatherData->temperature3 = $temperature3; 64 | $weatherData->temperature4 = $temperature4; 65 | $weatherData->humidity1 = $humidity1; 66 | $weatherData->humidity2 = $humidity2; 67 | $weatherData->humidity3 = $humidity3; 68 | $weatherData->humidity4 = $humidity4; 69 | $weatherData->pressure1 = $pressure1; 70 | $weatherData->pressure2 = $pressure2; 71 | $weatherData->pressure3 = $pressure3; 72 | $weatherData->pressure4 = $pressure4; 73 | $weatherData->illumination = $illumination; 74 | $weatherData->co2 = $co2level; 75 | 76 | //////////////////////////////////////////////////////////////////////////////////////////// 77 | 78 | $moduleData = (object) []; 79 | $moduleData->mac = $mac; 80 | $moduleData->ip = $ip; 81 | $moduleData->moduleName = $moduleName; 82 | $moduleData->moduleId = $moduleId; 83 | $moduleData->code = $code; 84 | $moduleData->delay = $delay; 85 | $moduleData->isAqara = $isAqara; 86 | 87 | //////////////////////////////////////////////////////////////////////////////////////////// 88 | 89 | $requester = new Requester; 90 | $id = $requester->addWeatherData($weatherData); 91 | $requester->updateModuleData($moduleData); 92 | 93 | //////////////////////////////////////////////////////////////////////////////////////////// 94 | 95 | $data = array( 96 | 'id' => $id, 97 | 'moduleid' => $moduleId, 98 | 'modulename' => $moduleName, 99 | 'temperature1' => $temperature1, 100 | 'temperature2' => $temperature2, 101 | 'temperature3' => $temperature3, 102 | 'temperature4' => $temperature4, 103 | 'humidity1' => $humidity1, 104 | 'humidity2' => $humidity2, 105 | 'humidity3' => $humidity3, 106 | 'humidity4' => $humidity4, 107 | 'pressure1' => $pressure1, 108 | 'pressure2' => $pressure2, 109 | 'pressure3' => $pressure3, 110 | 'pressure4' => $pressure4, 111 | 'illumination' => $illumination, 112 | 'co2' => $co2level, 113 | 'year' => (int)date('Y'), 114 | 'month' => (int)date('m'), 115 | 'day' => (int)date('d'), 116 | 'hour' => (int)date('H'), 117 | 'minute' => (int)date('i'), 118 | 'second' => (int)date('s') 119 | ); 120 | 121 | print json_encode($data); 122 | 123 | ?> -------------------------------------------------------------------------------- /www/charts.php: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | Домашняя метеостанция - Графики 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
32 |
33 | 38 |
39 |
40 | 41 |

Отображать данные с выбранных модулей (если ничего не выбрано - будут показаны все данные):

42 |
43 | 44 |
45 | 46 |
47 |
48 | 56 |
57 | 58 |
59 | 60 |

Отображать данные с сенсоров:

61 |
62 |
63 |
64 |
65 |
66 | 67 |
68 |

Здесь будут отображаться графики с данными с модулей, подключенных к Домашней метеостанции.

69 | 70 |
71 | 72 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /www/datas.php: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | Домашняя метеостанция - Данные 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
31 |
32 | 37 |
38 |
39 |

Отображать данные с выбранных модулей:

40 |
41 |
42 |

Отображать данные с сенсоров:

43 |
44 |
45 |
46 |
47 |
48 | 49 |
50 |
51 | 52 |
53 |
    54 |
  • 10
  • 55 |
  • 20
  • 56 |
  • 40
  • 57 |
58 |
59 |
60 | 61 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /www/dbconfig.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /www/emailConfig.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /www/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aproschenko-dev/WeatherHub/fa558ec22d82cfbc3136c29815904c5e255be8c9/www/favicon.ico -------------------------------------------------------------------------------- /www/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aproschenko-dev/WeatherHub/fa558ec22d82cfbc3136c29815904c5e255be8c9/www/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /www/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aproschenko-dev/WeatherHub/fa558ec22d82cfbc3136c29815904c5e255be8c9/www/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /www/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aproschenko-dev/WeatherHub/fa558ec22d82cfbc3136c29815904c5e255be8c9/www/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /www/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aproschenko-dev/WeatherHub/fa558ec22d82cfbc3136c29815904c5e255be8c9/www/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /www/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aproschenko-dev/WeatherHub/fa558ec22d82cfbc3136c29815904c5e255be8c9/www/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /www/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aproschenko-dev/WeatherHub/fa558ec22d82cfbc3136c29815904c5e255be8c9/www/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /www/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aproschenko-dev/WeatherHub/fa558ec22d82cfbc3136c29815904c5e255be8c9/www/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /www/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aproschenko-dev/WeatherHub/fa558ec22d82cfbc3136c29815904c5e255be8c9/www/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /www/images/bullet-black-alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aproschenko-dev/WeatherHub/fa558ec22d82cfbc3136c29815904c5e255be8c9/www/images/bullet-black-alt.png -------------------------------------------------------------------------------- /www/images/bullet-green-alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aproschenko-dev/WeatherHub/fa558ec22d82cfbc3136c29815904c5e255be8c9/www/images/bullet-green-alt.png -------------------------------------------------------------------------------- /www/images/bullet-orange-alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aproschenko-dev/WeatherHub/fa558ec22d82cfbc3136c29815904c5e255be8c9/www/images/bullet-orange-alt.png -------------------------------------------------------------------------------- /www/images/bullet-purple-alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aproschenko-dev/WeatherHub/fa558ec22d82cfbc3136c29815904c5e255be8c9/www/images/bullet-purple-alt.png -------------------------------------------------------------------------------- /www/images/bullet-red-alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aproschenko-dev/WeatherHub/fa558ec22d82cfbc3136c29815904c5e255be8c9/www/images/bullet-red-alt.png -------------------------------------------------------------------------------- /www/images/bullet-yellow-alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aproschenko-dev/WeatherHub/fa558ec22d82cfbc3136c29815904c5e255be8c9/www/images/bullet-yellow-alt.png -------------------------------------------------------------------------------- /www/images/home-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aproschenko-dev/WeatherHub/fa558ec22d82cfbc3136c29815904c5e255be8c9/www/images/home-icon.png -------------------------------------------------------------------------------- /www/include/common.php: -------------------------------------------------------------------------------- 1 | validateCookie($userCookie); 24 | } 25 | 26 | ?> -------------------------------------------------------------------------------- /www/include/header.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /www/include/highcharts.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /www/include/menu.php: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /www/index.php: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | Домашняя метеостанция - Главная 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |

С чего начать?

30 |

31 | На данной странице вы можете посмотреть данные со всех модулей, которые вы добавили в свою Домашнюю метеостанцию. Изначально вы увидете пустые карточки модулей, без данных с сенсоров.
32 | Чтобы увидеть данные с сенсоров - следуйте инструкции ниже.
33 | На странице Настройки вы можете увидеть все подключенные к системе модули в виде карточек.
34 | Для того, чтобы увидеть данные с сенсоров, подключенных к модулям, вам следует выбрать их в диалоговом окне, которое открывается при нажатии на кнопку "Параметры модуля" в списке ативных сенсоров.
35 | Отметив активные сенсоры, сохраните конфигурацию модуля, нажав кнопку "Сохранить". После этого на этой странице вы увидите получаемые с сенсоров данные. 36 |

37 | 38 |
39 | 40 |
41 |

Здесь будут отображаться карточки активных модулей, подключенных к Домашней метеостанции.

42 | 43 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /www/lib/PHPMailer/PHPMailerAutoload.php: -------------------------------------------------------------------------------- 1 | 8 | * @author Jim Jagielski (jimjag) 9 | * @author Andy Prevost (codeworxtech) 10 | * @author Brent R. Matzelle (original founder) 11 | * @copyright 2012 - 2014 Marcus Bointon 12 | * @copyright 2010 - 2012 Jim Jagielski 13 | * @copyright 2004 - 2009 Andy Prevost 14 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License 15 | * @note This program is distributed in the hope that it will be useful - WITHOUT 16 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 17 | * FITNESS FOR A PARTICULAR PURPOSE. 18 | */ 19 | 20 | /** 21 | * PHPMailer SPL autoloader. 22 | * @param string $classname The name of the class to load 23 | */ 24 | function PHPMailerAutoload($classname) 25 | { 26 | //Can't use __DIR__ as it's only in PHP 5.3+ 27 | $filename = dirname(__FILE__).DIRECTORY_SEPARATOR.'class.'.strtolower($classname).'.php'; 28 | if (is_readable($filename)) { 29 | require $filename; 30 | } 31 | } 32 | 33 | if (version_compare(PHP_VERSION, '5.1.2', '>=')) { 34 | //SPL autoloading was introduced in PHP 5.1.2 35 | if (version_compare(PHP_VERSION, '5.3.0', '>=')) { 36 | spl_autoload_register('PHPMailerAutoload', true, true); 37 | } else { 38 | spl_autoload_register('PHPMailerAutoload'); 39 | } 40 | } else { 41 | /** 42 | * Fall back to traditional autoload for old PHP versions 43 | * @param string $classname The name of the class to load 44 | */ 45 | function __autoload($classname) 46 | { 47 | PHPMailerAutoload($classname); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /www/lib/PHPMailer/class.phpmaileroauth.php: -------------------------------------------------------------------------------- 1 | 8 | * @author Jim Jagielski (jimjag) 9 | * @author Andy Prevost (codeworxtech) 10 | * @author Brent R. Matzelle (original founder) 11 | * @copyright 2012 - 2014 Marcus Bointon 12 | * @copyright 2010 - 2012 Jim Jagielski 13 | * @copyright 2004 - 2009 Andy Prevost 14 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License 15 | * @note This program is distributed in the hope that it will be useful - WITHOUT 16 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 17 | * FITNESS FOR A PARTICULAR PURPOSE. 18 | */ 19 | 20 | /** 21 | * PHPMailerOAuth - PHPMailer subclass adding OAuth support. 22 | * @package PHPMailer 23 | * @author @sherryl4george 24 | * @author Marcus Bointon (@Synchro) 25 | */ 26 | class PHPMailerOAuth extends PHPMailer 27 | { 28 | /** 29 | * The OAuth user's email address 30 | * @var string 31 | */ 32 | public $oauthUserEmail = ''; 33 | 34 | /** 35 | * The OAuth refresh token 36 | * @var string 37 | */ 38 | public $oauthRefreshToken = ''; 39 | 40 | /** 41 | * The OAuth client ID 42 | * @var string 43 | */ 44 | public $oauthClientId = ''; 45 | 46 | /** 47 | * The OAuth client secret 48 | * @var string 49 | */ 50 | public $oauthClientSecret = ''; 51 | 52 | /** 53 | * An instance of the PHPMailerOAuthGoogle class. 54 | * @var PHPMailerOAuthGoogle 55 | * @access protected 56 | */ 57 | protected $oauth = null; 58 | 59 | /** 60 | * Get a PHPMailerOAuthGoogle instance to use. 61 | * @return PHPMailerOAuthGoogle 62 | */ 63 | public function getOAUTHInstance() 64 | { 65 | if (!is_object($this->oauth)) { 66 | $this->oauth = new PHPMailerOAuthGoogle( 67 | $this->oauthUserEmail, 68 | $this->oauthClientSecret, 69 | $this->oauthClientId, 70 | $this->oauthRefreshToken 71 | ); 72 | } 73 | return $this->oauth; 74 | } 75 | 76 | /** 77 | * Initiate a connection to an SMTP server. 78 | * Overrides the original smtpConnect method to add support for OAuth. 79 | * @param array $options An array of options compatible with stream_context_create() 80 | * @uses SMTP 81 | * @access public 82 | * @return bool 83 | */ 84 | public function smtpConnect($options = array()) 85 | { 86 | if (is_null($this->smtp)) { 87 | $this->smtp = $this->getSMTPInstance(); 88 | } 89 | 90 | if (is_null($this->oauth)) { 91 | $this->oauth = $this->getOAUTHInstance(); 92 | } 93 | 94 | // Already connected? 95 | if ($this->smtp->connected()) { 96 | return true; 97 | } 98 | 99 | $this->smtp->setTimeout($this->Timeout); 100 | $this->smtp->setDebugLevel($this->SMTPDebug); 101 | $this->smtp->setDebugOutput($this->Debugoutput); 102 | $this->smtp->setVerp($this->do_verp); 103 | $hosts = explode(';', $this->Host); 104 | $lastexception = null; 105 | 106 | foreach ($hosts as $hostentry) { 107 | $hostinfo = array(); 108 | if (!preg_match('/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*):?([0-9]*)$/', trim($hostentry), $hostinfo)) { 109 | // Not a valid host entry 110 | continue; 111 | } 112 | // $hostinfo[2]: optional ssl or tls prefix 113 | // $hostinfo[3]: the hostname 114 | // $hostinfo[4]: optional port number 115 | // The host string prefix can temporarily override the current setting for SMTPSecure 116 | // If it's not specified, the default value is used 117 | $prefix = ''; 118 | $secure = $this->SMTPSecure; 119 | $tls = ($this->SMTPSecure == 'tls'); 120 | if ('ssl' == $hostinfo[2] or ('' == $hostinfo[2] and 'ssl' == $this->SMTPSecure)) { 121 | $prefix = 'ssl://'; 122 | $tls = false; // Can't have SSL and TLS at the same time 123 | $secure = 'ssl'; 124 | } elseif ($hostinfo[2] == 'tls') { 125 | $tls = true; 126 | // tls doesn't use a prefix 127 | $secure = 'tls'; 128 | } 129 | //Do we need the OpenSSL extension? 130 | $sslext = defined('OPENSSL_ALGO_SHA1'); 131 | if ('tls' === $secure or 'ssl' === $secure) { 132 | //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled 133 | if (!$sslext) { 134 | throw new phpmailerException($this->lang('extension_missing').'openssl', self::STOP_CRITICAL); 135 | } 136 | } 137 | $host = $hostinfo[3]; 138 | $port = $this->Port; 139 | $tport = (integer)$hostinfo[4]; 140 | if ($tport > 0 and $tport < 65536) { 141 | $port = $tport; 142 | } 143 | if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) { 144 | try { 145 | if ($this->Helo) { 146 | $hello = $this->Helo; 147 | } else { 148 | $hello = $this->serverHostname(); 149 | } 150 | $this->smtp->hello($hello); 151 | //Automatically enable TLS encryption if: 152 | // * it's not disabled 153 | // * we have openssl extension 154 | // * we are not already using SSL 155 | // * the server offers STARTTLS 156 | if ($this->SMTPAutoTLS and $sslext and $secure != 'ssl' and $this->smtp->getServerExt('STARTTLS')) { 157 | $tls = true; 158 | } 159 | if ($tls) { 160 | if (!$this->smtp->startTLS()) { 161 | throw new phpmailerException($this->lang('connect_host')); 162 | } 163 | // We must resend HELO after tls negotiation 164 | $this->smtp->hello($hello); 165 | } 166 | if ($this->SMTPAuth) { 167 | if (!$this->smtp->authenticate( 168 | $this->Username, 169 | $this->Password, 170 | $this->AuthType, 171 | $this->Realm, 172 | $this->Workstation, 173 | $this->oauth 174 | ) 175 | ) { 176 | throw new phpmailerException($this->lang('authenticate')); 177 | } 178 | } 179 | return true; 180 | } catch (phpmailerException $exc) { 181 | $lastexception = $exc; 182 | $this->edebug($exc->getMessage()); 183 | // We must have connected, but then failed TLS or Auth, so close connection nicely 184 | $this->smtp->quit(); 185 | } 186 | } 187 | } 188 | // If we get here, all connection attempts have failed, so close connection hard 189 | $this->smtp->close(); 190 | // As we've caught all exceptions, just report whatever the last one was 191 | if ($this->exceptions and !is_null($lastexception)) { 192 | throw $lastexception; 193 | } 194 | return false; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /www/lib/PHPMailer/class.phpmaileroauthgoogle.php: -------------------------------------------------------------------------------- 1 | 8 | * @author Jim Jagielski (jimjag) 9 | * @author Andy Prevost (codeworxtech) 10 | * @author Brent R. Matzelle (original founder) 11 | * @copyright 2012 - 2014 Marcus Bointon 12 | * @copyright 2010 - 2012 Jim Jagielski 13 | * @copyright 2004 - 2009 Andy Prevost 14 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License 15 | * @note This program is distributed in the hope that it will be useful - WITHOUT 16 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 17 | * FITNESS FOR A PARTICULAR PURPOSE. 18 | */ 19 | 20 | /** 21 | * PHPMailerOAuthGoogle - Wrapper for League OAuth2 Google provider. 22 | * @package PHPMailer 23 | * @author @sherryl4george 24 | * @author Marcus Bointon (@Synchro) 25 | * @link https://github.com/thephpleague/oauth2-client 26 | */ 27 | class PHPMailerOAuthGoogle 28 | { 29 | private $oauthUserEmail = ''; 30 | private $oauthRefreshToken = ''; 31 | private $oauthClientId = ''; 32 | private $oauthClientSecret = ''; 33 | 34 | /** 35 | * @param string $UserEmail 36 | * @param string $ClientSecret 37 | * @param string $ClientId 38 | * @param string $RefreshToken 39 | */ 40 | public function __construct( 41 | $UserEmail, 42 | $ClientSecret, 43 | $ClientId, 44 | $RefreshToken 45 | ) { 46 | $this->oauthClientId = $ClientId; 47 | $this->oauthClientSecret = $ClientSecret; 48 | $this->oauthRefreshToken = $RefreshToken; 49 | $this->oauthUserEmail = $UserEmail; 50 | } 51 | 52 | private function getProvider() 53 | { 54 | return new League\OAuth2\Client\Provider\Google([ 55 | 'clientId' => $this->oauthClientId, 56 | 'clientSecret' => $this->oauthClientSecret 57 | ]); 58 | } 59 | 60 | private function getGrant() 61 | { 62 | return new \League\OAuth2\Client\Grant\RefreshToken(); 63 | } 64 | 65 | private function getToken() 66 | { 67 | $provider = $this->getProvider(); 68 | $grant = $this->getGrant(); 69 | return $provider->getAccessToken($grant, ['refresh_token' => $this->oauthRefreshToken]); 70 | } 71 | 72 | public function getOauth64() 73 | { 74 | $token = $this->getToken(); 75 | return base64_encode("user=" . $this->oauthUserEmail . "\001auth=Bearer " . $token . "\001\001"); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /www/lib/PHPMailer/class.pop3.php: -------------------------------------------------------------------------------- 1 | 8 | * @author Jim Jagielski (jimjag) 9 | * @author Andy Prevost (codeworxtech) 10 | * @author Brent R. Matzelle (original founder) 11 | * @copyright 2012 - 2014 Marcus Bointon 12 | * @copyright 2010 - 2012 Jim Jagielski 13 | * @copyright 2004 - 2009 Andy Prevost 14 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License 15 | * @note This program is distributed in the hope that it will be useful - WITHOUT 16 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 17 | * FITNESS FOR A PARTICULAR PURPOSE. 18 | */ 19 | 20 | /** 21 | * PHPMailer POP-Before-SMTP Authentication Class. 22 | * Specifically for PHPMailer to use for RFC1939 POP-before-SMTP authentication. 23 | * Does not support APOP. 24 | * @package PHPMailer 25 | * @author Richard Davey (original author) 26 | * @author Marcus Bointon (Synchro/coolbru) 27 | * @author Jim Jagielski (jimjag) 28 | * @author Andy Prevost (codeworxtech) 29 | */ 30 | class POP3 31 | { 32 | /** 33 | * The POP3 PHPMailer Version number. 34 | * @var string 35 | * @access public 36 | */ 37 | public $Version = '5.2.15'; 38 | 39 | /** 40 | * Default POP3 port number. 41 | * @var integer 42 | * @access public 43 | */ 44 | public $POP3_PORT = 110; 45 | 46 | /** 47 | * Default timeout in seconds. 48 | * @var integer 49 | * @access public 50 | */ 51 | public $POP3_TIMEOUT = 30; 52 | 53 | /** 54 | * POP3 Carriage Return + Line Feed. 55 | * @var string 56 | * @access public 57 | * @deprecated Use the constant instead 58 | */ 59 | public $CRLF = "\r\n"; 60 | 61 | /** 62 | * Debug display level. 63 | * Options: 0 = no, 1+ = yes 64 | * @var integer 65 | * @access public 66 | */ 67 | public $do_debug = 0; 68 | 69 | /** 70 | * POP3 mail server hostname. 71 | * @var string 72 | * @access public 73 | */ 74 | public $host; 75 | 76 | /** 77 | * POP3 port number. 78 | * @var integer 79 | * @access public 80 | */ 81 | public $port; 82 | 83 | /** 84 | * POP3 Timeout Value in seconds. 85 | * @var integer 86 | * @access public 87 | */ 88 | public $tval; 89 | 90 | /** 91 | * POP3 username 92 | * @var string 93 | * @access public 94 | */ 95 | public $username; 96 | 97 | /** 98 | * POP3 password. 99 | * @var string 100 | * @access public 101 | */ 102 | public $password; 103 | 104 | /** 105 | * Resource handle for the POP3 connection socket. 106 | * @var resource 107 | * @access protected 108 | */ 109 | protected $pop_conn; 110 | 111 | /** 112 | * Are we connected? 113 | * @var boolean 114 | * @access protected 115 | */ 116 | protected $connected = false; 117 | 118 | /** 119 | * Error container. 120 | * @var array 121 | * @access protected 122 | */ 123 | protected $errors = array(); 124 | 125 | /** 126 | * Line break constant 127 | */ 128 | const CRLF = "\r\n"; 129 | 130 | /** 131 | * Simple static wrapper for all-in-one POP before SMTP 132 | * @param $host 133 | * @param integer|boolean $port The port number to connect to 134 | * @param integer|boolean $timeout The timeout value 135 | * @param string $username 136 | * @param string $password 137 | * @param integer $debug_level 138 | * @return boolean 139 | */ 140 | public static function popBeforeSmtp( 141 | $host, 142 | $port = false, 143 | $timeout = false, 144 | $username = '', 145 | $password = '', 146 | $debug_level = 0 147 | ) { 148 | $pop = new POP3; 149 | return $pop->authorise($host, $port, $timeout, $username, $password, $debug_level); 150 | } 151 | 152 | /** 153 | * Authenticate with a POP3 server. 154 | * A connect, login, disconnect sequence 155 | * appropriate for POP-before SMTP authorisation. 156 | * @access public 157 | * @param string $host The hostname to connect to 158 | * @param integer|boolean $port The port number to connect to 159 | * @param integer|boolean $timeout The timeout value 160 | * @param string $username 161 | * @param string $password 162 | * @param integer $debug_level 163 | * @return boolean 164 | */ 165 | public function authorise($host, $port = false, $timeout = false, $username = '', $password = '', $debug_level = 0) 166 | { 167 | $this->host = $host; 168 | // If no port value provided, use default 169 | if (false === $port) { 170 | $this->port = $this->POP3_PORT; 171 | } else { 172 | $this->port = (integer)$port; 173 | } 174 | // If no timeout value provided, use default 175 | if (false === $timeout) { 176 | $this->tval = $this->POP3_TIMEOUT; 177 | } else { 178 | $this->tval = (integer)$timeout; 179 | } 180 | $this->do_debug = $debug_level; 181 | $this->username = $username; 182 | $this->password = $password; 183 | // Reset the error log 184 | $this->errors = array(); 185 | // connect 186 | $result = $this->connect($this->host, $this->port, $this->tval); 187 | if ($result) { 188 | $login_result = $this->login($this->username, $this->password); 189 | if ($login_result) { 190 | $this->disconnect(); 191 | return true; 192 | } 193 | } 194 | // We need to disconnect regardless of whether the login succeeded 195 | $this->disconnect(); 196 | return false; 197 | } 198 | 199 | /** 200 | * Connect to a POP3 server. 201 | * @access public 202 | * @param string $host 203 | * @param integer|boolean $port 204 | * @param integer $tval 205 | * @return boolean 206 | */ 207 | public function connect($host, $port = false, $tval = 30) 208 | { 209 | // Are we already connected? 210 | if ($this->connected) { 211 | return true; 212 | } 213 | 214 | //On Windows this will raise a PHP Warning error if the hostname doesn't exist. 215 | //Rather than suppress it with @fsockopen, capture it cleanly instead 216 | set_error_handler(array($this, 'catchWarning')); 217 | 218 | if (false === $port) { 219 | $port = $this->POP3_PORT; 220 | } 221 | 222 | // connect to the POP3 server 223 | $this->pop_conn = fsockopen( 224 | $host, // POP3 Host 225 | $port, // Port # 226 | $errno, // Error Number 227 | $errstr, // Error Message 228 | $tval 229 | ); // Timeout (seconds) 230 | // Restore the error handler 231 | restore_error_handler(); 232 | 233 | // Did we connect? 234 | if (false === $this->pop_conn) { 235 | // It would appear not... 236 | $this->setError(array( 237 | 'error' => "Failed to connect to server $host on port $port", 238 | 'errno' => $errno, 239 | 'errstr' => $errstr 240 | )); 241 | return false; 242 | } 243 | 244 | // Increase the stream time-out 245 | stream_set_timeout($this->pop_conn, $tval, 0); 246 | 247 | // Get the POP3 server response 248 | $pop3_response = $this->getResponse(); 249 | // Check for the +OK 250 | if ($this->checkResponse($pop3_response)) { 251 | // The connection is established and the POP3 server is talking 252 | $this->connected = true; 253 | return true; 254 | } 255 | return false; 256 | } 257 | 258 | /** 259 | * Log in to the POP3 server. 260 | * Does not support APOP (RFC 2828, 4949). 261 | * @access public 262 | * @param string $username 263 | * @param string $password 264 | * @return boolean 265 | */ 266 | public function login($username = '', $password = '') 267 | { 268 | if (!$this->connected) { 269 | $this->setError('Not connected to POP3 server'); 270 | } 271 | if (empty($username)) { 272 | $username = $this->username; 273 | } 274 | if (empty($password)) { 275 | $password = $this->password; 276 | } 277 | 278 | // Send the Username 279 | $this->sendString("USER $username" . self::CRLF); 280 | $pop3_response = $this->getResponse(); 281 | if ($this->checkResponse($pop3_response)) { 282 | // Send the Password 283 | $this->sendString("PASS $password" . self::CRLF); 284 | $pop3_response = $this->getResponse(); 285 | if ($this->checkResponse($pop3_response)) { 286 | return true; 287 | } 288 | } 289 | return false; 290 | } 291 | 292 | /** 293 | * Disconnect from the POP3 server. 294 | * @access public 295 | */ 296 | public function disconnect() 297 | { 298 | $this->sendString('QUIT'); 299 | //The QUIT command may cause the daemon to exit, which will kill our connection 300 | //So ignore errors here 301 | try { 302 | @fclose($this->pop_conn); 303 | } catch (Exception $e) { 304 | //Do nothing 305 | }; 306 | } 307 | 308 | /** 309 | * Get a response from the POP3 server. 310 | * $size is the maximum number of bytes to retrieve 311 | * @param integer $size 312 | * @return string 313 | * @access protected 314 | */ 315 | protected function getResponse($size = 128) 316 | { 317 | $response = fgets($this->pop_conn, $size); 318 | if ($this->do_debug >= 1) { 319 | echo "Server -> Client: $response"; 320 | } 321 | return $response; 322 | } 323 | 324 | /** 325 | * Send raw data to the POP3 server. 326 | * @param string $string 327 | * @return integer 328 | * @access protected 329 | */ 330 | protected function sendString($string) 331 | { 332 | if ($this->pop_conn) { 333 | if ($this->do_debug >= 2) { //Show client messages when debug >= 2 334 | echo "Client -> Server: $string"; 335 | } 336 | return fwrite($this->pop_conn, $string, strlen($string)); 337 | } 338 | return 0; 339 | } 340 | 341 | /** 342 | * Checks the POP3 server response. 343 | * Looks for for +OK or -ERR. 344 | * @param string $string 345 | * @return boolean 346 | * @access protected 347 | */ 348 | protected function checkResponse($string) 349 | { 350 | if (substr($string, 0, 3) !== '+OK') { 351 | $this->setError(array( 352 | 'error' => "Server reported an error: $string", 353 | 'errno' => 0, 354 | 'errstr' => '' 355 | )); 356 | return false; 357 | } else { 358 | return true; 359 | } 360 | } 361 | 362 | /** 363 | * Add an error to the internal error store. 364 | * Also display debug output if it's enabled. 365 | * @param $error 366 | * @access protected 367 | */ 368 | protected function setError($error) 369 | { 370 | $this->errors[] = $error; 371 | if ($this->do_debug >= 1) { 372 | echo '
';
373 |             foreach ($this->errors as $error) {
374 |                 print_r($error);
375 |             }
376 |             echo '
'; 377 | } 378 | } 379 | 380 | /** 381 | * Get an array of error messages, if any. 382 | * @return array 383 | */ 384 | public function getErrors() 385 | { 386 | return $this->errors; 387 | } 388 | 389 | /** 390 | * POP3 connection error handler. 391 | * @param integer $errno 392 | * @param string $errstr 393 | * @param string $errfile 394 | * @param integer $errline 395 | * @access protected 396 | */ 397 | protected function catchWarning($errno, $errstr, $errfile, $errline) 398 | { 399 | $this->setError(array( 400 | 'error' => "Connecting to the POP3 server raised a PHP warning: ", 401 | 'errno' => $errno, 402 | 'errstr' => $errstr, 403 | 'errfile' => $errfile, 404 | 'errline' => $errline 405 | )); 406 | } 407 | } 408 | -------------------------------------------------------------------------------- /www/lib/PHPMailer/get_oauth_token.php: -------------------------------------------------------------------------------- 1 | //get_oauth_token.php 6 | * e.g.: http://localhost/phpmail/get_oauth_token.php 7 | * * Ensure dependencies are installed with 'composer install' 8 | * * Set up an app in your Google developer console 9 | * * Set the script address as the app's redirect URL 10 | * If no refresh token is obtained when running this file, revoke access to your app 11 | * using link: https://accounts.google.com/b/0/IssuedAuthSubTokens and run the script again. 12 | * This script requires PHP 5.4 or later 13 | * PHP Version 5.4 14 | */ 15 | 16 | namespace League\OAuth2\Client\Provider; 17 | 18 | require 'vendor/autoload.php'; 19 | 20 | use League\OAuth2\Client\Provider\Exception\IdentityProviderException; 21 | use League\OAuth2\Client\Token\AccessToken; 22 | use League\OAuth2\Client\Tool\BearerAuthorizationTrait; 23 | use Psr\Http\Message\ResponseInterface; 24 | 25 | session_start(); 26 | 27 | //If this automatic URL doesn't work, set it yourself manually 28 | $redirectUri = isset($_SERVER['HTTPS']) ? 'https://' : 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF']; 29 | //$redirectUri = 'http://localhost/phpmailer/get_oauth_token.php'; 30 | 31 | //These details obtained are by setting up app in Google developer console. 32 | $clientId = 'RANDOMCHARS-----duv1n2.apps.googleusercontent.com'; 33 | $clientSecret = 'RANDOMCHARS-----lGyjPcRtvP'; 34 | 35 | class Google extends AbstractProvider 36 | { 37 | use BearerAuthorizationTrait; 38 | 39 | const ACCESS_TOKEN_RESOURCE_OWNER_ID = 'id'; 40 | 41 | /** 42 | * @var string If set, this will be sent to google as the "access_type" parameter. 43 | * @link https://developers.google.com/accounts/docs/OAuth2WebServer#offline 44 | */ 45 | protected $accessType; 46 | 47 | /** 48 | * @var string If set, this will be sent to google as the "hd" parameter. 49 | * @link https://developers.google.com/accounts/docs/OAuth2Login#hd-param 50 | */ 51 | protected $hostedDomain; 52 | 53 | /** 54 | * @var string If set, this will be sent to google as the "scope" parameter. 55 | * @link https://developers.google.com/gmail/api/auth/scopes 56 | */ 57 | protected $scope; 58 | 59 | public function getBaseAuthorizationUrl() 60 | { 61 | return 'https://accounts.google.com/o/oauth2/auth'; 62 | } 63 | 64 | public function getBaseAccessTokenUrl(array $params) 65 | { 66 | return 'https://accounts.google.com/o/oauth2/token'; 67 | } 68 | 69 | public function getResourceOwnerDetailsUrl(AccessToken $token) 70 | { 71 | return ' '; 72 | } 73 | 74 | protected function getAuthorizationParameters(array $options) 75 | { 76 | if (is_array($this->scope)) { 77 | $separator = $this->getScopeSeparator(); 78 | $this->scope = implode($separator, $this->scope); 79 | } 80 | 81 | $params = array_merge( 82 | parent::getAuthorizationParameters($options), 83 | array_filter([ 84 | 'hd' => $this->hostedDomain, 85 | 'access_type' => $this->accessType, 86 | 'scope' => $this->scope, 87 | // if the user is logged in with more than one account ask which one to use for the login! 88 | 'authuser' => '-1' 89 | ]) 90 | ); 91 | return $params; 92 | } 93 | 94 | protected function getDefaultScopes() 95 | { 96 | return [ 97 | 'email', 98 | 'openid', 99 | 'profile', 100 | ]; 101 | } 102 | 103 | protected function getScopeSeparator() 104 | { 105 | return ' '; 106 | } 107 | 108 | protected function checkResponse(ResponseInterface $response, $data) 109 | { 110 | if (!empty($data['error'])) { 111 | $code = 0; 112 | $error = $data['error']; 113 | 114 | if (is_array($error)) { 115 | $code = $error['code']; 116 | $error = $error['message']; 117 | } 118 | 119 | throw new IdentityProviderException($error, $code, $data); 120 | } 121 | } 122 | 123 | protected function createResourceOwner(array $response, AccessToken $token) 124 | { 125 | return new GoogleUser($response); 126 | } 127 | } 128 | 129 | 130 | //Set Redirect URI in Developer Console as [https/http]:////get_oauth_token.php 131 | $provider = new Google( 132 | array( 133 | 'clientId' => $clientId, 134 | 'clientSecret' => $clientSecret, 135 | 'redirectUri' => $redirectUri, 136 | 'scope' => array('https://mail.google.com/'), 137 | 'accessType' => 'offline' 138 | ) 139 | ); 140 | 141 | if (!isset($_GET['code'])) { 142 | // If we don't have an authorization code then get one 143 | $authUrl = $provider->getAuthorizationUrl(); 144 | $_SESSION['oauth2state'] = $provider->getState(); 145 | header('Location: ' . $authUrl); 146 | exit; 147 | // Check given state against previously stored one to mitigate CSRF attack 148 | } elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) { 149 | unset($_SESSION['oauth2state']); 150 | exit('Invalid state'); 151 | } else { 152 | // Try to get an access token (using the authorization code grant) 153 | $token = $provider->getAccessToken( 154 | 'authorization_code', 155 | array( 156 | 'code' => $_GET['code'] 157 | ) 158 | ); 159 | 160 | // Use this to get a new access token if the old one expires 161 | echo 'Refresh Token: ' . $token->getRefreshToken(); 162 | } 163 | -------------------------------------------------------------------------------- /www/login.php: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | Домашняя метеостанция - Войти на сайт 18 | 19 | 20 | 23 | 24 | 25 | 26 |
27 |
28 |
29 |
30 |
31 |
32 | 33 |
34 |
35 |

Домашняя метеостанция

36 |
37 |
38 | 39 | 40 |
41 |
42 | 43 | 44 |
45 |
46 | 47 | 48 |
49 |
50 | 51 |
52 | 55 | 56 |
57 |
58 |
59 |
60 |
61 | 62 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /www/logout.php: -------------------------------------------------------------------------------- 1 | 21 | 22 | 23 | 24 | Домашняя метеостанция - Выход 25 | 26 | 29 | 30 | 31 | 32 |
33 |
34 |
35 |
36 |
37 |
38 | 39 |
40 |
41 |

Домашняя метеостанция

42 |
43 | 44 |
45 |
46 |
47 |
48 |
49 | 50 | 51 | -------------------------------------------------------------------------------- /www/queryData.php: -------------------------------------------------------------------------------- 1 | sensors = $requester->getData("SELECT * FROM WeatherSensor ORDER BY SortOrder"); 32 | } 33 | 34 | if ($getSensorsData) { 35 | $allData->sensorsData = $requester->getData("SELECT SensorID, TableVisibility, ChartVisibility FROM SensorData"); 36 | } 37 | 38 | if ($getModules) { 39 | 40 | $sortBy = (isset($_REQUEST["modulesSortBy"])) ? $_REQUEST["modulesSortBy"] : null; 41 | $sortClause = ""; 42 | if (isset($sortBy)) { 43 | $sortClause = " ORDER BY $sortBy"; 44 | } 45 | 46 | $whereClause = ""; 47 | if ($publicServer) { 48 | $whereClause = "WHERE ValidationCode = '" . $_SESSION[$userSessionVarName]->verificationCode . "'"; 49 | } 50 | 51 | $params = (object) []; 52 | $params->whereClause = $whereClause; 53 | $params->sortClause = $sortClause; 54 | $params->getModuleSensors = (isset($_REQUEST["getModuleSensors"]) ? (int)$_REQUEST["getModuleSensors"] : 0) == 1; 55 | $params->getModuleWeather = (isset($_REQUEST["getModuleWeather"]) ? (int)$_REQUEST["getModuleWeather"] : 0) == 1; 56 | 57 | $allData->modules = $requester->getModulesData($params); 58 | } 59 | 60 | if ($getWeather) { 61 | 62 | $interval = ""; 63 | 64 | $sortBy = isset($_REQUEST["sortBy"]) ? $_REQUEST["sortBy"] : "ID"; 65 | $sortAscending = isset($_REQUEST["sortAscending"]) ? (($_REQUEST["sortAscending"] == "true") ? "ASC" : "DESC") : "ASC"; 66 | $pageIndex = isset($_REQUEST["pageIndex"]) ? (int)$_REQUEST["pageIndex"] : 0; 67 | $pageSize = isset($_REQUEST["pageSize"]) ? (int)$_REQUEST["pageSize"] : 20; 68 | $queryType = $_REQUEST["queryType"]; 69 | $fromDataPage = isset($_REQUEST["fromDataPage"]) ? (int)$_REQUEST["fromDataPage"] : 0; 70 | $fromChartsPage = isset($_REQUEST["fromChartsPage"]) ? (int)$_REQUEST["fromChartsPage"] : 0; 71 | if ($queryType != "all") 72 | $interval = $_REQUEST["interval"]; 73 | 74 | $params = (object) []; 75 | $params->sortBy = $sortBy; 76 | $params->sortAscending = $sortAscending; 77 | $params->pageIndex = $pageIndex; 78 | $params->pageSize = $pageSize; 79 | $params->queryType = $queryType; 80 | $params->interval = $interval; 81 | $params->fromChartsPage = $fromChartsPage; 82 | $params->fromDataPage = $fromDataPage; 83 | 84 | $allData->weather = $requester->getWeatherData($params); 85 | } 86 | 87 | print json_encode($allData, JSON_UNESCAPED_UNICODE); 88 | 89 | ?> -------------------------------------------------------------------------------- /www/queryUserData.php: -------------------------------------------------------------------------------- 1 | checkUser($email); 22 | 23 | // create new user 24 | if ($count == 0) { 25 | $id = $requester->registerUser($email, $password); 26 | if ($id > 0) { 27 | $allData->result = true; 28 | $allData->userId = $id; 29 | } 30 | } else { 31 | $allData->alreadyRegistered = true; 32 | $allData->result = false; 33 | } 34 | } 35 | 36 | if ($action == "login") { 37 | $email = $_REQUEST["email"]; 38 | $password = $_REQUEST["password"]; 39 | $setCookie = $_REQUEST["setCookie"]; 40 | 41 | $result = $requester->loginUser($email, $password, $setCookie); 42 | $allData->result = $result; 43 | } 44 | 45 | if ($action == "validate") { 46 | $code = $_REQUEST["code"]; 47 | $requester->validateUser($code); 48 | $allData = $requester->getData("SELECT IsActive FROM WeatherUser WHERE VerificationCode = '$code'"); 49 | } 50 | 51 | print json_encode($allData, JSON_UNESCAPED_UNICODE); 52 | 53 | ?> -------------------------------------------------------------------------------- /www/register.php: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | Домашняя метеостанция - Регистрация 18 | 19 | 20 | 23 | 24 | 25 | 26 |
27 |
28 |
29 |
30 |
31 |
32 | 33 |
34 |
35 |

Домашняя метеостанция

36 |
37 |
38 | 39 | 40 |
41 |
42 | 43 | 44 |
45 |
46 | 47 |
48 | 51 | 52 |
53 |
54 |
55 |
56 |
57 | 58 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /www/restorePassword.php: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | Домашняя метеостанция - Восстановление пароля 18 | 19 | 20 | 23 | 24 | 25 | 26 |
27 |
28 |
29 |
30 |
31 |
32 | 33 |
34 |
35 |

Домашняя метеостанция

36 |
37 |
38 | 39 | 40 |
41 |
42 | 43 |
44 | 47 | 48 |
49 |
50 |
51 |
52 |
53 | 54 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /www/scripts/FormatUtil.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | var FormatUtils = new function () { 4 | var thisRef = this; 5 | 6 | thisRef.isTruncating = function (number, fractionDigits) { 7 | var hund = Math.pow(10, fractionDigits); 8 | return Math.round(number * hund) / hund != number; 9 | }; 10 | 11 | thisRef.formatUSCurrencyWithSubpenny = function (value, fractionDigits, decimalSeparator, groupSeparator) { 12 | var precision = Math.round(Math.abs(value) * 10000) % 100 < 0.01 ? 2 : 4; 13 | fractionDigits = fractionDigits || (value < 1 ? 4 : 2); 14 | return thisRef.formatCurrency(value, Math.min(fractionDigits, precision), decimalSeparator, groupSeparator); 15 | }; 16 | 17 | thisRef.formatCurrency = function (value, fractionDigits, decimalSeparator, groupSeparator) { 18 | /// Format given value to string in US number format. 19 | if (fractionDigits == null || isNaN(fractionDigits) || fractionDigits < 0) { 20 | fractionDigits = 2; 21 | } 22 | 23 | if (typeof (value) == "string") { 24 | if (value == "-" || value == "–" || value == "—") 25 | return "-"; 26 | 27 | value = parseFloat(value); 28 | } 29 | 30 | if (value == null) { 31 | value = 0; 32 | } 33 | 34 | if (isNaN(value) || !isFinite(value)) { 35 | return "NaN"; 36 | } 37 | 38 | if (decimalSeparator == null) 39 | decimalSeparator = "."; 40 | if (groupSeparator == null) 41 | groupSeparator = ","; 42 | 43 | var v = Math.abs(value); 44 | var t = v % 1; 45 | var fixed = t.toFixed(fractionDigits); 46 | var res = fixed.replace(".", decimalSeparator); 47 | res = res.substring(1, res.length); 48 | v = Math.floor(v); 49 | if (fixed >= 1.0) { 50 | v += 1; 51 | } 52 | 53 | while (v >= 1000) { 54 | t = v % 1000; 55 | 56 | res = t + res; 57 | if (t < 10) { 58 | res = "00" + res; 59 | } 60 | else if (t < 100) { 61 | res = "0" + res; 62 | } 63 | res = groupSeparator + res; 64 | 65 | v = Math.floor(v / 1000); 66 | } 67 | 68 | res = v.toString() + res; 69 | 70 | if (value < 0) { 71 | res = "-" + res; 72 | } 73 | 74 | return res; 75 | }; 76 | 77 | thisRef.formatCurrencyFull = function (value) { 78 | /// Format given value to string in US number format. Uses ($10) format for negative values. 79 | var text = "$" + thisRef.formatCurrency(Math.abs(value)); 80 | if (value < 0) text = "(" + text + ")"; 81 | return text; 82 | }; 83 | 84 | thisRef.formatCurrencyMinus = function (value) { 85 | /// Format given value to string in US number format. Uses minus for negative values. 86 | var text = "$" + thisRef.formatCurrency(Math.abs(value)); 87 | if (value < 0) { 88 | text = "-" + text; 89 | } 90 | return text; 91 | }; 92 | 93 | thisRef.formatFloatFixed = function (floatValue) { 94 | return floatValue.toFixed(2); 95 | }; 96 | 97 | thisRef.formatInt = function (intValue) { 98 | /// Return string for given integer value. 99 | return intValue.toString(); 100 | }; 101 | 102 | thisRef.parseCurrency = function (textValue, precision) { 103 | /// Parse given string value to number. Precision is optional and by default equals 2. 104 | return parseFloat(thisRef.prepareCurrencyToParse(textValue, precision)); 105 | }; 106 | 107 | thisRef.prepareCurrencyToParse = function (textValue, precision) { 108 | /// Replace separators in given string and returns it. Precision is optional and by default equals 2. 109 | if (typeof (textValue) == "undefined") { 110 | return NaN; 111 | } 112 | if (typeof (textValue) == "number") { 113 | return textValue; 114 | } 115 | 116 | if (typeof (precision) != "number" || precision < 0) { 117 | precision = 2; 118 | } 119 | 120 | var preResult = textValue; 121 | preResult = preResult.replace(String.fromCharCode(160), "").replace(" ", "").replace(",", ""); 122 | var dotIndex = preResult.indexOf('.'); 123 | 124 | if (dotIndex != -1) { 125 | preResult = preResult.substring(0, dotIndex + 1 + precision); 126 | if (preResult.charAt(preResult.length - 1 - precision) != '.') { 127 | preResult = preResult + "0"; 128 | } 129 | } 130 | 131 | return preResult; 132 | }; 133 | 134 | //parse date in the form MM/DD/YYYY 135 | //returs: 136 | // Date object containing date (hours, minutes, seconds, milliseconds set to 0) 137 | // null if text does not contain valid date 138 | thisRef.parseDate = function (textValue) { 139 | /// Return date object fron given string. 140 | if (textValue.indexOf(" ") != -1) 141 | textValue = textValue.split(" ")[0]; 142 | 143 | var vals = textValue.split("/"); 144 | if (vals.length != 3) { 145 | return null; 146 | } 147 | var dat = new Date(); 148 | dat.setMilliseconds(0); 149 | dat.setSeconds(0); 150 | dat.setMinutes(0); 151 | dat.setHours(0); 152 | var m = parseInt(vals[0], 10); 153 | if (m == null || isNaN(m) || m < 1 || m > 12) { 154 | return null; 155 | } 156 | 157 | var d = parseInt(vals[1], 10); 158 | if (d == null || isNaN(d) || d < 1 || d > 31) { 159 | return null; 160 | } 161 | 162 | var y = parseInt(vals[2], 10); 163 | if (y == null || isNaN(y) || y < 1970) { 164 | return null; 165 | } 166 | dat.setFullYear(y, m - 1, d); 167 | return dat; 168 | }; 169 | 170 | //date1 - Date object representing the first date 171 | //date2 -Date object representing the second date 172 | //Return values: 173 | // 0 - date1 = date2 174 | // 1 - date1 > date2 175 | // -1 - date1 < date2 176 | thisRef.compareDates = function (date1, date2) { 177 | /// Compare two dates ignoring time part. 178 | //alert("year1="+date1.getFullYear()+"; month1="+date1.getMonth()+"; day1="+date1.getDate()+" year2="+date2.getFullYear()+"; month2="+date2.getMonth()+"; day2="+date2.getDate()); 179 | if (date1.getFullYear() < date2.getFullYear()) { 180 | return -1; 181 | } 182 | else if (date1.getFullYear() > date2.getFullYear()) { 183 | return 1; 184 | } 185 | else { 186 | if (date1.getMonth() < date2.getMonth()) { 187 | return -1; 188 | } 189 | else if (date1.getMonth() > date2.getMonth()) { 190 | return 1; 191 | } 192 | else { 193 | if (date1.getDate() < date2.getDate()) { 194 | return -1; 195 | } 196 | else if (date1.getDate() > date2.getDate()) { 197 | return 1; 198 | } 199 | else { 200 | return 0; 201 | } 202 | } 203 | } 204 | }; 205 | 206 | thisRef.getURLParam = function (strParamName) { 207 | /// Return URL parameter value from its name. 208 | var strReturn = ""; 209 | var strHref = window.location.href; 210 | if (strHref.indexOf("?") > -1) { 211 | var strQueryString = strHref.substr(strHref.indexOf("?")).toLowerCase(); 212 | var aQueryString = strQueryString.split("&"); 213 | for (var iParam = 0; iParam < aQueryString.length; iParam++) { 214 | if (aQueryString[iParam].indexOf(strParamName.toLowerCase() + "=") > -1) { 215 | var aParam = aQueryString[iParam].split("="); 216 | strReturn = aParam[1]; 217 | break; 218 | } 219 | } 220 | } 221 | return unescape(strReturn); 222 | }; 223 | 224 | thisRef.formatDateHHmm = function (date) { 225 | /// Format given date to HH:mm format. 226 | if (date == null || !((date) instanceof (Date))) 227 | return ""; 228 | 229 | var res = ""; 230 | var h = date.getHours(); 231 | var min = date.getMinutes(); 232 | 233 | res += h + ":"; 234 | if (min < 10) 235 | res += "0"; 236 | res += min; 237 | 238 | return res; 239 | }; 240 | 241 | thisRef.formatDateHHmmss = function (date) { 242 | /// Format given date to HH:mm:ss format. 243 | if (date == null || !((date) instanceof (Date))) 244 | return ""; 245 | 246 | var res = ""; 247 | var sec = date.getSeconds(); 248 | if (sec < 10) 249 | res += "0"; 250 | res += sec; 251 | return thisRef.formatDateHHmm(date) + ":" + res; 252 | }; 253 | 254 | thisRef.formatTimeFromString = function (time) { 255 | /// Format given string in format HH:mm to hh:mm am/pm format. 256 | var arr = new String(time).split(":"); 257 | var h = parseInt(arr[0], 10); 258 | var m = parseInt(arr[1], 10); 259 | 260 | var ampm = (h > 1 && h <= 12) ? "am" : "pm"; 261 | return "{0}:{1} {2}".format((h > 12 ? h - 12 : h), (m >= 10 ? m : "0" + m), ampm); 262 | }; 263 | 264 | thisRef.formatTimeFromFullDateString = function (date) { 265 | /// Format given string in format MM/dd/yyyy HH:mm:ss to hh:mm am/pm format. 266 | 267 | var arr0 = new String(date).split(" "); 268 | var d = arr0[0]; 269 | var time = arr0[1]; 270 | 271 | var arr = new String(time).split(":"); 272 | var h = parseInt(arr[0], 10); 273 | var m = parseInt(arr[1], 10); 274 | var s = parseInt(arr[2], 10); 275 | 276 | var ampm = (h > 1 && h <= 12) ? "am" : "pm"; 277 | return "{0}:{1} {2}".format((h > 12 ? h - 12 : h), (m >= 10 ? m : "0" + m), ampm); 278 | }; 279 | 280 | thisRef.formatTodayMMDDYYYY = function (separator) { 281 | /// Format today date to MMDDYYYY format using given separator. 282 | var date = thisRef.getTodayDate(); 283 | return thisRef.formatDateMMDDYYYY(date, separator); 284 | }; 285 | 286 | thisRef.formatDateMMDDYYYY = function (date, separator) { 287 | /// Format given date to MMDDYYYY format using given separator. 288 | if (date == null || !((date) instanceof (Date))) 289 | return ""; 290 | 291 | var addLeadingZeros = arguments.length == 3 ? arguments[2] : false; 292 | var res = ""; 293 | if (addLeadingZeros && date.getMonth() < 9) 294 | res += "0"; 295 | res += (date.getMonth() + 1).toString(); 296 | res += separator; 297 | if (addLeadingZeros && date.getDate() < 10) 298 | res += "0"; 299 | res += date.getDate(); 300 | res += separator; 301 | res += date.getFullYear(); 302 | 303 | return res; 304 | }; 305 | 306 | thisRef.roundTo = function (value, digits) { 307 | /// Round given float to number with digits after dot. 308 | var power = Math.pow(10, digits); 309 | var temp = Math.round(value * power); 310 | return temp / power; 311 | }; 312 | 313 | thisRef.daysBeetweenDates = function (sDate, eDate) { 314 | /// Calculate days count between dates. 315 | return (Math.abs(Math.ceil((sDate - eDate) / 86400000)) + 1); 316 | }; 317 | 318 | thisRef.getTodayDate = function () { 319 | /// Returns today date object without time. 320 | var result = new Date(); 321 | result.setHours(0, 0, 0, 0); 322 | return result; 323 | }; 324 | 325 | // don't change this functions This functions returns Saturday Expiration 326 | thisRef.getExpirationDaysByDateSat = function (expString) { 327 | var expDate = thisRef.expDateToDate(expString); 328 | var today = thisRef.getTodayDate(); 329 | return thisRef.daysBeetweenDatesSat(expDate, today); 330 | }; 331 | 332 | thisRef.getDateByExpirationDaysSat = function (days) { 333 | var date = thisRef.getTodayDate(); 334 | date.setDate(date.getDate() + days); 335 | return date; 336 | }; 337 | 338 | thisRef.daysBeetweenDatesSat = function (sDate, eDate) { 339 | return (Math.round(Math.abs((sDate - eDate) / 86400000))); 340 | }; 341 | 342 | thisRef.getDateByExpirationDays = function (days) { 343 | var date = thisRef.getTodayDate(); 344 | date.setDate(date.getDate() + days - 1); 345 | return date; 346 | }; 347 | 348 | thisRef.getExpirationDaysByDate = function (expString) { 349 | var expDate = thisRef.expDateToDate(expString); 350 | var today = thisRef.getTodayDate(); 351 | return thisRef.daysBeetweenDates(expDate, today); 352 | }; 353 | }; 354 | -------------------------------------------------------------------------------- /www/scripts/chartsController.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aproschenko-dev/WeatherHub/fa558ec22d82cfbc3136c29815904c5e255be8c9/www/scripts/chartsController.js -------------------------------------------------------------------------------- /www/scripts/datasController.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aproschenko-dev/WeatherHub/fa558ec22d82cfbc3136c29815904c5e255be8c9/www/scripts/datasController.js -------------------------------------------------------------------------------- /www/scripts/dateFormat.min.js: -------------------------------------------------------------------------------- 1 | /*! jquery-dateFormat 18-05-2015 */ 2 | var DateFormat={};!function(a){var b=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],c=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],d=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],e=["January","February","March","April","May","June","July","August","September","October","November","December"],f={Jan:"01",Feb:"02",Mar:"03",Apr:"04",May:"05",Jun:"06",Jul:"07",Aug:"08",Sep:"09",Oct:"10",Nov:"11",Dec:"12"},g=/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.?\d{0,3}[Z\-+]?(\d{2}:?\d{2})?/;a.format=function(){function a(a){return b[parseInt(a,10)]||a}function h(a){return c[parseInt(a,10)]||a}function i(a){var b=parseInt(a,10)-1;return d[b]||a}function j(a){var b=parseInt(a,10)-1;return e[b]||a}function k(a){return f[a]||a}function l(a){var b,c,d,e,f,g=a,h="";return-1!==g.indexOf(".")&&(e=g.split("."),g=e[0],h=e[e.length-1]),f=g.split(":"),3===f.length?(b=f[0],c=f[1],d=f[2].replace(/\s.+/,"").replace(/[a-z]/gi,""),g=g.replace(/\s.+/,"").replace(/[a-z]/gi,""),{time:g,hour:b,minute:c,second:d,millis:h}):{time:"",hour:"",minute:"",second:"",millis:""}}function m(a,b){for(var c=b-String(a).length,d=0;c>d;d++)a="0"+a;return a}return{parseDate:function(a){var b,c,d={date:null,year:null,month:null,dayOfMonth:null,dayOfWeek:null,time:null};if("number"==typeof a)return this.parseDate(new Date(a));if("function"==typeof a.getFullYear)d.year=String(a.getFullYear()),d.month=String(a.getMonth()+1),d.dayOfMonth=String(a.getDate()),d.time=l(a.toTimeString()+"."+a.getMilliseconds());else if(-1!=a.search(g))b=a.split(/[T\+-]/),d.year=b[0],d.month=b[1],d.dayOfMonth=b[2],d.time=l(b[3].split(".")[0]);else switch(b=a.split(" "),6===b.length&&isNaN(b[5])&&(b[b.length]="()"),b.length){case 6:d.year=b[5],d.month=k(b[1]),d.dayOfMonth=b[2],d.time=l(b[3]);break;case 2:c=b[0].split("-"),d.year=c[0],d.month=c[1],d.dayOfMonth=c[2],d.time=l(b[1]);break;case 7:case 9:case 10:d.year=b[3],d.month=k(b[1]),d.dayOfMonth=b[2],d.time=l(b[4]);break;case 1:c=b[0].split(""),d.year=c[0]+c[1]+c[2]+c[3],d.month=c[5]+c[6],d.dayOfMonth=c[8]+c[9],d.time=l(c[13]+c[14]+c[15]+c[16]+c[17]+c[18]+c[19]+c[20]);break;default:return null}return d.date=d.time?new Date(d.year,d.month-1,d.dayOfMonth,d.time.hour,d.time.minute,d.time.second,d.time.millis):new Date(d.year,d.month-1,d.dayOfMonth),d.dayOfWeek=String(d.date.getDay()),d},date:function(b,c){try{var d=this.parseDate(b);if(null===d)return b;for(var e,f=d.year,g=d.month,k=d.dayOfMonth,l=d.dayOfWeek,n=d.time,o="",p="",q="",r=!1,s=0;s=12?"PM":"AM",o="";break;case"p":p+=n.hour>=12?"p.m.":"a.m.",o="";break;case"E":p+=h(l),o="";break;case"'":o="",r=!0;break;default:p+=t,o=""}}return p+=q}catch(w){return console&&console.log&&console.log(w),b}},prettyDate:function(a){var b,c,d;return("string"==typeof a||"number"==typeof a)&&(b=new Date(a)),"object"==typeof a&&(b=new Date(a.toString())),c=((new Date).getTime()-b.getTime())/1e3,d=Math.floor(c/86400),isNaN(d)||0>d?void 0:60>c?"just now":120>c?"1 minute ago":3600>c?Math.floor(c/60)+" minutes ago":7200>c?"1 hour ago":86400>c?Math.floor(c/3600)+" hours ago":1===d?"Yesterday":7>d?d+" days ago":31>d?Math.ceil(d/7)+" weeks ago":d>=31?"more than 5 weeks ago":void 0},toBrowserTimeZone:function(a,b){return this.date(new Date(a),b||"MM/dd/yyyy HH:mm:ss")}}}()}(DateFormat); -------------------------------------------------------------------------------- /www/scripts/indexController.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aproschenko-dev/WeatherHub/fa558ec22d82cfbc3136c29815904c5e255be8c9/www/scripts/indexController.js -------------------------------------------------------------------------------- /www/scripts/jquery.dropdown.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery Dropdown: A simple dropdown plugin 3 | * 4 | * Contribute: https://github.com/claviska/jquery-dropdown 5 | * 6 | * @license: MIT license: http://opensource.org/licenses/MIT 7 | * 8 | */ 9 | jQuery&&function($){function t(t,e){var n=t?$(this):e,d=$(n.attr("data-jq-dropdown")),a=n.hasClass("jq-dropdown-open");if(t){if($(t.target).hasClass("jq-dropdown-ignore"))return;t.preventDefault(),t.stopPropagation()}else if(n!==e.target&&$(e.target).hasClass("jq-dropdown-ignore"))return;o(),a||n.hasClass("jq-dropdown-disabled")||(n.addClass("jq-dropdown-open"),d.data("jq-dropdown-trigger",n).show(),r(),d.trigger("show",{jqDropdown:d,trigger:n}))}function o(t){var o=t?$(t.target).parents().addBack():null;if(o&&o.is(".jq-dropdown")){if(!o.is(".jq-dropdown-menu"))return;if(!o.is("A"))return}$(document).find(".jq-dropdown:visible").each(function(){var t=$(this);t.hide().removeData("jq-dropdown-trigger").trigger("hide",{jqDropdown:t})}),$(document).find(".jq-dropdown-open").removeClass("jq-dropdown-open")}function r(){var t=$(".jq-dropdown:visible").eq(0),o=t.data("jq-dropdown-trigger"),r=o?parseInt(o.attr("data-horizontal-offset")||0,10):null,e=o?parseInt(o.attr("data-vertical-offset")||0,10):null;0!==t.length&&o&&t.css(t.hasClass("jq-dropdown-relative")?{left:t.hasClass("jq-dropdown-anchor-right")?o.position().left-(t.outerWidth(!0)-o.outerWidth(!0))-parseInt(o.css("margin-right"),10)+r:o.position().left+parseInt(o.css("margin-left"),10)+r,top:o.position().top+o.outerHeight(!0)-parseInt(o.css("margin-top"),10)+e}:{left:t.hasClass("jq-dropdown-anchor-right")?o.offset().left-(t.outerWidth()-o.outerWidth())+r:o.offset().left+r,top:o.offset().top+o.outerHeight()+e})}$.extend($.fn,{jqDropdown:function(r,e){switch(r){case"show":return t(null,$(this)),$(this);case"hide":return o(),$(this);case"attach":return $(this).attr("data-jq-dropdown",e);case"detach":return o(),$(this).removeAttr("data-jq-dropdown");case"disable":return $(this).addClass("jq-dropdown-disabled");case"enable":return o(),$(this).removeClass("jq-dropdown-disabled")}}}),$(document).on("click.jq-dropdown","[data-jq-dropdown]",t),$(document).on("click.jq-dropdown",o),$(window).on("resize",r)}(jQuery); -------------------------------------------------------------------------------- /www/scripts/kalmanFilter.js: -------------------------------------------------------------------------------- 1 | // refer https://wouterbulten.nl/blog/tech/lightweight-javascript-library-for-noise-filtering/ 2 | 3 | // defaults are: R = 1, Q = 1, A = 1, B = 0, C = 1 4 | function kalman(r, q, a, b, c) { 5 | 6 | var R = r; 7 | var Q = q; 8 | var A = a; 9 | var B = b; 10 | var C = c; 11 | 12 | var cov = NaN; 13 | var x = NaN; 14 | 15 | // defaults are: u = 0 16 | function filter(z, u) { 17 | if (isNaN(x)) { 18 | x = (1 / C) * z; 19 | cov = (1 / C) * Q * (1 / C); 20 | } else { 21 | // Compute prediction 22 | var predX = (A * x) + (B * u); 23 | var predCov = ((A * cov) * A) + R; 24 | 25 | // Kalman gain 26 | var K = predCov * C * (1 / ((C * predCov * C) + Q)); 27 | 28 | // Correction 29 | x = predX + K * (z - (C * predX)); 30 | cov = predCov - (K * C * predCov); 31 | } 32 | 33 | return x; 34 | } 35 | this.filter = filter; 36 | 37 | } -------------------------------------------------------------------------------- /www/scripts/loginController.js: -------------------------------------------------------------------------------- 1 | 2 | var loginController = function(params) { 3 | 4 | var userNameField = null; 5 | var passwordField = null; 6 | var btnLogin = null; 7 | var errorPane = null; 8 | var rememberme = null; 9 | 10 | function initData() { 11 | userNameField = ge("username"); 12 | passwordField = ge("password"); 13 | btnLogin = ge("btnLogin"); 14 | errorPane = ge("errorPane"); 15 | rememberme = ge("rememberme"); 16 | } 17 | 18 | function disableForm(disable) { 19 | userNameField.disabled = passwordField.disabled = btnLogin.disabled = disable; 20 | } 21 | 22 | function validateForm(evt) { 23 | 24 | var errors = []; 25 | var email = userNameField.value; 26 | var password = passwordField.value; 27 | 28 | var emailIsValid = true; 29 | var passwordIsValid = true; 30 | 31 | errorPane.innerHTML = ""; 32 | 33 | if (isStringEmpty(email) || !validateEmail(email)) { 34 | errors.push("Введите валидный e-mail"); 35 | emailIsValid = false; 36 | } 37 | if (isStringEmpty(password)) { 38 | errors.push("Введите пароль"); 39 | passwordIsValid = false; 40 | } 41 | 42 | if (errors.length == 0) { 43 | errorPane.style.visibility = "hidden"; 44 | return true; 45 | } 46 | 47 | for (var i = 0; i < errors.length; i++) { 48 | errorPane.innerHTML += errors[i]; 49 | if (i != errors.length) { 50 | errorPane.innerHTML += "
"; 51 | } 52 | } 53 | 54 | if (!emailIsValid) { 55 | userNameField.focus(); 56 | } 57 | else if (!passwordIsValid) { 58 | passwordField.focus(); 59 | } 60 | 61 | errorPane.style.visibility = "visible"; 62 | 63 | return false; 64 | } 65 | 66 | function doLogin(evt) { 67 | EventHelper.cancel(evt); 68 | if (validateForm(evt)) { 69 | disableForm(true); 70 | loginUser(); 71 | } 72 | } 73 | 74 | function loginUser() { 75 | var email = userNameField.value; 76 | var password = passwordField.value; 77 | var setCookie = rememberme.checked; 78 | queryHelper.requestUserData({ 79 | action: "login", 80 | email: email, 81 | password: password, 82 | setCookie: setCookie ? 1 : 0 83 | }, loginUserCallback); 84 | } 85 | 86 | function loginUserCallback(payload) { 87 | if (payload.result === true) { 88 | document.location = "index.php"; 89 | } else { 90 | disableForm(false); 91 | errorPane.innerHTML = "Введены неверные e-mail и/или пароль."; 92 | errorPane.style.visibility = "visible"; 93 | 94 | userNameField.value = passwordField.value = ""; 95 | userNameField.focus(); 96 | } 97 | } 98 | 99 | function init() { 100 | initData(); 101 | userNameField.focus(); 102 | btnLogin.onclick = doLogin; 103 | } 104 | 105 | init(); 106 | 107 | }; 108 | -------------------------------------------------------------------------------- /www/scripts/queryHelper.js: -------------------------------------------------------------------------------- 1 | 2 | var queryHelper = new function() { 3 | 4 | var thisRef = this; 5 | 6 | function request(url, params, callback) { 7 | var request = $.ajax({ 8 | url: url, 9 | type: "post", 10 | data: params 11 | }); 12 | 13 | request.done(function (response){ 14 | var payload = JSON.parse(response); 15 | callback(payload); 16 | }); 17 | 18 | // Callback handler that will be called on failure 19 | request.fail(function (jqXHR, textStatus, errorThrown){ 20 | console.error( 21 | "The following error occurred: " + 22 | textStatus, errorThrown 23 | ); 24 | }); 25 | } 26 | 27 | function updateSensorData(params, callback) { 28 | request("updateSensorData.php", params, callback); 29 | } 30 | thisRef.updateSensorData = updateSensorData; 31 | 32 | function updateModuleData(params, callback) { 33 | request("updateModuleData.php", params, callback); 34 | } 35 | thisRef.updateModuleData = updateModuleData; 36 | 37 | function requestUserData(params, callback) { 38 | request("queryUserData.php", params, callback); 39 | } 40 | thisRef.requestUserData = requestUserData; 41 | 42 | function requestData(params, callback) { 43 | request("queryData.php", params, callback); 44 | } 45 | thisRef.requestData = requestData; 46 | }; 47 | -------------------------------------------------------------------------------- /www/scripts/registerController.js: -------------------------------------------------------------------------------- 1 | 2 | var registerController = function(params) { 3 | 4 | var userNameField = null; 5 | var passwordField = null; 6 | var btnRegister = null; 7 | var errorPane = null; 8 | 9 | function initData() { 10 | userNameField = ge("username"); 11 | passwordField = ge("password"); 12 | btnRegister = ge("btnRegister"); 13 | errorPane = ge("errorPane"); 14 | } 15 | 16 | function disableForm(disable) { 17 | userNameField.disabled = passwordField.disabled = btnRegister.disabled = disable; 18 | } 19 | 20 | function validateForm(evt) { 21 | 22 | var errors = []; 23 | var email = userNameField.value; 24 | var password = passwordField.value; 25 | 26 | var emailIsValid = true; 27 | var passwordIsValid = true; 28 | 29 | errorPane.innerHTML = ""; 30 | 31 | if (isStringEmpty(email) || !validateEmail(email)) { 32 | errors.push("Введите валидный e-mail"); 33 | emailIsValid = false; 34 | } 35 | if (password.length < 6) { 36 | errors.push("Введите пароль (минимум 6 символов)"); 37 | passwordIsValid = false; 38 | } 39 | 40 | if (errors.length == 0) { 41 | errorPane.style.visibility = "hidden"; 42 | return true; 43 | } 44 | 45 | for (var i = 0; i < errors.length; i++) { 46 | errorPane.innerHTML += errors[i]; 47 | if (i != errors.length) { 48 | errorPane.innerHTML += "
"; 49 | } 50 | } 51 | 52 | if (!emailIsValid) { 53 | userNameField.focus(); 54 | } 55 | else if (!passwordIsValid) { 56 | passwordField.focus(); 57 | } 58 | 59 | errorPane.style.visibility = "visible"; 60 | 61 | return false; 62 | } 63 | 64 | function registerUser() { 65 | var email = userNameField.value; 66 | var password = passwordField.value; 67 | queryHelper.requestUserData({ action: "register", email: email, password: password }, registerUserCallback); 68 | } 69 | 70 | function registerUserCallback(payload) { 71 | errorPane.style.visibility = "visible"; 72 | 73 | if (payload.result === true) { 74 | errorPane.innerHTML = "На указанный e-mail отправлен проверочный код. Введите его в личном кабинете пользователя в течение трёх дней для окончания регистрации. Для продолжения работы перейдите на страницу входа."; 75 | } else { 76 | disableForm(false); 77 | if (payload.alreadyRegistered) { 78 | errorPane.innerHTML = "Пользователь с указанным e-mail уже зарегистрирован. Введите другой e-mail."; 79 | userNameField.value = passwordField.value = ""; 80 | userNameField.focus(); 81 | } 82 | } 83 | } 84 | 85 | function doRegister(evt) { 86 | EventHelper.cancel(evt); 87 | if (validateForm(evt)) { 88 | disableForm(true); 89 | registerUser(); 90 | } 91 | } 92 | 93 | function init() { 94 | initData(); 95 | userNameField.focus(); 96 | btnRegister.onclick = doRegister; 97 | } 98 | 99 | init(); 100 | 101 | }; 102 | -------------------------------------------------------------------------------- /www/scripts/restoreController.js: -------------------------------------------------------------------------------- 1 | 2 | var restoreController = function(params) { 3 | 4 | function validateForm(evt) { 5 | var errors = []; 6 | var email = ge("username").value; 7 | 8 | var errorPane = ge("errorPane"); 9 | errorPane.innerHTML = ""; 10 | 11 | if (isStringEmpty(email)) { 12 | errors.push("Введите e-mail, указанный при регистрации"); 13 | } 14 | 15 | if (errors.length == 0) { 16 | errorPane.style.visibility = "hidden"; 17 | return true; 18 | } 19 | 20 | for (var i = 0; i < errors.length; i++) { 21 | errorPane.innerHTML += errors[i]; 22 | if (i != errors.length) { 23 | errorPane.innerHTML += "
"; 24 | } 25 | } 26 | 27 | if (isStringEmpty(email)) { 28 | ge("username").focus(); 29 | } 30 | 31 | EventHelper.cancel(evt); 32 | errorPane.style.visibility = "visible"; 33 | 34 | return false; 35 | } 36 | 37 | function doRestore(evt) { 38 | if (!validateForm(evt)) { 39 | } 40 | } 41 | 42 | function init() { 43 | ge("username").focus(); 44 | ge("btnRestore").onclick = doRestore; 45 | } 46 | 47 | init(); 48 | 49 | }; 50 | -------------------------------------------------------------------------------- /www/scripts/setupController.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aproschenko-dev/WeatherHub/fa558ec22d82cfbc3136c29815904c5e255be8c9/www/scripts/setupController.js -------------------------------------------------------------------------------- /www/scripts/userController.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aproschenko-dev/WeatherHub/fa558ec22d82cfbc3136c29815904c5e255be8c9/www/scripts/userController.js -------------------------------------------------------------------------------- /www/setup.php: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | Домашняя метеостанция - Настройки 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 78 | 79 |
80 |

Здесь будут отображаться карточки модулей, подключенных к Домашней метеостанции.

81 | 82 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /www/siteConfig.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /www/styles/awesome-bootstrap-checkbox.css: -------------------------------------------------------------------------------- 1 | .checkbox { 2 | padding-left: 20px; 3 | } 4 | .checkbox label { 5 | display: inline-block; 6 | vertical-align: middle; 7 | position: relative; 8 | padding-left: 5px; 9 | } 10 | .checkbox label::before { 11 | content: ""; 12 | display: inline-block; 13 | position: absolute; 14 | width: 17px; 15 | height: 17px; 16 | left: 0; 17 | margin-left: -20px; 18 | border: 1px solid #cccccc; 19 | border-radius: 3px; 20 | background-color: #fff; 21 | -webkit-transition: border 0.15s ease-in-out, color 0.15s ease-in-out; 22 | -o-transition: border 0.15s ease-in-out, color 0.15s ease-in-out; 23 | transition: border 0.15s ease-in-out, color 0.15s ease-in-out; 24 | } 25 | .checkbox label::after { 26 | display: inline-block; 27 | position: absolute; 28 | width: 16px; 29 | height: 16px; 30 | left: 0; 31 | top: 0; 32 | margin-left: -20px; 33 | padding-left: 3px; 34 | padding-top: 1px; 35 | font-size: 11px; 36 | color: #555555; 37 | } 38 | .checkbox input[type="checkbox"], 39 | .checkbox input[type="radio"] { 40 | opacity: 0; 41 | z-index: 1; 42 | } 43 | .checkbox input[type="checkbox"]:focus + label::before, 44 | .checkbox input[type="radio"]:focus + label::before { 45 | outline: thin dotted; 46 | outline: 5px auto -webkit-focus-ring-color; 47 | outline-offset: -2px; 48 | } 49 | .checkbox input[type="checkbox"]:checked + label::after, 50 | .checkbox input[type="radio"]:checked + label::after { 51 | font-family: "FontAwesome"; 52 | content: "\f00c"; 53 | } 54 | .checkbox input[type="checkbox"]:indeterminate + label::after, 55 | .checkbox input[type="radio"]:indeterminate + label::after { 56 | display: block; 57 | content: ""; 58 | width: 10px; 59 | height: 3px; 60 | background-color: #555555; 61 | border-radius: 2px; 62 | margin-left: -16.5px; 63 | margin-top: 7px; 64 | } 65 | .checkbox input[type="checkbox"]:disabled + label, 66 | .checkbox input[type="radio"]:disabled + label { 67 | opacity: 0.65; 68 | } 69 | .checkbox input[type="checkbox"]:disabled + label::before, 70 | .checkbox input[type="radio"]:disabled + label::before { 71 | background-color: #eeeeee; 72 | cursor: not-allowed; 73 | } 74 | .checkbox.checkbox-circle label::before { 75 | border-radius: 50%; 76 | } 77 | .checkbox.checkbox-inline { 78 | margin-top: 0; 79 | } 80 | 81 | .checkbox-primary input[type="checkbox"]:checked + label::before, 82 | .checkbox-primary input[type="radio"]:checked + label::before { 83 | background-color: #337ab7; 84 | border-color: #337ab7; 85 | } 86 | .checkbox-primary input[type="checkbox"]:checked + label::after, 87 | .checkbox-primary input[type="radio"]:checked + label::after { 88 | color: #fff; 89 | } 90 | 91 | .checkbox-danger input[type="checkbox"]:checked + label::before, 92 | .checkbox-danger input[type="radio"]:checked + label::before { 93 | background-color: #d9534f; 94 | border-color: #d9534f; 95 | } 96 | .checkbox-danger input[type="checkbox"]:checked + label::after, 97 | .checkbox-danger input[type="radio"]:checked + label::after { 98 | color: #fff; 99 | } 100 | 101 | .checkbox-info input[type="checkbox"]:checked + label::before, 102 | .checkbox-info input[type="radio"]:checked + label::before { 103 | background-color: #5bc0de; 104 | border-color: #5bc0de; 105 | } 106 | .checkbox-info input[type="checkbox"]:checked + label::after, 107 | .checkbox-info input[type="radio"]:checked + label::after { 108 | color: #fff; 109 | } 110 | 111 | .checkbox-warning input[type="checkbox"]:checked + label::before, 112 | .checkbox-warning input[type="radio"]:checked + label::before { 113 | background-color: #f0ad4e; 114 | border-color: #f0ad4e; 115 | } 116 | .checkbox-warning input[type="checkbox"]:checked + label::after, 117 | .checkbox-warning input[type="radio"]:checked + label::after { 118 | color: #fff; 119 | } 120 | 121 | .checkbox-success input[type="checkbox"]:checked + label::before, 122 | .checkbox-success input[type="radio"]:checked + label::before { 123 | background-color: #5cb85c; 124 | border-color: #5cb85c; 125 | } 126 | .checkbox-success input[type="checkbox"]:checked + label::after, 127 | .checkbox-success input[type="radio"]:checked + label::after { 128 | color: #fff; 129 | } 130 | 131 | .checkbox-primary input[type="checkbox"]:indeterminate + label::before, 132 | .checkbox-primary input[type="radio"]:indeterminate + label::before { 133 | background-color: #337ab7; 134 | border-color: #337ab7; 135 | } 136 | 137 | .checkbox-primary input[type="checkbox"]:indeterminate + label::after, 138 | .checkbox-primary input[type="radio"]:indeterminate + label::after { 139 | background-color: #fff; 140 | } 141 | 142 | .checkbox-danger input[type="checkbox"]:indeterminate + label::before, 143 | .checkbox-danger input[type="radio"]:indeterminate + label::before { 144 | background-color: #d9534f; 145 | border-color: #d9534f; 146 | } 147 | 148 | .checkbox-danger input[type="checkbox"]:indeterminate + label::after, 149 | .checkbox-danger input[type="radio"]:indeterminate + label::after { 150 | background-color: #fff; 151 | } 152 | 153 | .checkbox-info input[type="checkbox"]:indeterminate + label::before, 154 | .checkbox-info input[type="radio"]:indeterminate + label::before { 155 | background-color: #5bc0de; 156 | border-color: #5bc0de; 157 | } 158 | 159 | .checkbox-info input[type="checkbox"]:indeterminate + label::after, 160 | .checkbox-info input[type="radio"]:indeterminate + label::after { 161 | background-color: #fff; 162 | } 163 | 164 | .checkbox-warning input[type="checkbox"]:indeterminate + label::before, 165 | .checkbox-warning input[type="radio"]:indeterminate + label::before { 166 | background-color: #f0ad4e; 167 | border-color: #f0ad4e; 168 | } 169 | 170 | .checkbox-warning input[type="checkbox"]:indeterminate + label::after, 171 | .checkbox-warning input[type="radio"]:indeterminate + label::after { 172 | background-color: #fff; 173 | } 174 | 175 | .checkbox-success input[type="checkbox"]:indeterminate + label::before, 176 | .checkbox-success input[type="radio"]:indeterminate + label::before { 177 | background-color: #5cb85c; 178 | border-color: #5cb85c; 179 | } 180 | 181 | .checkbox-success input[type="checkbox"]:indeterminate + label::after, 182 | .checkbox-success input[type="radio"]:indeterminate + label::after { 183 | background-color: #fff; 184 | } 185 | 186 | .radio { 187 | padding-left: 20px; 188 | } 189 | .radio label { 190 | display: inline-block; 191 | vertical-align: middle; 192 | position: relative; 193 | padding-left: 5px; 194 | } 195 | .radio label::before { 196 | content: ""; 197 | display: inline-block; 198 | position: absolute; 199 | width: 17px; 200 | height: 17px; 201 | left: 0; 202 | margin-left: -20px; 203 | border: 1px solid #cccccc; 204 | border-radius: 50%; 205 | background-color: #fff; 206 | -webkit-transition: border 0.15s ease-in-out; 207 | -o-transition: border 0.15s ease-in-out; 208 | transition: border 0.15s ease-in-out; 209 | } 210 | .radio label::after { 211 | display: inline-block; 212 | position: absolute; 213 | content: " "; 214 | width: 11px; 215 | height: 11px; 216 | left: 3px; 217 | top: 3px; 218 | margin-left: -20px; 219 | border-radius: 50%; 220 | background-color: #555555; 221 | -webkit-transform: scale(0, 0); 222 | -ms-transform: scale(0, 0); 223 | -o-transform: scale(0, 0); 224 | transform: scale(0, 0); 225 | -webkit-transition: -webkit-transform 0.1s cubic-bezier(0.8, -0.33, 0.2, 1.33); 226 | -moz-transition: -moz-transform 0.1s cubic-bezier(0.8, -0.33, 0.2, 1.33); 227 | -o-transition: -o-transform 0.1s cubic-bezier(0.8, -0.33, 0.2, 1.33); 228 | transition: transform 0.1s cubic-bezier(0.8, -0.33, 0.2, 1.33); 229 | } 230 | .radio input[type="radio"] { 231 | opacity: 0; 232 | z-index: 1; 233 | } 234 | .radio input[type="radio"]:focus + label::before { 235 | outline: thin dotted; 236 | outline: 5px auto -webkit-focus-ring-color; 237 | outline-offset: -2px; 238 | } 239 | .radio input[type="radio"]:checked + label::after { 240 | -webkit-transform: scale(1, 1); 241 | -ms-transform: scale(1, 1); 242 | -o-transform: scale(1, 1); 243 | transform: scale(1, 1); 244 | } 245 | .radio input[type="radio"]:disabled + label { 246 | opacity: 0.65; 247 | } 248 | .radio input[type="radio"]:disabled + label::before { 249 | cursor: not-allowed; 250 | } 251 | .radio.radio-inline { 252 | margin-top: 0; 253 | } 254 | 255 | .radio-primary input[type="radio"] + label::after { 256 | background-color: #337ab7; 257 | } 258 | .radio-primary input[type="radio"]:checked + label::before { 259 | border-color: #337ab7; 260 | } 261 | .radio-primary input[type="radio"]:checked + label::after { 262 | background-color: #337ab7; 263 | } 264 | 265 | .radio-danger input[type="radio"] + label::after { 266 | background-color: #d9534f; 267 | } 268 | .radio-danger input[type="radio"]:checked + label::before { 269 | border-color: #d9534f; 270 | } 271 | .radio-danger input[type="radio"]:checked + label::after { 272 | background-color: #d9534f; 273 | } 274 | 275 | .radio-info input[type="radio"] + label::after { 276 | background-color: #5bc0de; 277 | } 278 | .radio-info input[type="radio"]:checked + label::before { 279 | border-color: #5bc0de; 280 | } 281 | .radio-info input[type="radio"]:checked + label::after { 282 | background-color: #5bc0de; 283 | } 284 | 285 | .radio-warning input[type="radio"] + label::after { 286 | background-color: #f0ad4e; 287 | } 288 | .radio-warning input[type="radio"]:checked + label::before { 289 | border-color: #f0ad4e; 290 | } 291 | .radio-warning input[type="radio"]:checked + label::after { 292 | background-color: #f0ad4e; 293 | } 294 | 295 | .radio-success input[type="radio"] + label::after { 296 | background-color: #5cb85c; 297 | } 298 | .radio-success input[type="radio"]:checked + label::before { 299 | border-color: #5cb85c; 300 | } 301 | .radio-success input[type="radio"]:checked + label::after { 302 | background-color: #5cb85c; 303 | } 304 | 305 | input[type="checkbox"].styled:checked + label:after, 306 | input[type="radio"].styled:checked + label:after { 307 | font-family: 'FontAwesome'; 308 | content: "\f00c"; 309 | } 310 | input[type="checkbox"] .styled:checked + label::before, 311 | input[type="radio"] .styled:checked + label::before { 312 | color: #fff; 313 | } 314 | input[type="checkbox"] .styled:checked + label::after, 315 | input[type="radio"] .styled:checked + label::after { 316 | color: #fff; 317 | } 318 | -------------------------------------------------------------------------------- /www/styles/bootstrap-theme.min.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["less/theme.less","less/mixins/vendor-prefixes.less","less/mixins/gradients.less","less/mixins/reset-filter.less"],"names":[],"mappings":";;;;AAmBA,YAAA,aAAA,UAAA,aAAA,aAAA,aAME,YAAA,EAAA,KAAA,EAAA,eC2CA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBDvCR,mBAAA,mBAAA,oBAAA,oBAAA,iBAAA,iBAAA,oBAAA,oBAAA,oBAAA,oBAAA,oBAAA,oBCsCA,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBDlCR,qBAAA,sBAAA,sBAAA,uBAAA,mBAAA,oBAAA,sBAAA,uBAAA,sBAAA,uBAAA,sBAAA,uBAAA,+BAAA,gCAAA,6BAAA,gCAAA,gCAAA,gCCiCA,mBAAA,KACQ,WAAA,KDlDV,mBAAA,oBAAA,iBAAA,oBAAA,oBAAA,oBAuBI,YAAA,KAyCF,YAAA,YAEE,iBAAA,KAKJ,aErEI,YAAA,EAAA,IAAA,EAAA,KACA,iBAAA,iDACA,iBAAA,4CAAA,iBAAA,qEAEA,iBAAA,+CCnBF,OAAA,+GH4CA,OAAA,0DACA,kBAAA,SAuC2C,aAAA,QAA2B,aAAA,KArCtE,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAgBN,aEtEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAiBN,aEvEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAkBN,UExEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,gBAAA,gBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,iBAAA,iBAEE,iBAAA,QACA,aAAA,QAMA,mBAAA,0BAAA,yBAAA,0BAAA,yBAAA,yBAAA,oBAAA,2BAAA,0BAAA,2BAAA,0BAAA,0BAAA,6BAAA,oCAAA,mCAAA,oCAAA,mCAAA,mCAME,iBAAA,QACA,iBAAA,KAmBN,aEzEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAoBN,YE1EI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,kBAAA,kBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,mBAAA,mBAEE,iBAAA,QACA,aAAA,QAMA,qBAAA,4BAAA,2BAAA,4BAAA,2BAAA,2BAAA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,+BAAA,sCAAA,qCAAA,sCAAA,qCAAA,qCAME,iBAAA,QACA,iBAAA,KA2BN,eAAA,WClCE,mBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,EAAA,IAAA,IAAA,iBD2CV,0BAAA,0BE3FI,iBAAA,QACA,iBAAA,oDACA,iBAAA,+CAAA,iBAAA,wEACA,iBAAA,kDACA,OAAA,+GF0FF,kBAAA,SAEF,yBAAA,+BAAA,+BEhGI,iBAAA,QACA,iBAAA,oDACA,iBAAA,+CAAA,iBAAA,wEACA,iBAAA,kDACA,OAAA,+GFgGF,kBAAA,SASF,gBE7GI,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GACA,OAAA,0DCnBF,kBAAA,SH+HA,cAAA,ICjEA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBD6DV,sCAAA,oCE7GI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SD2CF,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBD0EV,cAAA,iBAEE,YAAA,EAAA,IAAA,EAAA,sBAIF,gBEhII,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GACA,OAAA,0DCnBF,kBAAA,SHkJA,cAAA,IAHF,sCAAA,oCEhII,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SD2CF,mBAAA,MAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,gBDgFV,8BAAA,iCAYI,YAAA,EAAA,KAAA,EAAA,gBAKJ,qBAAA,kBAAA,mBAGE,cAAA,EAqBF,yBAfI,mDAAA,yDAAA,yDAGE,MAAA,KE7JF,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,UFqKJ,OACE,YAAA,EAAA,IAAA,EAAA,qBC3HA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,gBDsIV,eEtLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAKF,YEvLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAMF,eExLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAOF,cEzLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAeF,UEjMI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFuMJ,cE3MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFwMJ,sBE5MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFyMJ,mBE7MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF0MJ,sBE9MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF2MJ,qBE/MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF+MJ,sBElLI,iBAAA,yKACA,iBAAA,oKACA,iBAAA,iKFyLJ,YACE,cAAA,IC9KA,mBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,EAAA,IAAA,IAAA,iBDgLV,wBAAA,8BAAA,8BAGE,YAAA,EAAA,KAAA,EAAA,QEnOE,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFiOF,aAAA,QALF,+BAAA,qCAAA,qCAQI,YAAA,KAUJ,OCnME,mBAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,EAAA,IAAA,IAAA,gBD4MV,8BE5PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFyPJ,8BE7PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF0PJ,8BE9PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF2PJ,2BE/PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF4PJ,8BEhQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF6PJ,6BEjQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFoQJ,MExQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFsQF,aAAA,QC3NA,mBAAA,MAAA,EAAA,IAAA,IAAA,gBAAA,EAAA,IAAA,EAAA,qBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,gBAAA,EAAA,IAAA,EAAA"} -------------------------------------------------------------------------------- /www/styles/jquery.dropdown.min.css: -------------------------------------------------------------------------------- 1 | .jq-dropdown { 2 | position: absolute; 3 | z-index: 1039; 4 | display: none 5 | } 6 | 7 | .jq-dropdown .jq-dropdown-menu, .jq-dropdown .jq-dropdown-panel { 8 | min-width: 160px; 9 | max-width: 360px; 10 | list-style: none; 11 | background: #fff; 12 | border: solid 1px #ddd; 13 | border-radius: 4px; 14 | box-shadow: 0 5px 10px rgba(0, 0, 0, .2); 15 | overflow: visible; 16 | padding: 4px 0; 17 | margin: 0 18 | } 19 | 20 | .jq-dropdown .jq-dropdown-panel { 21 | padding: 10px 22 | } 23 | 24 | .jq-dropdown.jq-dropdown-tip { 25 | margin-top: 8px 26 | } 27 | 28 | .jq-dropdown.jq-dropdown-tip:before { 29 | position: absolute; 30 | top: -6px; 31 | left: 9px; 32 | content: ''; 33 | border-left: 7px solid transparent; 34 | border-right: 7px solid transparent; 35 | border-bottom: 7px solid #ddd; 36 | display: inline-block 37 | } 38 | 39 | .jq-dropdown.jq-dropdown-tip:after { 40 | position: absolute; 41 | top: -5px; 42 | left: 10px; 43 | content: ''; 44 | border-left: 6px solid transparent; 45 | border-right: 6px solid transparent; 46 | border-bottom: 6px solid #fff; 47 | display: inline-block 48 | } 49 | 50 | .jq-dropdown.jq-dropdown-tip.jq-dropdown-anchor-right:before { 51 | left: auto; 52 | right: 9px 53 | } 54 | 55 | .jq-dropdown.jq-dropdown-tip.jq-dropdown-anchor-right:after { 56 | left: auto; 57 | right: 10px 58 | } 59 | 60 | .jq-dropdown.jq-dropdown-scroll .jq-dropdown-menu, .jq-dropdown.jq-dropdown-scroll .jq-dropdown-panel { 61 | max-height: 180px; 62 | overflow: auto 63 | } 64 | 65 | .jq-dropdown .jq-dropdown-menu li { 66 | list-style: none; 67 | padding: 0 0; 68 | margin: 0; 69 | line-height: 18px 70 | } 71 | 72 | .jq-dropdown .jq-dropdown-menu label, .jq-dropdown .jq-dropdown-menu li > a { 73 | display: block; 74 | color: inherit; 75 | text-decoration: none; 76 | line-height: 18px; 77 | padding: 3px 15px; 78 | margin: 0; 79 | white-space: nowrap; 80 | font-family: Tahoma; 81 | font-size: 12px; 82 | } 83 | 84 | .jq-dropdown .jq-dropdown-menu label:hover, .jq-dropdown .jq-dropdown-menu li > a:hover { 85 | background-color: #f2f2f2; 86 | color: inherit; 87 | cursor: pointer 88 | } 89 | 90 | .jq-dropdown .jq-dropdown-menu .jq-dropdown-divider { 91 | font-size: 1px; 92 | border-top: solid 1px #e5e5e5; 93 | padding: 0; 94 | margin: 5px 0 95 | } 96 | 97 | .example { 98 | color: #08c; 99 | cursor: pointer; 100 | padding: 4px; 101 | border-radius: 4px; 102 | } 103 | 104 | .example:after { 105 | font-family: Tahoma; 106 | font-size: 12px; 107 | content: '↓'; 108 | margin-left: 6px; 109 | color: #08c; 110 | } 111 | 112 | .example:hover { 113 | background: #f2f2f2; 114 | } 115 | 116 | .example.jq-dropdown-open { 117 | background: #888; 118 | color: #fff; 119 | } 120 | 121 | .example.jq-dropdown-open:after { 122 | color: #fff; 123 | } 124 | -------------------------------------------------------------------------------- /www/styles/styles.css: -------------------------------------------------------------------------------- 1 | /* begin bootstrap override */ 2 | 3 | body { 4 | background: #2E3139 !important; 5 | color: #c0c4c8 !important; 6 | font-size: 14px !important; 7 | font-family: verdana !important; 8 | } 9 | 10 | .label { 11 | font-weight: normal !important; 12 | } 13 | 14 | h3 { 15 | font-size: 20px !important; 16 | margin-top: 10px !important; 17 | } 18 | 19 | .navbar-default { 20 | background: #2A2D35 !important; 21 | box-shadow: none !important; 22 | border-radius: 0 !important; 23 | border-color: #2A2D35 !important; 24 | font-size: 16px !important; 25 | } 26 | 27 | .panel { 28 | background: #3D3F48 !important; 29 | box-shadow: none !important; 30 | border-radius: 0 !important; 31 | border-color: #484c5a !important; 32 | } 33 | 34 | .panel-default>.panel-heading+.panel-collapse>.panel-body { 35 | border-top-color: #484c5a !important; 36 | } 37 | 38 | .panel-default>.panel-heading { 39 | background: #2A2D35 !important; 40 | box-shadow: none !important; 41 | border-radius: 0 !important; 42 | color: #c0c4c8 !important; 43 | font-size: 14px !important; 44 | } 45 | 46 | .navbar-default .navbar-nav>li>a { 47 | color: #c0c4c8 !important; 48 | } 49 | 50 | .navbar-default .navbar-nav>li>a:hover { 51 | color: white !important; 52 | } 53 | 54 | .navbar-brand, .navbar-nav>li>a { 55 | border-top: 4px #2A2D35 solid !important; 56 | border-bottom: 4px #2A2D35 solid !important; 57 | text-shadow: none !important; 58 | } 59 | 60 | .navbar-default .navbar-nav>.active>a { 61 | background: #2A2D35 !important; 62 | border-top: 4px #2A2D35 solid !important; 63 | border-bottom: 4px #F6A821 solid !important; 64 | } 65 | 66 | .thumbnail { 67 | background: #3D3F48 !important; 68 | border-color: #484c5a !important; 69 | border-radius: 0 !important; 70 | } 71 | 72 | .thumbnail .caption { 73 | color: #c0c4c8 !important; 74 | } 75 | 76 | .navbar-default .navbar-brand { 77 | color: #c0c4c8 !important; 78 | } 79 | 80 | .navbar-default .navbar-brand:hover { 81 | color: white !important; 82 | } 83 | 84 | hr { 85 | border-top: 1px solid #c0c4c8 !important; 86 | } 87 | 88 | .example, .example:after { 89 | color: #F6A821 !important; 90 | } 91 | 92 | .jq-dropdown .jq-dropdown-menu label:hover, .jq-dropdown .jq-dropdown-menu li > a:hover { 93 | background-color: #2E3139 !important; 94 | } 95 | 96 | .jq-dropdown.jq-dropdown-tip:after { 97 | border-bottom: 6px solid #484c5a !important; 98 | } 99 | 100 | .jq-dropdown.jq-dropdown-tip:before { 101 | border-bottom: 7px solid #484c5a !important; 102 | } 103 | 104 | .jq-dropdown .jq-dropdown-menu { 105 | background: #3D3F48 !important; 106 | border: 1px solid #484c5a !important; 107 | } 108 | 109 | .example.jq-dropdown-open { 110 | background: #2E3139 !important; 111 | } 112 | 113 | .example:hover { 114 | background: #2E3139 !important; 115 | } 116 | 117 | .checkbox label::before { 118 | background: #2E3139 !important; 119 | } 120 | 121 | .modal-content { 122 | background: #3D3F48 !important; 123 | } 124 | 125 | .form-control-label { 126 | margin-top: 7px; 127 | } 128 | 129 | .navbar-default .navbar-nav>.open>a { 130 | background: #3D3F48 !important; 131 | } 132 | 133 | .dropdown-menu { 134 | background: #2E3139 !important; 135 | } 136 | 137 | .dropdown-menu>li>a { 138 | color: #c0c4c8 !important; 139 | } 140 | 141 | .dropdown-menu > li > a:hover, 142 | .dropdown-menu > li > a:focus { 143 | background: #3D3F48 !important; 144 | } 145 | 146 | .close { 147 | color: #c0c4c8 !important; 148 | opacity: 1 !important; 149 | text-shadow: none !important; 150 | } 151 | 152 | .close:hover { 153 | color: #fff !important; 154 | } 155 | 156 | /* 157 | * Callouts 158 | * 159 | * Not quite alerts, but custom and helpful notes for folks reading the docs. 160 | * Requires a base and modifier class. 161 | */ 162 | 163 | /* Common styles for all types */ 164 | .bs-callout { 165 | padding: 20px; 166 | margin: 20px 0; 167 | border: 1px solid #484c5a; 168 | border-left: 3px solid; 169 | border-radius: 0; 170 | background-color: #3D3F48; 171 | } 172 | .bs-callout h4 { 173 | margin-top: 0; 174 | margin-bottom: 5px; 175 | } 176 | .bs-callout p:last-child { 177 | margin-bottom: 0; 178 | } 179 | .bs-callout code { 180 | border-radius: 3px; 181 | } 182 | 183 | /* Tighten up space between multiple callouts */ 184 | .bs-callout + .bs-callout { 185 | margin-top: -5px; 186 | } 187 | 188 | /* Variations */ 189 | .bs-callout-danger { 190 | border-left-color: #ce4844; 191 | } 192 | .bs-callout-danger h4 { 193 | color: #ce4844; 194 | } 195 | .bs-callout-warning { 196 | border-left-color: #F6A821; 197 | } 198 | .bs-callout-warning h4 { 199 | color: #F6A821; 200 | } 201 | .bs-callout-info { 202 | border-left-color: #1b809e; 203 | } 204 | .bs-callout-info h4 { 205 | color: #1b809e; 206 | } 207 | 208 | /* end bootstrap override */ 209 | 210 | .orange { 211 | color: #F6A821 !important; 212 | } 213 | 214 | .pageContainer { 215 | padding-left: 20px; 216 | padding-right: 20px; 217 | } 218 | 219 | .helpContainer { 220 | margin-left: 20px; 221 | margin-right: 20px; 222 | } 223 | 224 | .userPageContainer { 225 | width: 700px; 226 | } 227 | 228 | .fieldsContainer { 229 | width: 300px; 230 | } 231 | 232 | .floatRight { 233 | float: right; 234 | } 235 | 236 | .bold { 237 | font-weight: bold; 238 | } 239 | 240 | .moduleWidget { 241 | width: 360px !important; 242 | } 243 | 244 | .brandImage { 245 | margin-top: -15px; 246 | height: 50px; 247 | width: 50px; 248 | } 249 | 250 | #results { 251 | overflow-x: auto; 252 | } 253 | 254 | #results table { 255 | border-collapse: collapse; 256 | font-family: Tahoma, serif; 257 | font-size: 12px; 258 | } 259 | 260 | #results th { 261 | background-color: #2E3139; 262 | cursor: pointer; 263 | } 264 | 265 | #results td { 266 | cursor: default; 267 | } 268 | 269 | #results td a { 270 | color: black; 271 | } 272 | 273 | #results td, #results th { 274 | border: 1px solid #484c5a; 275 | height: 30px; 276 | white-space: nowrap; 277 | padding-left: 3px; 278 | padding-right: 3px; 279 | text-align: center; 280 | font-weight: normal; 281 | } 282 | 283 | #results tr { 284 | background-color: #3A3D45; 285 | } 286 | 287 | #results tr:nth-child(2n) { 288 | background-color: #44464F; 289 | } 290 | 291 | #results tr:hover { 292 | background-color: #2E3139; 293 | } 294 | 295 | #results .noData { 296 | background-color: #2E3139 !important; 297 | } 298 | 299 | .pager { 300 | height: 38px; 301 | width: 40px; 302 | border: 1px solid #585d6e; 303 | display: inline-block; 304 | margin: 5px !important; 305 | text-align: center; 306 | background-color: #3D3F48; 307 | padding-top: 10px; 308 | font-family: Tahoma, serif; 309 | font-size: 12px; 310 | cursor: pointer; 311 | border-radius: 5px; 312 | } 313 | 314 | .pager:hover { 315 | background-color: #2E3139; 316 | } 317 | 318 | .pagerDescription { 319 | height: 24px; 320 | padding-top: 10px; 321 | font-family: Tahoma, serif; 322 | font-size: 12px; 323 | display: inline-block; 324 | margin: 5px; 325 | text-align: center; 326 | } 327 | 328 | .chartController { 329 | height: 24px; 330 | padding-top: 10px; 331 | font-family: Tahoma, serif; 332 | font-size: 12px; 333 | margin: 5px; 334 | } 335 | 336 | .selectedPage { 337 | background-color: #2E3139; 338 | font-weight: bold; 339 | } 340 | 341 | .sortedColumn { 342 | font-weight: bold !important; 343 | } 344 | 345 | .colorWhite { 346 | background-color: white !important; 347 | } 348 | 349 | .colorRed { 350 | background-color: #FFDDDD !important; 351 | } 352 | 353 | .colorOrange { 354 | background-color: #FFEEDB !important; 355 | } 356 | 357 | .colorYellow { 358 | background-color: #FDFFE4 !important; 359 | } 360 | 361 | .colorBlue { 362 | background-color: #DEFFFE !important; 363 | } 364 | 365 | .colorGreen { 366 | background-color: #DDFFDC !important; 367 | } 368 | 369 | .actionbutton { 370 | width: 20px; 371 | height: 20px; 372 | border: 1px solid gray; 373 | display: inline-block; 374 | float: right; 375 | margin-bottom: -10px; 376 | margin-top: -4px; 377 | margin-right: 5px; 378 | cursor: pointer; 379 | background-repeat: no-repeat; 380 | background-position: -6px -6px; 381 | } 382 | 383 | .chartContainer { 384 | } 385 | 386 | .sensorWidget { 387 | } 388 | 389 | .inactiveModuleWidget { 390 | opacity: 0.5; 391 | } 392 | 393 | .jq-dropdown-disabled, .jq-dropdown-disabled:after { 394 | color: #c0c4c8 !important; 395 | } 396 | 397 | .panel-title>a { 398 | text-decoration: none !important; 399 | } 400 | 401 | .absolute-center { 402 | margin: auto; 403 | position: absolute; 404 | top: 0; left: 0; bottom: 0; right: 0; 405 | } 406 | 407 | .absolute-center.is-responsive { 408 | width: 100%; 409 | height: 50%; 410 | min-width: 200px; 411 | max-width: 400px; 412 | padding: 40px; 413 | } 414 | 415 | .glyphicon-text { 416 | font-size: 24px; 417 | margin-right: 20px; 418 | cursor: pointer; 419 | } 420 | 421 | .jumboMessage { 422 | font-size: 20px; 423 | font-weight: 200; 424 | text-align: center; 425 | margin-top: 50px; 426 | } 427 | 428 | .jumboLoginMessage { 429 | font-size: 20px; 430 | font-weight: 200; 431 | text-align: center; 432 | margin-top: 10px; 433 | } 434 | 435 | .noDataMessage { 436 | text-align: center; 437 | font-size: 16px; 438 | font-weight: 200; 439 | position: absolute; 440 | top: 200px; 441 | width: 100%; 442 | } 443 | 444 | .module-online { 445 | background-image: url(../images/bullet-green-alt.png); 446 | background-repeat: no-repeat; 447 | background-position-y: 5px; 448 | } 449 | 450 | .module-offline { 451 | background-image: url(../images/bullet-red-alt.png); 452 | background-repeat: no-repeat; 453 | background-position-y: 5px; 454 | } 455 | 456 | .module-inactive { 457 | background-image: url(../images/bullet-orange-alt.png); 458 | background-repeat: no-repeat; 459 | background-position-y: 5px; 460 | } 461 | 462 | .no-top-bottom-margin { 463 | margin-top: 0 !important; 464 | margin-bottom: 0 !important; 465 | } 466 | 467 | .blink_me_up { 468 | -webkit-animation-name: blinker_up; 469 | -webkit-animation-duration: 1s; 470 | -webkit-animation-timing-function: linear; 471 | -webkit-animation-iteration-count: 1; 472 | 473 | -moz-animation-name: blinker_up; 474 | -moz-animation-duration: 1s; 475 | -moz-animation-timing-function: linear; 476 | -moz-animation-iteration-count: 1; 477 | 478 | animation-name: blinker_up; 479 | animation-duration: 1s; 480 | animation-timing-function: linear; 481 | animation-iteration-count: 1; 482 | } 483 | 484 | @-moz-keyframes blinker_up { 485 | 0% { background-color: #D9534F; } 486 | 100% { background-color: #777; } 487 | } 488 | 489 | @-webkit-keyframes blinker_up { 490 | 0% { background-color: #D9534F; } 491 | 100% { background-color: #777; } 492 | } 493 | 494 | @keyframes blinker_up { 495 | 0% { background-color: #D9534F; } 496 | 100% { background-color: #777; } 497 | } 498 | 499 | .blink_me_down { 500 | -webkit-animation-name: blinker_down; 501 | -webkit-animation-duration: 1s; 502 | -webkit-animation-timing-function: linear; 503 | -webkit-animation-iteration-count: 1; 504 | 505 | -moz-animation-name: blinker_down; 506 | -moz-animation-duration: 1s; 507 | -moz-animation-timing-function: linear; 508 | -moz-animation-iteration-count: 1; 509 | 510 | animation-name: blinker_down; 511 | animation-duration: 1s; 512 | animation-timing-function: linear; 513 | animation-iteration-count: 1; 514 | } 515 | 516 | @-moz-keyframes blinker_down { 517 | 0% { background-color: #6FA2FF; } 518 | 100% { background-color: #777; } 519 | } 520 | 521 | @-webkit-keyframes blinker_down { 522 | 0% { background-color: #6FA2FF; } 523 | 100% { background-color: #777; } 524 | } 525 | 526 | @keyframes blinker_down { 527 | 0% { background-color: #6FA2FF; } 528 | 100% { background-color: #777; } 529 | } 530 | 531 | .unitSpan { 532 | font-size: 80%; 533 | } 534 | 535 | .editSensorDescriptionIcon { 536 | margin-left: 10px; 537 | cursor: pointer; 538 | } 539 | 540 | .editSensorDescriptionInput { 541 | height: 20px; 542 | color: black; 543 | } -------------------------------------------------------------------------------- /www/updateModuleData.php: -------------------------------------------------------------------------------- 1 | updateData("UPDATE WeatherModule SET Description = '$description' WHERE MAC = '$mac'"); 28 | } 29 | 30 | if (isset($_REQUEST["isActive"])) { 31 | $isActive = $_REQUEST["isActive"] == "true" ? 1 : 0; 32 | $requester->updateData("UPDATE WeatherModule SET IsActive = $isActive WHERE MAC = '$mac'"); 33 | } 34 | 35 | if (isset($_REQUEST["tableVisibility"])) { 36 | $tableVisibility = (int)$_REQUEST["tableVisibility"]; 37 | $requester->updateData("UPDATE WeatherModule SET TableVisibility = $tableVisibility WHERE MAC = '$mac'"); 38 | } 39 | 40 | if (isset($_REQUEST["chartVisibility"])) { 41 | $chartVisibility = (int)$_REQUEST["chartVisibility"]; 42 | $requester->updateData("UPDATE WeatherModule SET ChartVisibility = $chartVisibility WHERE MAC = '$mac'"); 43 | } 44 | 45 | if (isset($_REQUEST["updateSensors"])) { 46 | $requester->updateModuleSensorData($mac); 47 | } 48 | 49 | $whereClause = ""; 50 | if ($publicServer) { 51 | $whereClause = "WHERE ValidationCode = '" . $_SESSION[$userSessionVarName]->verificationCode . "'"; 52 | } 53 | 54 | $allData = (object) []; 55 | 56 | $params = (object) []; 57 | $params->whereClause = $whereClause; 58 | $params->sortClause = ""; 59 | $params->getModuleSensors = (isset($_REQUEST["getModuleSensors"]) ? (int)$_REQUEST["getModuleSensors"] : 0) == 1; 60 | $params->getModuleWeather = (isset($_REQUEST["getModuleWeather"]) ? (int)$_REQUEST["getModuleWeather"] : 0) == 1; 61 | 62 | $allData->modules = $requester->getModulesData($params); 63 | 64 | print json_encode($allData, JSON_UNESCAPED_UNICODE); 65 | 66 | ?> -------------------------------------------------------------------------------- /www/updateSensorData.php: -------------------------------------------------------------------------------- 1 | userId; 25 | } 26 | 27 | $allData = (object) []; 28 | 29 | $requester = new Requester; 30 | 31 | $sensorId = null; 32 | if (isset($_REQUEST["sensorId"])) { 33 | $sensorId = (int)$_REQUEST["sensorId"]; 34 | } 35 | 36 | if (isset($_REQUEST["description"])) { 37 | 38 | // called from Setup page 39 | 40 | $mac = $_REQUEST["mac"]; 41 | $description = $_REQUEST["description"]; 42 | $description = iconv('utf-8', 'windows-1251', $description); 43 | 44 | $requester->updateData("UPDATE ModuleSensor SET Description = '$description' WHERE SensorID = $sensorId AND ModuleID = (SELECT ModuleID FROM WeatherModule WHERE MAC = '$mac')"); 45 | $allData->moduleSensors = $requester->getData("SELECT ID, SensorID, Description FROM ModuleSensor WHERE SensorID = $sensorId AND ModuleID = (SELECT ModuleID FROM WeatherModule WHERE MAC = '$mac')"); 46 | 47 | } else { 48 | 49 | // called from Data or Charts pages 50 | 51 | $chartVisibility = null; 52 | $tableVisibility = null; 53 | 54 | if (isset($_REQUEST["chartVisibility"])) { 55 | $chartVisibility = (int)$_REQUEST["chartVisibility"]; 56 | } 57 | 58 | if (isset($_REQUEST["tableVisibility"])) { 59 | $tableVisibility = (int)$_REQUEST["tableVisibility"]; 60 | } 61 | 62 | $requester->updateSensorData($userId, $sensorId, $chartVisibility, $tableVisibility); 63 | $allData->sensorsData = $requester->getData("SELECT SensorID, TableVisibility, ChartVisibility FROM SensorData"); 64 | 65 | } 66 | 67 | print json_encode($allData, JSON_UNESCAPED_UNICODE); 68 | 69 | ?> -------------------------------------------------------------------------------- /www/user.php: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | Домашняя метеостанция - Личный кабинет 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 |
31 |
32 |
33 | 34 |
35 |

userEmail ?>

36 |
37 |
38 | isActive == 1) { ?> 39 |
40 | 41 |
42 |

verificationCode ?>

43 |
44 |
45 | 46 | 52 |
53 | 54 |
55 | 56 |
57 |
58 | 59 |
60 |
61 | 66 | 67 |
68 |

Что такое код валидации?

69 |

70 | Код валидации - это 16 цифр или букв, сгенерированных автоматически при создании пользователя.
71 | Он используется для того, чтобы сайт знал о валидности введенного при регистрации пользователем email.
72 | Также код используется для привязки модулей пользователя к его логину на сайте - для этого код валидации из письма следует указать в соответствующем поле на 73 | странице Setup каждого подключаемого модуля на странице Настройки. Пока это не сделано - увидеть данные с модуля в Домашней метеостанции будет нельзя.
74 | Никому не сообщайте свой код, если не хотите, чтобы данные с ваших модулей увидел кто-то еще. 75 |

76 |
77 |
78 |
79 |
80 | 81 |
82 |
83 |
84 |
85 |
86 |
87 | 88 | 89 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /xiaomi/config.js: -------------------------------------------------------------------------------- 1 | var config = { 2 | serverPort: 9898, 3 | multicastAddress: '224.0.0.50', 4 | multicastPort: 4321, 5 | sensorDelay: 60 6 | }; 7 | 8 | module.exports = config; -------------------------------------------------------------------------------- /xiaomi/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "modulesapp", 3 | "version": "1.0.0" 4 | } -------------------------------------------------------------------------------- /xiaomi/sensor.js: -------------------------------------------------------------------------------- 1 | var mysql = require('mysql'); 2 | const crypto = require('crypto'); 3 | var config = require('./config'); 4 | const dgram = require('dgram'); 5 | 6 | const serverPort = config.serverPort; 7 | const serverSocket = dgram.createSocket('udp4'); 8 | const multicastAddress = config.multicastAddress; 9 | const multicastPort = config.multicastPort; 10 | const sensorDelay = config.sensorDelay; 11 | 12 | const iv = Buffer.from([0x17, 0x99, 0x6d, 0x09, 0x3d, 0x28, 0xdd, 0xb3, 0xba, 0x69, 0x5a, 0x2e, 0x6f, 0x58, 0x56, 0x2e]); 13 | const gatewayPassword = '5uym4gcadgt4ph9f'; 14 | var gatewayToken; 15 | 16 | const express = require('express'); 17 | const app = express(); 18 | const port = 3000; 19 | app.get('/', (request, response) => { 20 | response.send('Hello from Express!'); 21 | }); 22 | 23 | app.get('/color', (request, response) => { 24 | response.send('Calling color.'); 25 | var deviceSid = "f0b429cc178e"; 26 | var command = { 27 | cmd: "write", 28 | model: "gateway", 29 | sid: deviceSid, 30 | data: {rgb: "1677734911", illumination: 1200, key: ""} 31 | }; 32 | sendWriteCommand(deviceSid, command, "9898", "192.168.0.100"); 33 | }); 34 | 35 | app.get('/plug', (request, response) => { 36 | response.send('Calling plug.'); 37 | var deviceSid = "158d000127883b"; 38 | var command = { 39 | cmd: "write", 40 | model: "plug", 41 | sid: deviceSid, 42 | data: {status: "on", key: ""} 43 | }; 44 | sendWriteCommand(deviceSid, command, "9898", "192.168.0.100"); 45 | }); 46 | 47 | app.listen(port, (err) => { 48 | if (err) { 49 | return console.log('something bad happened', err); 50 | } 51 | console.log(`server is listening on ${port}`); 52 | }); 53 | 54 | var con = mysql.createConnection({ 55 | host: "localhost", 56 | user: "phpmyadmin", 57 | password: "root", 58 | database: "homehub" 59 | }); 60 | 61 | con.connect(function(err) { 62 | if (err) { 63 | throw err; 64 | } 65 | console.log("Database connected!"); 66 | }); 67 | 68 | function getJsonParam(json, paramName) { 69 | var param = json[paramName]; 70 | if (param === undefined || param == "undefined") { 71 | return null; 72 | } 73 | return param; 74 | }; 75 | 76 | Date.prototype.Format = function(fmt) { 77 | var o = { 78 | "M+": this.getMonth() + 1, 79 | "d+": this.getDate(), 80 | "h+": this.getHours(), 81 | "m+": this.getMinutes(), 82 | "s+": this.getSeconds(), 83 | "q+": Math.floor((this.getMonth() + 3) / 3), 84 | "S": this.getMilliseconds() 85 | }; 86 | if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length)); 87 | for (var k in o) 88 | if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); 89 | return fmt; 90 | }; 91 | 92 | function sendCommand(jsonObject, port, address) { 93 | var cmdString = JSON.stringify(jsonObject); 94 | var message = new Buffer(cmdString); 95 | serverSocket.send(message, 0, cmdString.length, port, address); 96 | console.log('Sending \x1b[33m%s\x1b[0m to \x1b[36m%s:%d\x1b[0m.', cmdString, address, port); 97 | }; 98 | 99 | function sendWriteCommand(deviceSid, jsonObject, port, address) { 100 | var cipher = crypto.createCipheriv('aes-128-cbc', gatewayPassword, iv); 101 | console.log("gatewayToken", gatewayToken); 102 | var key = cipher.update(gatewayToken, "ascii", "hex"); 103 | cipher.final('hex'); // Useless data, don't know why yet. 104 | var serialNumber = new Date().Format("yyyyMMddhhmmss"); 105 | 106 | jsonObject.data.key = key; 107 | var msgTag = 'write_' + deviceSid + "_t" + serialNumber; 108 | 109 | sendCommand(jsonObject, port, address); 110 | }; 111 | 112 | serverSocket.on('message', function (msg, rinfo) { 113 | console.log('Received \x1b[33m%s\x1b[0m (%d bytes) from client \x1b[36m%s:%d\x1b[0m.', msg, msg.length, rinfo.address, rinfo.port); 114 | var json; 115 | try { 116 | json = JSON.parse(msg); 117 | } 118 | catch (e) { 119 | console.log('\x1b[31mUnexpected message: %s\x1b[0m.', msg); 120 | return; 121 | } 122 | 123 | var cmd = json['cmd']; 124 | var messageData = getJsonParam(json, "data"); 125 | var messageDataJson = messageData ? JSON.parse(messageData) : null; 126 | var voltage = messageDataJson ? messageDataJson['voltage'] : null; 127 | var model = getJsonParam(json, "model"); 128 | var sid = getJsonParam(json, "sid"); 129 | var token = getJsonParam(json, "token"); 130 | var short_id = getJsonParam(json, "short_id"); 131 | 132 | if (model === 'gateway' && token != null) { 133 | gatewayToken = token; 134 | } 135 | 136 | // clear DB for old rows 137 | var deleteSql = "delete from HubData where sid = '" + sid + "'"; 138 | con.query(deleteSql); 139 | 140 | var sql = "insert into HubData (cmd, model, sid, shortid, token, voltage) values (?)"; 141 | var hubValue = [cmd, model, sid, short_id, token, voltage]; 142 | con.query(sql, [hubValue], function (err, result) { 143 | if (err) { 144 | throw err; 145 | } 146 | }); 147 | 148 | if (cmd === 'iam') { 149 | var address = json['ip']; 150 | var port = json['port']; 151 | 152 | var command = { 153 | cmd: "get_id_list" 154 | }; 155 | console.log('Requesting devices list...'); 156 | sendCommand(command, port, address); 157 | } 158 | else if (cmd === 'get_id_list_ack') { 159 | var data = JSON.parse(json['data']); 160 | console.log('Received devices list: %d device(s) connected.', data.length); 161 | for (var index in data) { 162 | var sid = data[index]; 163 | var command = { 164 | cmd: "read", 165 | sid: new String(sid) 166 | }; 167 | 168 | sendCommand(command, rinfo.port, rinfo.address); 169 | } 170 | } 171 | else if (cmd === 'read_ack') { 172 | var data = JSON.parse(json['data']); 173 | var status = data['status']; 174 | 175 | if (model === 'sensor_ht') { 176 | var temperature = data['temperature'] ? data['temperature'] / 100.0 : 100; 177 | var humidity = data['humidity'] ? data['humidity'] / 100.0 : 0; 178 | var sensorSql = "insert into SensorData (sid, temperature, humidity) values (?)"; 179 | var sensorValue = [sid, temperature, humidity]; 180 | con.query(sensorSql, [sensorValue]); 181 | } 182 | } 183 | else if (cmd === 'report') { 184 | var data = JSON.parse(json['data']); 185 | var status = data['status']; 186 | 187 | if (model === "plug" || model === "magnet" || model === "motion" || model === "sensor_wleak.aq1") { 188 | var sensorSql = "insert into SensorData (sid, status) values (?)"; 189 | var sensorValue = [sid, status]; 190 | con.query(sensorSql, [sensorValue]); 191 | } 192 | } 193 | else if (cmd === 'heartbeat') { 194 | } 195 | }); 196 | 197 | // err - Error object, https://nodejs.org/api/errors.html 198 | serverSocket.on('error', function (err) { 199 | console.log('Error, message - %s, stack - %s.', err.message, err.stack); 200 | }); 201 | 202 | serverSocket.on('listening', function () { 203 | console.log('Starting a UDP server, listening on port %d.', serverPort); 204 | serverSocket.addMembership(multicastAddress); 205 | }) 206 | 207 | console.log('Starting Aqara daemon...'); 208 | 209 | serverSocket.bind(serverPort); 210 | 211 | function sendWhois() { 212 | var command = { 213 | cmd: "whois" 214 | }; 215 | var cmdString = JSON.stringify(command); 216 | var message = new Buffer(cmdString); 217 | serverSocket.send(message, 0, cmdString.length, multicastPort, multicastAddress); 218 | 219 | console.log('Sending WhoIs request to a multicast address \x1b[36m%s:%d\x1b[0m.', multicastAddress, multicastPort); 220 | } 221 | 222 | sendWhois(); 223 | 224 | setInterval(function () { 225 | console.log('Requesting data...'); 226 | sendWhois(); 227 | }, sensorDelay * 1000); 228 | --------------------------------------------------------------------------------