├── .gitignore ├── .travis.yml ├── ESP8266 ├── ESP8266_INSIDE.ino ├── ESP8266_OUTSIDE.ino └── README.md ├── LICENSE ├── Modules ├── Animations │ ├── HD │ │ ├── __init__.py │ │ ├── clear-day.png │ │ ├── clear-night.png │ │ ├── cloudy.png │ │ ├── error.png │ │ ├── fog.png │ │ ├── partly-cloudy-day.png │ │ ├── partly-cloudy-night.png │ │ ├── rain.png │ │ ├── raspberry.png │ │ ├── raspberry_boot.png │ │ ├── snow.png │ │ └── wind.png │ └── SD │ │ ├── __init__.py │ │ ├── clear-day.png │ │ ├── clear-night.png │ │ ├── cloudy.png │ │ ├── error.png │ │ ├── fog.png │ │ ├── partly-cloudy-day.png │ │ ├── partly-cloudy-night.png │ │ ├── rain.png │ │ ├── raspberry.png │ │ ├── raspberry_boot.png │ │ ├── snow.png │ │ └── wind.png ├── Config.py ├── Data.py ├── Driver │ ├── Adafruit_BME280.py │ ├── Adafruit_PureIO │ │ ├── __init__.py │ │ └── smbus.py │ ├── BicolorBargraph24.py │ ├── GPIO.py │ ├── HT16K33.py │ ├── I2C.py │ ├── MCP9808.py │ ├── Matrix16x8.py │ ├── Platform.py │ └── __init__.py ├── Endpoint.py ├── Fonts │ ├── __init__.py │ └── custom_font.py ├── RainData.py ├── SensorData.py ├── Update.py ├── UpdateLog.py ├── UpdateUnicorn.py ├── WebApp.py ├── __init__.py ├── check_alarms.py ├── clear.py ├── init_blinkt.py ├── init_buttons.py ├── init_logging.py ├── init_matrix.py ├── init_unicorn.py ├── static │ ├── css │ │ ├── bootstrap.min.css │ │ ├── cover.css │ │ └── theme.css │ ├── icons │ │ ├── favicon.ico │ │ └── favicon.png │ └── js │ │ └── bootstrap.bundle.min.js ├── templates │ ├── base.html │ ├── home.html │ ├── node.html │ ├── rpi.html │ └── sensors.html ├── update_blinkt.py └── update_matrix.py ├── README.md ├── WeatherPi.py ├── config.example.json ├── logs └── README.md ├── python.sh ├── requirements.txt └── scripts ├── PiButtons.service ├── README.md ├── WeatherPi.service └── turnLedsOff.service /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .DS_Store 90 | .ropeproject 91 | /Modules/_test_* 92 | /logs/*.json 93 | /config.json 94 | .idea 95 | /.travis.yml 96 | /request.rqst 97 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.5" 4 | # command to install dependencies 5 | install: "pip install -r requirements.txt" 6 | # command to run tests 7 | script: nosetests 8 | # sudo: required -------------------------------------------------------------------------------- /ESP8266/ESP8266_INSIDE.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "Adafruit_MQTT.h" 10 | #include "Adafruit_MQTT_Client.h" 11 | 12 | /************************* WiFi Access Point *********************************/ 13 | 14 | #define WLAN_SSID "xxxxxxxx" 15 | #define WLAN_PASS "xxxxxxxx" 16 | 17 | /************************* Adafruit.io Setup *********************************/ 18 | 19 | #define AIO_SERVER "io.adafruit.com" 20 | #define AIO_SERVERPORT 1883 // use 8883 for SSL 21 | #define AIO_USERNAME "xxxxxxxx" 22 | #define AIO_KEY "xxxxxxxx" 23 | 24 | /************ Setup WiFi ******************/ 25 | 26 | // Create an ESP8266 WiFiClient class to connect to the MQTT server. 27 | WiFiClient client; 28 | // or... use WiFiFlientSecure for SSL 29 | //WiFiClientSecure client; 30 | 31 | /****************************** MQTT ***************************************/ 32 | 33 | // Store the MQTT server, username, and password in flash memory. 34 | // This is required for using the Adafruit MQTT library. 35 | const char MQTT_SERVER[] PROGMEM = AIO_SERVER; 36 | const char MQTT_USERNAME[] PROGMEM = AIO_USERNAME; 37 | const char MQTT_PASSWORD[] PROGMEM = AIO_KEY; 38 | 39 | // Setup the MQTT client class by passing in the WiFi client and MQTT server and login details. 40 | Adafruit_MQTT_Client mqtt(&client, MQTT_SERVER, AIO_SERVERPORT, MQTT_USERNAME, MQTT_PASSWORD); 41 | 42 | /****************************** Feeds ***************************************/ 43 | 44 | // Notice MQTT paths for AIO follow the form: /feeds/ 45 | const char SENSORTEMPINSIDE[] PROGMEM = AIO_USERNAME "/feeds/sensortempinside"; 46 | Adafruit_MQTT_Publish sensortempinside = Adafruit_MQTT_Publish(&mqtt, SENSORTEMPINSIDE); 47 | 48 | const char SENSORPRESSUREINSIDE[] PROGMEM = AIO_USERNAME "/feeds/sensorpressureinside"; 49 | Adafruit_MQTT_Publish sensorpressureinside = Adafruit_MQTT_Publish(&mqtt, SENSORPRESSUREINSIDE); 50 | 51 | const char SENSORHUMIDITYINSDE[] PROGMEM = AIO_USERNAME "/feeds/sensorhumidityinside"; 52 | Adafruit_MQTT_Publish sensorhumidityinside = Adafruit_MQTT_Publish(&mqtt, SENSORHUMIDITYINSDE); 53 | 54 | const char FEATHERBATTERYINSIDE[] PROGMEM = AIO_USERNAME "/feeds/featherbatteryinside"; 55 | Adafruit_MQTT_Publish featherbatteryinside = Adafruit_MQTT_Publish(&mqtt, FEATHERBATTERYINSIDE); 56 | 57 | /************************* THingSpeak IO *********************************/ 58 | 59 | String apiKey = "xxxxxxxx"; // This WriteAPI key is generated by ThingSpeak.com for your channel 60 | const char* server = "api.thingspeak.com"; 61 | 62 | /****************************** Display ***************************************/ 63 | 64 | #define OLED_RESET 3 65 | Adafruit_SSD1306 display(OLED_RESET); 66 | 67 | #define LOGO16_GLCD_HEIGHT 16 68 | #define LOGO16_GLCD_WIDTH 16 69 | static const unsigned char PROGMEM logo16_glcd_bmp[] = 70 | { B00000000, B11000000, 71 | B00000001, B11000000, 72 | B00000001, B11000000, 73 | B00000011, B11100000, 74 | B11110011, B11100000, 75 | B11111110, B11111000, 76 | B01111110, B11111111, 77 | B00110011, B10011111, 78 | B00011111, B11111100, 79 | B00001101, B01110000, 80 | B00011011, B10100000, 81 | B00111111, B11100000, 82 | B00111111, B11110000, 83 | B01111100, B11110000, 84 | B01110000, B01110000, 85 | B00000000, B00110000 86 | }; 87 | 88 | /****************************** current sensor ***************************************/ 89 | 90 | Adafruit_INA219 ina219; 91 | 92 | /****************************** Adafruit_BME280 ***************************************/ 93 | 94 | Adafruit_BME280 bme; // I2C 95 | 96 | /*************************** Sketch Code ************************************/ 97 | 98 | // Bug workaround for Arduino 1.6.6, it seems to need a function declaration 99 | // for some reason (only affects ESP8266, likely an arduino-builder bug). 100 | void MQTT_connect(); 101 | 102 | void setup() { 103 | // beginn 104 | Serial.begin(115200); 105 | delay(10); 106 | 107 | Serial.println("\nFeather Huzzah ESP8266 + FeatherWing OLED + BME280 + INA219"); 108 | 109 | Serial.println("\nESP8266 in normal mode"); 110 | 111 | // Connect to WiFi access point. 112 | Serial.print("\nConnecting to "); 113 | Serial.println(WLAN_SSID); 114 | 115 | WiFi.begin(WLAN_SSID, WLAN_PASS); 116 | while (WiFi.status() != WL_CONNECTED) { 117 | delay(500); 118 | Serial.print("."); 119 | } 120 | 121 | Serial.println(); 122 | Serial.println("\nWiFi connected"); 123 | Serial.print("\nIP address: "); Serial.println(WiFi.localIP()); 124 | 125 | // init the sensor 126 | if (!bme.begin()) { 127 | Serial.println("Could not find a valid BME280 sensor, check wiring!"); 128 | while (1); 129 | } 130 | 131 | // Initialize the INA219. 132 | ina219.begin(); 133 | 134 | // by default, we'll generate the high voltage from the 3.3v line internally! (neat!) 135 | display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // initialize with the I2C addr 0x3C (for the 128x32) 136 | // init done 137 | 138 | // Clear the buffer. 139 | display.clearDisplay(); 140 | display.setTextSize(1); 141 | display.setTextColor(WHITE); 142 | 143 | // define the led's 144 | pinMode(0, OUTPUT); // red LED 145 | pinMode(2, OUTPUT); // blue LED 146 | 147 | // Ensure the connection to the MQTT server is alive (this will make the first 148 | // connection and automatically reconnect when disconnected). See the MQTT_connect 149 | // function definition further below. 150 | MQTT_connect(); 151 | 152 | float temp = bme.readTemperature(); 153 | float pressure = bme.readPressure() / 100.0F; 154 | float humidity = bme.readHumidity(); 155 | 156 | float shuntvoltage = 0; 157 | float busvoltage = 0; 158 | float current_mA = 0; 159 | float loadvoltage = 0; 160 | 161 | shuntvoltage = ina219.getShuntVoltage_mV(); 162 | busvoltage = ina219.getBusVoltage_V(); 163 | current_mA = ina219.getCurrent_mA(); 164 | loadvoltage = busvoltage + (shuntvoltage / 1000); 165 | 166 | // print to console 167 | 168 | Serial.println("\nWrite to Console"); 169 | 170 | Serial.println("\nMeasuring temp, pressure and humidity with BME280 ..."); 171 | 172 | Serial.print("\nTemperatur = "); Serial.print(temp); Serial.println(" *C"); 173 | Serial.print("Luftdruck = "); Serial.print(pressure); Serial.println(" hPa"); 174 | Serial.print("Luftfeuchtigkeit = "); Serial.print(humidity); Serial.println(" %"); 175 | 176 | Serial.println("\nMeasuring voltage and current with INA219 ..."); 177 | 178 | Serial.print("\nBus Voltage: "); Serial.print(busvoltage); Serial.println(" V"); 179 | Serial.print("Shunt Voltage: "); Serial.print(shuntvoltage); Serial.println(" mV"); 180 | Serial.print("Load Voltage: "); Serial.print(loadvoltage); Serial.println(" V"); 181 | Serial.print("Current: "); Serial.print(current_mA); Serial.println(" mA"); 182 | 183 | blink_red(); 184 | 185 | // print to display 186 | 187 | Serial.println("\nWrite to Display"); 188 | 189 | display.setCursor(0, 0); 190 | display.print("Temperatur = "); display.print(temp, 2); display.println(" *C"); 191 | 192 | display.setCursor(0, 8); 193 | display.print("Luftdruck = "); display.print(pressure, 0); display.println(" hPa"); 194 | 195 | display.setCursor(0, 16); 196 | display.print("rLF = "); display.print(humidity, 2); display.println(" %"); 197 | 198 | display.setCursor(0, 24); 199 | display.print("Spannung = "); display.print(busvoltage, 2); display.println(" V"); 200 | 201 | display.display(); 202 | 203 | blink_red(); 204 | 205 | // write stuff to adafruit.io 206 | 207 | Serial.println("\nSend data to adafruit.io"); 208 | 209 | // Now we can publish stuff! 210 | Serial.print(F("\nSending SensorTempInside val ")); 211 | Serial.print(temp, 2); 212 | Serial.print("..."); 213 | if (! sensortempinside.publish(temp, 2)) { 214 | Serial.println("Failed"); 215 | } else { 216 | Serial.println("OK!"); 217 | } 218 | 219 | blink_blue(); 220 | 221 | Serial.print(F("Sending SensorPressureInside val ")); 222 | Serial.print(pressure, 1); 223 | Serial.print("..."); 224 | if (! sensorpressureinside.publish(pressure, 2)) { 225 | Serial.println("Failed"); 226 | } else { 227 | Serial.println("OK!"); 228 | } 229 | 230 | blink_blue(); 231 | 232 | Serial.print("Sending SensorHumidityInside val "); 233 | Serial.print(humidity, 2); 234 | Serial.print("..."); 235 | if (! sensorhumidityinside.publish(humidity, 2)) { 236 | Serial.println(F("Failed")); 237 | } else { 238 | Serial.println("OK!"); 239 | } 240 | 241 | blink_blue(); 242 | 243 | Serial.print(F("Sending FeatherBatteryInside val ")); 244 | Serial.print(busvoltage, 2); 245 | Serial.print("..."); 246 | if (! featherbatteryinside.publish(busvoltage, 2)) { 247 | Serial.println("Failed"); 248 | } else { 249 | Serial.println("OK!"); 250 | } 251 | 252 | blink_blue(); 253 | 254 | // write stuff to thingspeak 255 | 256 | if (client.connect(server, 80)) { // "184.106.153.149" or api.thingspeak.com // Connect to the ThingsSpeak.com website 257 | 258 | String postStr = apiKey; // Build the update string 259 | 260 | postStr += "&field5="; 261 | postStr += String(temp, 2); 262 | 263 | postStr += "&field6="; 264 | postStr += String(pressure, 2); 265 | 266 | postStr += "&field7="; 267 | postStr += String(humidity, 2); 268 | 269 | postStr += "&field8="; 270 | postStr += String(busvoltage, 2); 271 | 272 | client.print("POST /update HTTP/1.1\n"); 273 | client.print("Host: api.thingspeak.com\n"); 274 | client.print("Connection: close\n"); 275 | client.print("X-THINGSPEAKAPIKEY: " + apiKey + "\n"); 276 | client.print("Content-Type: application/x-www-form-urlencoded\n"); 277 | client.print("Content-Length: "); 278 | client.print(postStr.length()); 279 | client.print("\n\n"); 280 | client.print(postStr); 281 | client.stop(); 282 | 283 | Serial.println("\nSend data to thingspeak: "); 284 | Serial.println(); 285 | Serial.println(postStr); 286 | 287 | } 288 | 289 | blink_blue(); 290 | 291 | WiFi.disconnect(); 292 | Serial.println("\nWifi disconnected"); 293 | 294 | // going to deep sleep to save battery 295 | // Time to sleep (in seconds): 296 | const int sleepTimeS = 100; 297 | 298 | // Sleep 299 | Serial.println("\nESP8266 in sleep mode"); 300 | ESP.deepSleep(sleepTimeS * 1000000); 301 | delay(100); // this seems to be important, otherwise the board will not go into deep sleep 302 | 303 | } 304 | 305 | void blink_blue() { 306 | 307 | digitalWrite(2, LOW); 308 | delay(200); 309 | digitalWrite(2, HIGH); 310 | delay(200); 311 | 312 | } 313 | 314 | void blink_red() { 315 | 316 | digitalWrite(0, LOW); 317 | delay(200); 318 | digitalWrite(0, HIGH); 319 | delay(200); 320 | 321 | } 322 | 323 | // Function to connect and reconnect as necessary to the MQTT server. 324 | // Should be called in the loop function and it will take care if connecting. 325 | void MQTT_connect() { 326 | int8_t ret; 327 | 328 | // Stop if already connected. 329 | if (mqtt.connected()) { 330 | return; 331 | } 332 | 333 | Serial.print("\nConnecting to MQTT... "); 334 | 335 | uint8_t retries = 3; 336 | while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected 337 | Serial.println(mqtt.connectErrorString(ret)); 338 | Serial.println("Retrying MQTT connection in 5 seconds..."); 339 | mqtt.disconnect(); 340 | delay(5000); // wait 5 seconds 341 | retries--; 342 | if (retries == 0) { 343 | // basically die and wait for WDT to reset me 344 | while (1); 345 | } 346 | } 347 | Serial.println("MQTT Connected!"); 348 | } 349 | 350 | void loop() { 351 | 352 | // loop 353 | 354 | } -------------------------------------------------------------------------------- /ESP8266/ESP8266_OUTSIDE.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "Adafruit_MQTT.h" 10 | #include "Adafruit_MQTT_Client.h" 11 | 12 | /************************* WiFi Access Point *********************************/ 13 | 14 | #define WLAN_SSID "xxxxxxxx" 15 | #define WLAN_PASS "xxxxxxxx" 16 | 17 | /************************* Adafruit.io Setup *********************************/ 18 | 19 | #define AIO_SERVER "io.adafruit.com" 20 | #define AIO_SERVERPORT 1883 // use 8883 for SSL 21 | #define AIO_USERNAME "xxxxxxxx" 22 | #define AIO_KEY "xxxxxxxx" 23 | 24 | /************ Setup WiFi ******************/ 25 | 26 | // Create an ESP8266 WiFiClient class to connect to the MQTT server. 27 | WiFiClient client; 28 | // or... use WiFiFlientSecure for SSL 29 | //WiFiClientSecure client; 30 | 31 | /****************************** MQTT ***************************************/ 32 | 33 | // Store the MQTT server, username, and password in flash memory. 34 | // This is required for using the Adafruit MQTT library. 35 | const char MQTT_SERVER[] PROGMEM = AIO_SERVER; 36 | const char MQTT_USERNAME[] PROGMEM = AIO_USERNAME; 37 | const char MQTT_PASSWORD[] PROGMEM = AIO_KEY; 38 | 39 | // Setup the MQTT client class by passing in the WiFi client and MQTT server and login details. 40 | Adafruit_MQTT_Client mqtt(&client, MQTT_SERVER, AIO_SERVERPORT, MQTT_USERNAME, MQTT_PASSWORD); 41 | 42 | /****************************** Feeds ***************************************/ 43 | 44 | // Notice MQTT paths for AIO follow the form: /feeds/ 45 | const char SENSORTEMPOUTSIDE[] PROGMEM = AIO_USERNAME "/feeds/sensortempoutside"; 46 | Adafruit_MQTT_Publish sensortempoutside = Adafruit_MQTT_Publish(&mqtt, SENSORTEMPOUTSIDE); 47 | 48 | const char SENSORPRESSUREOUTSIDE[] PROGMEM = AIO_USERNAME "/feeds/sensorpressureoutside"; 49 | Adafruit_MQTT_Publish sensorpressureoutside = Adafruit_MQTT_Publish(&mqtt, SENSORPRESSUREOUTSIDE); 50 | 51 | const char SENSORHUMIDITYOUTSIDE[] PROGMEM = AIO_USERNAME "/feeds/sensorhumidityoutside"; 52 | Adafruit_MQTT_Publish sensorhumidityoutside = Adafruit_MQTT_Publish(&mqtt, SENSORHUMIDITYOUTSIDE); 53 | 54 | const char FEATHERBATTERYOUTSIDE[] PROGMEM = AIO_USERNAME "/feeds/featherbatteryoutside"; 55 | Adafruit_MQTT_Publish featherbatteryoutside = Adafruit_MQTT_Publish(&mqtt, FEATHERBATTERYOUTSIDE); 56 | 57 | /************************* THingSpeak IO *********************************/ 58 | 59 | String apiKey = "xxxxxxxx"; // This WriteAPI key is generated by ThingSpeak.com for your channel 60 | const char* server = "api.thingspeak.com"; 61 | 62 | /****************************** Display ***************************************/ 63 | 64 | #define OLED_RESET 3 65 | Adafruit_SSD1306 display(OLED_RESET); 66 | 67 | #define LOGO16_GLCD_HEIGHT 16 68 | #define LOGO16_GLCD_WIDTH 16 69 | static const unsigned char PROGMEM logo16_glcd_bmp[] = 70 | { B00000000, B11000000, 71 | B00000001, B11000000, 72 | B00000001, B11000000, 73 | B00000011, B11100000, 74 | B11110011, B11100000, 75 | B11111110, B11111000, 76 | B01111110, B11111111, 77 | B00110011, B10011111, 78 | B00011111, B11111100, 79 | B00001101, B01110000, 80 | B00011011, B10100000, 81 | B00111111, B11100000, 82 | B00111111, B11110000, 83 | B01111100, B11110000, 84 | B01110000, B01110000, 85 | B00000000, B00110000 86 | }; 87 | 88 | #if (SSD1306_LCDHEIGHT != 32) 89 | #error("Height incorrect, please fix Adafruit_SSD1306.h!"); 90 | #endif 91 | 92 | /****************************** current sensor ***************************************/ 93 | 94 | Adafruit_INA219 ina219; 95 | 96 | /****************************** Adafruit_BME280 ***************************************/ 97 | 98 | Adafruit_BME280 bme; // I2C 99 | 100 | /*************************** Sketch Code ************************************/ 101 | 102 | // Bug workaround for Arduino 1.6.6, it seems to need a function declaration 103 | // for some reason (only affects ESP8266, likely an arduino-builder bug). 104 | void MQTT_connect(); 105 | 106 | void setup() { 107 | // beginn 108 | Serial.begin(115200); 109 | delay(10); 110 | 111 | Serial.println("\nFeather Huzzah ESP8266 + FeatherWing OLED + BME280 + INA219"); 112 | 113 | Serial.println("\nESP8266 in normal mode"); 114 | 115 | // Connect to WiFi access point. 116 | Serial.print("\nConnecting to "); 117 | Serial.println(WLAN_SSID); 118 | 119 | WiFi.begin(WLAN_SSID, WLAN_PASS); 120 | while (WiFi.status() != WL_CONNECTED) { 121 | delay(500); 122 | Serial.print("."); 123 | } 124 | 125 | Serial.println(); 126 | Serial.println("\nWiFi connected"); 127 | Serial.print("\nIP address: "); Serial.println(WiFi.localIP()); 128 | 129 | // init the sensor 130 | if (!bme.begin()) { 131 | Serial.println("\nCould not find a valid BME280 sensor, check wiring!"); 132 | while (1); 133 | } 134 | 135 | Serial.println("\nMeasuring temp, pressure and humidity with BME280 ..."); 136 | 137 | // Initialize the INA219. 138 | ina219.begin(); 139 | 140 | Serial.println("\nMeasuring voltage and current with INA219 ..."); 141 | 142 | // by default, we'll generate the high voltage from the 3.3v line internally! (neat!) 143 | display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // initialize with the I2C addr 0x3C (for the 128x32) 144 | // init done 145 | 146 | // Show image buffer on the display hardware. 147 | // Since the buffer is intialized with an Adafruit splashscreen 148 | // internally, this will display the splashscreen. 149 | // display.display(); 150 | // delay(2000); 151 | 152 | // Clear the buffer. 153 | display.clearDisplay(); 154 | display.setTextSize(1); 155 | display.setTextColor(WHITE); 156 | 157 | // define the led's 158 | pinMode(0, OUTPUT); // red LED 159 | pinMode(2, OUTPUT); // blue LED 160 | 161 | // Ensure the connection to the MQTT server is alive (this will make the first 162 | // connection and automatically reconnect when disconnected). See the MQTT_connect 163 | // function definition further below. 164 | MQTT_connect(); 165 | 166 | float temp = bme.readTemperature(); 167 | float pressure = bme.readPressure() / 100.0F; 168 | float humidity = bme.readHumidity(); 169 | 170 | float shuntvoltage = 0; 171 | float busvoltage = 0; 172 | float current_mA = 0; 173 | float loadvoltage = 0; 174 | 175 | shuntvoltage = ina219.getShuntVoltage_mV(); 176 | busvoltage = ina219.getBusVoltage_V(); 177 | current_mA = ina219.getCurrent_mA(); 178 | loadvoltage = busvoltage + (shuntvoltage / 1000); 179 | 180 | // print to console 181 | 182 | Serial.println("\nWrite to Console"); 183 | 184 | Serial.print("\nTemperatur = "); 185 | Serial.print(temp); 186 | Serial.println(" *C"); 187 | 188 | Serial.print("Luftdruck = "); 189 | Serial.print(pressure); 190 | Serial.println(" hPa"); 191 | 192 | Serial.print("Luftfeuchtigkeit = "); 193 | Serial.print(humidity); 194 | Serial.println(" %"); 195 | 196 | Serial.print("\nBus Voltage: "); Serial.print(busvoltage); Serial.println(" V"); 197 | Serial.print("Shunt Voltage: "); Serial.print(shuntvoltage); Serial.println(" mV"); 198 | Serial.print("Load Voltage: "); Serial.print(loadvoltage); Serial.println(" V"); 199 | Serial.print("Current: "); Serial.print(current_mA); Serial.println(" mA"); 200 | 201 | blink_red(); 202 | 203 | // print to display 204 | 205 | Serial.println("\nWrite to Display"); 206 | 207 | display.setCursor(0, 0); 208 | display.print("Temperatur = "); 209 | display.print(temp, 2); 210 | display.println(" *C"); 211 | 212 | display.setCursor(0, 8); 213 | display.print("Luftdruck = "); 214 | display.print(pressure, 0); 215 | display.println(" hPa"); 216 | 217 | display.setCursor(0, 16); 218 | display.print("rLF = "); 219 | display.print(humidity, 2); 220 | display.println(" %"); 221 | 222 | display.setCursor(0, 24); 223 | display.print("Spannung = "); 224 | display.print(busvoltage, 2); 225 | display.println(" V"); 226 | 227 | display.display(); 228 | 229 | blink_red(); 230 | 231 | // write stuff to adafruit.io 232 | 233 | Serial.println("\nSend data to adafruit.io"); 234 | 235 | // Now we can publish stuff! 236 | Serial.print(F("\nSending SensorTempOutside val ")); 237 | Serial.print(temp, 2); 238 | Serial.print("..."); 239 | if (! sensortempoutside.publish(temp, 2)) { 240 | Serial.println(F("Failed")); 241 | } else { 242 | Serial.println(F("OK!")); 243 | } 244 | 245 | blink_blue(); 246 | 247 | Serial.print(F("Sending SensorPressureOutside val ")); 248 | Serial.print(pressure, 1); 249 | Serial.print("..."); 250 | if (! sensorpressureoutside.publish(pressure, 2)) { 251 | Serial.println(F("Failed")); 252 | } else { 253 | Serial.println(F("OK!")); 254 | } 255 | 256 | blink_blue(); 257 | 258 | Serial.print(F("Sending SensorHumidityOutside val ")); 259 | Serial.print(humidity, 2); 260 | Serial.print("..."); 261 | if (! sensorhumidityoutside.publish(humidity, 2)) { 262 | Serial.println(F("Failed")); 263 | } else { 264 | Serial.println(F("OK!")); 265 | } 266 | 267 | blink_blue(); 268 | 269 | Serial.print(F("Sending FeatherBatteryOutside val ")); 270 | Serial.print(busvoltage, 2); 271 | Serial.print("..."); 272 | if (! featherbatteryoutside.publish(busvoltage, 2)) { 273 | Serial.println(F("Failed")); 274 | } else { 275 | Serial.println(F("OK!")); 276 | } 277 | 278 | blink_blue(); 279 | 280 | // write stuff to thingspeak 281 | 282 | if (client.connect(server, 80)) { // "184.106.153.149" or api.thingspeak.com // Connect to the ThingsSpeak.com website 283 | 284 | String postStr = apiKey; // Build the update string 285 | 286 | postStr += "&field1="; 287 | postStr += String(temp, 2); 288 | 289 | postStr += "&field2="; 290 | postStr += String(pressure, 2); 291 | 292 | postStr += "&field3="; 293 | postStr += String(humidity, 2); 294 | 295 | postStr += "&field4="; 296 | postStr += String(busvoltage, 2); 297 | 298 | client.print("POST /update HTTP/1.1\n"); 299 | client.print("Host: api.thingspeak.com\n"); 300 | client.print("Connection: close\n"); 301 | client.print("X-THINGSPEAKAPIKEY: " + apiKey + "\n"); 302 | client.print("Content-Type: application/x-www-form-urlencoded\n"); 303 | client.print("Content-Length: "); 304 | client.print(postStr.length()); 305 | client.print("\n\n"); 306 | client.print(postStr); 307 | client.stop(); 308 | 309 | Serial.println("\nSend data to thingspeak: "); 310 | Serial.println(); 311 | Serial.println(postStr); 312 | 313 | } 314 | 315 | blink_blue(); 316 | 317 | WiFi.disconnect(); 318 | Serial.println("\nWifi disconnected"); 319 | 320 | // going to deep sleep to save battery 321 | // Time to sleep (in seconds): 322 | const int sleepTimeS = 100; 323 | 324 | // Sleep 325 | Serial.println("\nESP8266 in sleep mode"); 326 | ESP.deepSleep(sleepTimeS * 1000000); 327 | delay(100); // this seems to be important, otherwise the board will not go into deep sleep 328 | } 329 | 330 | void loop() { 331 | 332 | } 333 | 334 | void blink_blue() { 335 | 336 | digitalWrite(2, LOW); 337 | delay(200); 338 | digitalWrite(2, HIGH); 339 | delay(200); 340 | 341 | } 342 | 343 | void blink_red() { 344 | 345 | digitalWrite(0, LOW); 346 | delay(200); 347 | digitalWrite(0, HIGH); 348 | delay(200); 349 | 350 | } 351 | 352 | // Function to connect and reconnect as necessary to the MQTT server. 353 | // Should be called in the loop function and it will take care if connecting. 354 | void MQTT_connect() { 355 | int8_t ret; 356 | 357 | // Stop if already connected. 358 | if (mqtt.connected()) { 359 | return; 360 | } 361 | 362 | Serial.print("\nConnecting to MQTT... "); 363 | 364 | uint8_t retries = 3; 365 | while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected 366 | Serial.println(mqtt.connectErrorString(ret)); 367 | Serial.println("Retrying MQTT connection in 5 seconds..."); 368 | mqtt.disconnect(); 369 | delay(5000); // wait 5 seconds 370 | retries--; 371 | if (retries == 0) { 372 | // basically die and wait for WDT to reset me 373 | while (1); 374 | } 375 | } 376 | Serial.println("MQTT Connected!"); 377 | } -------------------------------------------------------------------------------- /ESP8266/README.md: -------------------------------------------------------------------------------- 1 | # these are some arduino sketches for a ESP8266 IoT-Data-Logger -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 LoveBootCaptain 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Modules/Animations/HD/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- -------------------------------------------------------------------------------- /Modules/Animations/HD/clear-day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoveBootCaptain/WeatherPi/24132f392080a61cabaa6fe99c3ef3ee684ccd77/Modules/Animations/HD/clear-day.png -------------------------------------------------------------------------------- /Modules/Animations/HD/clear-night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoveBootCaptain/WeatherPi/24132f392080a61cabaa6fe99c3ef3ee684ccd77/Modules/Animations/HD/clear-night.png -------------------------------------------------------------------------------- /Modules/Animations/HD/cloudy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoveBootCaptain/WeatherPi/24132f392080a61cabaa6fe99c3ef3ee684ccd77/Modules/Animations/HD/cloudy.png -------------------------------------------------------------------------------- /Modules/Animations/HD/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoveBootCaptain/WeatherPi/24132f392080a61cabaa6fe99c3ef3ee684ccd77/Modules/Animations/HD/error.png -------------------------------------------------------------------------------- /Modules/Animations/HD/fog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoveBootCaptain/WeatherPi/24132f392080a61cabaa6fe99c3ef3ee684ccd77/Modules/Animations/HD/fog.png -------------------------------------------------------------------------------- /Modules/Animations/HD/partly-cloudy-day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoveBootCaptain/WeatherPi/24132f392080a61cabaa6fe99c3ef3ee684ccd77/Modules/Animations/HD/partly-cloudy-day.png -------------------------------------------------------------------------------- /Modules/Animations/HD/partly-cloudy-night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoveBootCaptain/WeatherPi/24132f392080a61cabaa6fe99c3ef3ee684ccd77/Modules/Animations/HD/partly-cloudy-night.png -------------------------------------------------------------------------------- /Modules/Animations/HD/rain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoveBootCaptain/WeatherPi/24132f392080a61cabaa6fe99c3ef3ee684ccd77/Modules/Animations/HD/rain.png -------------------------------------------------------------------------------- /Modules/Animations/HD/raspberry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoveBootCaptain/WeatherPi/24132f392080a61cabaa6fe99c3ef3ee684ccd77/Modules/Animations/HD/raspberry.png -------------------------------------------------------------------------------- /Modules/Animations/HD/raspberry_boot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoveBootCaptain/WeatherPi/24132f392080a61cabaa6fe99c3ef3ee684ccd77/Modules/Animations/HD/raspberry_boot.png -------------------------------------------------------------------------------- /Modules/Animations/HD/snow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoveBootCaptain/WeatherPi/24132f392080a61cabaa6fe99c3ef3ee684ccd77/Modules/Animations/HD/snow.png -------------------------------------------------------------------------------- /Modules/Animations/HD/wind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoveBootCaptain/WeatherPi/24132f392080a61cabaa6fe99c3ef3ee684ccd77/Modules/Animations/HD/wind.png -------------------------------------------------------------------------------- /Modules/Animations/SD/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- -------------------------------------------------------------------------------- /Modules/Animations/SD/clear-day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoveBootCaptain/WeatherPi/24132f392080a61cabaa6fe99c3ef3ee684ccd77/Modules/Animations/SD/clear-day.png -------------------------------------------------------------------------------- /Modules/Animations/SD/clear-night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoveBootCaptain/WeatherPi/24132f392080a61cabaa6fe99c3ef3ee684ccd77/Modules/Animations/SD/clear-night.png -------------------------------------------------------------------------------- /Modules/Animations/SD/cloudy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoveBootCaptain/WeatherPi/24132f392080a61cabaa6fe99c3ef3ee684ccd77/Modules/Animations/SD/cloudy.png -------------------------------------------------------------------------------- /Modules/Animations/SD/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoveBootCaptain/WeatherPi/24132f392080a61cabaa6fe99c3ef3ee684ccd77/Modules/Animations/SD/error.png -------------------------------------------------------------------------------- /Modules/Animations/SD/fog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoveBootCaptain/WeatherPi/24132f392080a61cabaa6fe99c3ef3ee684ccd77/Modules/Animations/SD/fog.png -------------------------------------------------------------------------------- /Modules/Animations/SD/partly-cloudy-day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoveBootCaptain/WeatherPi/24132f392080a61cabaa6fe99c3ef3ee684ccd77/Modules/Animations/SD/partly-cloudy-day.png -------------------------------------------------------------------------------- /Modules/Animations/SD/partly-cloudy-night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoveBootCaptain/WeatherPi/24132f392080a61cabaa6fe99c3ef3ee684ccd77/Modules/Animations/SD/partly-cloudy-night.png -------------------------------------------------------------------------------- /Modules/Animations/SD/rain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoveBootCaptain/WeatherPi/24132f392080a61cabaa6fe99c3ef3ee684ccd77/Modules/Animations/SD/rain.png -------------------------------------------------------------------------------- /Modules/Animations/SD/raspberry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoveBootCaptain/WeatherPi/24132f392080a61cabaa6fe99c3ef3ee684ccd77/Modules/Animations/SD/raspberry.png -------------------------------------------------------------------------------- /Modules/Animations/SD/raspberry_boot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoveBootCaptain/WeatherPi/24132f392080a61cabaa6fe99c3ef3ee684ccd77/Modules/Animations/SD/raspberry_boot.png -------------------------------------------------------------------------------- /Modules/Animations/SD/snow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoveBootCaptain/WeatherPi/24132f392080a61cabaa6fe99c3ef3ee684ccd77/Modules/Animations/SD/snow.png -------------------------------------------------------------------------------- /Modules/Animations/SD/wind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoveBootCaptain/WeatherPi/24132f392080a61cabaa6fe99c3ef3ee684ccd77/Modules/Animations/SD/wind.png -------------------------------------------------------------------------------- /Modules/Config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | import json 4 | 5 | from init_logging import log_string 6 | 7 | 8 | class Config: 9 | def __init__(self): 10 | 11 | # read the config file 12 | self.config_data = open('/home/pi/WeatherPi/config.json').read() 13 | 14 | self.config = json.loads(self.config_data) 15 | 16 | log_string('config file read by module {}'.format(self.__class__)) 17 | 18 | def get_config(self): 19 | return self.config 20 | 21 | 22 | if __name__ == '__main__': 23 | Config().get_config() 24 | -------------------------------------------------------------------------------- /Modules/Data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | import json 4 | 5 | from Config import Config 6 | from init_logging import log_string 7 | 8 | 9 | class Data: 10 | def __init__(self): 11 | 12 | log_string('Run Update module by: {}'.format(self.__class__)) 13 | 14 | # set the configs 15 | self.config = Config().get_config() 16 | 17 | self.api_path = '/home/pi/WeatherPi/logs/api.json' 18 | self.api_data = self.json_data(self.api_path) 19 | 20 | # self.test_path = '/home/pi/WeatherPi/logs/test_weather_with_alarm.json' 21 | 22 | self.sensor_path = '/home/pi/WeatherPi/logs/sensors.json' 23 | self.sensor_data = self.json_data(self.sensor_path) 24 | 25 | # api data 26 | self.api_time = self.api_data['currently']['time'] 27 | self.temp_api = self.api_data['currently']['temperature'] 28 | self.rain_percentage_data = self.api_data['currently']['precipProbability'] 29 | self.rain_percentage = self.rain_percentage_data * 100 30 | self.forecast = self.api_data['currently']['summary'].encode('UTF-8') 31 | self.weather_icon = self.api_data['currently']['icon'] 32 | self.hourly_data = self.api_data['hourly']['data'] 33 | self.hourly_forecast = self.api_data['hourly']['summary'] 34 | self.daily = self.api_data['daily']['data'] 35 | self.forecast_today = self.daily[0] 36 | self.temp_range_today_min = int(self.forecast_today['temperatureMin']) 37 | self.temp_range_today_max = int(self.forecast_today['temperatureMax']) 38 | 39 | # generated string 40 | self.forecast_today = '{} bis {}°C - {}'.format( 41 | self.temp_range_today_min, 42 | self.temp_range_today_max, 43 | self.hourly_forecast.encode('UTF-8') 44 | ) 45 | 46 | # variables for weather icon 47 | self.version_path = self.config['UNICORN_VERSION'] + '/' 48 | self.folder_path = '/home/pi/WeatherPi/Modules/Animations/' 49 | self.path = self.folder_path + self.version_path 50 | self.icon_extension = '.' + 'png' 51 | 52 | self.icon_path = self.path + self.weather_icon + self.icon_extension 53 | 54 | # sensor data base 55 | self.sensor_base = [item for item in self.sensor_data['devices'] 56 | if item.get('_id') == self.config['NETATMO_DEVICE_ID']][0] 57 | 58 | # sensor data Wohnzimmer 59 | self.sensor_temp_wohnzimmer = self.sensor_base['dashboard_data']['Temperature'] 60 | self.sensor_pressure_wohnzimmer = self.sensor_base['dashboard_data']['AbsolutePressure'] 61 | self.sensor_humidity_wohnzimmer = self.sensor_base['dashboard_data']['Humidity'] 62 | 63 | # sensor data modules 64 | self.sensor_modules = self.sensor_base['modules'] 65 | 66 | # sensor data Kinderzimmer 67 | self.sensor_kinderzimmer = [item for item in self.sensor_modules 68 | if item.get('_id') == self.config['NETATMO_SENSOR_KINDER']][0] 69 | 70 | self.sensor_temp_kinderzimmer = self.sensor_kinderzimmer['dashboard_data']['Temperature'] 71 | 72 | # sensor data Schlafzimmer 73 | self.sensor_schlafzimmer = [item for item in self.sensor_modules 74 | if item.get('_id') == self.config['NETATMO_SENSOR_SCHLAF']][0] 75 | 76 | self.sensor_temp_schlafzimmer = self.sensor_schlafzimmer['dashboard_data']['Temperature'] 77 | 78 | # sensor data Balkon 79 | self.sensor_outside = [item for item in self.sensor_modules 80 | if item.get('_id') == self.config['NETATMO_SENSOR_BALKON']][0] 81 | 82 | self.sensor_temp_outside = self.sensor_outside['dashboard_data']['Temperature'] 83 | self.sensor_humidity_outside = self.sensor_outside['dashboard_data']['Humidity'] 84 | 85 | def json_data(self, path): 86 | 87 | try: 88 | log_string('read data from path: {}'.format(path)) 89 | 90 | data = open(path).read() 91 | json_data = json.loads(data) 92 | 93 | return json_data 94 | 95 | except IOError: 96 | 97 | log_string('ERROR - file read by module'.format(self.__class__)) 98 | 99 | 100 | if __name__ == '__main__': 101 | print(Data().sensor_temp_outside) 102 | -------------------------------------------------------------------------------- /Modules/Driver/Adafruit_BME280.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Adafruit Industries 2 | # Author: Tony DiCola 3 | # 4 | # Based on the BMP280 driver with BME280 changes provided by 5 | # David J Taylor, Edinburgh (www.satsignal.eu) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | # THE SOFTWARE. 24 | import logging 25 | import time 26 | 27 | 28 | # BME280 default address. 29 | BME280_I2CADDR = 0x77 30 | 31 | # Operating Modes 32 | BME280_OSAMPLE_1 = 1 33 | BME280_OSAMPLE_2 = 2 34 | BME280_OSAMPLE_4 = 3 35 | BME280_OSAMPLE_8 = 4 36 | BME280_OSAMPLE_16 = 5 37 | 38 | # BME280 Registers 39 | 40 | BME280_REGISTER_DIG_T1 = 0x88 # Trimming parameter registers 41 | BME280_REGISTER_DIG_T2 = 0x8A 42 | BME280_REGISTER_DIG_T3 = 0x8C 43 | 44 | BME280_REGISTER_DIG_P1 = 0x8E 45 | BME280_REGISTER_DIG_P2 = 0x90 46 | BME280_REGISTER_DIG_P3 = 0x92 47 | BME280_REGISTER_DIG_P4 = 0x94 48 | BME280_REGISTER_DIG_P5 = 0x96 49 | BME280_REGISTER_DIG_P6 = 0x98 50 | BME280_REGISTER_DIG_P7 = 0x9A 51 | BME280_REGISTER_DIG_P8 = 0x9C 52 | BME280_REGISTER_DIG_P9 = 0x9E 53 | 54 | BME280_REGISTER_DIG_H1 = 0xA1 55 | BME280_REGISTER_DIG_H2 = 0xE1 56 | BME280_REGISTER_DIG_H3 = 0xE3 57 | BME280_REGISTER_DIG_H4 = 0xE4 58 | BME280_REGISTER_DIG_H5 = 0xE5 59 | BME280_REGISTER_DIG_H6 = 0xE6 60 | BME280_REGISTER_DIG_H7 = 0xE7 61 | 62 | BME280_REGISTER_CHIPID = 0xD0 63 | BME280_REGISTER_VERSION = 0xD1 64 | BME280_REGISTER_SOFTRESET = 0xE0 65 | 66 | BME280_REGISTER_CONTROL_HUM = 0xF2 67 | BME280_REGISTER_CONTROL = 0xF4 68 | BME280_REGISTER_CONFIG = 0xF5 69 | BME280_REGISTER_PRESSURE_DATA = 0xF7 70 | BME280_REGISTER_TEMP_DATA = 0xFA 71 | BME280_REGISTER_HUMIDITY_DATA = 0xFD 72 | 73 | 74 | class BME280(object): 75 | def __init__(self, mode=BME280_OSAMPLE_1, address=BME280_I2CADDR, i2c=None, 76 | **kwargs): 77 | self._logger = logging.getLogger('Adafruit_BMP.BMP085') 78 | # Check that mode is valid. 79 | if mode not in [BME280_OSAMPLE_1, BME280_OSAMPLE_2, BME280_OSAMPLE_4, 80 | BME280_OSAMPLE_8, BME280_OSAMPLE_16]: 81 | raise ValueError( 82 | 'Unexpected mode value {0}. Set mode to one of BME280_ULTRALOWPOWER, BME280_STANDARD, BME280_HIGHRES, or BME280_ULTRAHIGHRES'.format(mode)) 83 | self._mode = mode 84 | # Create I2C device. 85 | if i2c is None: 86 | import I2C as I2C 87 | i2c = I2C 88 | self._device = i2c.get_i2c_device(address, **kwargs) 89 | # Load calibration values. 90 | self._load_calibration() 91 | self._device.write8(BME280_REGISTER_CONTROL, 0x3F) 92 | self.t_fine = 0.0 93 | 94 | def _load_calibration(self): 95 | 96 | self.dig_T1 = self._device.readU16LE(BME280_REGISTER_DIG_T1) 97 | self.dig_T2 = self._device.readS16LE(BME280_REGISTER_DIG_T2) 98 | self.dig_T3 = self._device.readS16LE(BME280_REGISTER_DIG_T3) 99 | 100 | self.dig_P1 = self._device.readU16LE(BME280_REGISTER_DIG_P1) 101 | self.dig_P2 = self._device.readS16LE(BME280_REGISTER_DIG_P2) 102 | self.dig_P3 = self._device.readS16LE(BME280_REGISTER_DIG_P3) 103 | self.dig_P4 = self._device.readS16LE(BME280_REGISTER_DIG_P4) 104 | self.dig_P5 = self._device.readS16LE(BME280_REGISTER_DIG_P5) 105 | self.dig_P6 = self._device.readS16LE(BME280_REGISTER_DIG_P6) 106 | self.dig_P7 = self._device.readS16LE(BME280_REGISTER_DIG_P7) 107 | self.dig_P8 = self._device.readS16LE(BME280_REGISTER_DIG_P8) 108 | self.dig_P9 = self._device.readS16LE(BME280_REGISTER_DIG_P9) 109 | 110 | self.dig_H1 = self._device.readU8(BME280_REGISTER_DIG_H1) 111 | self.dig_H2 = self._device.readS16LE(BME280_REGISTER_DIG_H2) 112 | self.dig_H3 = self._device.readU8(BME280_REGISTER_DIG_H3) 113 | self.dig_H6 = self._device.readS8(BME280_REGISTER_DIG_H7) 114 | 115 | h4 = self._device.readS8(BME280_REGISTER_DIG_H4) 116 | h4 = (h4 << 24) >> 20 117 | self.dig_H4 = h4 | (self._device.readU8(BME280_REGISTER_DIG_H5) & 0x0F) 118 | 119 | h5 = self._device.readS8(BME280_REGISTER_DIG_H6) 120 | h5 = (h5 << 24) >> 20 121 | self.dig_H5 = h5 | ( 122 | self._device.readU8(BME280_REGISTER_DIG_H5) >> 4 & 0x0F) 123 | 124 | ''' 125 | print '0xE4 = {0:2x}'.format (self._device.readU8 (BME280_REGISTER_DIG_H4)) 126 | print '0xE5 = {0:2x}'.format (self._device.readU8 (BME280_REGISTER_DIG_H5)) 127 | print '0xE6 = {0:2x}'.format (self._device.readU8 (BME280_REGISTER_DIG_H6)) 128 | print 'dig_H1 = {0:d}'.format (self.dig_H1) 129 | print 'dig_H2 = {0:d}'.format (self.dig_H2) 130 | print 'dig_H3 = {0:d}'.format (self.dig_H3) 131 | print 'dig_H4 = {0:d}'.format (self.dig_H4) 132 | print 'dig_H5 = {0:d}'.format (self.dig_H5) 133 | print 'dig_H6 = {0:d}'.format (self.dig_H6) 134 | ''' 135 | 136 | def read_raw_temp(self): 137 | """Reads the raw (uncompensated) temperature from the sensor.""" 138 | meas = self._mode 139 | self._device.write8(BME280_REGISTER_CONTROL_HUM, meas) 140 | meas = self._mode << 5 | self._mode << 2 | 1 141 | self._device.write8(BME280_REGISTER_CONTROL, meas) 142 | sleep_time = 0.00125 + 0.0023 * (1 << self._mode) 143 | sleep_time = sleep_time + 0.0023 * (1 << self._mode) + 0.000575 144 | sleep_time = sleep_time + 0.0023 * (1 << self._mode) + 0.000575 145 | time.sleep(sleep_time) # Wait the required time 146 | msb = self._device.readU8(BME280_REGISTER_TEMP_DATA) 147 | lsb = self._device.readU8(BME280_REGISTER_TEMP_DATA + 1) 148 | xlsb = self._device.readU8(BME280_REGISTER_TEMP_DATA + 2) 149 | raw = ((msb << 16) | (lsb << 8) | xlsb) >> 4 150 | return raw 151 | 152 | def read_raw_pressure(self): 153 | """Reads the raw (uncompensated) pressure level from the sensor.""" 154 | """Assumes that the temperature has already been read """ 155 | """i.e. that enough delay has been provided""" 156 | msb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA) 157 | lsb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA + 1) 158 | xlsb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA + 2) 159 | raw = ((msb << 16) | (lsb << 8) | xlsb) >> 4 160 | return raw 161 | 162 | def read_raw_humidity(self): 163 | """Assumes that the temperature has already been read """ 164 | """i.e. that enough delay has been provided""" 165 | msb = self._device.readU8(BME280_REGISTER_HUMIDITY_DATA) 166 | lsb = self._device.readU8(BME280_REGISTER_HUMIDITY_DATA + 1) 167 | raw = (msb << 8) | lsb 168 | return raw 169 | 170 | def read_temperature(self): 171 | """Gets the compensated temperature in degrees celsius.""" 172 | # float in Python is double precision 173 | UT = float(self.read_raw_temp()) 174 | var1 = (UT / 16384.0 - self.dig_T1 / 1024.0) * float(self.dig_T2) 175 | var2 = ((UT / 131072.0 - self.dig_T1 / 8192.0) * ( 176 | UT / 131072.0 - self.dig_T1 / 8192.0)) * float(self.dig_T3) 177 | self.t_fine = int(var1 + var2) 178 | temp = (var1 + var2) / 5120.0 179 | return temp 180 | 181 | def read_pressure(self): 182 | """Gets the compensated pressure in Pascals.""" 183 | adc = self.read_raw_pressure() 184 | var1 = self.t_fine / 2.0 - 64000.0 185 | var2 = var1 * var1 * self.dig_P6 / 32768.0 186 | var2 = var2 + var1 * self.dig_P5 * 2.0 187 | var2 = var2 / 4.0 + self.dig_P4 * 65536.0 188 | var1 = ( 189 | self.dig_P3 * var1 * var1 / 524288.0 + self.dig_P2 * var1) / 524288.0 190 | var1 = (1.0 + var1 / 32768.0) * self.dig_P1 191 | if var1 == 0: 192 | return 0 193 | p = 1048576.0 - adc 194 | p = ((p - var2 / 4096.0) * 6250.0) / var1 195 | var1 = self.dig_P9 * p * p / 2147483648.0 196 | var2 = p * self.dig_P8 / 32768.0 197 | p = p + (var1 + var2 + self.dig_P7) / 16.0 198 | return p 199 | 200 | def read_humidity(self): 201 | adc = self.read_raw_humidity() 202 | # print 'Raw humidity = {0:d}'.format (adc) 203 | h = self.t_fine - 76800.0 204 | h = (adc - (self.dig_H4 * 64.0 + self.dig_H5 / 16384.8 * h)) * ( 205 | self.dig_H2 / 65536.0 * (1.0 + self.dig_H6 / 67108864.0 * h * ( 206 | 1.0 + self.dig_H3 / 67108864.0 * h))) 207 | h = h * (1.0 - self.dig_H1 * h / 524288.0) 208 | if h > 100: 209 | h = 100 210 | elif h < 0: 211 | h = 0 212 | return h -------------------------------------------------------------------------------- /Modules/Driver/Adafruit_PureIO/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- -------------------------------------------------------------------------------- /Modules/Driver/Adafruit_PureIO/smbus.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Adafruit Industries 2 | # Author: Tony DiCola 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | import struct 22 | from ctypes import * 23 | from fcntl import ioctl 24 | 25 | # I2C C API constants (from linux kernel headers) 26 | I2C_M_TEN = 0x0010 # this is a ten bit chip address 27 | I2C_M_RD = 0x0001 # read data, from slave to master 28 | I2C_M_STOP = 0x8000 # if I2C_FUNC_PROTOCOL_MANGLING 29 | I2C_M_NOSTART = 0x4000 # if I2C_FUNC_NOSTART 30 | I2C_M_REV_DIR_ADDR = 0x2000 # if I2C_FUNC_PROTOCOL_MANGLING 31 | I2C_M_IGNORE_NAK = 0x1000 # if I2C_FUNC_PROTOCOL_MANGLING 32 | I2C_M_NO_RD_ACK = 0x0800 # if I2C_FUNC_PROTOCOL_MANGLING 33 | I2C_M_RECV_LEN = 0x0400 # length will be first received byte 34 | 35 | I2C_SLAVE = 0x0703 # Use this slave address 36 | I2C_SLAVE_FORCE = 0x0706 # Use this slave address, even if 37 | # is already in use by a driver! 38 | I2C_TENBIT = 0x0704 # 0 for 7 bit addrs, != 0 for 10 bit 39 | I2C_FUNCS = 0x0705 # Get the adapter functionality mask 40 | I2C_RDWR = 0x0707 # Combined R/W transfer (one STOP only) 41 | I2C_PEC = 0x0708 # != 0 to use PEC with SMBus 42 | I2C_SMBUS = 0x0720 # SMBus transfer 43 | 44 | 45 | # ctypes versions of I2C structs defined by kernel. 46 | class i2c_msg(Structure): 47 | _fields_ = [ 48 | ('addr', c_uint16), 49 | ('flags', c_uint16), 50 | ('len', c_uint16), 51 | ('buf', POINTER(c_uint8)) 52 | ] 53 | 54 | 55 | class i2c_rdwr_ioctl_data(Structure): 56 | _fields_ = [ 57 | ('msgs', POINTER(i2c_msg)), 58 | ('nmsgs', c_uint32) 59 | ] 60 | 61 | 62 | def make_i2c_rdwr_data(messages): 63 | """Utility function to create and return an i2c_rdwr_ioctl_data structure 64 | populated with a list of specified I2C messages. The messages parameter 65 | should be a list of tuples which represent the individual I2C messages to 66 | send in this transaction. Tuples should contain 4 elements: address value, 67 | flags value, buffer length, ctypes c_uint8 pointer to buffer. 68 | """ 69 | # Create message array and populate with provided data. 70 | msg_data_type = i2c_msg * len(messages) 71 | msg_data = msg_data_type() 72 | for i, m in enumerate(messages): 73 | msg_data[i].addr = m[0] & 0x7F 74 | msg_data[i].flags = m[1] 75 | msg_data[i].len = m[2] 76 | msg_data[i].buf = m[3] 77 | # Now build the data structure. 78 | data = i2c_rdwr_ioctl_data() 79 | data.msgs = msg_data 80 | data.nmsgs = len(messages) 81 | return data 82 | 83 | 84 | # Create an interface that mimics the Python SMBus API. 85 | class SMBus(object): 86 | """I2C interface that mimics the Python SMBus API but is implemented with 87 | pure Python calls to ioctl and direct /dev/i2c device access. 88 | """ 89 | 90 | def __init__(self, bus=None): 91 | """Create a new smbus instance. Bus is an optional parameter that 92 | specifies the I2C bus number to use, for example 1 would use device 93 | /dev/i2c-1. If bus is not specified then the open function should be 94 | called to open the bus. 95 | """ 96 | self._device = None 97 | if bus is not None: 98 | self.open(bus) 99 | 100 | def __del__(self): 101 | """Clean up any resources used by the SMBus instance.""" 102 | self.close() 103 | 104 | def __enter__(self): 105 | """Context manager enter function.""" 106 | # Just return this object so it can be used in a with statement, like 107 | # with SMBus(1) as bus: 108 | # # do stuff! 109 | return self 110 | 111 | def __exit__(self, exc_type, exc_val, exc_tb): 112 | """Context manager exit function, ensures resources are cleaned up.""" 113 | self.close() 114 | return False # Don't suppress exceptions. 115 | 116 | def open(self, bus): 117 | """Open the smbus interface on the specified bus.""" 118 | # Close the device if it's already open. 119 | if self._device is not None: 120 | self.close() 121 | # Try to open the file for the specified bus. Must turn off buffering 122 | # or else Python 3 fails (see: https://bugs.python.org/issue20074) 123 | self._device = open('/dev/i2c-{0}'.format(bus), 'r+b', buffering=0) 124 | # TODO: Catch IOError and throw a better error message that describes 125 | # what's wrong (i.e. I2C may not be enabled or the bus doesn't exist). 126 | 127 | def close(self): 128 | """Close the smbus connection. You cannot make any other function 129 | calls on the bus unless open is called!""" 130 | if self._device is not None: 131 | self._device.close() 132 | self._device = None 133 | 134 | def _select_device(self, addr): 135 | """Set the address of the device to communicate with on the I2C bus.""" 136 | ioctl(self._device.fileno(), I2C_SLAVE, addr & 0x7F) 137 | 138 | def read_byte(self, addr): 139 | """Read a single byte from the specified device.""" 140 | assert self._device is not None, 'Bus must be opened before operations are made against it!' 141 | self._select_device(addr) 142 | return ord(self._device.read(1)) 143 | 144 | def read_byte_data(self, addr, cmd): 145 | """Read a single byte from the specified cmd register of the device.""" 146 | assert self._device is not None, 'Bus must be opened before operations are made against it!' 147 | # Build ctypes values to marshall between ioctl and Python. 148 | reg = c_uint8(cmd) 149 | result = c_uint8() 150 | # Build ioctl request. 151 | request = make_i2c_rdwr_data([ 152 | (addr, 0, 1, pointer(reg)), # Write cmd register. 153 | (addr, I2C_M_RD, 1, pointer(result)) # Read 1 byte as result. 154 | ]) 155 | # Make ioctl call and return result data. 156 | ioctl(self._device.fileno(), I2C_RDWR, request) 157 | return result.value 158 | 159 | def read_word_data(self, addr, cmd): 160 | """Read a word (2 bytes) from the specified cmd register of the device. 161 | Note that this will interpret data using the endianness of the processor 162 | running Python (typically little endian)! 163 | """ 164 | assert self._device is not None, 'Bus must be opened before operations are made against it!' 165 | # Build ctypes values to marshall between ioctl and Python. 166 | reg = c_uint8(cmd) 167 | result = c_uint16() 168 | # Build ioctl request. 169 | request = make_i2c_rdwr_data([ 170 | (addr, 0, 1, pointer(reg)), # Write cmd register. 171 | (addr, I2C_M_RD, 2, cast(pointer(result), POINTER(c_uint8))) # Read word (2 bytes). 172 | ]) 173 | # Make ioctl call and return result data. 174 | ioctl(self._device.fileno(), I2C_RDWR, request) 175 | return result.value 176 | 177 | def read_block_data(self, addr, cmd): 178 | """Perform a block read from the specified cmd register of the device. 179 | The amount of data read is determined by the first byte send back by 180 | the device. Data is returned as a bytearray. 181 | """ 182 | # TODO: Unfortunately this will require calling the low level I2C 183 | # access ioctl to trigger a proper read_block_data. The amount of data 184 | # returned isn't known until the device starts responding so an I2C_RDWR 185 | # ioctl won't work. 186 | raise NotImplementedError() 187 | 188 | def read_i2c_block_data(self, addr, cmd, length=32): 189 | """Perform a read from the specified cmd register of device. Length number 190 | of bytes (default of 32) will be read and returned as a bytearray. 191 | """ 192 | assert self._device is not None, 'Bus must be opened before operations are made against it!' 193 | # Build ctypes values to marshall between ioctl and Python. 194 | reg = c_uint8(cmd) 195 | result = create_string_buffer(length) 196 | # Build ioctl request. 197 | request = make_i2c_rdwr_data([ 198 | (addr, 0, 1, pointer(reg)), # Write cmd register. 199 | (addr, I2C_M_RD, length, cast(result, POINTER(c_uint8))) # Read data. 200 | ]) 201 | # Make ioctl call and return result data. 202 | ioctl(self._device.fileno(), I2C_RDWR, request) 203 | return bytearray(result.raw) # Use .raw instead of .value which will stop at a null byte! 204 | 205 | def write_quick(self, addr): 206 | """Write a single byte to the specified device.""" 207 | # What a strange function, from the python-smbus source this appears to 208 | # just write a single byte that initiates a write to the specified device 209 | # address (but writes no data!). The functionality is duplicated below 210 | # but the actual use case for this is unknown. 211 | assert self._device is not None, 'Bus must be opened before operations are made against it!' 212 | # Build ioctl request. 213 | request = make_i2c_rdwr_data([ 214 | (addr, 0, 0, None), # Write with no data. 215 | ]) 216 | # Make ioctl call and return result data. 217 | ioctl(self._device.fileno(), I2C_RDWR, request) 218 | 219 | def write_byte(self, addr, val): 220 | """Write a single byte to the specified device.""" 221 | assert self._device is not None, 'Bus must be opened before operations are made against it!' 222 | self._select_device(addr) 223 | data = bytearray(1) 224 | data[0] = val & 0xFF 225 | self._device.write(data) 226 | 227 | def write_byte_data(self, addr, cmd, val): 228 | """Write a byte of data to the specified cmd register of the device. 229 | """ 230 | assert self._device is not None, 'Bus must be opened before operations are made against it!' 231 | # Construct a string of data to send with the command register and byte value. 232 | data = bytearray(2) 233 | data[0] = cmd & 0xFF 234 | data[1] = val & 0xFF 235 | # Send the data to the device. 236 | self._select_device(addr) 237 | self._device.write(data) 238 | 239 | def write_word_data(self, addr, cmd, val): 240 | """Write a word (2 bytes) of data to the specified cmd register of the 241 | device. Note that this will write the data in the endianness of the 242 | processor running Python (typically little endian)! 243 | """ 244 | assert self._device is not None, 'Bus must be opened before operations are made against it!' 245 | # Construct a string of data to send with the command register and word value. 246 | data = struct.pack('=BH', cmd & 0xFF, val & 0xFFFF) 247 | # Send the data to the device. 248 | self._select_device(addr) 249 | self._device.write(data) 250 | 251 | def write_block_data(self, addr, cmd, vals): 252 | """Write a block of data to the specified cmd register of the device. 253 | The amount of data to write should be the first byte inside the vals 254 | string/bytearray and that count of bytes of data to write should follow 255 | it. 256 | """ 257 | # Just use the I2C block data write to write the provided values and 258 | # their length as the first byte. 259 | data = bytearray(len(vals) + 1) 260 | data[0] = len(vals) & 0xFF 261 | data[1:] = vals[0:] 262 | self.write_i2c_block_data(addr, cmd, data) 263 | 264 | def write_i2c_block_data(self, addr, cmd, vals): 265 | """Write a buffer of data to the specified cmd register of the device. 266 | """ 267 | assert self._device is not None, 'Bus must be opened before operations are made against it!' 268 | # Construct a string of data to send, including room for the command register. 269 | data = bytearray(len(vals) + 1) 270 | data[0] = cmd & 0xFF # Command register at the start. 271 | data[1:] = vals[0:] # Copy in the block data (ugly but necessary to ensure 272 | # the entire write happens in one transaction). 273 | # Send the data to the device. 274 | self._select_device(addr) 275 | self._device.write(data) 276 | 277 | def process_call(self, addr, cmd, val): 278 | """Perform a smbus process call by writing a word (2 byte) value to 279 | the specified register of the device, and then reading a word of response 280 | data (which is returned). 281 | """ 282 | assert self._device is not None, 'Bus must be opened before operations are made against it!' 283 | # Build ctypes values to marshall between ioctl and Python. 284 | data = create_string_buffer(struct.pack('=BH', cmd, val)) 285 | result = c_uint16() 286 | # Build ioctl request. 287 | request = make_i2c_rdwr_data([ 288 | (addr, 0, 3, cast(pointer(data), POINTER(c_uint8))), # Write data. 289 | (addr, I2C_M_RD, 2, cast(pointer(result), POINTER(c_uint8))) # Read word (2 bytes). 290 | ]) 291 | # Make ioctl call and return result data. 292 | ioctl(self._device.fileno(), I2C_RDWR, request) 293 | # Note the python-smbus code appears to have a rather serious bug and 294 | # does not return the result value! This is fixed below by returning it. 295 | return result.value 296 | -------------------------------------------------------------------------------- /Modules/Driver/BicolorBargraph24.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Adafruit Industries 2 | # Author: Tony DiCola 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | from __future__ import division 22 | 23 | import HT16K33 24 | 25 | # Color values as convenient globals. 26 | # This is a bitmask value where the first bit is green, and the second bit is 27 | # red. If both bits are set the color is yellow (red + green light). 28 | OFF = 0 29 | GREEN = 1 30 | RED = 2 31 | YELLOW = 3 32 | 33 | 34 | class BicolorBargraph24(HT16K33.HT16K33): 35 | """Bi-color 24 segment bar graph LED backpack display.""" 36 | 37 | def __init__(self, **kwargs): 38 | """Initialize display. All arguments will be passed to the HT16K33 class 39 | initializer, including optional I2C address and bus number parameters. 40 | """ 41 | super(BicolorBargraph24, self).__init__(**kwargs) 42 | 43 | def set_bar(self, bar, value): 44 | """Set bar to desired color. Bar should be a value of 0 to 23, and value 45 | should be OFF, GREEN, RED, or YELLOW. 46 | """ 47 | if bar < 0 or bar > 23: 48 | # Ignore out of bounds bars. 49 | return 50 | # Compute cathode and anode value. 51 | c = (bar if bar < 12 else bar - 12) // 4 52 | a = bar % 4 53 | if bar >= 12: 54 | a += 4 55 | # Set green LED based on 1st bit in value. 56 | self.set_led(c*16+a+8, 1 if value & GREEN > 0 else 0) 57 | # Set red LED based on 2nd bit in value. 58 | self.set_led(c*16+a, 1 if value & RED > 0 else 0) -------------------------------------------------------------------------------- /Modules/Driver/GPIO.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Adafruit Industries 2 | # Author: Tony DiCola 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | 22 | import Platform as Platform 23 | 24 | OUT = 0 25 | IN = 1 26 | HIGH = True 27 | LOW = False 28 | 29 | 30 | class BaseGPIO(object): 31 | """Base class for implementing simple digital IO for a platform. 32 | Implementors are expected to subclass from this and provide an implementation 33 | of the setup, output, and input functions.""" 34 | 35 | def setup(self, pin, mode): 36 | """Set the input or output mode for a specified pin. Mode should be 37 | either OUT or IN.""" 38 | raise NotImplementedError 39 | 40 | def output(self, pin, value): 41 | """Set the specified pin the provided high/low value. Value should be 42 | either HIGH/LOW or a boolean (true = high).""" 43 | raise NotImplementedError 44 | 45 | def input(self, pin): 46 | """Read the specified pin and return HIGH/true if the pin is pulled high, 47 | or LOW/false if pulled low.""" 48 | raise NotImplementedError 49 | 50 | def set_high(self, pin): 51 | """Set the specified pin HIGH.""" 52 | self.output(pin, HIGH) 53 | 54 | def set_low(self, pin): 55 | """Set the specified pin LOW.""" 56 | self.output(pin, LOW) 57 | 58 | def is_high(self, pin): 59 | """Return true if the specified pin is pulled high.""" 60 | return self.input(pin) == HIGH 61 | 62 | def is_low(self, pin): 63 | """Return true if the specified pin is pulled low.""" 64 | return self.input(pin) == LOW 65 | 66 | def output_pins(self, pins): 67 | """Set multiple pins high or low at once. Pins should be a dict of pin 68 | name to pin value (HIGH/True for 1, LOW/False for 0). All provided pins 69 | will be set to the given values. 70 | """ 71 | # General implementation just loops through pins and writes them out 72 | # manually. This is not optimized, but subclasses can choose to implement 73 | # a more optimal batch output implementation. See the MCP230xx class for 74 | # example of optimized implementation. 75 | for pin, value in pins.iteritems(): 76 | self.output(pin, value) 77 | 78 | def setup_pins(self, pins): 79 | """Setup multiple pins as inputs or outputs at once. Pins should be a 80 | dict of pin name to pin type (IN or OUT). 81 | """ 82 | # General implementation that can be improved by subclasses. 83 | for pin, value in pins.iteritems(): 84 | self.setup(pin, value) 85 | 86 | 87 | class RPiGPIOAdapter(BaseGPIO): 88 | """GPIO implementation for the Raspberry Pi using the RPi.GPIO library.""" 89 | 90 | def __init__(self, rpi_gpio, mode=None): 91 | self.rpi_gpio = rpi_gpio 92 | # Suppress warnings about GPIO in use. 93 | rpi_gpio.setwarnings(False) 94 | if mode == rpi_gpio.BOARD or mode == rpi_gpio.BCM: 95 | rpi_gpio.setmode(mode) 96 | elif mode is not None: 97 | raise ValueError('Unexpected value for mode. Must be BOARD or BCM.') 98 | else: 99 | # Default to BCM numbering if not told otherwise. 100 | rpi_gpio.setmode(rpi_gpio.BCM) 101 | 102 | def setup(self, pin, mode): 103 | """Set the input or output mode for a specified pin. Mode should be 104 | either OUTPUT or INPUT. 105 | """ 106 | self.rpi_gpio.setup(pin, self.rpi_gpio.IN if mode == IN else \ 107 | self.rpi_gpio.OUT) 108 | 109 | def output(self, pin, value): 110 | """Set the specified pin the provided high/low value. Value should be 111 | either HIGH/LOW or a boolean (true = high). 112 | """ 113 | self.rpi_gpio.output(pin, value) 114 | 115 | def input(self, pin): 116 | """Read the specified pin and return HIGH/true if the pin is pulled high, 117 | or LOW/false if pulled low. 118 | """ 119 | return self.rpi_gpio.input(pin) 120 | 121 | 122 | class AdafruitBBIOAdapter(BaseGPIO): 123 | """GPIO implementation for the Beaglebone Black using the Adafruit_BBIO 124 | library. 125 | """ 126 | 127 | def __init__(self, bbio_gpio): 128 | self.bbio_gpio = bbio_gpio 129 | 130 | def setup(self, pin, mode): 131 | """Set the input or output mode for a specified pin. Mode should be 132 | either OUTPUT or INPUT. 133 | """ 134 | self.bbio_gpio.setup(pin, self.bbio_gpio.IN if mode == IN else \ 135 | self.bbio_gpio.OUT) 136 | 137 | def output(self, pin, value): 138 | """Set the specified pin the provided high/low value. Value should be 139 | either HIGH/LOW or a boolean (true = high). 140 | """ 141 | self.bbio_gpio.output(pin, value) 142 | 143 | def input(self, pin): 144 | """Read the specified pin and return HIGH/true if the pin is pulled high, 145 | or LOW/false if pulled low. 146 | """ 147 | return self.bbio_gpio.input(pin) 148 | 149 | 150 | def get_platform_gpio(**keywords): 151 | """Attempt to return a GPIO instance for the platform which the code is being 152 | executed on. Currently supports only the Raspberry Pi using the RPi.GPIO 153 | library and Beaglebone Black using the Adafruit_BBIO library. Will throw an 154 | exception if a GPIO instance can't be created for the current platform. The 155 | returned GPIO object is an instance of BaseGPIO. 156 | """ 157 | plat = Platform.platform_detect() 158 | if plat == Platform.RASPBERRY_PI: 159 | import RPi.GPIO 160 | return RPiGPIOAdapter(RPi.GPIO, **keywords) 161 | elif plat == Platform.BEAGLEBONE_BLACK: 162 | import Adafruit_BBIO.GPIO 163 | return AdafruitBBIOAdapter(Adafruit_BBIO.GPIO, **keywords) 164 | elif plat == Platform.UNKNOWN: 165 | raise RuntimeError('Could not determine platform.') 166 | -------------------------------------------------------------------------------- /Modules/Driver/HT16K33.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Adafruit Industries 2 | # Author: Tony DiCola 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | from __future__ import division 22 | 23 | # Constants 24 | DEFAULT_ADDRESS = 0x70 25 | HT16K33_BLINK_CMD = 0x80 26 | HT16K33_BLINK_DISPLAYON = 0x01 27 | HT16K33_BLINK_OFF = 0x00 28 | HT16K33_BLINK_2HZ = 0x02 29 | HT16K33_BLINK_1HZ = 0x04 30 | HT16K33_BLINK_HALFHZ = 0x06 31 | HT16K33_SYSTEM_SETUP = 0x20 32 | HT16K33_OSCILLATOR = 0x01 33 | HT16K33_CMD_BRIGHTNESS = 0xE0 34 | 35 | 36 | class HT16K33(object): 37 | """Driver for interfacing with a Holtek HT16K33 16x8 LED driver.""" 38 | 39 | def __init__(self, address=DEFAULT_ADDRESS, i2c=None, **kwargs): 40 | """Create an HT16K33 driver for devie on the specified I2C address 41 | (defaults to 0x70) and I2C bus (defaults to platform specific bus). 42 | """ 43 | if i2c is None: 44 | import Adafruit_GPIO.I2C as I2C 45 | i2c = I2C 46 | self._device = i2c.get_i2c_device(address, **kwargs) 47 | self.buffer = bytearray([0] * 16) 48 | 49 | def begin(self): 50 | """Initialize driver with LEDs enabled and all turned off.""" 51 | # Turn on the oscillator. 52 | self._device.writeList(HT16K33_SYSTEM_SETUP | HT16K33_OSCILLATOR, []) 53 | # Turn display on with no blinking. 54 | self.set_blink(HT16K33_BLINK_OFF) 55 | # Set display to full brightness. 56 | self.set_brightness(15) 57 | 58 | def set_blink(self, frequency): 59 | """Blink display at specified frequency. Note that frequency must be a 60 | value allowed by the HT16K33, specifically one of: HT16K33_BLINK_OFF, 61 | HT16K33_BLINK_2HZ, HT16K33_BLINK_1HZ, or HT16K33_BLINK_HALFHZ. 62 | """ 63 | if frequency not in [HT16K33_BLINK_OFF, HT16K33_BLINK_2HZ, 64 | HT16K33_BLINK_1HZ, HT16K33_BLINK_HALFHZ]: 65 | raise ValueError( 66 | 'Frequency must be one of HT16K33_BLINK_OFF, HT16K33_BLINK_2HZ, HT16K33_BLINK_1HZ, or HT16K33_BLINK_HALFHZ.') 67 | self._device.writeList(HT16K33_BLINK_CMD | HT16K33_BLINK_DISPLAYON | frequency, []) 68 | 69 | def set_brightness(self, brightness): 70 | """Set brightness of entire display to specified value (16 levels, from 71 | 0 to 15). 72 | """ 73 | if brightness < 0 or brightness > 15: 74 | raise ValueError('Brightness must be a value of 0 to 15.') 75 | self._device.writeList(HT16K33_CMD_BRIGHTNESS | brightness, []) 76 | 77 | def set_led(self, led, value): 78 | """Sets specified LED (value of 0 to 127) to the specified value, 0/False 79 | for off and 1 (or any True/non-zero value) for on. 80 | """ 81 | if led < 0 or led > 127: 82 | raise ValueError('LED must be value of 0 to 127.') 83 | # Calculate position in byte buffer and bit offset of desired LED. 84 | pos = led // 8 85 | offset = led % 8 86 | if not value: 87 | # Turn off the specified LED (set bit to zero). 88 | self.buffer[pos] &= ~(1 << offset) 89 | else: 90 | # Turn on the speciried LED (set bit to one). 91 | self.buffer[pos] |= (1 << offset) 92 | 93 | def write_display(self): 94 | """Write display buffer to display hardware.""" 95 | for i, value in enumerate(self.buffer): 96 | self._device.write8(i, value) 97 | 98 | def clear(self): 99 | """Clear contents of display buffer.""" 100 | for i, value in enumerate(self.buffer): 101 | self.buffer[i] = 0 102 | -------------------------------------------------------------------------------- /Modules/Driver/I2C.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Adafruit Industries 2 | # Author: Tony DiCola 3 | # Based on Adafruit_I2C.py created by Kevin Townsend. 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | import logging 23 | import subprocess 24 | 25 | import Modules.Driver.Platform as Platform 26 | 27 | 28 | def reverseByteOrder(data): 29 | """Reverses the byte order of an int (16-bit) or long (32-bit) value.""" 30 | # Courtesy Vishal Sapre 31 | byteCount = len(hex(data)[2:].replace('L', '')[::2]) 32 | val = 0 33 | for i in range(byteCount): 34 | val = (val << 8) | (data & 0xff) 35 | data >>= 8 36 | return val 37 | 38 | 39 | def get_default_bus(): 40 | """Return the default bus number based on the device platform. For a 41 | Raspberry Pi either bus 0 or 1 (based on the Pi revision) will be returned. 42 | For a Beaglebone Black the first user accessible bus, 1, will be returned. 43 | """ 44 | plat = Platform.platform_detect() 45 | if plat == Platform.RASPBERRY_PI: 46 | if Platform.pi_revision() == 1: 47 | # Revision 1 Pi uses I2C bus 0. 48 | return 0 49 | else: 50 | # Revision 2 Pi uses I2C bus 1. 51 | return 1 52 | elif plat == Platform.BEAGLEBONE_BLACK: 53 | # Beaglebone Black has multiple I2C buses, default to 1 (P9_19 and P9_20). 54 | return 1 55 | else: 56 | raise RuntimeError('Could not determine default I2C bus for platform.') 57 | 58 | 59 | def get_i2c_device(address, busnum=None, i2c_interface=None, **kwargs): 60 | """Return an I2C device for the specified address and on the specified bus. 61 | If busnum isn't specified, the default I2C bus for the platform will attempt 62 | to be detected. 63 | """ 64 | if busnum is None: 65 | busnum = get_default_bus() 66 | return Device(address, busnum, i2c_interface, **kwargs) 67 | 68 | 69 | def require_repeated_start(): 70 | """Enable repeated start conditions for I2C register reads. This is the 71 | normal behavior for I2C, however on some platforms like the Raspberry Pi 72 | there are bugs which disable repeated starts unless explicitly enabled with 73 | this function. See this thread for more details: 74 | http://www.raspberrypi.org/forums/viewtopic.php?f=44&t=15840 75 | """ 76 | plat = Platform.platform_detect() 77 | if plat == Platform.RASPBERRY_PI: 78 | # On the Raspberry Pi there is a bug where register reads don't send a 79 | # repeated start condition like the kernel smbus I2C driver functions 80 | # define. As a workaround this bit in the BCM2708 driver sysfs tree can 81 | # be changed to enable I2C repeated starts. 82 | subprocess.check_call('chmod 666 /sys/module/i2c_bcm2708/parameters/combined', shell=True) 83 | subprocess.check_call('echo -n 1 > /sys/module/i2c_bcm2708/parameters/combined', shell=True) 84 | # Other platforms are a no-op because they (presumably) have the correct 85 | # behavior and send repeated starts. 86 | 87 | 88 | class Device(object): 89 | """Class for communicating with an I2C device using the adafruit-pureio pure 90 | python smbus library, or other smbus compatible I2C interface. Allows reading 91 | and writing 8-bit, 16-bit, and byte array values to registers 92 | on the device.""" 93 | 94 | def __init__(self, address, busnum, i2c_interface=None): 95 | """Create an instance of the I2C device at the specified address on the 96 | specified I2C bus number.""" 97 | self._address = address 98 | if i2c_interface is None: 99 | # Use pure python I2C interface if none is specified. 100 | import Adafruit_PureIO.smbus 101 | self._bus = Adafruit_PureIO.smbus.SMBus(busnum) 102 | else: 103 | # Otherwise use the provided class to create an smbus interface. 104 | self._bus = i2c_interface(busnum) 105 | self._logger = logging.getLogger('Adafruit_I2C.Device.Bus.{0}.Address.{1:#0X}' \ 106 | .format(busnum, address)) 107 | 108 | def writeRaw8(self, value): 109 | """Write an 8-bit value on the bus (without register).""" 110 | value = value & 0xFF 111 | self._bus.write_byte(self._address, value) 112 | self._logger.debug("Wrote 0x%02X", 113 | value) 114 | 115 | def write8(self, register, value): 116 | """Write an 8-bit value to the specified register.""" 117 | value = value & 0xFF 118 | self._bus.write_byte_data(self._address, register, value) 119 | self._logger.debug("Wrote 0x%02X to register 0x%02X", 120 | value, register) 121 | 122 | def write16(self, register, value): 123 | """Write a 16-bit value to the specified register.""" 124 | value = value & 0xFFFF 125 | self._bus.write_word_data(self._address, register, value) 126 | self._logger.debug("Wrote 0x%04X to register pair 0x%02X, 0x%02X", 127 | value, register, register + 1) 128 | 129 | def writeList(self, register, data): 130 | """Write bytes to the specified register.""" 131 | self._bus.write_i2c_block_data(self._address, register, data) 132 | self._logger.debug("Wrote to register 0x%02X: %s", 133 | register, data) 134 | 135 | def readList(self, register, length): 136 | """Read a length number of bytes from the specified register. Results 137 | will be returned as a bytearray.""" 138 | results = self._bus.read_i2c_block_data(self._address, register, length) 139 | self._logger.debug("Read the following from register 0x%02X: %s", 140 | register, results) 141 | return results 142 | 143 | def readRaw8(self): 144 | """Read an 8-bit value on the bus (without register).""" 145 | result = self._bus.read_byte(self._address) & 0xFF 146 | self._logger.debug("Read 0x%02X", 147 | result) 148 | return result 149 | 150 | def readU8(self, register): 151 | """Read an unsigned byte from the specified register.""" 152 | result = self._bus.read_byte_data(self._address, register) & 0xFF 153 | self._logger.debug("Read 0x%02X from register 0x%02X", 154 | result, register) 155 | return result 156 | 157 | def readS8(self, register): 158 | """Read a signed byte from the specified register.""" 159 | result = self.readU8(register) 160 | if result > 127: 161 | result -= 256 162 | return result 163 | 164 | def readU16(self, register, little_endian=True): 165 | """Read an unsigned 16-bit value from the specified register, with the 166 | specified endianness (default little endian, or least significant byte 167 | first).""" 168 | result = self._bus.read_word_data(self._address, register) & 0xFFFF 169 | self._logger.debug("Read 0x%04X from register pair 0x%02X, 0x%02X", 170 | result, register, register + 1) 171 | # Swap bytes if using big endian because read_word_data assumes little 172 | # endian on ARM (little endian) systems. 173 | if not little_endian: 174 | result = ((result << 8) & 0xFF00) + (result >> 8) 175 | return result 176 | 177 | def readS16(self, register, little_endian=True): 178 | """Read a signed 16-bit value from the specified register, with the 179 | specified endianness (default little endian, or least significant byte 180 | first).""" 181 | result = self.readU16(register, little_endian) 182 | if result > 32767: 183 | result -= 65536 184 | return result 185 | 186 | def readU16LE(self, register): 187 | """Read an unsigned 16-bit value from the specified register, in little 188 | endian byte order.""" 189 | return self.readU16(register, little_endian=True) 190 | 191 | def readU16BE(self, register): 192 | """Read an unsigned 16-bit value from the specified register, in big 193 | endian byte order.""" 194 | return self.readU16(register, little_endian=False) 195 | 196 | def readS16LE(self, register): 197 | """Read a signed 16-bit value from the specified register, in little 198 | endian byte order.""" 199 | return self.readS16(register, little_endian=True) 200 | 201 | def readS16BE(self, register): 202 | """Read a signed 16-bit value from the specified register, in big 203 | endian byte order.""" 204 | return self.readS16(register, little_endian=False) 205 | -------------------------------------------------------------------------------- /Modules/Driver/MCP9808.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Adafruit Industries 2 | # Author: Tony DiCola 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | 22 | import logging 23 | 24 | # Default I2C address for device. 25 | MCP9808_I2CADDR_DEFAULT = 0x18 26 | 27 | # Register addresses. 28 | MCP9808_REG_CONFIG = 0x01 29 | MCP9808_REG_UPPER_TEMP = 0x02 30 | MCP9808_REG_LOWER_TEMP = 0x03 31 | MCP9808_REG_CRIT_TEMP = 0x04 32 | MCP9808_REG_AMBIENT_TEMP = 0x05 33 | MCP9808_REG_MANUF_ID = 0x06 34 | MCP9808_REG_DEVICE_ID = 0x07 35 | 36 | # Configuration register values. 37 | MCP9808_REG_CONFIG_SHUTDOWN = 0x0100 38 | MCP9808_REG_CONFIG_CRITLOCKED = 0x0080 39 | MCP9808_REG_CONFIG_WINLOCKED = 0x0040 40 | MCP9808_REG_CONFIG_INTCLR = 0x0020 41 | MCP9808_REG_CONFIG_ALERTSTAT = 0x0010 42 | MCP9808_REG_CONFIG_ALERTCTRL = 0x0008 43 | MCP9808_REG_CONFIG_ALERTSEL = 0x0002 44 | MCP9808_REG_CONFIG_ALERTPOL = 0x0002 45 | MCP9808_REG_CONFIG_ALERTMODE = 0x0001 46 | 47 | 48 | class MCP9808(object): 49 | """Class to represent an Adafruit MCP9808 precision temperature measurement 50 | board. 51 | """ 52 | 53 | def __init__(self, address=MCP9808_I2CADDR_DEFAULT, i2c=None, **kwargs): 54 | """Initialize MCP9808 device on the specified I2C address and bus number. 55 | Address defaults to 0x18 and bus number defaults to the appropriate bus 56 | for the hardware. 57 | """ 58 | self._logger = logging.getLogger('Adafruit_MCP9808.MCP9808') 59 | if i2c is None: 60 | import I2C as I2C 61 | i2c = I2C 62 | self._device = i2c.get_i2c_device(address, **kwargs) 63 | 64 | def begin(self): 65 | """Start taking temperature measurements. Returns True if the device is 66 | intialized, False otherwise. 67 | """ 68 | # Check manufacturer and device ID match expected values. 69 | mid = self._device.readU16BE(MCP9808_REG_MANUF_ID) 70 | did = self._device.readU16BE(MCP9808_REG_DEVICE_ID) 71 | self._logger.debug('Read manufacturer ID: {0:04X}'.format(mid)) 72 | self._logger.debug('Read device ID: {0:04X}'.format(did)) 73 | return mid == 0x0054 and did == 0x0400 74 | 75 | def readTempC(self): 76 | """Read sensor and return its value in degrees celsius.""" 77 | # Read temperature register value. 78 | t = self._device.readU16BE(MCP9808_REG_AMBIENT_TEMP) 79 | self._logger.debug('Raw ambient temp register value: 0x{0:04X}'.format(t & 0xFFFF)) 80 | # Scale and convert to signed value. 81 | temp = (t & 0x0FFF) / 16.0 82 | if t & 0x1000: 83 | temp -= 256.0 84 | return temp 85 | -------------------------------------------------------------------------------- /Modules/Driver/Matrix16x8.py: -------------------------------------------------------------------------------- 1 | # Based heavily off of 2 | # Matrix8x8.py 3 | # by: 4 | # Copyright (c) 2014 Adafruit Industries 5 | # Author: Tony DiCola 6 | # 7 | import time 8 | 9 | import HT16K33 10 | 11 | 12 | class Matrix16x8(HT16K33.HT16K33): 13 | """Single color 16x8 matrix LED backpack display.""" 14 | 15 | def __init__(self, **kwargs): 16 | """Initialize display. All arguments will be passed to the HT16K33 class 17 | initializer, including optional I2C address and bus number parameters. 18 | """ 19 | super(Matrix16x8, self).__init__(**kwargs) 20 | self.max_x = 15 21 | 22 | def set_pixel(self, x, y, value): 23 | """Set pixel at position x, y to the given value. X and Y should be values 24 | of 0 to 16. Value should be 0 for off and non-zero for on. 25 | """ 26 | if x < 0 or x > self.max_x or y < 0 or y > 7: 27 | # Ignore out of bounds pixels. 28 | return 29 | 30 | led_number = (y * 16) + x 31 | self.set_led(led_number, value) 32 | 33 | def set_image(self, image): 34 | """Set display buffer to Python Image Library image. Image will be converted 35 | to 1 bit color and non-zero color values will light the LEDs. 36 | """ 37 | imwidth, imheight = image.size 38 | if imwidth != 16 or imheight != 8: 39 | raise ValueError('Image must be an 16x8 (15x7) pixels in size.') 40 | # Convert image to 1 bit color and grab all the pixels. 41 | pix = image.convert('1').load() 42 | # Loop through each pixel and write the display buffer pixel. 43 | for x in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]: 44 | for y in [0, 1, 2, 3, 4, 5, 6, 7]: 45 | color = pix[(x, y)] 46 | 47 | # Developer Note: 48 | # I did notice some random leds flicker on the far right 49 | # edge ( or the x=0 edge ) so until I figure out why 50 | # I decided to just blank out this row. 51 | if x == 0: 52 | color = 0 53 | 54 | # Handle the color of the pixel, off or on. 55 | if color == 0: 56 | self.set_pixel(x, y, 0) 57 | else: 58 | self.set_pixel(x, y, 1) 59 | 60 | def display_16x8_buffer(self, value): 61 | """ 62 | Display the contents of the value buffer which is assumed 63 | to be a 16x8 buffer of data to turn pixels on and off 64 | :param value: byte buffer 65 | :return: none 66 | """ 67 | for x in range(0, 16): 68 | column_byte = value[x] 69 | column_bits_string = bin(column_byte)[2:].zfill(8) 70 | for y in range(0, 8): 71 | column_bit = column_bits_string[y] 72 | if column_bit == '0': 73 | self.set_pixel(x, y, 0) 74 | else: 75 | self.set_pixel(x, y, 1) 76 | # self.set_brightness(self.brightness) 77 | self.write_display() 78 | 79 | def get_message_buffer(self, message, bitmap_font): 80 | the_message = message 81 | message_buffer = [] 82 | for theChar in the_message: 83 | message_buffer = message_buffer + bitmap_font[theChar] 84 | 85 | return message_buffer 86 | 87 | def scroll_buffer(self, message_buffer, delay=0.2): 88 | start_index = 0 89 | end_index = start_index + 16 90 | while end_index < len(message_buffer): 91 | self.display_16x8_buffer(message_buffer[start_index:end_index]) 92 | time.sleep(delay) 93 | end_index += 1 94 | start_index += 1 95 | 96 | def scroll_message(self, message, bitmap_font, delay=0.02): 97 | the_message = " " + message + " " 98 | message_buffer = [] 99 | for theChar in the_message: 100 | message_buffer = message_buffer + bitmap_font[theChar] 101 | 102 | self.scroll_buffer(message_buffer, delay) 103 | -------------------------------------------------------------------------------- /Modules/Driver/Platform.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Adafruit Industries 2 | # Author: Tony DiCola 3 | 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | 11 | # The above copyright notice and this permission notice shall be included in all 12 | # copies or substantial portions of the Software. 13 | 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | # SOFTWARE. 21 | import platform 22 | import re 23 | 24 | # Platform identification constants. 25 | UNKNOWN = 0 26 | RASPBERRY_PI = 1 27 | BEAGLEBONE_BLACK = 2 28 | 29 | 30 | def platform_detect(): 31 | """Detect if running on the Raspberry Pi or Beaglebone Black and return the 32 | platform type. Will return RASPBERRY_PI, BEAGLEBONE_BLACK, or UNKNOWN.""" 33 | 34 | # TODO: Is there a better way to check if running on BBB or Pi? Relying on 35 | # the architecture name is brittle because new boards running armv6 or armv7 36 | # might come along and conflict with this simple identification scheme. One 37 | # option might be switching to read /proc/cpuinfo. 38 | plat = platform.platform() 39 | 40 | # Handle Raspberry Pi 41 | # Platform output on Raspbian testing/jessie ~May 2014: 42 | # Linux-3.10.25+-armv6l-with-debian-7.4 43 | if plat.lower().find('armv6l-with-debian') > -1: 44 | return RASPBERRY_PI 45 | # Handle pidora distribution. 46 | elif plat.lower().find('raspberry_pi') > -1: 47 | return RASPBERRY_PI 48 | # Handle arch distribution. 49 | elif plat.lower().find('arch-armv6l') > -1: 50 | return RASPBERRY_PI 51 | # Handle Beaglebone Black 52 | # Platform output on Debian ~May 2014: 53 | # Linux-3.8.13-bone47-armv7l-with-debian-7.4 54 | elif plat.lower().find('armv7l-with-debian') > -1: 55 | return BEAGLEBONE_BLACK 56 | # Handle Beaglebone Black 57 | # Platform output on Ubuntu ~July 2014: 58 | # Linux-3.8.13-bone56-armv7l-with-Ubuntu-14.04-trusty 59 | elif plat.lower().find('armv7l-with-ubuntu') > -1: 60 | return BEAGLEBONE_BLACK 61 | elif plat.lower().find('armv7l-with-glibc2.4') > -1: 62 | return BEAGLEBONE_BLACK 63 | else: 64 | return UNKNOWN 65 | 66 | 67 | def pi_revision(): 68 | """Detect the revision number of a Raspberry Pi, useful for changing 69 | functionality like default I2C bus based on revision.""" 70 | # Revision list available at: http://elinux.org/RPi_HardwareHistory#Board_Revision_History 71 | with open('/proc/cpuinfo', 'r') as infile: 72 | for line in infile: 73 | # Match a line of the form "Revision : 0002" while ignoring extra 74 | # info in front of the revsion (like 1000 when the Pi was over-volted). 75 | match = re.match('Revision\s+:\s+.*(\w{4})$', line) 76 | if match and match.group(1) in ['0000', '0002', '0003']: 77 | # Return revision 1 if revision ends with 0000, 0002 or 0003. 78 | return 1 79 | elif match: 80 | # Assume revision 2 if revision ends with any other 4 chars. 81 | return 2 82 | # Couldn't find the revision, throw an exception. 83 | raise RuntimeError('Could not determine Raspberry Pi revision.') 84 | -------------------------------------------------------------------------------- /Modules/Driver/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- -------------------------------------------------------------------------------- /Modules/Endpoint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | import os 4 | from datetime import datetime 5 | import pytz 6 | import tzlocal 7 | 8 | from itertools import izip 9 | 10 | from Data import Data 11 | from UpdateLog import log_string 12 | 13 | 14 | class Endpoint(Data): 15 | 16 | def node_data(self): 17 | log_string('requesting node data') 18 | 19 | rain_forecast = [] 20 | 21 | for item in self.hourly_data[:8]: 22 | rain_percentage = item['precipProbability'] * 100 23 | rain_forecast.append(int(rain_percentage)) 24 | 25 | time_str = datetime.fromtimestamp(int(self.api_time)).strftime('%H:%M:%S') 26 | 27 | # response 28 | node_data = { 29 | "temp": self.sensor_temp_outside, 30 | "icon": self.weather_icon, 31 | "summary": self.forecast, 32 | "rain_forecast": rain_forecast, 33 | "updated": time_str 34 | } 35 | 36 | log_string('returned data: {}'.format(node_data)) 37 | 38 | return node_data 39 | 40 | def sensor_module_data(self): 41 | log_string('requesting sensor data') 42 | 43 | wohnzimmer = self.sensor_base['dashboard_data'] 44 | kinderzimmer = self.sensor_kinderzimmer['dashboard_data'] 45 | schlafzimmer = self.sensor_schlafzimmer['dashboard_data'] 46 | balkon = self.sensor_outside['dashboard_data'] 47 | 48 | def get_updated_time(sensor_time): 49 | 50 | update_time = datetime.utcfromtimestamp(sensor_time) 51 | local_time = datetime.utcnow() 52 | last_update_time = int((local_time - update_time).seconds / 60) 53 | 54 | return last_update_time 55 | 56 | # response 57 | data = { 58 | "Wohnzimmer": { 59 | "Temperatur": wohnzimmer['Temperature'], 60 | "Luftdruck": wohnzimmer['Pressure'], 61 | "Kohlendioxyd": wohnzimmer['CO2'], 62 | "Luftfeuchtigkeit": wohnzimmer['Humidity'], 63 | "Lautstärke": wohnzimmer['Noise'], 64 | "Updated": get_updated_time(wohnzimmer['time_utc']) 65 | }, 66 | "Kinderzimmer": { 67 | "Temperatur": kinderzimmer['Temperature'], 68 | "Kohlendioxyd": kinderzimmer['CO2'], 69 | "Luftfeuchtigkeit": kinderzimmer['Humidity'], 70 | "Updated": get_updated_time(kinderzimmer['time_utc']) 71 | }, 72 | "Schlafzimmer": { 73 | "Temperatur": schlafzimmer['Temperature'], 74 | "Kohlendioxyd": schlafzimmer['CO2'], 75 | "Luftfeuchtigkeit": schlafzimmer['Humidity'], 76 | "Updated": get_updated_time(schlafzimmer['time_utc']) 77 | }, 78 | "Balkon": { 79 | "Temperatur": balkon['Temperature'], 80 | "Luftfeuchtigkeit": balkon['Humidity'], 81 | "Updated": get_updated_time(balkon['time_utc']) 82 | } 83 | } 84 | 85 | log_string('returned data: {}'.format(data)) 86 | 87 | return data 88 | 89 | @staticmethod 90 | def rpi_stats(): 91 | # cpu stats 92 | res = os.popen('vcgencmd measure_temp').readline() 93 | cpu_temp = res.replace("temp=", "").replace("'C\n", "") 94 | 95 | cpu_use = ( 96 | str(os.popen("TERM=vt100 top -b -n 1 | awk '/Cpu\(s\):/ {print $2}'").readline().strip()).replace(',', '.')) 97 | 98 | # ram 99 | ram_data = os.popen('free') 100 | ram_line_1 = ram_data.readline().split() 101 | ram_line_2 = ram_data.readline().split()[1:] 102 | ram_line_2 = [int(x) for x in ram_line_2] 103 | ram_line_2 = [int(x / 1024) for x in ram_line_2] 104 | 105 | ram = dict(izip(ram_line_1, ram_line_2)) # dict(zip(ram_line_1, ram_line_2)) 106 | 107 | ram_used = int(ram['used'] * 100 / ram['total']) 108 | ram['used_prec'] = ram_used 109 | 110 | # disk 111 | disk_data = os.popen("df -m /") 112 | disk_line_1 = disk_data.readline().replace( 113 | '1M-Blöcke', 'total').replace( 114 | 'Verw%', 'used_prec').replace( 115 | 'Verf\xc3\xbcgbar', 'free').replace( 116 | 'Benutzt', 'used').split()[1:5] 117 | disk_line_2 = disk_data.readline().replace('%', '').split()[1:5] 118 | disk_line_2 = [int(x) for x in disk_line_2] 119 | 120 | disk = dict(izip(disk_line_1, disk_line_2)) 121 | 122 | time_str = datetime.now().strftime('%H:%M:%S') 123 | 124 | # response 125 | rpi_stats = { 126 | "CPU": { 127 | "temp": float(cpu_temp), 128 | "usage": float(cpu_use) 129 | }, 130 | "RAM": ram, 131 | "DISK": disk, 132 | "updated": time_str 133 | } 134 | 135 | log_string('returned data: {}'.format(rpi_stats)) 136 | 137 | return rpi_stats 138 | -------------------------------------------------------------------------------- /Modules/Fonts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoveBootCaptain/WeatherPi/24132f392080a61cabaa6fe99c3ef3ee684ccd77/Modules/Fonts/__init__.py -------------------------------------------------------------------------------- /Modules/Fonts/custom_font.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # temp_digits = { 5 | # '0':[0x00, 0x7e, 0x81, 0x7e], 6 | # '1':[0x00, 0x40, 0xff, 0x00], 7 | # '2':[0x00, 0x47, 0x89, 0x71], 8 | # '3':[0x00, 0x41, 0x89, 0x77], 9 | # '4':[0x00, 0xf8, 0x18, 0xff], 10 | # '5':[0x00, 0xf9, 0x99, 0x9f], 11 | # '6':[0x00, 0xff, 0x99, 0x9f], 12 | # '7':[0x00, 0xc0, 0xc0, 0xff], 13 | # '8':[0x00, 0x77, 0x89, 0x77], 14 | # '9':[0x00, 0xf8, 0x88, 0xff], 15 | # '*':[0x00, 0xe, 0xd1, 0xd1], 16 | # '+':[0x00, 0x08, 0x1c, 0x08] 17 | # } 18 | 19 | 20 | temp_digits = { 21 | '0': [0x7e, 0x81, 0x7e, 0x00], 22 | '1': [0x40, 0xff, 0x00, 0x00], 23 | '2': [0x47, 0x89, 0x71, 0x00], 24 | '3': [0x41, 0x89, 0x77, 0x00], 25 | '4': [0xf8, 0x18, 0xff, 0x00], 26 | '5': [0xf9, 0x99, 0x9f, 0x00], 27 | '6': [0xff, 0x99, 0x9f, 0x00], 28 | '7': [0xc0, 0xc0, 0xff, 0x00], 29 | '8': [0x77, 0x89, 0x77, 0x00], 30 | '9': [0xf8, 0x88, 0xff, 0x00], 31 | 'C': [0x7e, 0x81, 0x81, 0x00], 32 | '*': [0x0e, 0xd1, 0xd1, 0x00], 33 | 'o': [0x00, 0xc0, 0xc0, 0x00], 34 | '+': [0x08, 0x1c, 0x08, 0x00], 35 | '-': [0x08, 0x08, 0x08, 0x00], 36 | '%': [0x0, 0x0, 0xc3, 0xc4, 0x18, 0x23, 0xc3, 0x0, 0x0] 37 | } 38 | 39 | digits = { 40 | '0': [0x00, 0x7e, 0x81, 0x7e], 41 | '1': [0x00, 0x40, 0xff, 0x00], 42 | '2': [0x00, 0x47, 0x89, 0x71], 43 | '3': [0x00, 0x41, 0x89, 0x77], 44 | '4': [0x00, 0xf8, 0x18, 0xff], 45 | '5': [0x00, 0xf9, 0x99, 0x9f], 46 | '6': [0x00, 0xff, 0x99, 0x9f], 47 | '7': [0x00, 0xc0, 0xc0, 0xff], 48 | '8': [0x00, 0x77, 0x89, 0x77], 49 | '9': [0x00, 0xf8, 0x88, 0xff] 50 | } 51 | 52 | hours = { 53 | '1': [0x00, 0x00, 0x00, 0x00, 0x40, 0xff, 0x00, 0x24], 54 | '2': [0x00, 0x00, 0x00, 0x47, 0x89, 0x71, 0x00, 0x24], 55 | '3': [0x00, 0x00, 0x00, 0x41, 0x89, 0x77, 0x00, 0x24], 56 | '4': [0x00, 0x00, 0x00, 0xf8, 0x18, 0xff, 0x00, 0x24], 57 | '5': [0x00, 0x00, 0x00, 0xf9, 0x99, 0x9f, 0x00, 0x24], 58 | '6': [0x00, 0x00, 0x00, 0xff, 0x99, 0x9f, 0x00, 0x24], 59 | '7': [0x00, 0x00, 0x00, 0xc0, 0xc0, 0xff, 0x00, 0x24], 60 | '8': [0x00, 0x00, 0x00, 0x77, 0x89, 0x77, 0x00, 0x24], 61 | '9': [0x00, 0x00, 0x00, 0xf8, 0x88, 0xff, 0x00, 0x24], 62 | '10': [0x40, 0xff, 0x00, 0x7e, 0x81, 0x7e, 0x00, 0x24], 63 | '11': [0x00, 0x40, 0xff, 0x00, 0x40, 0xff, 0x00, 0x24], 64 | '12': [0x40, 0xff, 0x00, 0x47, 0x89, 0x71, 0x00, 0x24] 65 | } 66 | 67 | hours_no_colon = { 68 | '1': [0x00, 0x00, 0x00, 0x00, 0x40, 0xff, 0x00, 0x00], 69 | '2': [0x00, 0x00, 0x00, 0x47, 0x89, 0x71, 0x00, 0x00], 70 | '3': [0x00, 0x00, 0x00, 0x41, 0x89, 0x77, 0x00, 0x00], 71 | '4': [0x00, 0x00, 0x00, 0xf8, 0x18, 0xff, 0x00, 0x00], 72 | '5': [0x00, 0x00, 0x00, 0xf9, 0x99, 0x9f, 0x00, 0x00], 73 | '6': [0x00, 0x00, 0x00, 0xff, 0x99, 0x9f, 0x00, 0x00], 74 | '7': [0x00, 0x00, 0x00, 0xc0, 0xc0, 0xff, 0x00, 0x00], 75 | '8': [0x00, 0x00, 0x00, 0x77, 0x89, 0x77, 0x00, 0x00], 76 | '9': [0x00, 0x00, 0x00, 0xf8, 0x88, 0xff, 0x00, 0x00], 77 | '10': [0x40, 0xff, 0x00, 0x7e, 0x81, 0x7e, 0x00, 0x00], 78 | '11': [0x00, 0x40, 0xff, 0x00, 0x40, 0xff, 0x00, 0x00], 79 | '12': [0x40, 0xff, 0x00, 0x47, 0x89, 0x71, 0x00, 0x00] 80 | } 81 | 82 | # rotated so that 0,0 is the upper left corner of the 16x8 LED backpack matrix 83 | textFont2 = { 84 | ' ': [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0], 85 | '!': [0x0, 0x0, 0x60, 0xfa, 0xfa, 0x60, 0x0, 0x0], 86 | '"': [0x0, 0xc0, 0xe0, 0x0, 0x0, 0xe0, 0xc0, 0x0], 87 | '#': [0x28, 0xfe, 0xfe, 0x28, 0xfe, 0xfe, 0x28, 0x0], 88 | '%': [0x62, 0x66, 0xc, 0x18, 0x30, 0x66, 0x46, 0x0], 89 | '&': [0xc, 0x5e, 0xf2, 0xba, 0xec, 0x5e, 0x12, 0x0], 90 | '(': [0x0, 0x0, 0x38, 0x7c, 0xc6, 0x82, 0x0, 0x0], 91 | '<': [0x0, 0x0, 0x10, 0x38, 0x6c, 0xc6, 0x82, 0x0], 92 | '@': [0x7c, 0xfe, 0x82, 0xba, 0xba, 0xf8, 0x78, 0x0], 93 | '~': [0x40, 0xc0, 0x80, 0xc0, 0x40, 0xc0, 0x80, 0x0], 94 | ')': [0x0, 0x0, 0x82, 0xc6, 0x7c, 0x38, 0x0, 0x0], 95 | '-': [0x0, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x0], 96 | '\\': [0x80, 0xc0, 0x60, 0x30, 0x18, 0xc, 0x6, 0x0], 97 | '`': [0x0, 0x0, 0x80, 0xc0, 0x60, 0x20, 0x0, 0x0], 98 | '|': [0x0, 0x0, 0x0, 0xfe, 0xfe, 0x0, 0x0, 0x0], 99 | "'": [0x0, 0x0, 0x20, 0xe0, 0xc0, 0x0, 0x0, 0x0], 100 | '+': [0x0, 0x10, 0x10, 0x7c, 0x7c, 0x10, 0x10, 0x0], 101 | '/': [0x6, 0xc, 0x18, 0x30, 0x60, 0xc0, 0x80, 0x0], 102 | '[': [0x0, 0x0, 0xfe, 0xfe, 0x82, 0x82, 0x0, 0x0], 103 | '_': [0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1], 104 | '{': [0x0, 0x10, 0x10, 0x7c, 0xee, 0x82, 0x82, 0x0], 105 | '*': [0x10, 0x54, 0x7c, 0x38, 0x38, 0x7c, 0x54, 0x10], 106 | '.': [0x0, 0x0, 0x0, 0x6, 0x6, 0x0, 0x0, 0x0], 107 | ',': [0x0, 0x0, 0x0, 0x60, 0x60, 0x0, 0x0, 0x0], 108 | ';': [0x40, 0xc0, 0x80, 0x9a, 0xba, 0xe0, 0x40, 0x0], 109 | '?': [0x40, 0xc0, 0x80, 0x9a, 0xba, 0xe0, 0x40, 0x0], 110 | ':': [0x0, 0x0, 0x0, 0x66, 0x66, 0x0, 0x0, 0x0], 111 | '>': [0x0, 0x82, 0xc6, 0x6c, 0x38, 0x10, 0x0, 0x0], 112 | '^': [0x10, 0x30, 0x60, 0xc0, 0x60, 0x30, 0x10, 0x0], 113 | '=': [0x0, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x0], 114 | ']': [0x0, 0x0, 0x82, 0x82, 0xfe, 0xfe, 0x0, 0x0], 115 | '}': [0x0, 0x82, 0x82, 0xee, 0x7c, 0x10, 0x10, 0x0], 116 | 'D': [0x82, 0xfe, 0xfe, 0x82, 0xc6, 0x7c, 0x38, 0x0], 117 | 'H': [0xfe, 0xfe, 0x10, 0x10, 0x10, 0xfe, 0xfe, 0x0], 118 | 'L': [0x82, 0xfe, 0xfe, 0x82, 0x2, 0x6, 0xe, 0x0], 119 | 'P': [0x82, 0xfe, 0xfe, 0x92, 0x90, 0xf0, 0x60, 0x0], 120 | 'T': [0x0, 0xe0, 0xc2, 0xfe, 0xfe, 0xc2, 0xe0, 0x0], 121 | 'X': [0xc6, 0xee, 0x38, 0x10, 0x38, 0xee, 0xc6, 0x0], 122 | 'd': [0x1c, 0x3e, 0x22, 0xa2, 0xfc, 0xfe, 0x2, 0x0], 123 | 'h': [0x82, 0xfe, 0xfe, 0x10, 0x20, 0x3e, 0x1e, 0x0], 124 | 'l': [0x0, 0x0, 0x82, 0xfe, 0xfe, 0x2, 0x0, 0x0], 125 | 'p': [0x21, 0x3f, 0x1f, 0x25, 0x24, 0x3c, 0x18, 0x0], 126 | 't': [0x20, 0x20, 0xfc, 0xfe, 0x22, 0x26, 0x4, 0x0], 127 | 'x': [0x22, 0x36, 0x1c, 0x8, 0x1c, 0x36, 0x22, 0x0], 128 | '3': [0x44, 0xc6, 0x92, 0x92, 0x92, 0xfe, 0x6c, 0x0], 129 | '7': [0xc0, 0xc0, 0x8e, 0x9e, 0xb0, 0xe0, 0xc0, 0x0], 130 | 'C': [0x38, 0x7c, 0xc6, 0x82, 0x82, 0xc6, 0x44, 0x0], 131 | 'G': [0x38, 0x7c, 0xc6, 0x82, 0x8a, 0xcc, 0x4e, 0x0], 132 | 'K': [0x82, 0xfe, 0xfe, 0x10, 0x38, 0xee, 0xc6, 0x0], 133 | 'O': [0x7c, 0xfe, 0x82, 0x82, 0x82, 0xfe, 0x7c, 0x0], 134 | 'S': [0x44, 0xe6, 0xb2, 0x92, 0x9a, 0xce, 0x44, 0x0], 135 | 'W': [0xfc, 0xfe, 0x6, 0x1c, 0x6, 0xfe, 0xfc, 0x0], 136 | 'c': [0x1c, 0x3e, 0x22, 0x22, 0x22, 0x36, 0x14, 0x0], 137 | 'g': [0x19, 0x3d, 0x25, 0x25, 0x1f, 0x3e, 0x20, 0x0], 138 | 'k': [0x82, 0xfe, 0xfe, 0x8, 0x1c, 0x36, 0x22, 0x0], 139 | 'o': [0x1c, 0x3e, 0x22, 0x22, 0x22, 0x3e, 0x1c, 0x0], 140 | 's': [0x12, 0x3a, 0x2a, 0x2a, 0x2a, 0x2e, 0x24, 0x0], 141 | 'w': [0x3c, 0x3e, 0x6, 0x1c, 0x6, 0x3e, 0x3c, 0x0], 142 | '2': [0x42, 0xc6, 0x8e, 0x9a, 0x92, 0xf6, 0x66, 0x0], 143 | '6': [0x3c, 0x7e, 0xd2, 0x92, 0x92, 0x1e, 0xc, 0x0], 144 | 'B': [0x82, 0xfe, 0xfe, 0x92, 0x92, 0xfe, 0x6c, 0x0], 145 | 'F': [0x82, 0xfe, 0xfe, 0x92, 0xb8, 0x80, 0xc0, 0x0], 146 | 'J': [0xc, 0xe, 0x2, 0x82, 0xfe, 0xfc, 0x80, 0x0], 147 | 'N': [0xfe, 0xfe, 0x60, 0x30, 0x18, 0xfe, 0xfe, 0x0], 148 | 'R': [0x82, 0xfe, 0xfe, 0x90, 0x98, 0xfe, 0x66, 0x0], 149 | 'V': [0xf8, 0xfc, 0x6, 0x2, 0x6, 0xfc, 0xf8, 0x0], 150 | 'Z': [0xe2, 0xc6, 0x8e, 0x9a, 0xb2, 0xe6, 0xce, 0x0], 151 | 'b': [0x82, 0xfe, 0xfc, 0x22, 0x22, 0x3e, 0x1c, 0x0], 152 | 'f': [0x12, 0x7e, 0xfe, 0x92, 0x90, 0xc0, 0x40, 0x0], 153 | 'j': [0x0, 0x6, 0x7, 0x1, 0x1, 0xbf, 0xbe, 0x0], 154 | 'n': [0x20, 0x3e, 0x1e, 0x20, 0x20, 0x3e, 0x1e, 0x0], 155 | 'r': [0x22, 0x3e, 0x1e, 0x32, 0x20, 0x30, 0x10, 0x0], 156 | 'v': [0x38, 0x3c, 0x6, 0x2, 0x6, 0x3c, 0x38, 0x0], 157 | 'z': [0x0, 0x32, 0x26, 0x2e, 0x3a, 0x32, 0x26, 0x0], 158 | '1': [0x0, 0x2, 0x42, 0xfe, 0xfe, 0x2, 0x2, 0x0], 159 | '5': [0xf4, 0xf6, 0x92, 0x92, 0x92, 0x9e, 0x8c, 0x0], 160 | '9': [0x60, 0xf2, 0x92, 0x92, 0x96, 0xfc, 0x78, 0x0], 161 | 'A': [0x3e, 0x7e, 0xd0, 0x90, 0xd0, 0x7e, 0x3e, 0x0], 162 | 'E': [0x82, 0xfe, 0xfe, 0x92, 0xba, 0x82, 0xc6, 0x0], 163 | 'I': [0x0, 0x0, 0x82, 0xfe, 0xfe, 0x82, 0x0, 0x0], 164 | 'M': [0xfe, 0xfe, 0x70, 0x38, 0x70, 0xfe, 0xfe, 0x0], 165 | 'Q': [0x7c, 0xfe, 0x82, 0x82, 0x87, 0xff, 0x7d, 0x0], 166 | 'U': [0xfc, 0xfe, 0x2, 0x2, 0x2, 0xfe, 0xfc, 0x0], 167 | 'Y': [0x0, 0xe0, 0xf2, 0x1e, 0x1e, 0xf2, 0xe0, 0x0], 168 | 'a': [0x4, 0x2e, 0x2a, 0x2a, 0x3c, 0x1e, 0x2, 0x0], 169 | 'e': [0x1c, 0x3e, 0x2a, 0x2a, 0x2a, 0x3a, 0x18, 0x0], 170 | 'i': [0x0, 0x0, 0x22, 0xbe, 0xbe, 0x2, 0x0, 0x0], 171 | 'matrix': [0x3e, 0x3e, 0x30, 0x1e, 0x30, 0x3e, 0x1e, 0x0], 172 | 'q': [0x18, 0x3c, 0x24, 0x25, 0x1f, 0x3f, 0x21, 0x0], 173 | 'u': [0x3c, 0x3e, 0x2, 0x2, 0x3c, 0x3e, 0x2, 0x0], 174 | 'y': [0x39, 0x3d, 0x5, 0x5, 0x5, 0x3f, 0x3e, 0x0], 175 | '0': [0x7c, 0xfe, 0x8a, 0x92, 0xa2, 0xfe, 0x7c, 0x0], 176 | '4': [0x18, 0x38, 0x68, 0xca, 0xfe, 0xfe, 0xa, 0x0], 177 | '8': [0x6c, 0xfe, 0x92, 0x92, 0x92, 0xfe, 0x6c, 0x0] 178 | 179 | } 180 | 181 | shapes = { 182 | 'all_on': [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], 183 | 'danbo': [0x7e, 0x42, 0x52, 0x42, 0x42, 0x52, 0x42, 0x7e], 184 | 'heart1': [0x30, 0x48, 0x44, 0x22, 0x22, 0x44, 0x48, 0x30], 185 | 'heart2': [0x38, 0x44, 0x42, 0x21, 0x21, 0x42, 0x44, 0x38], 186 | 'heart1F': [0x30, 0x78, 0x7c, 0x3e, 0x3e, 0x7c, 0x78, 0x30], 187 | 'heart2F': [0x38, 0x7c, 0x7e, 0x3f, 0x3f, 0x7e, 0x7c, 0x38], 188 | 'arrow': [0x10, 0x30, 0x50, 0x9f, 0x9f, 0x50, 0x30, 0x10], 189 | 'tree1': [0x4, 0xc, 0x3c, 0xff, 0xff, 0x3c, 0xc, 0x4], 190 | 'tree2': [0x2, 0xe, 0x3e, 0xff, 0xff, 0x3e, 0xe, 0x2], 191 | 'invader1': [0x19, 0x3a, 0x6d, 0xfa, 0xfa, 0x6d, 0x3a, 0x19], 192 | 'invader2': [0x18, 0x3b, 0x6c, 0xfa, 0xfa, 0x6c, 0x3b, 0x18], 193 | 'clock1': [0x3c, 0x42, 0x89, 0x89, 0xb9, 0x81, 0x42, 0x3c], 194 | 'smile': [0x3c, 0x42, 0xa9, 0x85, 0x85, 0xa9, 0x42, 0x3c], 195 | 'smileL': [0x3c, 0x42, 0x89, 0xb5, 0x85, 0xb5, 0x42, 0x3c], 196 | 'bunny1': [0x1e, 0xf1, 0xf5, 0x13, 0x13, 0xf5, 0xf1, 0x1e], 197 | 'bunny2': [0x5e, 0xf1, 0xf5, 0x13, 0x13, 0xf5, 0xf1, 0x5e], 198 | 'bunny3': [0x3e, 0xe1, 0xe9, 0x23, 0x23, 0xe9, 0xe1, 0x3e], 199 | 'all_off': [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0], 200 | 'empty': [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0], 201 | } 202 | -------------------------------------------------------------------------------- /Modules/RainData.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | from Data import Data 4 | from Fonts.custom_font import * 5 | from init_logging import log_string 6 | 7 | 8 | class RainData(Data): 9 | 10 | forecast_range_hour = 24 # use len(hourly) for full data (48h) 11 | 12 | percent = "%" 13 | 14 | def rain_probability(self): 15 | 16 | rain_probability = self.rain_percentage_data # json_data['currently']['precipProbability'] 17 | 18 | rain_probability *= 100 19 | 20 | log_string('Regenwahrscheinlichkeit: {}%'.format(rain_probability)) 21 | 22 | rain_probability_data = str(int(round(rain_probability))).zfill(2) 23 | 24 | log_string('Regenwahrscheinlichkeit als String für Output: {}%'.format(rain_probability_data)) 25 | 26 | the_output_data = digits[rain_probability_data[0]] + digits[rain_probability_data[1]] + temp_digits[self.percent[0]] 27 | 28 | return the_output_data 29 | 30 | def rain_forecast(self): 31 | percentage_list = [] 32 | color_list = [] 33 | 34 | hourly_data = self.hourly_data 35 | 36 | for item in hourly_data: 37 | rain_percentage = item['precipProbability'] * 100 38 | percentage_list.append(round(rain_percentage)) 39 | 40 | color = 0 41 | 42 | for percentage in percentage_list[:self.forecast_range_hour]: 43 | 44 | if percentage == 0: 45 | color = "G" # GREEN 46 | elif 0 < percentage <= 30: 47 | color = "Y" # YELLOW 48 | elif 31 <= percentage <= 100: 49 | color = "R" # RED 50 | 51 | color_list.append(color) 52 | 53 | green_list = [i for i, color in enumerate(color_list) if color == "G"] 54 | yellow_list = [i for i, color in enumerate(color_list) if color == "Y"] 55 | red_list = [i for i, color in enumerate(color_list) if color == "R"] 56 | 57 | the_output_data = { 58 | "GREEN": green_list, 59 | "YELLOW": yellow_list, 60 | "RED": red_list 61 | } 62 | 63 | log_string('Regenwahrscheinlichkeit 24h: {}\n' 64 | 'Farben auf Display: {}\n' 65 | 'Farbenliste als Array: {}'.format( 66 | percentage_list[:self.forecast_range_hour], 67 | color_list, 68 | the_output_data 69 | )) 70 | 71 | return the_output_data 72 | 73 | 74 | if __name__ == '__main__': 75 | RainData().rain_forecast() 76 | RainData().rain_probability() 77 | 78 | -------------------------------------------------------------------------------- /Modules/SensorData.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | from Data import Data 4 | from Fonts.custom_font import * 5 | from init_logging import log_string 6 | 7 | 8 | class SensorData(Data): 9 | 10 | celsius = "*" # °c 11 | Celsius = "C" # C 12 | unit = "o" # 13 | 14 | def temp_inside(self): 15 | 16 | sensor_temp_inside = self.sensor_temp_wohnzimmer 17 | 18 | output_temp = str(int(round(sensor_temp_inside))).zfill(2) 19 | 20 | the_output_data = digits[output_temp[0]] + digits[output_temp[1]] + \ 21 | temp_digits[self.unit[0]] + temp_digits[self.Celsius[0]] 22 | 23 | log_string('Temperature Inside: {}°C'.format(output_temp)) 24 | 25 | return the_output_data 26 | 27 | def temp_outside(self): 28 | 29 | sensor_temp_outside = self.sensor_temp_outside 30 | log_string('sensor_temp_outside: {}'.format(sensor_temp_outside)) 31 | 32 | sensor_outside_int = int(round(sensor_temp_outside)) 33 | log_string('sensor_temp_outside rounded: {}'.format(sensor_outside_int)) 34 | 35 | sensor_outside_data = str(abs(sensor_outside_int)).zfill(2) 36 | log_string('format absolute sensor_temp_outside to string: {}'.format(sensor_outside_data)) 37 | 38 | if sensor_outside_int < 0: 39 | 40 | plus_minus = "-" 41 | log_string('Vorzeichen: {}'.format(plus_minus)) 42 | 43 | else: 44 | 45 | plus_minus = "+" 46 | log_string('Vorzeichen: {}'.format(plus_minus)) 47 | 48 | the_output_data = temp_digits[plus_minus[0]] + \ 49 | temp_digits[sensor_outside_data[0]] + \ 50 | temp_digits[sensor_outside_data[1]] + \ 51 | temp_digits[self.celsius[0]] 52 | 53 | log_string('Temperature Outside: {}{}°C'.format(plus_minus, sensor_outside_data)) 54 | 55 | return the_output_data 56 | 57 | 58 | if __name__ == '__main__': 59 | 60 | SensorData().temp_inside() 61 | SensorData().temp_outside() 62 | 63 | -------------------------------------------------------------------------------- /Modules/Update.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | import json 4 | 5 | import requests 6 | 7 | from Config import Config 8 | from init_blinkt import blink 9 | from init_logging import log_string 10 | 11 | 12 | class Update: 13 | def __init__(self): 14 | 15 | log_string('Run Update module by: {}'.format(self.__class__)) 16 | 17 | # set the configs 18 | self.config = Config().get_config() 19 | 20 | # parameter for the api request.rqst url 21 | self.FORECAST_IO_KEY = self.config['FORECAST_IO_KEY'] 22 | self.LATITUDE = self.config['LATITUDE'] 23 | self.LONGITUDE = self.config['LONGITUDE'] 24 | self.FORECAST_URL = 'https://api.forecast.io/forecast/' # endpoint for the API 25 | self.options = '?lang=de&units=si&exclude=flags' # some options, see details in API documentation 26 | 27 | # parameter for the sensor request.rqst url 28 | self.username = self.config['NETATMO_USERNAME'] 29 | self.password = self.config['NETATMO_PASSWORD'] 30 | self.client_id = self.config['NETATMO_CLIENT_ID'] 31 | self.client_secret = self.config['NETATMO_CLIENT_SECRET'] 32 | self.device_id = self.config['NETATMO_DEVICE_ID'] 33 | 34 | def update_api(self): 35 | log_string('get latest weather from forecast.io') 36 | 37 | api_request_url = self.FORECAST_URL + self.FORECAST_IO_KEY + '/' + str(self.LATITUDE) + ',' + \ 38 | str(self.LONGITUDE) + self.options 39 | 40 | log_string('build request url: {}'.format(api_request_url)) 41 | 42 | try: 43 | log_string('Try to get data online...') 44 | data = requests.get(api_request_url, timeout=5).json() 45 | 46 | # save data dict as json file 47 | with open('/home/pi/WeatherPi/logs/api.json', 'w') as outputfile: 48 | json.dump(data, outputfile, indent=2, sort_keys=True) 49 | 50 | log_string('json data saved to temporary file') 51 | 52 | blink('white') 53 | 54 | except StandardError: 55 | 56 | log_string('ConnectionError - fallback to cached Data') 57 | 58 | def auth_netatmo(self): 59 | 60 | payload = {'grant_type': 'password', 61 | 'username': self.username, 62 | 'password': self.password, 63 | 'client_id': self.client_id, 64 | 'client_secret': self.client_secret, 65 | 'scope': 'read_station'} 66 | 67 | try: 68 | response = requests.post("https://api.netatmo.com/oauth2/token", data=payload) 69 | response.raise_for_status() 70 | access_token = response.json()["access_token"] 71 | refresh_token = response.json()["refresh_token"] 72 | scope = response.json()["scope"] 73 | print("Your access token is:", access_token) 74 | print("Your refresh token is:", refresh_token) 75 | print("Your scopes are:", scope) 76 | 77 | log_string('tokens generated') 78 | 79 | return access_token 80 | 81 | except requests.exceptions.HTTPError as error: 82 | log_string(error.response.status_code) 83 | log_string(error.response.text) 84 | 85 | def update_sensors(self): 86 | 87 | payload = { 88 | 'access_token': self.auth_netatmo(), 89 | 'device_id': self.device_id 90 | } 91 | 92 | try: 93 | response = requests.post("https://api.netatmo.com/api/getstationsdata", params=payload) 94 | response.raise_for_status() 95 | data = response.json()["body"] 96 | 97 | # save data dict as json file 98 | with open('/home/pi/WeatherPi/logs/sensors.json', 'w') as outputfile: 99 | json.dump(data, outputfile, indent=2, sort_keys=True) 100 | 101 | log_string('sensor data saved to /logs') 102 | return data 103 | 104 | except requests.exceptions.HTTPError as error: 105 | log_string(error.response.status_code) 106 | log_string(error.response.text) 107 | 108 | def update_json(self): 109 | self.update_api() 110 | self.update_sensors() 111 | 112 | 113 | if __name__ == '__main__': 114 | Update().update_json() 115 | 116 | -------------------------------------------------------------------------------- /Modules/UpdateLog.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | from datetime import datetime 4 | 5 | from Data import Data 6 | from init_blinkt import * 7 | from init_logging import weather_logger 8 | 9 | 10 | class UpdateLog(Data): 11 | def create_log(self): 12 | 13 | current_time = datetime.now().isoformat() 14 | 15 | io_str = '{} bis {}°C - {}'.format( 16 | self.temp_range_today_min, 17 | self.temp_range_today_max, 18 | self.hourly_forecast.encode('UTF-8') 19 | ) 20 | 21 | latitude = config['LATITUDE'] 22 | longitude = config['LONGITUDE'] 23 | 24 | log_string_base = '[timestamp={}], [temp_api={}], [sensor_temp_inside={}], [sensor_temp_outside={}], ' \ 25 | '[rain_percentage={}], [sensor_pressure_inside={}], [sensor_humidity_inside={}],' \ 26 | '[sensor_humidity_outside={}], [summary="{}"],' \ 27 | ' [next_weather_today="{}"], [latitude={}], [longitude={}]]'.format( 28 | current_time, 29 | self.temp_api, 30 | self.sensor_temp_wohnzimmer, 31 | self.sensor_temp_outside, 32 | self.rain_percentage, 33 | self.sensor_pressure_wohnzimmer, 34 | self.sensor_humidity_wohnzimmer, 35 | self.sensor_humidity_outside, 36 | self.forecast, 37 | io_str, 38 | latitude, 39 | longitude 40 | ) 41 | 42 | # write log_string to log file 43 | weather_logger.info(log_string_base) 44 | 45 | log_string('created log entry: {}'.format(log_string_base)) 46 | 47 | blink('green') 48 | 49 | 50 | if __name__ == '__main__': 51 | 52 | UpdateLog().create_log() 53 | 54 | -------------------------------------------------------------------------------- /Modules/UpdateUnicorn.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | import os.path 4 | 5 | from PIL import Image 6 | 7 | from Data import Data 8 | from init_blinkt import * 9 | from init_unicorn import * 10 | 11 | img_file = None 12 | 13 | width, height = unicorn.get_shape() 14 | 15 | 16 | class UpdateIcon(Data): 17 | 18 | def set_icon_path(self): 19 | 20 | global img_file 21 | 22 | """ 23 | known conditions: clear-day, clear-night, partly-cloudy-day, partly-cloudy-night, wind, cloudy, rain, snow, fog 24 | """ 25 | 26 | icon = self.weather_icon 27 | 28 | log_string('Icon to show on Unicorn_HAT: {}'.format(icon)) 29 | 30 | icon_path = self.icon_path 31 | 32 | log_string('The icon path should be: {}. Checking it...'.format(icon_path)) 33 | # check if file and path are valid 34 | if os.path.isfile(icon_path): 35 | 36 | log_string('The File: {} is valid and present!'.format(icon_path)) 37 | log_string('Updating Unicorn with new weather condition icon: {}.'.format(icon)) 38 | 39 | # set new icon_path 40 | img_file = icon_path 41 | 42 | blink('red') 43 | 44 | return img_file 45 | 46 | else: 47 | 48 | log_string('Error - there is no icon for this weather condition. Please check.') 49 | 50 | log_string('Updating Unicorn icon to error screen') 51 | 52 | # set new icon_path 53 | 54 | icon_path = self.path + 'error' + self.icon_extension 55 | 56 | img_file = icon_path 57 | 58 | blink('red') 59 | 60 | return img_file 61 | 62 | 63 | class UniCorn(Data): 64 | 65 | def update_unicorn(self): 66 | 67 | unicorn.clear() 68 | 69 | global img_file 70 | 71 | log_string('Start Unicorn image loop') 72 | 73 | while img_file: 74 | 75 | img = Image.open(img_file) 76 | 77 | self.draw_unicorn(img) 78 | 79 | else: 80 | 81 | log_string('Something went wrong while picking up the img') 82 | pass 83 | 84 | @staticmethod 85 | def draw_unicorn(image): 86 | 87 | # this is the original pimoroni function for drawing sprites 88 | 89 | for o_x in range(int(image.size[0] / width)): 90 | 91 | for o_y in range(int(image.size[1] / height)): 92 | 93 | valid = False 94 | 95 | for x in range(width): 96 | 97 | for y in range(height): 98 | pixel = image.getpixel(((o_x * width) + y, (o_y * height) + x)) 99 | # print(pixel) 100 | r, g, b = int(pixel[0]), int(pixel[1]), int(pixel[2]) 101 | if r or g or b: 102 | valid = True 103 | unicorn.set_pixel(x, y, r, g, b) 104 | 105 | if valid: 106 | unicorn.show() 107 | time.sleep(0.25) 108 | 109 | def test_unicorn(self): 110 | print('Testing all images in folder {}'.format(self.path)) 111 | 112 | for image in os.listdir(self.path): 113 | 114 | if image.endswith(self.icon_extension): 115 | 116 | print('Testing image: {}'.format(self.path + image)) 117 | 118 | img = Image.open(self.path + image) 119 | 120 | self.draw_unicorn(img) 121 | 122 | else: 123 | 124 | print('Not using this file, not an image: {}'.format(file)) 125 | 126 | unicorn.clear() 127 | unicorn.show() 128 | 129 | def draw_single_icon(self, animation): 130 | unicorn.clear() 131 | 132 | single_file = self.path + animation + self.icon_extension 133 | 134 | log_string('Start drawing single icon or animation: {}'.format(animation)) 135 | 136 | img = Image.open(single_file) 137 | 138 | self.draw_unicorn(img) 139 | 140 | 141 | if __name__ == '__main__': 142 | 143 | try: 144 | 145 | unicorn_init() 146 | UniCorn().draw_single_icon('raspberry_boot') 147 | # UniCorn().test_unicorn() 148 | UpdateIcon().set_icon_path() 149 | UniCorn().update_unicorn() 150 | 151 | except KeyboardInterrupt: 152 | 153 | unicorn.clear() 154 | unicorn.show() 155 | -------------------------------------------------------------------------------- /Modules/WebApp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | from flask import Flask, jsonify, render_template 4 | 5 | from Endpoint import Endpoint 6 | 7 | app = Flask(__name__) 8 | 9 | 10 | @app.route('/api/node') 11 | def get_node_data(): 12 | return jsonify(Endpoint().node_data()) 13 | 14 | 15 | @app.route('/api/sensors') 16 | def get_sensor_data(): 17 | return jsonify(Endpoint().sensor_module_data()) 18 | 19 | 20 | @app.route('/api/rpi') 21 | def get_rpi_data(): 22 | return jsonify(Endpoint().rpi_stats()) 23 | 24 | 25 | @app.route('/') 26 | def index(): 27 | return render_template('home.html') 28 | 29 | 30 | @app.route('/node') 31 | def node(): 32 | return render_template('node.html', node_data=Endpoint().node_data()) 33 | 34 | 35 | @app.route('/sensors') 36 | def sensors(): 37 | return render_template('sensors.html', sensor_data=Endpoint().sensor_module_data()) 38 | 39 | 40 | @app.route('/rpi') 41 | def rpi(): 42 | return render_template('rpi.html', rpi_data=Endpoint().rpi_stats()) 43 | 44 | 45 | # @app.route('/base') 46 | # def base(): 47 | # blink('yellow') 48 | # return render_template('base.html') 49 | 50 | if __name__ == '__main__': 51 | 52 | app.run(debug=True, host='0.0.0.0', port=4545) 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /Modules/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- -------------------------------------------------------------------------------- /Modules/check_alarms.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | import time 4 | from collections import namedtuple 5 | from datetime import datetime 6 | 7 | from Data import Data 8 | from init_logging import log_string 9 | 10 | TFM = '%d.%m.%Y %H:%M:%S' 11 | 12 | 13 | def time_convert(time_stamp): 14 | return str(datetime.fromtimestamp(int(time_stamp)).strftime(TFM)) 15 | 16 | 17 | def check_alarms(): 18 | 19 | weather = Data().api_data 20 | 21 | alarms = {} 22 | 23 | try: 24 | 25 | alarm_weather = weather['alerts'] 26 | 27 | Alarm = namedtuple('Alarm', ['severity', 'title', 'start_time', 'expires', 'duration', 'lasts', 'description']) 28 | 29 | for idx, alarm_obj in enumerate(alarm_weather): 30 | 31 | severity, title, time_start, expires, description = alarm_obj['severity'], alarm_obj['title'], \ 32 | alarm_obj['time'], alarm_obj['expires'], \ 33 | alarm_obj['description'].encode('UTF-8') 34 | 35 | alarm_start_time = time_convert(int(time_start)) 36 | alarm_expire_time = time_convert(int(expires)) 37 | alarm_duration = str(datetime.fromtimestamp(expires) - datetime.fromtimestamp(time_start)) 38 | 39 | alarm_lasts = (datetime.fromtimestamp(expires) - datetime.fromtimestamp(time.time())).seconds / 60.0 / 60.0 40 | 41 | alarm_lasts = int(round(alarm_lasts, 0)) 42 | 43 | alarm = Alarm(str(severity), str(title), 44 | alarm_start_time, alarm_expire_time, alarm_duration, alarm_lasts, 45 | str(description)) 46 | 47 | if alarm.severity == 'warning': 48 | 49 | log_string('warning Alarm: {}'.format(alarm)) 50 | alarms[idx] = {'severity': alarm.severity, 'duration': alarm_lasts} 51 | 52 | elif alarm.severity == 'watch': 53 | 54 | log_string('watch Alarm: {}'.format(alarm)) 55 | alarms[idx] = {'severity': alarm.severity, 'duration': alarm_lasts} 56 | 57 | elif alarm.severity == 'advisory': 58 | 59 | log_string('watch Alarm: {}'.format(alarm)) 60 | alarms[idx] = {'severity': alarm.severity, 'duration': alarm_lasts} 61 | 62 | else: 63 | alarms = None 64 | 65 | log_string('Alle Wetter Alarme: {}'.format(alarms)) 66 | return alarms 67 | 68 | except KeyError: 69 | 70 | log_string('no weather alerts') 71 | return None 72 | 73 | 74 | if __name__ == '__main__': 75 | 76 | check_alarms() 77 | -------------------------------------------------------------------------------- /Modules/clear.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | from init_blinkt import * 4 | from init_matrix import * 5 | from init_unicorn import * 6 | 7 | 8 | def clear_all(): 9 | 10 | for matrix in matrix_list: 11 | 12 | matrix.clear() 13 | matrix.write_display() 14 | 15 | bargraph.clear() 16 | bargraph.write_display() 17 | 18 | unicorn.clear() 19 | unicorn.show() 20 | 21 | blinkt.clear() 22 | blinkt.show() 23 | 24 | log_string('Clear Display') 25 | 26 | 27 | if __name__ == '__main__': 28 | 29 | clear_all() 30 | -------------------------------------------------------------------------------- /Modules/init_blinkt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | import time 4 | 5 | import blinkt 6 | 7 | from Config import Config 8 | from init_logging import log_string 9 | 10 | # read the config file 11 | config = Config().get_config() 12 | 13 | BRIGHTNESS = config['BLINKT_BRIGHTNESS'] 14 | 15 | leds = [0, 0, 0, 0, 0, 16, 64, 255, 64, 16, 0, 0, 0, 0, 0] 16 | 17 | start_time = time.time() 18 | 19 | 20 | def blinkt_init(): 21 | 22 | blinkt.set_brightness(BRIGHTNESS) 23 | blinkt.clear() 24 | blinkt.show() 25 | 26 | 27 | def blink(color): 28 | 29 | blinkt_init() 30 | 31 | for y in range(9): 32 | 33 | delta = (time.time() - start_time) * 16 34 | 35 | # Triangle wave, a snappy ping-pong effect 36 | offset = int(abs((delta % 16) - 8)) 37 | 38 | if color == 'white': 39 | 40 | for i in range(8): 41 | 42 | blinkt.set_pixel(i, leds[offset + i], leds[offset + i], leds[offset + i]) 43 | 44 | blinkt.show() 45 | 46 | elif color == 'red': 47 | 48 | for i in range(8): 49 | 50 | blinkt.set_pixel(i, leds[offset + i], 0, 0) 51 | 52 | blinkt.show() 53 | 54 | elif color == 'yellow': 55 | 56 | for i in range(8): 57 | 58 | blinkt.set_pixel(i, leds[offset + i], leds[offset + i], 0) 59 | 60 | blinkt.show() 61 | 62 | elif color == 'blue': 63 | 64 | for i in range(8): 65 | 66 | blinkt.set_pixel(i, 0, 0, leds[offset + i]) 67 | 68 | blinkt.show() 69 | 70 | elif color == 'green': 71 | 72 | for i in range(8): 73 | 74 | blinkt.set_pixel(i, 0, leds[offset + i], 0) 75 | 76 | blinkt.show() 77 | 78 | else: 79 | 80 | log_string('ERROR: Blinkt Color {} not set. Showing white.'.format(color)) 81 | 82 | for i in range(8): 83 | 84 | blinkt.set_pixel(i, leds[offset + i], leds[offset + i], leds[offset + i]) 85 | 86 | blinkt.show() 87 | 88 | time.sleep(0.1) 89 | 90 | blinkt.clear() 91 | blinkt.show() 92 | 93 | log_string('Blinkt: {}'.format(color)) 94 | 95 | 96 | if __name__ == '__main__': 97 | 98 | blinkt_init() 99 | 100 | blink('white') 101 | blink('red') 102 | blink('blue') 103 | blink('yellow') 104 | blink('green') 105 | 106 | blinkt.clear() 107 | blinkt.show() 108 | -------------------------------------------------------------------------------- /Modules/init_buttons.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | import os 4 | import time 5 | 6 | import RPi.GPIO as GPIO 7 | 8 | from init_logging import * 9 | 10 | GPIO.setmode(GPIO.BCM) 11 | GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_UP) 12 | GPIO.setup(27, GPIO.IN, pull_up_down=GPIO.PUD_UP) 13 | GPIO.setup(22, GPIO.IN, pull_up_down=GPIO.PUD_UP) 14 | 15 | 16 | def start_service(start): 17 | 18 | log_string('Button pressed:{}\n' 19 | 'start WeatherPi service'.format(start)) 20 | 21 | os.system('sudo systemctl stop WeatherPi.service') 22 | os.system('sudo systemctl start WeatherPi.service') 23 | 24 | 25 | def stop_service(stop): 26 | 27 | log_string('Button pressed:{}\n' 28 | 'stop WeatherPi service'.format(stop)) 29 | 30 | os.system('sudo systemctl stop WeatherPi.service') 31 | 32 | 33 | def restart_service(restart): 34 | 35 | log_string('Button pressed:{}\n' 36 | 'restart WeatherPi service'.format(restart)) 37 | 38 | os.system('sudo systemctl stop WeatherPi.service') 39 | os.system('sudo systemctl restart WeatherPi.service') 40 | 41 | 42 | def shutdown_service(shutdown): 43 | log_string('Button pressed:{}\n' 44 | 'shutdown Pi'.format(shutdown)) 45 | 46 | os.system('sudo shutdown now') 47 | 48 | 49 | def reboot_service(reboot): 50 | log_string('Button pressed:{}\n' 51 | 'reboot Pi'.format(reboot)) 52 | 53 | os.system('sudo shutdown -r now') 54 | 55 | 56 | GPIO.add_event_detect(22, GPIO.FALLING, callback=restart_service, bouncetime=1000) 57 | GPIO.add_event_detect(27, GPIO.FALLING, callback=stop_service, bouncetime=1000) 58 | GPIO.add_event_detect(17, GPIO.FALLING, callback=shutdown_service, bouncetime=1000) 59 | 60 | if __name__ == '__main__': 61 | 62 | try: 63 | while True: 64 | time.sleep(1) 65 | except KeyboardInterrupt: 66 | os.system('sudo service WeatherPi stop') 67 | GPIO.cleanup() 68 | -------------------------------------------------------------------------------- /Modules/init_logging.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # create some logger details 4 | import logging.handlers 5 | 6 | # create a weather logger 7 | 8 | weather_path = '/home/pi/WeatherPi/logs/Weather_Log_Data.log' 9 | WEATHER_LOG_FILENAME = weather_path 10 | 11 | # Set up a specific logger with our desired output level 12 | weather_logger = logging.getLogger('WeatherLogger') 13 | weather_logger.setLevel(logging.INFO) 14 | 15 | # Add the log message handler to the logger and make a log-rotation of 100 files with max. 10MB per file 16 | weather_handler = logging.handlers.RotatingFileHandler(WEATHER_LOG_FILENAME, maxBytes=10485760, backupCount=100) 17 | weather_logger.addHandler(weather_handler) 18 | 19 | 20 | # create a debug logger 21 | 22 | debug_path = '/home/pi/WeatherPi/logs/Debug_Log.log' 23 | DEBUG_LOG_FILENAME = debug_path 24 | 25 | # Set up a specific logger with our desired output level 26 | debug_logger = logging.getLogger('werkzeug') 27 | debug_logger.setLevel(logging.DEBUG) 28 | 29 | 30 | # Add the log message handler to the logger and make a log-rotation of 100 files with max. 10MB per file 31 | debug_handler = logging.handlers.RotatingFileHandler(DEBUG_LOG_FILENAME, maxBytes=100000, backupCount=1) 32 | debug_logger.addHandler(debug_handler) 33 | 34 | 35 | def log_string(string): 36 | 37 | print(string) 38 | debug_logger.debug(string) 39 | -------------------------------------------------------------------------------- /Modules/init_matrix.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | from Config import Config 4 | from Driver.BicolorBargraph24 import BicolorBargraph24 5 | from Driver.Matrix16x8 import Matrix16x8 6 | 7 | # read the config file 8 | config = Config().get_config() 9 | 10 | BRIGHTNESS = config['MATRIX_BRIGHTNESS'] 11 | 12 | matrix_green = Matrix16x8(address=0x70) 13 | matrix_red = Matrix16x8(address=0x71) 14 | matrix_blue = Matrix16x8(address=0x72) 15 | matrix_orange = Matrix16x8(address=0x73) 16 | 17 | bargraph = BicolorBargraph24(address=0x74) 18 | 19 | matrix_list = (matrix_green, matrix_red, matrix_blue, matrix_orange) 20 | 21 | 22 | def matrix_init(): 23 | global matrix_list 24 | for matrix in matrix_list: 25 | matrix.begin() 26 | matrix.clear() 27 | matrix.set_brightness(BRIGHTNESS) 28 | matrix.write_display() 29 | 30 | bargraph.begin() 31 | bargraph.clear() 32 | bargraph.set_brightness(BRIGHTNESS) 33 | bargraph.write_display() 34 | 35 | 36 | if __name__ == '__main__': 37 | 38 | matrix_init() 39 | -------------------------------------------------------------------------------- /Modules/init_unicorn.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | from Config import Config 5 | from init_logging import * 6 | 7 | # read the config file 8 | config = Config().get_config() 9 | 10 | UNICORN_VERSION = config['UNICORN_VERSION'] 11 | BRIGHTNESS = config['UNICORN_BRIGHTNESS'] 12 | 13 | if UNICORN_VERSION == "HD": 14 | 15 | import unicornhathd as unicorn 16 | 17 | log_string('Unicorn Version set to HD') 18 | 19 | 20 | elif UNICORN_VERSION == "SD": 21 | 22 | import unicornhat as unicorn 23 | 24 | log_string('Unicorn Version set to SD') 25 | 26 | else: 27 | 28 | log_string('No valid Unicorn Version found in config file - use "SD" or "HD"') 29 | 30 | 31 | def unicorn_init(): 32 | unicorn.brightness(BRIGHTNESS) 33 | 34 | if UNICORN_VERSION == "HD": 35 | unicorn.rotation(0) 36 | elif UNICORN_VERSION == "SD": 37 | unicorn.rotation(90) 38 | else: 39 | log_string('No valid Unicorn Version found in config file - use "SD" or "HD"') 40 | unicorn.clear() 41 | unicorn.show() 42 | 43 | 44 | if __name__ == '__main__': 45 | unicorn_init() 46 | -------------------------------------------------------------------------------- /Modules/static/css/cover.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Globals 3 | */ 4 | 5 | /* Links */ 6 | a, 7 | a:focus, 8 | a:hover { 9 | color: #fff; 10 | } 11 | 12 | /* Custom default button */ 13 | /*.btn-secondary,*/ 14 | /*.btn-secondary:hover,*/ 15 | /*.btn-secondary:focus {*/ 16 | /*color: #333;*/ 17 | /*text-shadow: none; !* Prevent inheritance from `body` *!*/ 18 | /*background-color: #fff;*/ 19 | /*border: .05rem solid #fff;*/ 20 | /*}*/ 21 | 22 | /* 23 | * Base structure 24 | */ 25 | 26 | html, 27 | body { 28 | height: 100%; 29 | background-color: #333; 30 | } 31 | 32 | body { 33 | display: -ms-flexbox; 34 | display: -webkit-box; 35 | display: flex; 36 | -ms-flex-pack: center; 37 | -webkit-box-pack: center; 38 | justify-content: center; 39 | color: #fff; 40 | text-shadow: 0 .05rem .1rem rgba(0, 0, 0, .5); 41 | /*box-shadow: inset 0 0 5rem rgba(0, 0, 0, .5);*/ 42 | } 43 | 44 | .cover-container { 45 | max-width: 42em; 46 | } 47 | 48 | /* 49 | * Header 50 | */ 51 | .masthead { 52 | margin-bottom: 2rem; 53 | } 54 | 55 | .masthead-brand { 56 | margin-bottom: 0; 57 | } 58 | 59 | .nav-masthead .nav-link { 60 | padding: .25rem 0; 61 | font-weight: 700; 62 | color: rgba(255, 255, 255, .5); 63 | background-color: transparent; 64 | border-bottom: .25rem solid transparent; 65 | } 66 | 67 | .nav-masthead .nav-link:hover, 68 | .nav-masthead .nav-link:focus { 69 | border-bottom-color: rgba(255, 255, 255, .25); 70 | } 71 | 72 | .nav-masthead .nav-link + .nav-link { 73 | margin-left: 1rem; 74 | } 75 | 76 | .nav-masthead .active { 77 | color: #fff; 78 | border-bottom-color: #fff; 79 | } 80 | 81 | .list-group-item { 82 | background-color: inherit; 83 | } 84 | 85 | @media (min-width: 48em) { 86 | .masthead-brand { 87 | float: left; 88 | } 89 | 90 | .nav-masthead { 91 | float: right; 92 | } 93 | } 94 | 95 | /* 96 | * Cover 97 | */ 98 | .cover { 99 | padding: 0 1.5rem; 100 | } 101 | 102 | .cover .btn-lg { 103 | padding: .75rem 1.25rem; 104 | font-weight: 700; 105 | } 106 | 107 | /* 108 | * Footer 109 | */ 110 | .mastfoot { 111 | color: rgba(255, 255, 255, .5); 112 | } 113 | 114 | .jumbotron { 115 | background-color: rgba(0, 0, 0, .03); 116 | margin-bottom: 0 117 | } 118 | 119 | @media (min-width: 576px) { 120 | .jumbotron { 121 | padding: 3rem 2rem; 122 | } 123 | } 124 | 125 | .table-dark { 126 | 127 | background-color: rgba(0, 0, 0, .03); 128 | } 129 | 130 | /* 131 | * Circle Charts 132 | */ 133 | 134 | .flex-wrapper { 135 | display: flex; 136 | flex-flow: row nowrap; 137 | } 138 | 139 | .single-chart { 140 | justify-content: space-around; 141 | } 142 | 143 | .circular-chart { 144 | display: block; 145 | margin: 10px auto; 146 | max-width: 65%; 147 | max-height: 250px; 148 | } 149 | 150 | .circle-bg { 151 | fill: none; 152 | stroke: #333; 153 | stroke-width: 3.8; 154 | } 155 | 156 | .circle { 157 | fill: none; 158 | stroke-width: 2.8; 159 | stroke-linecap: round; 160 | animation: progress 0.75s ease-out forwards; 161 | } 162 | 163 | @keyframes progress { 164 | 0% { 165 | stroke-dasharray: 0 100; 166 | } 167 | } 168 | 169 | .circular-chart.orange .circle { 170 | stroke: #ffc107; 171 | } 172 | 173 | .circular-chart.green .circle { 174 | stroke: #28a745; 175 | } 176 | 177 | .circular-chart.blue .circle { 178 | stroke: #17a2b8; 179 | } 180 | 181 | .circular-chart.red .circle { 182 | stroke: #dc3545; 183 | } 184 | .percentage { 185 | fill: #6c757d; 186 | font-size: 0.5em; 187 | text-anchor: middle; 188 | } -------------------------------------------------------------------------------- /Modules/static/css/theme.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Globals 3 | */ 4 | 5 | /* Links */ 6 | a, 7 | a:focus, 8 | a:hover { 9 | color: #fff; 10 | } 11 | 12 | /* Custom default button */ 13 | /*.btn-secondary,*/ 14 | /*.btn-secondary:hover,*/ 15 | /*.btn-secondary:focus {*/ 16 | /*color: #333;*/ 17 | /*text-shadow: none; !* Prevent inheritance from `body` *!*/ 18 | /*background-color: #fff;*/ 19 | /*border: .05rem solid #fff;*/ 20 | /*}*/ 21 | 22 | /* 23 | * Base structure 24 | */ 25 | 26 | html, 27 | body { 28 | height: 100%; 29 | background-color: #333; 30 | } 31 | 32 | body { 33 | display: -ms-flexbox; 34 | display: -webkit-box; 35 | display: flex; 36 | -ms-flex-pack: center; 37 | -webkit-box-pack: center; 38 | justify-content: center; 39 | color: #fff; 40 | text-shadow: 0 .05rem .1rem rgba(0, 0, 0, .5); 41 | } 42 | 43 | .icon { 44 | width: 16px; 45 | height: 16px; 46 | background-image: url("/static/icons/favicon.png"); 47 | } 48 | 49 | .content { 50 | 51 | } 52 | 53 | .cover-container { 54 | max-width: 42em; 55 | } 56 | 57 | /* 58 | * Header 59 | */ 60 | .masthead { 61 | margin-bottom: 2rem; 62 | border-bottom: 1px solid rgba(0, 0, 0, .1); 63 | } 64 | 65 | .masthead-brand { 66 | margin-bottom: 0; 67 | } 68 | 69 | .nav-masthead .nav-link { 70 | padding: .25rem 0; 71 | font-weight: 700; 72 | color: rgba(255, 255, 255, .5); 73 | background-color: transparent; 74 | border-bottom: .25rem solid transparent; 75 | } 76 | 77 | .nav-masthead .nav-link:hover, 78 | .nav-masthead .nav-link:focus { 79 | border-bottom-color: rgba(255, 255, 255, .25); 80 | } 81 | 82 | .nav-masthead .nav-link + .nav-link { 83 | margin-left: 1rem; 84 | } 85 | 86 | .nav-masthead .active { 87 | color: #fff; 88 | border-bottom-color: #fff; 89 | } 90 | 91 | .list-group-item { 92 | background-color: inherit; 93 | } 94 | 95 | @media (min-width: 48em) { 96 | .masthead-brand { 97 | float: left; 98 | } 99 | 100 | .nav-masthead { 101 | float: right; 102 | } 103 | } 104 | 105 | /* 106 | * Cover 107 | */ 108 | .cover { 109 | padding: 0 1.5rem; 110 | } 111 | 112 | .cover .btn-lg { 113 | padding: .75rem 1.25rem; 114 | font-weight: 700; 115 | } 116 | 117 | /* 118 | * Footer 119 | */ 120 | .mastfoot { 121 | color: rgba(255, 255, 255, .5); 122 | } 123 | 124 | .jumbotron { 125 | background-color: rgba(0, 0, 0, .03); 126 | margin-bottom: 0 127 | } 128 | 129 | @media (min-width: 576px) { 130 | .jumbotron { 131 | padding: 3rem 2rem; 132 | } 133 | } 134 | 135 | .table-dark { 136 | 137 | background-color: rgba(0, 0, 0, .03); 138 | } 139 | 140 | .table-dark td, .table-dark th, .table-dark thead th { 141 | border-color: rgba(0, 0, 0, .1); 142 | } 143 | 144 | /* 145 | * Circle Charts 146 | */ 147 | 148 | .flex-wrapper { 149 | display: flex; 150 | flex-flow: row nowrap; 151 | } 152 | 153 | .single-chart { 154 | justify-content: space-around; 155 | } 156 | 157 | .circular-chart { 158 | display: block; 159 | margin: 10px auto; 160 | max-width: 65%; 161 | max-height: 250px; 162 | } 163 | 164 | .circle-bg { 165 | fill: none; 166 | stroke: #333; 167 | stroke-width: 3.8; 168 | } 169 | 170 | .circle { 171 | fill: none; 172 | stroke-width: 2.8; 173 | stroke-linecap: round; 174 | animation: progress 0.75s ease-out forwards; 175 | } 176 | 177 | @keyframes progress { 178 | 0% { 179 | stroke-dasharray: 0 100; 180 | } 181 | } 182 | 183 | .circular-chart.orange .circle { 184 | stroke: #ffc107; 185 | } 186 | 187 | .circular-chart.green .circle { 188 | stroke: #28a745; 189 | } 190 | 191 | .circular-chart.blue .circle { 192 | stroke: #17a2b8; 193 | } 194 | 195 | .circular-chart.red .circle { 196 | stroke: #dc3545; 197 | } 198 | 199 | .percentage { 200 | fill: #6c757d; 201 | font-size: 0.5em; 202 | text-anchor: middle; 203 | } -------------------------------------------------------------------------------- /Modules/static/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoveBootCaptain/WeatherPi/24132f392080a61cabaa6fe99c3ef3ee684ccd77/Modules/static/icons/favicon.ico -------------------------------------------------------------------------------- /Modules/static/icons/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoveBootCaptain/WeatherPi/24132f392080a61cabaa6fe99c3ef3ee684ccd77/Modules/static/icons/favicon.png -------------------------------------------------------------------------------- /Modules/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {% block title %}{% endblock %} | WeatherPi 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {% block head %}{% endblock %} 19 | 20 | 21 | 22 | 23 |
24 |
25 |
26 |

