Skip to content

Commit 25bb626

Browse files
committed
V5.4 Snake & Home Assistant
New FUN Menu including Snake Readme on how to add the OpenCO2-Sensor to Home Assistant. The hostname was changed to OpenCO2. This also enables http://openco2:9925
1 parent 24bbb80 commit 25bb626

File tree

5 files changed

+248
-16
lines changed

5 files changed

+248
-16
lines changed

OpenCO2_Sensor.ino

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
- WiFiManager: https://github.com/tzapu/WiFiManager
1111
- ArduinoMqttClient (if MQTT is defined)
1212
*/
13-
#define VERSION "v5.3"
13+
#define VERSION "v5.4"
1414

1515
#define HEIGHT_ABOVE_SEA_LEVEL 50 // Berlin
1616
#define TZ_DATA "CET-1CEST,M3.5.0,M10.5.0/3" // Europe/Berlin time zone from https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
@@ -714,10 +714,7 @@ void startWiFi() {
714714
wifiManager.addParameter(&custom_api_token);
715715
#endif /* MQTT */
716716

717-
char Hostname[28];
718-
snprintf(Hostname, 28, "OpenCO2-Sensor%llX", ESP.getEfuseMac());
719-
WiFi.setHostname(Hostname); // hostname when connected to home network
720-
717+
WiFi.setHostname("OpenCO2"); // hostname when connected to home network
721718
wifiManager.setConfigPortalBlocking(false);
722719
wifiManager.setWiFiAutoReconnect(true);
723720
wifiManager.autoConnect("OpenCO2 Sensor"); // name of broadcasted SSID

README.md

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,32 @@ The integrated, industry-leading humidity and temperature sensor offers high acc
2929

3030
For displaying the air quality as a traffic light (green, yellow, red, magenta). Brightness and color are adjustable via software.
3131

32+
# Home Assistant
33+
34+
Add this config to the `configuration.yaml` of your Home Assistant. Below Version 5.4 please change `OpenCO2` to the IP address.
35+
```
36+
rest:
37+
scan_interval: 60
38+
resource: http://OpenCO2:9925/metrics
39+
method: GET
40+
sensor:
41+
- name: "CO2"
42+
device_class: carbon_dioxide
43+
unique_id: "d611314f-9010-4d0d-aa3b-37c7f350c82f"
44+
value_template: >
45+
{{ value | regex_findall_index("(?:rco2.*})(\d+)") }}
46+
- name: "Temperature"
47+
unique_id: "d611314f-9010-4d0d-aa3b-37c7f350c821"
48+
device_class: temperature
49+
value_template: >
50+
{{ value | regex_findall_index("(?:atmp.*})((?:\d|\.)+)") }}
51+
- name: "Humidity"
52+
unique_id: "d611314f-9010-4d0d-aa3b-37c7f350c822"
53+
device_class: humidity
54+
value_template: >
55+
{{ value | regex_findall_index("(?:rhum.*})((?:\d|\.)+)") }}
56+
```
57+
3258
# 3D-printed housing
3359

3460
Size: 4.7 x 4.1 x 2.4 cm
@@ -51,10 +77,11 @@ Press the Menu button on the backside of the OpenCO2 Sensor. Select an option vi
5177

5278
# Wi-Fi
5379

54-
Enable Wi-Fi via the Menu button. When power is connected, an access point `OpenCO2 Sensor` is enabled. Connect to it and navigate to http://192.168.4.1 (it will open automatically on modern Smartphones). Insert your home Wi-Fi credentials under `Configure WiFi`. Choose your network name from the list in the top and insert the password. Click `Save`. The sensor will now be automatically connected. Navigate to IP:9925 to see current co2/temperature/humidity measurements.
80+
Enable Wi-Fi via the Menu button. When power is connected, an access point `OpenCO2 Sensor` is enabled. Connect to it and navigate to http://192.168.4.1 (it will open automatically on modern Smartphones). Insert your home Wi-Fi credentials under `Configure WiFi`. Choose your network name from the list in the top and insert the password. Click `Save`. The sensor will now be automatically connected. Navigate to [openco2:9925](http://OpenCO2:9925) to see a graph of CO2, Temperature and Humidity measurements.
5581
![alt text](https://github.com/davidkreidler/OpenCO2_Sensor/raw/main/pictures/setup.jpg)
82+
![alt text](https://github.com/davidkreidler/OpenCO2_Sensor/raw/main/pictures/website.png)
5683

57-
# Machine-readable Measurements over WiFi
84+
# Machine-readable Measurements over WiFi
5885
Connect the sensor to your Wi-Fi network and find out the IP of the sensor in your home network via the ui of your router or on the sensor itself via the button under Menu -> Info .
5986
`curl [IP]:9925/metrics`
6087

epd_abstraction.h

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ enum MenuOptions {
1010
HISTORY,
1111
WLAN,
1212
INFO,
13-
RAINBOW,
13+
FUN,
1414
NUM_OPTIONS
1515
};
1616
enum LEDMenuOptions {
@@ -29,6 +29,13 @@ enum DisplayMenuOptions {
2929
EXIT_DISPLAY,
3030
NUM_DISPLAY_OPTIONS
3131
};
32+
enum FUNMenuOptions {
33+
RAINBOW,
34+
SNAKE,
35+
//TICTACTOE,
36+
EXIT_FUN,
37+
NUM_FUN_OPTIONS
38+
};
3239

3340
/* ENGLISH */
3441
const char* EnglishMenuItems[NUM_OPTIONS] = {
@@ -38,7 +45,7 @@ const char* EnglishMenuItems[NUM_OPTIONS] = {
3845
"History",
3946
"Wi-Fi",
4047
"Info",
41-
"Rainbow"//"Santa"
48+
"Fun"
4249
};
4350
const char* EnglishLEDmenuItems[NUM_LED_OPTIONS] = {
4451
"Battery",
@@ -54,6 +61,12 @@ const char* EnglishOptionsMenuItems[NUM_DISPLAY_OPTIONS] = {
5461
"Font",
5562
"Exit"
5663
};
64+
const char* EnglishFUNmenuItems[NUM_FUN_OPTIONS] = {
65+
"Rainbow",
66+
"Snake",
67+
//"TicTacToe",
68+
"Exit"
69+
};
5770

5871
/* GERMAN */
5972
const char* GermanMenuItems[NUM_OPTIONS] = {
@@ -63,7 +76,7 @@ const char* GermanMenuItems[NUM_OPTIONS] = {
6376
"Historie",
6477
"WLAN",
6578
"Info",
66-
"Regenbogen"//"Weihnachten"
79+
"Spass"
6780
};
6881
const char* GermanLEDmenuItems[NUM_LED_OPTIONS] = {
6982
"Batterie",
@@ -79,6 +92,12 @@ const char* GermanOptionsMenuItems[NUM_DISPLAY_OPTIONS] = {
7992
"Schrift",
8093
"Beenden"
8194
};
95+
const char* GermanFUNmenuItems[NUM_FUN_OPTIONS] = {
96+
"Regenbogen",
97+
"Snake",
98+
//"TicTacToe",
99+
"Beenden"
100+
};
82101

83102
typedef struct {
84103
uint8_t humidity : 7; // 7 bits (range 0 to 100)

epd_abstraction.ino

Lines changed: 195 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,8 @@ void handleButtonPress() {
100100
displayinfo();
101101
while (digitalRead(BUTTON) != 0) delay(100); // wait for button press
102102
break;
103-
case RAINBOW:
104-
rainbowMode();
105-
setLED(co2);
106-
refreshes = 1;
103+
case FUN:
104+
FUNMenu();
107105
break;
108106
}
109107
clearMenu();
@@ -260,6 +258,59 @@ void OptionsMenu() {
260258
}
261259
}
262260

261+
void FUNMenu() {
262+
uint8_t selectedOption = 0;
263+
displayFUNMenu(selectedOption);
264+
uint16_t mspressed;
265+
unsigned long menuStartTime = millis();
266+
267+
for (;;) {
268+
if ((millis() - menuStartTime) > 20000) return; // display FUN Menu up to 20 sec
269+
mspressed = 0;
270+
if (digitalRead(BUTTON) == 0) {
271+
while(digitalRead(BUTTON) == 0) { // calculate how long BUTTON is pressed
272+
delay(100);
273+
mspressed += 100;
274+
if (mspressed > 1000) break;
275+
}
276+
if (mspressed > 1000) { // long press
277+
switch (selectedOption) {
278+
case RAINBOW:
279+
rainbowMode();
280+
setLED(co2);
281+
refreshes = 1;
282+
break;
283+
case SNAKE:
284+
displaySnake();
285+
refreshes = 1;
286+
break;
287+
/*case TICTACTOE:
288+
displayTicTacToe();
289+
refreshes = 1;
290+
break;*/
291+
case EXIT_FUN:
292+
return;
293+
}
294+
setLED(co2);
295+
displayFUNMenu(selectedOption);
296+
menuStartTime = millis(); // display FUN Menu again for 20 sec
297+
} else { // goto next Menu point
298+
buttonPressedAgain = true; // display at least once
299+
while (buttonPressedAgain) {
300+
buttonPressedAgain = false;
301+
selectedOption++;
302+
selectedOption %= NUM_FUN_OPTIONS;
303+
attachInterrupt(digitalPinToInterrupt(BUTTON), buttonInterrupt, FALLING);
304+
displayFUNMenu(selectedOption);
305+
detachInterrupt(digitalPinToInterrupt(BUTTON));
306+
menuStartTime = millis(); // display FUN Menu again for 20 sec
307+
if (digitalRead(BUTTON) == 0) break; // long press detected
308+
}
309+
}
310+
}
311+
}
312+
}
313+
263314
void displayWelcome() {
264315
EEPROM.begin(2); // EEPROM_SIZE
265316
EEPROM.write(0, 1);
@@ -754,6 +805,22 @@ void displayOptionsMenu(uint8_t selectedOption) {
754805
updateDisplay();
755806
}
756807

808+
void displayFUNMenu(uint8_t selectedOption) {
809+
const char* FUNmenuItem;
810+
Paint_Clear(WHITE);
811+
Paint_DrawString_EN(75, 0, "FUN", &Font24, WHITE, BLACK);
812+
Paint_DrawLine(10, 23, 190, 23, BLACK, DOT_PIXEL_1X1, LINE_STYLE_SOLID);
813+
814+
for (int i=0; i<NUM_FUN_OPTIONS; i++) {
815+
if (english) FUNmenuItem = EnglishFUNmenuItems[i];
816+
else FUNmenuItem = GermanFUNmenuItems[i];
817+
Paint_DrawString_EN(5, 25*(i+1), FUNmenuItem, &Font24, WHITE, BLACK);
818+
}
819+
820+
invertSelected(selectedOption);
821+
updateDisplay();
822+
}
823+
757824
void invertSelected(uint8_t selectedOption) {
758825
#ifdef EINK_1IN54V2
759826
for (int x = 0; x < 200; x++) {
@@ -773,6 +840,129 @@ void invertSelected(uint8_t selectedOption) {
773840
#endif
774841
}
775842

843+
void updateSnakeDisplay(int snakeHeadX, int snakeHeadY, int snakeTailX, int snakeTailY) {
844+
Paint_DrawRectangle(snakeTailX*25, snakeTailY*25, snakeTailX*25+20, snakeTailY*25+20, WHITE, DOT_PIXEL_2X2, DRAW_FILL_FULL); // delete tail
845+
Paint_DrawRectangle(snakeHeadX*25+1, snakeHeadY*25+1, snakeHeadX*25+19, snakeHeadY*25+19, BLACK, DOT_PIXEL_2X2, DRAW_FILL_EMPTY); // add head
846+
EPD_1IN54_V2_DisplayPart(BlackImage);
847+
}
848+
849+
enum direction {
850+
DOWN,
851+
LEFT,
852+
UP,
853+
RIGHT,
854+
DIRECTION_COUNT
855+
};
856+
struct position {
857+
int x;
858+
int y;
859+
};
860+
struct position Snake[64];
861+
struct position Apple;
862+
int snakeLength = 1;
863+
864+
void newApplePosition() {
865+
Apple.x = rand() % (8);
866+
Apple.y = rand() % (8);
867+
for (int i=0; i<snakeLength; i++) {
868+
if (Snake[i].x == Apple.x && Snake[i].y == Apple.y) {
869+
newApplePosition();
870+
return;
871+
}
872+
}
873+
Paint_DrawLine(Apple.x*25+2, Apple.y*25+2, Apple.x*25+18, Apple.y*25+18, BLACK, DOT_PIXEL_2X2, LINE_STYLE_SOLID);
874+
Paint_DrawLine(Apple.x*25+18, Apple.y*25+2, Apple.x*25+2, Apple.y*25+18, BLACK, DOT_PIXEL_2X2, LINE_STYLE_SOLID);
875+
}
876+
877+
void displaySnake() {
878+
Paint_Clear(WHITE);
879+
if (comingFromDeepSleep && HWSubRev > 1) {
880+
EPD_1IN54_V2_Init_Partial_After_Powerdown();
881+
EPD_1IN54_V2_writePrevImage(BlackImage);
882+
} else {
883+
EPD_1IN54_V2_Init_Partial();
884+
}
885+
886+
Snake[0].x = 3;
887+
Snake[0].y = 2;
888+
Snake[1].x = 3;
889+
Snake[1].y = 3;
890+
enum direction snakeDirection = DOWN;
891+
newApplePosition();
892+
893+
attachInterrupt(digitalPinToInterrupt(BUTTON), buttonInterrupt, FALLING);
894+
while (true) {
895+
updateSnakeDisplay(Snake[snakeLength].x, Snake[snakeLength].y, Snake[0].x, Snake[0].y);
896+
897+
if (buttonPressedAgain) {
898+
snakeDirection = (enum direction)((snakeDirection + 1) % DIRECTION_COUNT);
899+
buttonPressedAgain = false;
900+
}
901+
902+
if ( Snake[snakeLength].x == Apple.x && Snake[snakeLength].y == Apple.y) {
903+
snakeLength++;
904+
Snake[snakeLength].x = Snake[snakeLength-1].x;
905+
Snake[snakeLength].y = Snake[snakeLength-1].y;
906+
newApplePosition();
907+
}
908+
909+
for(int i=0; i<snakeLength-1; i++) {
910+
if (Snake[snakeLength].x == Snake[i].x
911+
&& Snake[snakeLength].y == Snake[i].y ) {
912+
gameOver();
913+
return;
914+
}
915+
}
916+
917+
for(int i=0; i<snakeLength; i++) {
918+
Snake[i].x = Snake[i+1].x;
919+
Snake[i].y = Snake[i+1].y;
920+
}
921+
922+
switch (snakeDirection) {
923+
case DOWN:
924+
Snake[snakeLength].y = Snake[snakeLength].y+1;
925+
break;
926+
case LEFT:
927+
Snake[snakeLength].x = Snake[snakeLength].x-1;
928+
break;
929+
case UP:
930+
Snake[snakeLength].y = Snake[snakeLength].y-1;
931+
break;
932+
case RIGHT:
933+
Snake[snakeLength].x = Snake[snakeLength].x+1;
934+
break;
935+
case DIRECTION_COUNT:
936+
break;
937+
}
938+
939+
if (Snake[snakeLength].x > 7
940+
|| Snake[snakeLength].x < 0
941+
|| Snake[snakeLength].y > 7
942+
|| Snake[snakeLength].y < 0 ) {
943+
gameOver();
944+
return;
945+
}
946+
}
947+
}
948+
949+
void gameOver() {
950+
Paint_DrawString_EN(15, 88, "GAME OVER!", &Font24, WHITE, BLACK);
951+
int x = 100-23;
952+
if (snakeLength>9) x-=23;
953+
if (snakeLength>99) x-=23;
954+
Paint_DrawNum(x, 200-70, snakeLength-1, &big, BLACK, WHITE);
955+
EPD_1IN54_V2_DisplayPart(BlackImage);
956+
detachInterrupt(digitalPinToInterrupt(BUTTON));
957+
snakeLength = 1;
958+
delay(1000);
959+
while (digitalRead(BUTTON) != 0) delay(100); // wait for button press
960+
}
961+
962+
void displayTicTacToe() {
963+
return;
964+
}
965+
776966
void displayFlappyBird() {
777967
int birdpos = 100;
778968
#define numObsticals 5
@@ -861,8 +1051,7 @@ void displayWiFi(bool useWiFi) {
8611051
esp_qrcode_config_t cfg = ESP_QRCODE_CONFIG();
8621052
esp_qrcode_generate(&cfg, buffer);
8631053

864-
Paint_DrawString_EN(1, 1, ip.c_str(), &Font16, BLACK, WHITE);
865-
Paint_DrawString_EN(1, 184, ":9925", &Font16, BLACK, WHITE);
1054+
Paint_DrawString_EN(16, 200-21, "openco2:9925", &Font20, BLACK, WHITE);
8661055
} else {
8671056
char const *buffer = "WIFI:T:;S:OpenCO2 Sensor;P:;;";
8681057
esp_qrcode_config_t cfg = ESP_QRCODE_CONFIG();

pictures/website.png

99.6 KB
Loading

0 commit comments

Comments
 (0)