WeatherPi

27 | 28 | 34 | 35 |
36 |
37 | 38 | {% block main %}{% endblock %} 39 | 40 | {% block footer %} 41 |
42 |
43 |
44 |

WeatherPi is made with by 45 | @lovebootcaptain
46 | Visit github for more 47 | information about the project. 48 |

49 |
50 |
51 | {% endblock %} 52 | 53 |
54 | 55 | {% block scripts %}{% endblock %} 56 | 57 | -------------------------------------------------------------------------------- /Modules/templates/home.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block home %}active{% endblock %} 4 | 5 | {% block title %} 6 | WeatherPi Dashboard 7 | {% endblock %} 8 | 9 | {% block description %} 10 | Wetterdaten von Sensoren und API's liebevoll gesammelt, ausgewertet, ausgeliefert und angezeigt von einem RaspberryPi Zero 11 | {% endblock %} 12 | 13 | {% block main %} 14 |
15 |
16 |
17 |

WeatherPi Dashboard

18 |
19 |
20 |

Wetterdaten von Sensoren und API's liebevoll gesammelt, ausgewertet, aufbereitet, 21 | ausgeliefert und angezeigt von einem einzigen 22 |

RaspberryPi Zero

23 |
24 | 30 |
31 |
32 | {% endblock %} 33 | 34 | {% block footer %} 35 | {{ super() }} 36 | {% endblock %} -------------------------------------------------------------------------------- /Modules/templates/node.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block node %}active{% endblock %} 4 | 5 | {% block title %} 6 | Node: UnicornPi - Dashboard 7 | {% endblock %} 8 | 9 | {% block description %} 10 | Data monitor for the UniCornPi Display Node 11 | {% endblock %} 12 | 13 | 14 | {% block main %} 15 |
16 | {% if node_data %} 17 |
18 | 19 |
20 |
21 |
22 |

UnicornPi Monitor

23 |
24 |
25 |
26 | 27 |
28 |
29 |
30 | Temperatur 31 |
32 |
33 | 34 |
35 |
36 | {{ node_data['temp'] }} °C 37 |
38 | Balkon 39 |
40 |
41 | 42 |
43 |
44 | 45 |
46 |
47 | Wetterbedingungen 48 |
49 |
50 | {{ node_data['summary'].decode('utf-8') }} 53 |
54 | {{ node_data['summary'].decode('utf-8') }} 55 |
56 |
57 |
58 | 59 |
60 |
61 |
62 | Regenwahrscheinlichkeit 8h 63 |
64 | 65 |
66 |
67 | {% for item in node_data['rain_forecast'] %} 68 | {% if item == 0 %} 69 |
75 | {{ item }} % 76 |
77 | {% elif item > 0 and item < 15 %} 78 |
84 | {{ item }} % 85 |
86 | {% else %} 87 |
93 | {{ item }} % 94 |
95 | {% endif %} 96 | {% endfor %} 97 |
98 |
99 |
100 |
101 | 102 |
103 |
104 |
105 | 106 |
107 | 110 |
111 |
112 | 113 |
114 | {% else %} 115 |

No Data

116 |

please try again later

117 | {% endif %} 118 |
119 | {% endblock %} 120 | 121 | {% block footer %} 122 | {{super()}} 123 | {% endblock %} 124 | 125 | {% block scripts %} 126 | 128 | 129 | 132 | 133 | 138 | 143 | {% endblock %} -------------------------------------------------------------------------------- /Modules/templates/rpi.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block rpi_stats %}active{% endblock %} 4 | 5 | {% block title %} 6 | RPI Stats 7 | {% endblock %} 8 | 9 | {% block description %} 10 | Some useful stats from the heart of the RaspberryPi Zero. CPU, RAM and DISK Dashboard 11 | {% endblock %} 12 | 13 | {% block main %} 14 |
15 |
16 | 17 |
18 | 19 |
20 |
21 | CPU Temp 22 |
23 |
24 |
25 | 26 | 28 | 30 | {{ rpi_data['CPU']['temp'] }}°C 31 | 32 |
33 |
34 |
35 | 36 |
37 |
38 | CPU Usage 39 |
40 |
41 |
42 | 43 | 45 | 47 | {{ rpi_data['CPU']['usage'] }}% 48 | 49 |
50 |
51 |
52 | 53 |
54 | 55 |
56 | 57 |
58 |
59 |
60 |
61 | 62 |
63 | 68 |
69 |
70 | 75 |
76 |
77 |
78 |
79 |
80 | 81 |
82 | 83 |
84 | 85 |
86 |
87 | RAM used 88 |
89 |
90 | 91 | 93 | 96 | {{ rpi_data['RAM']['used_prec'] }}% 97 | 98 | 99 |
100 |
101 | 102 |
103 |
104 | RAM stats 105 |
106 |
107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 |
Total{{ rpi_data['RAM']['total'] }} MB
Used{{ rpi_data['RAM']['used'] }} MB
Free{{ rpi_data['RAM']['free'] }} MB
123 |
124 |
125 | 126 |
127 | 128 |
129 | 130 |
131 |
132 | 133 |
134 |
135 | DISK used 136 |
137 |
138 | 139 | 141 | 144 | {{ rpi_data['DISK']['used_prec'] }}% 146 | 147 | 148 | 149 |
150 |
151 |
152 |
153 | DISK stats 154 |
155 |
156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 |
Total{{ rpi_data['DISK']['total'] }} MB
Used{{ rpi_data['DISK']['used'] }} MB
Free{{ rpi_data['DISK']['free'] }} MB
172 |
173 |
174 | 175 |
176 |
177 | 178 |
179 | 180 |
181 |
182 |
183 | 184 |
185 | 188 |
189 |
190 |
191 | 192 |
193 | {% endblock %} 194 | 195 | {% block footer %} 196 | {{ super() }} 197 | {% endblock %} 198 | 199 | {% block scripts %} 200 | 202 | 203 | 206 | 207 | 208 | 209 | 214 | {% endblock %} -------------------------------------------------------------------------------- /Modules/templates/sensors.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block sensors %}active{% endblock %} 4 | 5 | {% block title %} 6 | Sensors Dashboard 7 | {% endblock %} 8 | 9 | {% block description %} 10 | Dashboard for all data from sensor nodes 11 | {% endblock %} 12 | 13 | {% block main %} 14 |
15 | {% if sensor_data %} 16 |
17 |
18 |
19 |
20 | Wohnzimmer 21 |
22 |
23 | 24 | 25 | {% for key, value in sensor_data['Wohnzimmer'].iteritems() %} 26 | {% if key == 'Updated' %} 27 | {% else %} 28 | 29 | 30 | 31 | 32 | {% endif %} 33 | {% endfor %} 34 | 35 |
{{ key.decode('utf-8') }}{{ value }}
36 |
37 | 41 |
42 |
43 | 44 |
45 | 46 |
47 |
48 | Kinderzimmer 49 |
50 |
51 | 52 | 53 | {% for key, value in sensor_data['Kinderzimmer'].iteritems() %} 54 | {% if key == 'Updated' %} 55 | {% else %} 56 | 57 | 58 | 59 | 60 | {% endif %} 61 | {% endfor %} 62 | 63 |
{{ key.decode('utf-8') }}{{ value }}
64 |
65 | 69 |
70 | 71 |
72 |
73 | Schlafzimmer 74 |
75 |
76 | 77 | 78 | {% for key, value in sensor_data['Schlafzimmer'].iteritems() %} 79 | {% if key == 'Updated' %} 80 | {% else %} 81 | 82 | 83 | 84 | 85 | {% endif %} 86 | {% endfor %} 87 | 88 |
{{ key.decode('utf-8') }}{{ value }}
89 |
90 | 94 |
95 | 96 |
97 |
98 |
99 |
100 | Balkon 101 |
102 |
103 | 104 | 105 | {% for key, value in sensor_data['Balkon'].iteritems() %} 106 | {% if key == 'Updated' %} 107 | {% else %} 108 | 109 | 110 | 111 | 112 | {% endif %} 113 | {% endfor %} 114 | 115 |
{{ key.decode('utf-8') }}{{ value }}
116 |
117 | 121 |
122 |
123 | 124 |
125 |
126 |
127 | 128 |
129 |
130 |
131 | 132 |
133 | {% else %} 134 |

No Data

135 |

please try again later

136 | {% endif %} 137 |
138 | {% endblock %} 139 | 140 | {% block footer %} 141 | {{ super() }} 142 | {% endblock %} 143 | 144 | {% block scripts %} 145 | 147 | 148 | 153 | {% endblock %} -------------------------------------------------------------------------------- /Modules/update_blinkt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | from init_blinkt import * 4 | 5 | 6 | def update_blinkt(): 7 | 8 | blink('yellow') 9 | 10 | 11 | if __name__ == '__main__': 12 | 13 | update_blinkt() 14 | -------------------------------------------------------------------------------- /Modules/update_matrix.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | import threading 4 | 5 | from Fonts.custom_font import * 6 | from RainData import RainData 7 | from SensorData import SensorData 8 | from check_alarms import * 9 | from clear import clear_all 10 | from init_blinkt import * 11 | from init_matrix import * 12 | 13 | # read the config file 14 | config = Config().get_config() 15 | 16 | THREADING_TIMER = config['THREADING_TIMER'] 17 | 18 | threads = [] 19 | 20 | OFF = 0 21 | GREEN = 1 22 | RED = 2 23 | YELLOW = 3 24 | 25 | 26 | def update_matrix(): 27 | 28 | temp_data = SensorData() 29 | 30 | sensor_temp_outside = temp_data.temp_outside() 31 | rain_probability = RainData().rain_probability() 32 | sensor_temp_inside = temp_data.temp_inside() 33 | 34 | update_list = { 35 | 1: ('Grüne Matrix', matrix_green, 'sensor_temp_outside', sensor_temp_outside), 36 | 2: ('Orange Matrix', matrix_orange, 'rain_probability', rain_probability), 37 | 3: ('Rote Matrix', matrix_red, 'sensor_temp_inside', sensor_temp_inside) 38 | } 39 | 40 | try: 41 | 42 | for matrix, value in update_list.items(): 43 | 44 | value[1].clear() 45 | value[1].display_16x8_buffer(value[3]) 46 | value[1].write_display() 47 | 48 | log_string('{} updated mit {} - {}'.format(value[0], value[2], value[3])) 49 | 50 | blink('red') 51 | time.sleep(0.25) 52 | 53 | except IOError: 54 | 55 | log_string('Matrix Error') 56 | 57 | return 58 | 59 | 60 | def blink_bar(duration, forecast, bar_color): 61 | 62 | log_string('Alarm Zeitraum für Bargraph: {} Farbe: {}'.format(duration, bar_color)) 63 | 64 | def fade_sleep(): 65 | time.sleep(0.05) 66 | 67 | def blink_sleep(): 68 | time.sleep(0.5) 69 | 70 | def fade(): 71 | bargraph.write_display() 72 | fade_sleep() 73 | 74 | def long_sleep(): 75 | time.sleep(5) 76 | 77 | def bar_fade_off(): 78 | for a in reversed(range(duration)): 79 | bargraph.set_bar(a, OFF) 80 | fade() 81 | 82 | def bar_off(): 83 | for b in range(duration): 84 | bargraph.set_bar(b, OFF) 85 | bargraph.write_display() 86 | blink_sleep() 87 | 88 | def bar_on(): 89 | for c in range(duration): 90 | bargraph.set_bar(c, bar_color) 91 | bargraph.write_display() 92 | blink_sleep() 93 | 94 | while True: 95 | 96 | for i in forecast["GREEN"]: 97 | bargraph.set_bar(i, GREEN) 98 | fade() 99 | 100 | for i in forecast["YELLOW"]: 101 | bargraph.set_bar(i, YELLOW) 102 | fade() 103 | 104 | for i in forecast["RED"]: 105 | bargraph.set_bar(i, RED) 106 | fade() 107 | 108 | long_sleep() 109 | long_sleep() 110 | bar_fade_off() 111 | 112 | for x in range(duration): 113 | bargraph.set_bar(x, bar_color) 114 | fade() 115 | 116 | blink_sleep() 117 | bar_off() 118 | bar_on() 119 | bar_off() 120 | bar_on() 121 | 122 | bar_fade_off() 123 | 124 | 125 | def set_bars(forecast): 126 | 127 | bargraph.clear() 128 | 129 | alarms = check_alarms() 130 | 131 | if alarms: 132 | 133 | for k, alarm in alarms.items(): 134 | 135 | if alarm['severity'] == 'warning': 136 | max_alarm = alarm['duration'] 137 | max_alarm_duration = int(max_alarm) 138 | 139 | if max_alarm_duration == 0: 140 | max_alarm_duration += 1 141 | 142 | blink_bar(max_alarm_duration, forecast, RED) 143 | 144 | elif alarm['severity'] == 'watch': 145 | max_alarm = max(alarms[idx]['duration'] for idx, alarm in enumerate(alarms)) 146 | max_alarm_duration = int(max_alarm) 147 | 148 | if max_alarm_duration == 0: 149 | max_alarm_duration += 1 150 | 151 | blink_bar(max_alarm_duration, forecast, YELLOW) 152 | 153 | elif alarm['severity'] == 'advisory': 154 | max_alarm = alarm['duration'] 155 | max_alarm_duration = int(max_alarm) 156 | 157 | if max_alarm_duration == 0: 158 | max_alarm_duration += 1 159 | 160 | blink_bar(max_alarm_duration, forecast, GREEN) 161 | 162 | else: 163 | 164 | for i in forecast["GREEN"]: 165 | bargraph.set_bar(i, GREEN) 166 | 167 | for i in forecast["YELLOW"]: 168 | bargraph.set_bar(i, YELLOW) 169 | 170 | for i in forecast["RED"]: 171 | bargraph.set_bar(i, RED) 172 | 173 | bargraph.write_display() 174 | 175 | 176 | def update_bargraph(): 177 | 178 | try: 179 | 180 | rain_forecast = RainData().rain_forecast() 181 | 182 | set_bars(rain_forecast) 183 | 184 | log_string('Bargraph Updated GREEN: {},\n' 185 | 'Bargraph Updated YELLOW: {},\n' 186 | 'Bargraph Updated RED: {}'.format( 187 | rain_forecast["GREEN"], 188 | rain_forecast["YELLOW"], 189 | rain_forecast["RED"] 190 | )) 191 | 192 | blink('red') 193 | 194 | except IOError: 195 | 196 | log_string('Bargraph Error') 197 | 198 | return 199 | 200 | 201 | hours_colon = hours 202 | hours_no_colon = hours_no_colon 203 | digits = digits 204 | 205 | colon_on = True 206 | 207 | 208 | def update_clock_matrix(): 209 | 210 | global threads 211 | 212 | try: 213 | 214 | clock_timer = threading.Timer(1, update_clock_matrix) 215 | clock_timer.start() 216 | 217 | threads.append(clock_timer) 218 | 219 | global colon_on 220 | 221 | the_hour = time.strftime("%I") 222 | the_min = time.strftime("%M") 223 | 224 | if the_hour.startswith("0"): 225 | the_hour = the_hour[1:] 226 | 227 | if colon_on: 228 | colon_on = False 229 | the_hour_data = hours_colon[the_hour] 230 | else: 231 | colon_on = True 232 | the_hour_data = hours_no_colon[the_hour] 233 | 234 | the_min_data = digits[the_min[0]] + digits[the_min[1]] 235 | time_buffer = the_hour_data + the_min_data 236 | 237 | matrix_blue.clear() 238 | 239 | matrix_blue.display_16x8_buffer(time_buffer) 240 | matrix_blue.write_display() 241 | 242 | # log_string = 'Blue Matrix (Time) updated - {}:{} - {}'.format(the_hour, the_min, time_buffer) 243 | # 244 | # print(log_string) 245 | # debug_logger.debug(log_string) 246 | 247 | except KeyboardInterrupt: 248 | 249 | clear_all() 250 | 251 | 252 | if __name__ == '__main__': 253 | 254 | try: 255 | 256 | matrix_init() 257 | 258 | update_clock_matrix() 259 | update_matrix() 260 | update_bargraph() 261 | 262 | except KeyboardInterrupt: 263 | 264 | clear_all() 265 | 266 | 267 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WeatherPi 2 | 3 | [![Build Status](https://travis-ci.org/LoveBootCaptain/WeatherPi.svg?branch=master)](https://travis-ci.org/LoveBootCaptain/WeatherPi) 4 | 5 | WeatherPi 6 | -------------------------------------------------------------------------------- /WeatherPi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | from multiprocessing import Process 4 | import os 5 | 6 | from Modules.Update import Update 7 | from Modules.UpdateLog import UpdateLog 8 | from Modules.UpdateUnicorn import UniCorn, UpdateIcon 9 | from Modules.init_unicorn import unicorn_init 10 | from Modules.update_matrix import * 11 | 12 | processes_bar = [] 13 | 14 | 15 | def quit_all(): 16 | 17 | for thread in threads: 18 | thread.cancel() 19 | thread.join() 20 | 21 | 22 | def main(): 23 | 24 | main_thread = threading.Timer(THREADING_TIMER, main) 25 | 26 | main_thread.start() 27 | 28 | threads.append(main_thread) 29 | 30 | try: 31 | 32 | Update().update_json() 33 | 34 | UpdateLog().create_log() 35 | 36 | update_matrix() 37 | 38 | p = Process(target=update_bargraph) 39 | 40 | for process in processes_bar: 41 | process.terminate() 42 | 43 | p.start() 44 | processes_bar.append(p) 45 | 46 | UpdateIcon().set_icon_path() 47 | 48 | except KeyboardInterrupt: 49 | 50 | quit_all() 51 | clear_all() 52 | 53 | 54 | if __name__ == '__main__': 55 | 56 | Update().update_json() 57 | os.system('Modules/WebApp.py &') 58 | log_string('api endpoints created') 59 | 60 | try: 61 | 62 | matrix_init() 63 | unicorn_init() 64 | blinkt_init() 65 | 66 | UniCorn().draw_single_icon('raspberry_boot') 67 | 68 | update_clock_matrix() 69 | 70 | main() 71 | 72 | UniCorn().update_unicorn() 73 | 74 | except KeyboardInterrupt: 75 | 76 | quit_all() 77 | clear_all() 78 | -------------------------------------------------------------------------------- /config.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "FORECAST_IO_KEY": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 3 | "LATITUDE": "xxx.xxx", 4 | "LONGITUDE": "xxx.xxx", 5 | "ADAFRUIT_IO_KEY_DASHBOARD": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 6 | "ADAFRUIT_IO_KEY_SENSORS": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 7 | "THINGSPEAK_API_KEY": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 8 | "THREADING_TIMER": 100, 9 | "MATRIX_BRIGHTNESS": 1, 10 | "BLINKT_BRIGHTNESS": 0.05, 11 | "UNICORN_VERSION":"HD", 12 | "UNICORN_BRIGHTNESS": 1.0 13 | } 14 | -------------------------------------------------------------------------------- /logs/README.md: -------------------------------------------------------------------------------- 1 | this is where your log files are saved -------------------------------------------------------------------------------- /python.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | sudo python "$@" -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | tzlocal 2 | pytz 3 | flask 4 | requests 5 | blinkt 6 | pillow 7 | unicornhat 8 | unicornhathd 9 | numpy 10 | -------------------------------------------------------------------------------- /scripts/PiButtons.service: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | [Unit] 3 | Description=PiButtons 4 | 5 | [Service] 6 | WorkingDirectory=/home/pi/WeatherPi/Modules/ 7 | ExecStart=/home/pi/WeatherPi/Modules/init_buttons.py 8 | Restart=always 9 | StandardOutput=syslog 10 | StandardError=syslog 11 | SyslogIdentifier=notell 12 | User=root 13 | Group=root 14 | Environment=NODE_ENV=production 15 | 16 | [Install] 17 | WantedBy=multi-user.target 18 | -------------------------------------------------------------------------------- /scripts/README.md: -------------------------------------------------------------------------------- 1 | # these scripts help to run WeatherPi as a linux service 2 | 3 | - buttons.sh is for mapping some function to gpio-buttons on the pi 4 | - turnLedsOff.sh turns all leds and addon-boards off at boot and shutdown/reboot 5 | - WeatherPi.sh is the main application and can benefit from the other two services 6 | 7 | ## make files executable and copy the services 8 | 9 | cd ~/WeatherPi/Modules/ 10 | 11 | sudo chmod +x clear.py 12 | sudo chmod +x init_buttons.py 13 | 14 | cd .. 15 | 16 | sudo chmod +x WeatherPi.py 17 | 18 | cd scripts/ 19 | 20 | sudo chmod +x *.sh 21 | 22 | sudo cp *.sh /etc/init.d/ 23 | 24 | sudo reboot 25 | 26 | ## test the services 27 | 28 | sudo service WeatherPi start 29 | sudo service WeatherPi stop 30 | sudo service WeatherPi restart 31 | 32 | sudo service turnLedsOff start 33 | sudo service turnLedsOff stop 34 | 35 | sudo service buttons start 36 | sudo service buttons stop 37 | 38 | ## start services at boot 39 | 40 | sudo update-rc.d turnLedsOff.sh defaults 41 | sudo update-rc.d buttons.sh defaults 42 | sudo update-rc.d WeatherPi.sh defaults 43 | 44 | ## remove services from boot 45 | 46 | sudo update-rc.d turnLedsOff.sh remove 47 | sudo update-rc.d buttons.sh remove 48 | sudo update-rc.d WeatherPi.sh remove -------------------------------------------------------------------------------- /scripts/WeatherPi.service: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | [Unit] 3 | Description=WeatherPi 4 | Wants=network-online.target 5 | After=network-online.target 6 | 7 | [Service] 8 | WorkingDirectory=/home/pi/WeatherPi/ 9 | ExecStartPre=/home/pi/WeatherPi/Modules/clear.py 10 | ExecStart=/home/pi/WeatherPi/WeatherPi.py 11 | ExecStopPost=/home/pi/WeatherPi/Modules/clear.py 12 | Restart=always 13 | StandardOutput=syslog 14 | StandardError=syslog 15 | SyslogIdentifier=notell 16 | User=root 17 | Group=root 18 | Environment=NODE_ENV=production 19 | 20 | [Install] 21 | WantedBy=multi-user.target 22 | -------------------------------------------------------------------------------- /scripts/turnLedsOff.service: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | [Unit] 3 | Description=turnLedsOff 4 | 5 | [Service] 6 | WorkingDirectory=/home/pi/WeatherPi/Modules/ 7 | ExecStart=/home/pi/WeatherPi/Modules/clear.py 8 | ExecStop=/home/pi/WeatherPi/Modules/clear.py 9 | StandardOutput=syslog 10 | StandardError=syslog 11 | SyslogIdentifier=notell 12 | User=root 13 | Group=root 14 | Environment=NODE_ENV=production 15 | 16 | [Install] 17 | WantedBy=multi-user.target 18 | --------------------------------------------------------------------------------