├── img ├── 01.jpg ├── 03.jpg ├── 04.jpg ├── 05.jpg └── 03 copy.jpg ├── HW ├── ESP-VINDRIKTNING_V1.pdf └── ESP-VINDRIKTNING_v2_0.pdf ├── Production ├── ESP-VINDRIKTNING_v2_0_2022-04-13.zip ├── ESP-VINDRIKTNING_v2_0_bottom_bom.csv ├── ESP-VINDRIKTNING_v2_0_bottom_cpl.csv ├── ESP-VINDRIKTNING_v2_0_top_bom.csv └── ESP-VINDRIKTNING_v2_0_top_cpl.csv ├── version.md ├── SW ├── ESP-Vindriktning_OLED │ ├── pm1006.h │ ├── pm1006.cpp │ └── ESP-Vindriktning_OLED.ino ├── VINDRIKTNING_plus_Scd4x │ ├── pm1006.h │ ├── pm1006.cpp │ └── VINDRIKTNING_plus_Scd4x.ino ├── ESP-Vindriktning_UART_TMEPcz │ ├── pm1006.h │ ├── pm1006.cpp │ ├── ESP-Vindriktning_UART_TMEPcz_SHT40.ino │ └── ESP-Vindriktning_UART_TMEPcz_SCD41.ino ├── ESPHome.yaml └── VINDRIKTNING │ └── VINDRIKTNING.ino ├── .gitignore ├── README.md ├── README_CZ.md └── LICENSE.txt /img/01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiTricker/ESP-Vindriktning/main/img/01.jpg -------------------------------------------------------------------------------- /img/03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiTricker/ESP-Vindriktning/main/img/03.jpg -------------------------------------------------------------------------------- /img/04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiTricker/ESP-Vindriktning/main/img/04.jpg -------------------------------------------------------------------------------- /img/05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiTricker/ESP-Vindriktning/main/img/05.jpg -------------------------------------------------------------------------------- /img/03 copy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiTricker/ESP-Vindriktning/main/img/03 copy.jpg -------------------------------------------------------------------------------- /HW/ESP-VINDRIKTNING_V1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiTricker/ESP-Vindriktning/main/HW/ESP-VINDRIKTNING_V1.pdf -------------------------------------------------------------------------------- /HW/ESP-VINDRIKTNING_v2_0.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiTricker/ESP-Vindriktning/main/HW/ESP-VINDRIKTNING_v2_0.pdf -------------------------------------------------------------------------------- /Production/ESP-VINDRIKTNING_v2_0_2022-04-13.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiTricker/ESP-Vindriktning/main/Production/ESP-VINDRIKTNING_v2_0_2022-04-13.zip -------------------------------------------------------------------------------- /Production/ESP-VINDRIKTNING_v2_0_bottom_bom.csv: -------------------------------------------------------------------------------- 1 | Comment,Designator,Footprint,LCSC Part # 2 | 47k,R6,R0402, 3 | 100n,C8 C11 C12 C13,C0402, 4 | PT26-21B/TR8,PT1,PT26-21B/TR8,C131248 5 | WS2812B,D1 D2 D4,WS2812B_2020,C965555 6 | -------------------------------------------------------------------------------- /version.md: -------------------------------------------------------------------------------- 1 | ### Version 2.1 2 | - Phototransistor pin changed from GPIO4 to GPIO33 (ADC1_CHANNEL_5) - cause ADC2 (GPIO4) is not available when using WiFi | Změna pinu fototranzistoru z GPIO4 na GPIO33 (ADC1_CHANNEL_5) - ADC2 (GPIO4) není dostupné při používání WiFi 3 | #### Version 2.0 4 | - Logic level converter for LEDs added | Přidán převodník logické úrovně pro LED -------------------------------------------------------------------------------- /Production/ESP-VINDRIKTNING_v2_0_bottom_cpl.csv: -------------------------------------------------------------------------------- 1 | Designator,Mid X,Mid Y,Layer,Rotation 2 | C8, 2.72,33.02,Bottom,270.0 3 | C11, 2.54,48.46,Bottom,270.0 4 | C12, 2.69,63.98,Bottom,270.0 5 | C13, 0.00,53.34,Bottom,0.0 6 | D1, 0.00,33.00,Bottom,180.0 7 | D2, 0.00,48.50,Bottom,180.0 8 | D4, 0.00,64.01,Bottom,180.0 9 | PT1, 0.00,56.25,Bottom,180.0 10 | R6, 0.00,54.46,Bottom,180.0 11 | -------------------------------------------------------------------------------- /Production/ESP-VINDRIKTNING_v2_0_top_bom.csv: -------------------------------------------------------------------------------- 1 | Comment,Designator,Footprint,LCSC Part # 2 | 1.25-2P,FAN,1.25-2P,C393945 3 | 1N5819WS,D3,SOD323-R,C191023 4 | 1uF,C3,C0603, 5 | 4 7uF,C4,C0603,C19666 6 | 5.1k,R4 R5,R0402,C25905 7 | 10k,R1 R2 R7 R8 R9 R14,R0402, 8 | 10μF,C17,C0603, 9 | 12MHz,Q6,3.2X2.5_KX-7,C97242 10 | 20p,C1 C2,C0402, 11 | 47uF,C7,C0603, 12 | 100k,R3,R0402, 13 | 100n,C9 C10 C15,C0603, 14 | 100nF,C5,C0402, 15 | 100nF,C6,C0603, 16 | 100uF,C14,C1206,C15008 17 | 470,R18 R19,R0402, 18 | A1501WV-S-4P,SENSOR,A1501WV-S-4P,C225162 19 | BSS138,Q2 Q3,SOT23-3,C78284 20 | CH340C,IC3,SOIC16, 21 | ESP32-WROOM-32D,ESP32,ESP32WROOM32D,C82899 22 | HT7833,LDO,SOT23-5, 23 | JST-SH-4_VERT,J2,1X04_1MM_RA_VERT,C371588 24 | JST-SH-6_VERT,J3 J4,1X06_1MM_RA_VERT,C145963 25 | MMDT3904,Q1,SC70-6,C268586 26 | PI4ULS5V202UEX,IC1,MSOP8,C481697 27 | TS-1088R-02026,RESET,TS1088R02026, 28 | 918-418K2022Y40000,J1,918-418K2022Y40000,C168704 29 | -------------------------------------------------------------------------------- /Production/ESP-VINDRIKTNING_v2_0_top_cpl.csv: -------------------------------------------------------------------------------- 1 | Designator,Mid X,Mid Y,Layer,Rotation 2 | C1, 2.67, 5.82,Top,180.0 3 | C2, 3.86,10.62,Top,180.0 4 | C3, 7.52,18.34,Top,0.0 5 | C4, 7.37,26.06,Top,0.0 6 | C5,-11.35,46.41,Top,180.0 7 | C6,-13.26,44.04,Top,0.0 8 | C7,-13.26,41.99,Top,0.0 9 | C9, 3.84,56.59,Top,90.0 10 | C10, 3.86,65.30,Top,270.0 11 | C14, 5.18,14.78,Top,270.0 12 | C15,-2.97,10.16,Top,180.0 13 | C17,-4.75,20.09,Top,0.0 14 | D3, 7.01,53.92,Top,270.0 15 | ESP32,-10.68,57.28,Top,0.0 16 | FAN, 9.39,53.94,Top,270.0 17 | IC1, 5.11,61.06,Top,0.0 18 | IC3,-1.24,15.14,Top,0.0 19 | J2,-0.03,69.82,Top,180.0 20 | J3,15.34,65.96,Top,90.0 21 | J4,15.34,53.94,Top,90.0 22 | LDO, 7.21,22.66,Top,90.0 23 | Q1, 0.56,21.79,Top,90.0 24 | Q2, 1.93,41.78,Top,90.0 25 | Q3,-4.52,41.10,Top,90.0 26 | Q6, 2.64, 8.20,Top,180.0 27 | R1,-13.89,46.41,Top,0.0 28 | R2, 2.90,44.81,Top,90.0 29 | R3, 1.85,44.81,Top,90.0 30 | R4,-2.77, 6.81,Top,270.0 31 | R5,-1.32, 6.83,Top,270.0 32 | R7,-4.98,44.04,Top,0.0 33 | R8, 1.42,35.76,Top,0.0 34 | R9, 2.64,21.79,Top,270.0 35 | R14,-1.50,21.79,Top,90.0 36 | R18,-7.87,24.38,Top,270.0 37 | R19,-6.83,24.38,Top,270.0 38 | RESET, 8.28,11.84,Top,90.0 39 | SENSOR, 9.50,68.96,Top,0.0 40 | J1, 0.00, 1.60,Top,0.0 41 | -------------------------------------------------------------------------------- /SW/ESP-Vindriktning_OLED/pm1006.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | typedef enum { 7 | PM1006_HEADER, 8 | PM1006_LENGTH, 9 | PM1006_DATA, 10 | PM1006_CHECK 11 | } pm1006_state_t; 12 | 13 | class PM1006 { 14 | 15 | private: 16 | Stream *_serial; 17 | bool _debug; 18 | 19 | pm1006_state_t _state; 20 | size_t _rxlen; 21 | size_t _index; 22 | uint8_t _txbuf[16]; 23 | uint8_t _rxbuf[20]; 24 | uint8_t _checksum; 25 | 26 | bool send_command(size_t cmd_len, const uint8_t *cmd_data); 27 | int build_tx(size_t cmd_len, const uint8_t *cmd_data); 28 | bool process_rx(uint8_t c); 29 | 30 | public: 31 | static const int BIT_RATE = 9600; 32 | 33 | /** 34 | * Constructor. 35 | * 36 | * @param serial the serial port, NOTE: the serial port has to be configured for a bit rate of PM1006::BIT_RATE ! 37 | */ 38 | explicit PM1006(Stream *serial, bool debug = false); 39 | 40 | /** 41 | * Reads the PM2.5 value (plus some other yet unknown parameters) 42 | * 43 | * @param pm the PM2.5 value 44 | * @return true if the value was read successfully 45 | */ 46 | bool read_pm25(uint16_t *pm); 47 | 48 | }; 49 | 50 | -------------------------------------------------------------------------------- /SW/VINDRIKTNING_plus_Scd4x/pm1006.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | typedef enum { 7 | PM1006_HEADER, 8 | PM1006_LENGTH, 9 | PM1006_DATA, 10 | PM1006_CHECK 11 | } pm1006_state_t; 12 | 13 | class PM1006 { 14 | 15 | private: 16 | Stream *_serial; 17 | bool _debug; 18 | 19 | pm1006_state_t _state; 20 | size_t _rxlen; 21 | size_t _index; 22 | uint8_t _txbuf[16]; 23 | uint8_t _rxbuf[20]; 24 | uint8_t _checksum; 25 | 26 | bool send_command(size_t cmd_len, const uint8_t *cmd_data); 27 | int build_tx(size_t cmd_len, const uint8_t *cmd_data); 28 | bool process_rx(uint8_t c); 29 | 30 | public: 31 | static const int BIT_RATE = 9600; 32 | 33 | /** 34 | * Constructor. 35 | * 36 | * @param serial the serial port, NOTE: the serial port has to be configured for a bit rate of PM1006::BIT_RATE ! 37 | */ 38 | explicit PM1006(Stream *serial, bool debug = false); 39 | 40 | /** 41 | * Reads the PM2.5 value (plus some other yet unknown parameters) 42 | * 43 | * @param pm the PM2.5 value 44 | * @return true if the value was read successfully 45 | */ 46 | bool read_pm25(uint16_t *pm); 47 | 48 | }; 49 | 50 | -------------------------------------------------------------------------------- /SW/ESP-Vindriktning_UART_TMEPcz/pm1006.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | typedef enum { 7 | PM1006_HEADER, 8 | PM1006_LENGTH, 9 | PM1006_DATA, 10 | PM1006_CHECK 11 | } pm1006_state_t; 12 | 13 | class PM1006 { 14 | 15 | private: 16 | Stream *_serial; 17 | bool _debug; 18 | 19 | pm1006_state_t _state; 20 | size_t _rxlen; 21 | size_t _index; 22 | uint8_t _txbuf[16]; 23 | uint8_t _rxbuf[20]; 24 | uint8_t _checksum; 25 | 26 | bool send_command(size_t cmd_len, const uint8_t *cmd_data); 27 | int build_tx(size_t cmd_len, const uint8_t *cmd_data); 28 | bool process_rx(uint8_t c); 29 | 30 | public: 31 | static const int BIT_RATE = 9600; 32 | 33 | /** 34 | * Constructor. 35 | * 36 | * @param serial the serial port, NOTE: the serial port has to be configured for a bit rate of PM1006::BIT_RATE ! 37 | */ 38 | explicit PM1006(Stream *serial, bool debug = false); 39 | 40 | /** 41 | * Reads the PM2.5 value (plus some other yet unknown parameters) 42 | * 43 | * @param pm the PM2.5 value 44 | * @return true if the value was read successfully 45 | */ 46 | bool read_pm25(uint16_t *pm); 47 | 48 | }; 49 | 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## LaskaKit Useful stuff 3 | ################# 4 | 5 | ## AVR Development 6 | *.eep 7 | *.elf 8 | *.lst 9 | *.lss 10 | *.sym 11 | *.d 12 | *.o 13 | *.srec 14 | *.map 15 | 16 | ## Notepad++ backup files 17 | *.bak 18 | 19 | ############# 20 | ## Eagle 21 | ############# 22 | 23 | *.epf 24 | *.brd 25 | *.sch 26 | 27 | # Ignore the board and schematic backup files 28 | *.b#? 29 | *.s#? 30 | *.l#? 31 | 32 | 33 | ################# 34 | ## Visual Studio 35 | ################# 36 | 37 | ## Ignore Visual Studio temporary files, build results, and 38 | ## files generated by popular Visual Studio add-ons. 39 | 40 | # User-specific files 41 | *.suo 42 | *.user 43 | *.sln.docstates 44 | 45 | # Build results 46 | [Dd]ebug/ 47 | [Rr]elease/ 48 | *_i.c 49 | *_p.c 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.vspscc 64 | .builds 65 | *.dotCover 66 | 67 | .pio 68 | .vscode/.browse.c_cpp.db* 69 | .vscode/c_cpp_properties.json 70 | .vscode/launch.json 71 | .vscode/ipch 72 | 73 | ############ 74 | ## Windows 75 | ############ 76 | 77 | # Windows image file caches 78 | Thumbs.db 79 | 80 | # Folder config file 81 | Desktop.ini 82 | 83 | 84 | ############# 85 | ## Mac OS 86 | ############# 87 | 88 | .DS_Store 89 | 90 | 91 | ############# 92 | ## Linux 93 | ############# 94 | 95 | # backup files (*.bak on Win) 96 | *~ 97 | 98 | 99 | ############# 100 | ## Python 101 | ############# 102 | 103 | *.py[co] 104 | 105 | # Packages 106 | *.egg 107 | *.egg-info 108 | dist 109 | build 110 | eggs 111 | parts 112 | bin 113 | var 114 | sdist 115 | develop-eggs 116 | .installed.cfg 117 | 118 | # Installer logs 119 | pip-log.txt 120 | 121 | # Unit test / coverage reports 122 | .coverage 123 | .tox 124 | 125 | #Translations 126 | *.mo 127 | 128 | #Mr Developer 129 | .mr.developer.cfg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![LASKAKIT ESP-VINDRIKTNING](https://github.com/LaskaKit/ESP-Vindriktning/blob/main/img/01.jpg) 2 | # LASKAKIT ESP-VINDRIKTNING 3 | 4 | Ikea Vindriktning is very good and really cheap product which is able to show the quality of air in the room by three LEDs. The original Vindriktning just measure the dust. 5 | It is not so much for customer. 6 | 7 | We decided to create our own replaced board what will be compatible with the original board but add with Wi-Fi and Bluetooth connectivity, optionaly more sensors, and addressable LEDs. Thanks to this, you get powerful product what may measure the quality of air in the details. 8 | 9 | ## Programming 10 | The LASKAKIT ESP-VINDRIKTNING as well as Ikea Vindriktning includes USB-C connector. The orginal board is using the USB-C just for power supply, the ESP-VINDRIKTNING is using USB-C for power and also for programming, because the board includes built-in USB-UART IC as programmer. 11 | 12 | ## Connectors 13 | The LASKAKIT ESP-VINDRIKTNING contains six connectors. Two of them are used for original dust sensor - sensor and fan. The third connector is called uŠup and it is compatible with STEMMA QT and Qwiic JST-SH. Thanks to this connector, you can add [temperature/humidity sensor SHT40](https://www.laskakit.cz/laskakit-sht40-senzor-teploty-a-vlhkosti-vzduchu/) or [SCD41 (sensor of CO2/temperature/humidity)](https://www.laskakit.cz/laskakit-scd41-senzor-co2--teploty-a-vlhkosti-vzduchu/) and improve the measurement of air. 14 | Fourth connector is just 4pin header and you can solder what you want. The 4pin connector includes I2C and power supply with 3.3V. 15 | The reset of the connectors contains power supply (3.3V) and GPIO which you can use for your purpose. Also the hardware SPI may be used from this connector. 16 | 17 | ## LEDs 18 | The number of LEDs is the same with original board. The LEDs are RGB and addressable, it means you can define the color on separately each LED. At the end, you can indicate dust on the first LED, CO2 on the second LED and temperature on third LED. 19 | 20 | ## Example codes 21 | For the fastest development, we prepared a few examples where we show how to easily use LASKAKIT ESP-VINDRIKTNING alone or with SCD41 22 | 23 | ## Link 24 | The product is available on our store https://www.laskakit.cz/laskakit-esp-vindriktning-esp-32-i2c/ 25 | Optional parts: 26 | 27 | SCD41 (CO2/temperature/humidity) https://www.laskakit.cz/laskakit-scd41-senzor-co2--teploty-a-vlhkosti-vzduchu/ 28 | 29 | SHT40 (temperature/humidity) https://www.laskakit.cz/laskakit-sht40-senzor-teploty-a-vlhkosti-vzduchu/ 30 | -------------------------------------------------------------------------------- /README_CZ.md: -------------------------------------------------------------------------------- 1 | ![LASKAKIT ESP-VINDRIKTNING](https://github.com/LaskaKit/ESP-Vindriktning/blob/main/img/01.jpg) 2 | # LASKAKIT ESP-VINDRIKTNING 3 | 4 | Ikea Vindriktning je zajímavý a levný produkt firmy Ikea, který dokáže měřit kvalitu vzduchu - prašnost ve vzduchu. A to je všechno. 5 | 6 | Rozhodli jsme se vytvořit vlastní desku kterou jenom vyměníš - stačí odšroubovat a přišroubovat 7 šroubků. 7 | Naše deska LASKAKIT ESP-VINDRIKTNING má konektory na stejném místě jako originální deska, ale navíc obsahuje výkonný a populární čip ESP32, který kromě komunikace s čidlem prachu 8 | disponuje i možností připojení přes Wi-Fi a Bluetooth. Volitelně můžeš dokoupit i další čidla - SHT40 pro měření teploty a vlhkost nebo SCD41 pro měření CO2/teploty/vlhkosti. 9 | 10 | ## Programování 11 | LASKAKIT ESP-VINDRIKTNING stejně jako originální Ikea Vindriktning obsahuje USB-C konektor. U originální desky slouží pouze k napájení, u naší desky je kromě napájení možné ho použít i pro programování. 12 | Stačí jí tedy připojit do USB konektoru a otevřít třeba Arduino IDE. 13 | 14 | ## Konektory 15 | LASKAKIT ESP-VINDRIKTNING obsahuje celkem šest konektorů. První dva slouží k připojení originální čidla - čidla prašnosti (čidlo a ventilátor). Třetí [konektor je uŠup](https://blog.laskarduino.cz/predstavujeme-univerzalni-konektor-pro-propojeni-modulu-a-cidel-%ce%bcsup/), 16 | který je kompatibilní s STEMMA QT a Qwiic JST-SH 17 | Díky tomuto konektoru můžeš přidat čidlo [tepoty/vlhkosti SHT40](https://www.laskakit.cz/laskakit-sht40-senzor-teploty-a-vlhkosti-vzduchu/) nebo [SCD41 (čidlo CO2/teploty/vlhkosti)](https://www.laskakit.cz/laskakit-scd41-senzor-co2--teploty-a-vlhkosti-vzduchu/) 18 | a vytvořit tak komplexní měření kvality vzduchu. 19 | Čtvrtým konektorem je prostý 4pinový hřebínek s I2C a napájením (3.3V) 20 | Zbylé dva konektory obsahují GPIO a 3.3V napájení. GPIO je možné využít s hardwarovým SPI k připojení například displeje. 21 | 22 | ## LEDky 23 | Počet LEDek je stejný jako u původní desky. LEDky jsou ale RGB a adresovatelné, to znamená, že každá LEDka dokáže měnit barvu dle dat poslaných přes jednovodičovou sběrnici, 24 | takže první LED může indikovat koncentraci CO2, druhá teplotu, třetí prašnost. 25 | 26 | ## Vzorové kódy 27 | Pro rychlejší start s LASKAKIT ESP-VINDRIKTNING jsme napsali několik vzorových kódů, které můžeš jednoduše upravit pro vlastní potřeby. 28 | Zároveň jsme rozsáhlý článek i na náš blog - https://blog.laskarduino.cz/senzor-prachovych-castic-ikea-vindriktning-vylepseny-o-cidlo-co2-teploty-vlhkosti-bluetooth-wi-fi-komunikaci-a-s-vizualizaci-dat-na-tmep-cz/ 29 | 30 | ## Odkaz 31 | Deska je dostupná na našem e-shopu https://www.laskakit.cz/laskakit-esp-vindriktning-esp-32-i2c/ 32 | Další volitelné části: 33 | 34 | SCD41 (CO2/teplota/vlhkost) https://www.laskakit.cz/laskakit-scd41-senzor-co2--teploty-a-vlhkosti-vzduchu/ 35 | 36 | SHT40 (teplota/vlhkost) https://www.laskakit.cz/laskakit-sht40-senzor-teploty-a-vlhkosti-vzduchu/ 37 | -------------------------------------------------------------------------------- /SW/ESP-Vindriktning_OLED/pm1006.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "pm1006.h" 8 | 9 | #define DEFAULT_TIMEOUT 1000 10 | 11 | PM1006::PM1006(Stream * serial, bool debug) 12 | { 13 | _serial = serial; 14 | _debug = debug; 15 | 16 | _state = PM1006_HEADER; 17 | _rxlen = 0; 18 | _index = 0; 19 | memset(_rxbuf, 0, sizeof(_rxbuf)); 20 | memset(_txbuf, 0, sizeof(_txbuf)); 21 | _checksum = 0; 22 | } 23 | 24 | bool PM1006::read_pm25(uint16_t *pm) 25 | { 26 | uint8_t cmd[] = {0x0B, 0x01}; 27 | if (send_command(2, cmd) && (_rxlen > 4) && (_rxbuf[0] == cmd[0])) { 28 | *pm = (_rxbuf[3] << 8) + _rxbuf[4]; 29 | return true; 30 | } 31 | return false; 32 | } 33 | 34 | // sends a command and waits for response, returns length of response 35 | bool PM1006::send_command(size_t cmd_len, const uint8_t *cmd_data) 36 | { 37 | // build and send command 38 | int txlen = build_tx(cmd_len, cmd_data); 39 | _serial->write(_txbuf, txlen); 40 | 41 | // wait for response 42 | unsigned long start = millis(); 43 | while ((millis() - start) < DEFAULT_TIMEOUT) { 44 | while (_serial->available()) { 45 | char c = _serial->read(); 46 | if (process_rx(c)) { 47 | return true; 48 | } 49 | } 50 | yield(); 51 | } 52 | 53 | // timeout 54 | return false; 55 | } 56 | 57 | // builds a tx buffer, returns length of tx data 58 | int PM1006::build_tx(size_t cmd_len, const uint8_t *cmd_data) 59 | { 60 | int len = 0; 61 | _txbuf[len++] = 0x11; 62 | _txbuf[len++] = cmd_len; 63 | for (size_t i = 0; i < cmd_len; i++) { 64 | _txbuf[len++] = cmd_data[i]; 65 | } 66 | uint8_t sum = 0; 67 | for (int i = 0; i < len; i++) { 68 | sum += _txbuf[i]; 69 | } 70 | _txbuf[len++] = (256 - sum) & 0xFF; 71 | return len; 72 | } 73 | 74 | // processes one rx character, returns true if a valid frame was found 75 | bool PM1006::process_rx(uint8_t c) 76 | { 77 | switch (_state) { 78 | case PM1006_HEADER: 79 | _checksum = c; 80 | if (c == 0x16) { 81 | _state = PM1006_LENGTH; 82 | } 83 | break; 84 | 85 | case PM1006_LENGTH: 86 | _checksum += c; 87 | if (c <= sizeof(_rxbuf)) { 88 | _rxlen = c; 89 | _index = 0; 90 | _state = (_rxlen > 0) ? PM1006_DATA : PM1006_CHECK; 91 | } else { 92 | _state = PM1006_HEADER; 93 | } 94 | break; 95 | 96 | case PM1006_DATA: 97 | _checksum += c; 98 | _rxbuf[_index++] = c; 99 | if (_index == _rxlen) { 100 | _state = PM1006_CHECK; 101 | } 102 | break; 103 | 104 | case PM1006_CHECK: 105 | _checksum += c; 106 | _state = PM1006_HEADER; 107 | return (_checksum == 0); 108 | 109 | default: 110 | _state = PM1006_HEADER; 111 | break; 112 | } 113 | return false; 114 | } 115 | 116 | -------------------------------------------------------------------------------- /SW/VINDRIKTNING_plus_Scd4x/pm1006.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "pm1006.h" 8 | 9 | #define DEFAULT_TIMEOUT 1000 10 | 11 | PM1006::PM1006(Stream * serial, bool debug) 12 | { 13 | _serial = serial; 14 | _debug = debug; 15 | 16 | _state = PM1006_HEADER; 17 | _rxlen = 0; 18 | _index = 0; 19 | memset(_rxbuf, 0, sizeof(_rxbuf)); 20 | memset(_txbuf, 0, sizeof(_txbuf)); 21 | _checksum = 0; 22 | } 23 | 24 | bool PM1006::read_pm25(uint16_t *pm) 25 | { 26 | uint8_t cmd[] = {0x0B, 0x01}; 27 | if (send_command(2, cmd) && (_rxlen > 4) && (_rxbuf[0] == cmd[0])) { 28 | *pm = (_rxbuf[3] << 8) + _rxbuf[4]; 29 | return true; 30 | } 31 | return false; 32 | } 33 | 34 | // sends a command and waits for response, returns length of response 35 | bool PM1006::send_command(size_t cmd_len, const uint8_t *cmd_data) 36 | { 37 | // build and send command 38 | int txlen = build_tx(cmd_len, cmd_data); 39 | _serial->write(_txbuf, txlen); 40 | 41 | // wait for response 42 | unsigned long start = millis(); 43 | while ((millis() - start) < DEFAULT_TIMEOUT) { 44 | while (_serial->available()) { 45 | char c = _serial->read(); 46 | if (process_rx(c)) { 47 | return true; 48 | } 49 | } 50 | yield(); 51 | } 52 | 53 | // timeout 54 | return false; 55 | } 56 | 57 | // builds a tx buffer, returns length of tx data 58 | int PM1006::build_tx(size_t cmd_len, const uint8_t *cmd_data) 59 | { 60 | int len = 0; 61 | _txbuf[len++] = 0x11; 62 | _txbuf[len++] = cmd_len; 63 | for (size_t i = 0; i < cmd_len; i++) { 64 | _txbuf[len++] = cmd_data[i]; 65 | } 66 | uint8_t sum = 0; 67 | for (int i = 0; i < len; i++) { 68 | sum += _txbuf[i]; 69 | } 70 | _txbuf[len++] = (256 - sum) & 0xFF; 71 | return len; 72 | } 73 | 74 | // processes one rx character, returns true if a valid frame was found 75 | bool PM1006::process_rx(uint8_t c) 76 | { 77 | switch (_state) { 78 | case PM1006_HEADER: 79 | _checksum = c; 80 | if (c == 0x16) { 81 | _state = PM1006_LENGTH; 82 | } 83 | break; 84 | 85 | case PM1006_LENGTH: 86 | _checksum += c; 87 | if (c <= sizeof(_rxbuf)) { 88 | _rxlen = c; 89 | _index = 0; 90 | _state = (_rxlen > 0) ? PM1006_DATA : PM1006_CHECK; 91 | } else { 92 | _state = PM1006_HEADER; 93 | } 94 | break; 95 | 96 | case PM1006_DATA: 97 | _checksum += c; 98 | _rxbuf[_index++] = c; 99 | if (_index == _rxlen) { 100 | _state = PM1006_CHECK; 101 | } 102 | break; 103 | 104 | case PM1006_CHECK: 105 | _checksum += c; 106 | _state = PM1006_HEADER; 107 | return (_checksum == 0); 108 | 109 | default: 110 | _state = PM1006_HEADER; 111 | break; 112 | } 113 | return false; 114 | } 115 | 116 | -------------------------------------------------------------------------------- /SW/ESP-Vindriktning_UART_TMEPcz/pm1006.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "pm1006.h" 8 | 9 | #define DEFAULT_TIMEOUT 1000 10 | 11 | PM1006::PM1006(Stream * serial, bool debug) 12 | { 13 | _serial = serial; 14 | _debug = debug; 15 | 16 | _state = PM1006_HEADER; 17 | _rxlen = 0; 18 | _index = 0; 19 | memset(_rxbuf, 0, sizeof(_rxbuf)); 20 | memset(_txbuf, 0, sizeof(_txbuf)); 21 | _checksum = 0; 22 | } 23 | 24 | bool PM1006::read_pm25(uint16_t *pm) 25 | { 26 | uint8_t cmd[] = {0x0B, 0x01}; 27 | if (send_command(2, cmd) && (_rxlen > 4) && (_rxbuf[0] == cmd[0])) { 28 | *pm = (_rxbuf[3] << 8) + _rxbuf[4]; 29 | return true; 30 | } 31 | return false; 32 | } 33 | 34 | // sends a command and waits for response, returns length of response 35 | bool PM1006::send_command(size_t cmd_len, const uint8_t *cmd_data) 36 | { 37 | // build and send command 38 | int txlen = build_tx(cmd_len, cmd_data); 39 | _serial->write(_txbuf, txlen); 40 | 41 | // wait for response 42 | unsigned long start = millis(); 43 | while ((millis() - start) < DEFAULT_TIMEOUT) { 44 | while (_serial->available()) { 45 | char c = _serial->read(); 46 | if (process_rx(c)) { 47 | return true; 48 | } 49 | } 50 | yield(); 51 | } 52 | 53 | // timeout 54 | return false; 55 | } 56 | 57 | // builds a tx buffer, returns length of tx data 58 | int PM1006::build_tx(size_t cmd_len, const uint8_t *cmd_data) 59 | { 60 | int len = 0; 61 | _txbuf[len++] = 0x11; 62 | _txbuf[len++] = cmd_len; 63 | for (size_t i = 0; i < cmd_len; i++) { 64 | _txbuf[len++] = cmd_data[i]; 65 | } 66 | uint8_t sum = 0; 67 | for (int i = 0; i < len; i++) { 68 | sum += _txbuf[i]; 69 | } 70 | _txbuf[len++] = (256 - sum) & 0xFF; 71 | return len; 72 | } 73 | 74 | // processes one rx character, returns true if a valid frame was found 75 | bool PM1006::process_rx(uint8_t c) 76 | { 77 | switch (_state) { 78 | case PM1006_HEADER: 79 | _checksum = c; 80 | if (c == 0x16) { 81 | _state = PM1006_LENGTH; 82 | } 83 | break; 84 | 85 | case PM1006_LENGTH: 86 | _checksum += c; 87 | if (c <= sizeof(_rxbuf)) { 88 | _rxlen = c; 89 | _index = 0; 90 | _state = (_rxlen > 0) ? PM1006_DATA : PM1006_CHECK; 91 | } else { 92 | _state = PM1006_HEADER; 93 | } 94 | break; 95 | 96 | case PM1006_DATA: 97 | _checksum += c; 98 | _rxbuf[_index++] = c; 99 | if (_index == _rxlen) { 100 | _state = PM1006_CHECK; 101 | } 102 | break; 103 | 104 | case PM1006_CHECK: 105 | _checksum += c; 106 | _state = PM1006_HEADER; 107 | return (_checksum == 0); 108 | 109 | default: 110 | _state = PM1006_HEADER; 111 | break; 112 | } 113 | return false; 114 | } 115 | 116 | -------------------------------------------------------------------------------- /SW/VINDRIKTNING_plus_Scd4x/VINDRIKTNING_plus_Scd4x.ino: -------------------------------------------------------------------------------- 1 | #include "pm1006.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #define PIN_FAN 12 7 | #define PIN_LED 25 8 | #define RXD2 16 9 | #define TXD2 17 10 | 11 | #define BRIGHTNESS 10 12 | 13 | #define PM_LED 1 14 | #define TEMP_LED 2 15 | #define CO2_LED 3 16 | 17 | static PM1006 pm1006(&Serial2); 18 | Adafruit_NeoPixel rgbWS = Adafruit_NeoPixel(3, PIN_LED, NEO_GRB + NEO_KHZ800); 19 | SensirionI2CScd4x scd4x; 20 | 21 | void setup() { 22 | pinMode(PIN_FAN, OUTPUT); // Fan 23 | rgbWS.begin(); // WS2718 24 | rgbWS.setBrightness(BRIGHTNESS); 25 | 26 | Serial.begin(115200); 27 | Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2); 28 | 29 | Wire.begin(); 30 | uint16_t error; 31 | char errorMessage[256]; 32 | scd4x.begin(Wire); 33 | 34 | Serial.println("Start..."); 35 | delay(500); 36 | Serial.println("1. LED Green"); 37 | setColorWS(0, 255, 0, PM_LED); 38 | delay(1000); 39 | Serial.println("2. LED Green"); 40 | setColorWS(0, 255, 0, 2); 41 | delay(1000); 42 | Serial.println("3. LED Green"); 43 | setColorWS(0, 255, 0, 3); 44 | delay(1000); 45 | setColorWS(0, 0, 0, 1); 46 | setColorWS(0, 0, 0, 2); 47 | setColorWS(0, 0, 0, 3); 48 | 49 | // stop potentially previously started measurement 50 | error = scd4x.stopPeriodicMeasurement(); 51 | if (error) { 52 | Serial.print("SCD41 Error trying to execute stopPeriodicMeasurement(): "); 53 | errorToString(error, errorMessage, 256); 54 | Serial.println(errorMessage); 55 | alert(CO2_LED); 56 | } 57 | 58 | uint16_t serial0; 59 | uint16_t serial1; 60 | uint16_t serial2; 61 | error = scd4x.getSerialNumber(serial0, serial1, serial2); 62 | if (error) { 63 | Serial.print("SCD41 Error trying to execute getSerialNumber(): "); 64 | errorToString(error, errorMessage, 256); 65 | Serial.println(errorMessage); 66 | alert(CO2_LED); 67 | } else { 68 | printSerialNumber(serial0, serial1, serial2); 69 | } 70 | 71 | // Start Measurement 72 | error = scd4x.startPeriodicMeasurement(); 73 | if (error) { 74 | Serial.print("SCD41 Error trying to execute startPeriodicMeasurement(): "); 75 | errorToString(error, errorMessage, 256); 76 | Serial.println(errorMessage); 77 | alert(CO2_LED); 78 | } 79 | 80 | Serial.println("Waiting for first measurement... (5 sec)"); 81 | } 82 | 83 | void loop() { 84 | uint16_t error; 85 | char errorMessage[256]; 86 | 87 | digitalWrite(PIN_FAN, HIGH); 88 | Serial.println("Fan ON"); 89 | delay(10000); 90 | 91 | uint16_t pm2_5; 92 | if (pm1006.read_pm25(&pm2_5)) { 93 | printf("PM2.5 = %u\n", pm2_5); 94 | } else { 95 | Serial.println("Measurement failed!"); 96 | alert(PM_LED); 97 | } 98 | 99 | delay(1000); 100 | digitalWrite(PIN_FAN, LOW); 101 | Serial.println("Fan OFF"); 102 | 103 | uint16_t co2; 104 | float temperature; 105 | float humidity; 106 | error = scd4x.readMeasurement(co2, temperature, humidity); 107 | if (error) { 108 | Serial.print("SCD41 Error trying to execute readMeasurement(): "); 109 | errorToString(error, errorMessage, 256); 110 | Serial.println(errorMessage); 111 | alert(CO2_LED); 112 | } else if (co2 == 0) { 113 | Serial.println("Invalid sample detected, skipping."); 114 | } else { 115 | //temperature = temperature -4.0; 116 | 117 | Serial.print("Co2:"); 118 | Serial.print(co2); 119 | Serial.print("\t"); 120 | Serial.print(" Temperature:"); 121 | Serial.print(temperature); 122 | Serial.print("\t"); 123 | Serial.print(" Humidity:"); 124 | Serial.println(humidity); 125 | 126 | if(co2 < 1000){ 127 | setColorWS(0, 255, 0, CO2_LED); 128 | } 129 | 130 | if((co2 >= 1000) && (co2 < 1200)){ 131 | setColorWS(128, 255, 0, CO2_LED); 132 | } 133 | 134 | if((co2 >= 1200) && (co2 < 1500)){ 135 | setColorWS(255, 255, 0, CO2_LED); 136 | } 137 | 138 | if((co2 >= 1500) && (co2 < 2000)){ 139 | setColorWS(255, 128, 0, CO2_LED); 140 | } 141 | 142 | if(co2 >= 2000){ 143 | setColorWS(255, 0, 0, CO2_LED); 144 | } 145 | 146 | if(temperature < 23.0){ 147 | setColorWS(0, 0, 255, TEMP_LED); 148 | } 149 | 150 | if((temperature >= 23.0) && (temperature < 28.0)){ 151 | setColorWS(0, 255, 0, TEMP_LED); 152 | } 153 | 154 | if(temperature >= 28.0){ 155 | setColorWS(255, 0, 0, TEMP_LED); 156 | } 157 | } 158 | 159 | // PM LED 160 | if(pm2_5 < 30){ 161 | setColorWS(0, 255, 0, PM_LED); 162 | } 163 | 164 | if((pm2_5 >= 30) && (pm2_5 < 40)){ 165 | setColorWS(128, 255, 0, PM_LED); 166 | } 167 | 168 | if((pm2_5 >= 40) && (pm2_5 < 80)){ 169 | setColorWS(255, 255, 0, PM_LED); 170 | } 171 | 172 | if((pm2_5 >= 80) && (pm2_5 < 90)){ 173 | setColorWS(255, 128, 0, PM_LED); 174 | } 175 | 176 | if(pm2_5 >= 90){ 177 | setColorWS(255, 0, 0, PM_LED); 178 | } 179 | 180 | delay(60000); 181 | } 182 | 183 | void alert(int id){ 184 | int i = 0; 185 | while (1){ 186 | if (i > 10){ 187 | Serial.println("Maybe need Reboot..."); 188 | //ESP.restart(); 189 | break; 190 | } 191 | rgbWS.setBrightness(255); 192 | setColorWS(255, 0, 0, id); 193 | delay(200); 194 | rgbWS.setBrightness(BRIGHTNESS); 195 | setColorWS(0, 0, 0, id); 196 | delay(200); 197 | i++; 198 | } 199 | } 200 | 201 | void setColorWS(byte r, byte g, byte b, int id) { // r = hodnota cervene, g = hodnota zelene, b = hodnota modre, id = cislo LED v poradi, kterou budeme nastavovat(1 = 1. LED, 2 = 2. LED atd.) 202 | uint32_t rgb; 203 | rgb = rgbWS.Color(r, g, b); // Konverze vstupnich hodnot R, G, B do pomocne promenne 204 | rgbWS.setPixelColor(id - 1, rgb); // Nastavi pozadovanou barvu pro konkretni led = pozice LED zacinaji od nuly 205 | rgbWS.show(); // Zaktualizuje barvu 206 | } 207 | 208 | void printUint16Hex(uint16_t value) { 209 | Serial.print(value < 4096 ? "0" : ""); 210 | Serial.print(value < 256 ? "0" : ""); 211 | Serial.print(value < 16 ? "0" : ""); 212 | Serial.print(value, HEX); 213 | } 214 | 215 | void printSerialNumber(uint16_t serial0, uint16_t serial1, uint16_t serial2) { 216 | Serial.print("SCD41 Serial: 0x"); 217 | printUint16Hex(serial0); 218 | printUint16Hex(serial1); 219 | printUint16Hex(serial2); 220 | Serial.println(); 221 | } 222 | -------------------------------------------------------------------------------- /SW/ESPHome.yaml: -------------------------------------------------------------------------------- 1 | substitutions: 2 | device_name: air-quality 3 | wifi_ssid: !secret wifi_ssid 4 | wifi_password: !secret wifi_password 5 | api_password: !secret api_password 6 | ota_password: !secret ota_password 7 | 8 | globals: 9 | - id: max_brightness 10 | type: float 11 | restore_value: no 12 | initial_value: '0.5' 13 | - id: min_brightness 14 | type: float 15 | restore_value: no 16 | initial_value: '0.1' 17 | 18 | esphome: 19 | name: ${device_name} 20 | comment: IKEA LaskaKit ESP-VINDRIKTNING ESP-32 I2C 21 | on_boot: 22 | - light.turn_on: 23 | id: status 24 | brightness: 0% 25 | 26 | esp32: 27 | board: nodemcu-32s 28 | framework: 29 | type: arduino 30 | 31 | wifi: 32 | ssid: ${wifi_ssid} 33 | password: ${wifi_password} 34 | api: 35 | password: ${api_password} 36 | ota: 37 | password: ${ota_password} 38 | 39 | logger: 40 | 41 | uart: 42 | rx_pin: 16 43 | tx_pin: 17 44 | baud_rate: 9600 45 | id: uart2 46 | 47 | sensor: 48 | - platform: pm1006 49 | pm_2_5: 50 | name: "Particulate Matter 2.5µm Concentration" 51 | id: pm 52 | accuracy_decimals: 1 53 | on_value: 54 | then: 55 | - if: 56 | condition: 57 | lambda: 'return x < 30;' 58 | then: 59 | - light.addressable_set: 60 | id: status 61 | range_from: 0 62 | range_to: 0 63 | red: 0.0 64 | green: 1.0 65 | blue: 0.0 66 | color_brightness: !lambda |- 67 | return id(sun_elevation).state > 0 ? id(max_brightness) : id(min_brightness); 68 | else: 69 | - if: 70 | condition: 71 | lambda: 'return x > 90;' 72 | then: 73 | - light.addressable_set: 74 | id: status 75 | range_from: 0 76 | range_to: 0 77 | red: 1.0 78 | green: 0.0 79 | blue: 0.0 80 | color_brightness: !lambda |- 81 | return id(sun_elevation).state > 0 ? id(max_brightness) : id(min_brightness); 82 | else: 83 | - light.addressable_set: 84 | id: status 85 | range_from: 0 86 | range_to: 0 87 | red: 1.0 88 | green: 0.72 89 | blue: 0.0 90 | color_brightness: !lambda |- 91 | return id(sun_elevation).state > 0 ? id(max_brightness) : id(min_brightness); 92 | uart_id: uart2 93 | update_interval: 20s 94 | - platform: scd4x 95 | co2: 96 | name: "CO2" 97 | id: co2 98 | on_value: 99 | then: 100 | - if: 101 | condition: 102 | lambda: 'return x < 1000;' 103 | then: 104 | - light.addressable_set: 105 | id: status 106 | range_from: 1 107 | range_to: 1 108 | red: 0.0 109 | green: 1.0 110 | blue: 0.0 111 | color_brightness: !lambda |- 112 | return id(sun_elevation).state > 0 ? id(max_brightness) : id(min_brightness); 113 | else: 114 | - if: 115 | condition: 116 | lambda: 'return x > 1500;' 117 | then: 118 | - light.addressable_set: 119 | id: status 120 | range_from: 1 121 | range_to: 1 122 | red: 1.0 123 | green: 0.0 124 | blue: 0.0 125 | color_brightness: !lambda |- 126 | return id(sun_elevation).state > 0 ? id(max_brightness) : id(min_brightness); 127 | else: 128 | - light.addressable_set: 129 | id: status 130 | range_from: 1 131 | range_to: 1 132 | red: 1.0 133 | green: 0.72 134 | blue: 0.0 135 | color_brightness: !lambda |- 136 | return id(sun_elevation).state > 0 ? id(max_brightness) : id(min_brightness); 137 | humidity: 138 | name: "Humidity" 139 | id: humidity 140 | on_value: 141 | then: 142 | - if: 143 | condition: 144 | lambda: 'return x < 30;' 145 | then: 146 | - light.addressable_set: 147 | id: status 148 | range_from: 2 149 | range_to: 2 150 | red: 1.0 151 | green: 0.0 152 | blue: 0.0 153 | color_brightness: !lambda |- 154 | return id(sun_elevation).state > 0 ? id(max_brightness) : id(min_brightness); 155 | else: 156 | - if: 157 | condition: 158 | lambda: 'return x > 60;' 159 | then: 160 | - light.addressable_set: 161 | id: status 162 | range_from: 2 163 | range_to: 2 164 | red: 0.0 165 | green: 0.0 166 | blue: 1.0 167 | color_brightness: !lambda |- 168 | return id(sun_elevation).state > 0 ? id(max_brightness) : id(min_brightness); 169 | else: 170 | - light.addressable_set: 171 | id: status 172 | range_from: 2 173 | range_to: 2 174 | red: 0.0 175 | green: 1.0 176 | blue: 0.0 177 | color_brightness: !lambda |- 178 | return id(sun_elevation).state > 0 ? id(max_brightness) : id(min_brightness); 179 | - platform: homeassistant 180 | id: sun_elevation 181 | entity_id: sun.sun 182 | attribute: elevation 183 | - platform: wifi_signal 184 | name: "Air quality WiFi Signal Sensor" 185 | entity_category: diagnostic 186 | disabled_by_default: true 187 | update_interval: 60s 188 | 189 | switch: 190 | - platform: gpio 191 | pin: 12 192 | id: fan 193 | restore_mode: ALWAYS_ON 194 | disabled_by_default: true 195 | entity_category: diagnostic 196 | 197 | light: 198 | - platform: neopixelbus 199 | type: GRB 200 | variant: WS2812 201 | pin: GPIO25 202 | num_leds: 3 203 | id: status 204 | restore_mode: ALWAYS_ON 205 | method: 206 | type: esp32_rmt 207 | channel: 6 208 | 209 | i2c: 210 | sda: 21 211 | scl: 22 212 | scan: true 213 | id: bus_a 214 | 215 | button: 216 | - platform: restart 217 | name: "Restart ${device_name}" 218 | disabled_by_default: true 219 | entity_category: diagnostic 220 | -------------------------------------------------------------------------------- /SW/VINDRIKTNING/VINDRIKTNING.ino: -------------------------------------------------------------------------------- 1 | // LaskaKit ESP-VINDRIKTNING v2.0 demo software 2 | 3 | #include "pm1006.h" 4 | #include 5 | #include 6 | #include 7 | 8 | #define PIN_FAN 12 9 | #define PIN_LED 25 10 | #define RXD2 16 11 | #define TXD2 17 12 | 13 | #define BRIGHTNESS 50 14 | #define BR_NIGHT 5 15 | 16 | #define PM_LED 1 17 | #define TEMP_LED 2 18 | #define CO2_LED 3 19 | 20 | #define PIN_AMBIENT_LIGHT 4 21 | #define DN 3700 // night level 22 | 23 | #define TEMP_OFFSET 3.02 24 | 25 | static PM1006 pm1006(&Serial2); 26 | Adafruit_NeoPixel rgbWS = Adafruit_NeoPixel(3, PIN_LED, NEO_GRB + NEO_KHZ800); 27 | SensirionI2CScd4x scd4x; 28 | 29 | void setup() { 30 | pinMode(PIN_FAN, OUTPUT); // Fan 31 | pinMode(PIN_AMBIENT_LIGHT, INPUT); // Ambient light 32 | 33 | rgbWS.begin(); // WS2718 34 | rgbWS.setBrightness(BRIGHTNESS); 35 | 36 | Serial.begin(115200); 37 | Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2); 38 | 39 | Wire.begin(); 40 | uint16_t error; 41 | char errorMessage[256]; 42 | scd4x.begin(Wire); 43 | 44 | Serial.println("Start..."); 45 | delay(500); 46 | Serial.println("1. LED Green"); 47 | setColorWS(0, 255, 0, PM_LED); 48 | delay(1000); 49 | Serial.println("2. LED Green"); 50 | setColorWS(0, 255, 0, 2); 51 | delay(1000); 52 | Serial.println("3. LED Green"); 53 | setColorWS(0, 255, 0, 3); 54 | delay(1000); 55 | setColorWS(0, 0, 0, 1); 56 | setColorWS(0, 0, 0, 2); 57 | setColorWS(0, 0, 0, 3); 58 | 59 | // stop potentially previously started measurement 60 | error = scd4x.stopPeriodicMeasurement(); 61 | if (error) { 62 | Serial.print("SCD41 Error trying to execute stopPeriodicMeasurement(): "); 63 | errorToString(error, errorMessage, 256); 64 | Serial.println(errorMessage); 65 | alert(CO2_LED); 66 | } 67 | 68 | uint16_t serial0; 69 | uint16_t serial1; 70 | uint16_t serial2; 71 | error = scd4x.getSerialNumber(serial0, serial1, serial2); 72 | if (error) { 73 | Serial.print("SCD41 Error trying to execute getSerialNumber(): "); 74 | errorToString(error, errorMessage, 256); 75 | Serial.println(errorMessage); 76 | alert(CO2_LED); 77 | } else { 78 | printSerialNumber(serial0, serial1, serial2); 79 | } 80 | 81 | // Start Measurement 82 | error = scd4x.startPeriodicMeasurement(); 83 | if (error) { 84 | Serial.print("SCD41 Error trying to execute startPeriodicMeasurement(): "); 85 | errorToString(error, errorMessage, 256); 86 | Serial.println(errorMessage); 87 | alert(CO2_LED); 88 | } 89 | 90 | Serial.println("Waiting for first measurement... (5 sec)"); 91 | } 92 | 93 | void loop() { 94 | uint16_t error; 95 | char errorMessage[256]; 96 | 97 | int al = analogRead(PIN_AMBIENT_LIGHT); 98 | 99 | if (al >= DN){ 100 | rgbWS.setBrightness(BR_NIGHT); 101 | }else{ 102 | rgbWS.setBrightness(BRIGHTNESS); 103 | } 104 | 105 | Serial.print("Ambient light: "); 106 | Serial.println(al); 107 | 108 | digitalWrite(PIN_FAN, HIGH); 109 | Serial.println("Fan ON"); 110 | delay(10000); 111 | 112 | uint16_t pm2_5; 113 | if (pm1006.read_pm25(&pm2_5)) { 114 | printf("PM2.5 = %u\n", pm2_5); 115 | } else { 116 | Serial.println("Measurement failed!"); 117 | alert(PM_LED); 118 | } 119 | 120 | delay(1000); 121 | digitalWrite(PIN_FAN, LOW); 122 | Serial.println("Fan OFF"); 123 | 124 | uint16_t co2; 125 | float temperature; 126 | float humidity; 127 | error = scd4x.readMeasurement(co2, temperature, humidity); 128 | if (error) { 129 | Serial.print("SCD41 Error trying to execute readMeasurement(): "); 130 | errorToString(error, errorMessage, 256); 131 | Serial.println(errorMessage); 132 | alert(CO2_LED); 133 | } else if (co2 == 0) { 134 | Serial.println("Invalid sample detected, skipping."); 135 | } else { 136 | temperature = temperature - TEMP_OFFSET; 137 | 138 | Serial.print("Co2:"); 139 | Serial.print(co2); 140 | Serial.print("\t"); 141 | Serial.print(" Temperature:"); 142 | Serial.print(temperature); 143 | Serial.print("\t"); 144 | Serial.print(" Humidity:"); 145 | Serial.println(humidity); 146 | 147 | // CO2 LED 148 | if(co2 < 1000){ 149 | setColorWS(0, 255, 0, CO2_LED); 150 | } 151 | 152 | if((co2 >= 1000) && (co2 < 1200)){ 153 | setColorWS(128, 255, 0, CO2_LED); 154 | } 155 | 156 | if((co2 >= 1200) && (co2 < 1500)){ 157 | setColorWS(255, 255, 0, CO2_LED); 158 | } 159 | 160 | if((co2 >= 1500) && (co2 < 2000)){ 161 | setColorWS(255, 128, 0, CO2_LED); 162 | } 163 | 164 | if(co2 >= 2000){ 165 | setColorWS(255, 0, 0, CO2_LED); 166 | } 167 | 168 | // Temperature LED 169 | if(temperature < 23.0){ 170 | setColorWS(0, 0, 255, TEMP_LED); 171 | } 172 | 173 | if((temperature >= 23.0) && (temperature < 27.0)){ 174 | setColorWS(0, 255, 0, TEMP_LED); 175 | } 176 | 177 | if(temperature >= 27.0){ 178 | setColorWS(255, 0, 0, TEMP_LED); 179 | } 180 | } 181 | 182 | // PM LED 183 | if(pm2_5 < 30){ 184 | setColorWS(0, 255, 0, PM_LED); 185 | } 186 | 187 | if((pm2_5 >= 30) && (pm2_5 < 40)){ 188 | setColorWS(128, 255, 0, PM_LED); 189 | } 190 | 191 | if((pm2_5 >= 40) && (pm2_5 < 80)){ 192 | setColorWS(255, 255, 0, PM_LED); 193 | } 194 | 195 | if((pm2_5 >= 80) && (pm2_5 < 90)){ 196 | setColorWS(255, 128, 0, PM_LED); 197 | } 198 | 199 | if(pm2_5 >= 90){ 200 | setColorWS(255, 0, 0, PM_LED); 201 | } 202 | 203 | delay(30000); 204 | } 205 | 206 | void alert(int id){ 207 | int i = 0; 208 | while (1){ 209 | if (i > 10){ 210 | Serial.println("Maybe need Reboot..."); 211 | //ESP.restart(); 212 | break; 213 | } 214 | rgbWS.setBrightness(255); 215 | setColorWS(255, 0, 0, id); 216 | delay(200); 217 | rgbWS.setBrightness(BRIGHTNESS); 218 | setColorWS(0, 0, 0, id); 219 | delay(200); 220 | i++; 221 | } 222 | } 223 | 224 | void setColorWS(byte r, byte g, byte b, int id) { // r = hodnota cervene, g = hodnota zelene, b = hodnota modre, id = cislo LED v poradi, kterou budeme nastavovat(1 = 1. LED, 2 = 2. LED atd.) 225 | uint32_t rgb; 226 | rgb = rgbWS.Color(r, g, b); // Konverze vstupnich hodnot R, G, B do pomocne promenne 227 | rgbWS.setPixelColor(id - 1, rgb); // Nastavi pozadovanou barvu pro konkretni led = pozice LED zacinaji od nuly 228 | rgbWS.show(); // Zaktualizuje barvu 229 | } 230 | 231 | void printUint16Hex(uint16_t value) { 232 | Serial.print(value < 4096 ? "0" : ""); 233 | Serial.print(value < 256 ? "0" : ""); 234 | Serial.print(value < 16 ? "0" : ""); 235 | Serial.print(value, HEX); 236 | } 237 | 238 | void printSerialNumber(uint16_t serial0, uint16_t serial1, uint16_t serial2) { 239 | Serial.print("SCD41 Serial: 0x"); 240 | printUint16Hex(serial0); 241 | printUint16Hex(serial1); 242 | printUint16Hex(serial2); 243 | Serial.println(); 244 | } 245 | -------------------------------------------------------------------------------- /SW/ESP-Vindriktning_UART_TMEPcz/ESP-Vindriktning_UART_TMEPcz_SHT40.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Vzorovy kod od TMEP.cz zalozeny na laskakit.cz ESP-VINDRIKTNING 3 | * Misto SCD40 je LaskaKit SHT40, takze teplota a vlhkost bez CO2. 4 | * 5 | * Kod posle pres seriovy port (UART) a zaroven na server TMEP.cz 6 | * 7 | * Vytvoreno (c) laskakit.cz 2022, mirna modifikace na "jen" teplotu, 8 | * vlhkost (a pranost) - MultiTricker TMEP.cz 9 | * 10 | * V Arduino IDE jsem po doinstalovani ESP32 desek pouzil board: ESP32 Dev Module 11 | * 12 | * Potrebne knihovny - na pranost: 13 | * https://github.com/bertrik/pm1006 //PM1006 14 | * 15 | * Na LaskaKit SHT40 staci v Arduino IDE Library manageru vyhledat a 16 | * nainstalovat vc. zavislosti "Adafruit SHT4x" 17 | * https://github.com/adafruit/Adafruit_SHT4X 18 | * 19 | * Na TMEPu zvol typ cidla "Teplota, vlhkost a CO2", treti promennou (CO2) 20 | * na zalozce "Zasilane hodnoty" pojmenuj jako "Prasnost" (vc. diakritiky ;)) 21 | * 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include "Adafruit_SHT4x.h" 28 | 29 | /* LaskaKit ESP-VINDRIKTNING - cidlo prasnosti PM1006 */ 30 | #include "pm1006.h" 31 | 32 | /* RGB adresovatelne LED */ 33 | #include 34 | 35 | /* LaskaKit ESP-VINDRIKTNING - cidlo prasnosti PM1006 */ 36 | #define PIN_FAN 12 // spinani ventilatoru 37 | #define RXD2 16 // UART - RX 38 | #define TXD2 17 // UART - TX 39 | 40 | /* Nastaveni RGB LED */ 41 | #define BRIGHTNESS 5 42 | #define PIN_LED 25 43 | #define PM_LED 0 44 | #define TEMP_LED 1 45 | #define HUM_LED 2 46 | 47 | /*--------------------- UPRAV NASTAVENI ---------------------*/ 48 | const char* ssid = "SSID"; 49 | const char* password = "PASSWORD"; 50 | 51 | // Vypln domenu pro zapis hodnot, kterou najdes u vytvoreneho cidla na tmep.cz 52 | // Nemas? registrace je za minutu a cidlo pridas okamzite ;) 53 | String serverName = "http://TVOJE_DOMENA_PRO_ZAPIS.tmep.cz/index.php?"; 54 | 55 | // Muzes vyplnit vlastni pojmenovani GUID, pokud si je na TMEPu nastavis na jine nez vychozi 56 | String GUID_TEPLOTA = "temp"; 57 | String GUID_VLHKOST = "humi"; 58 | String GUID_PRASNOST = "CO2"; 59 | /*------------------------ KONEC UPRAV -----------------------*/ 60 | 61 | /* LaskaKit ESP-VINDRIKTNING - cidlo prasnosti PM1006, nastaveni UART2 */ 62 | static PM1006 pm1006(&Serial2); 63 | 64 | /* LaskaKit ESP-VINDRIKTNING s čidlem LaskaKit SHT40 */ 65 | Adafruit_SHT4x sht4 = Adafruit_SHT4x(); 66 | 67 | /* RGB adresovatelne LED */ 68 | Adafruit_NeoPixel pixels = Adafruit_NeoPixel(3, PIN_LED, NEO_GRB + NEO_KHZ800); 69 | 70 | void setup() { 71 | pinMode(PIN_FAN, OUTPUT); // Ventilator pro cidlo prasnosti PM1006 72 | Serial.begin(115200); 73 | Serial.println("BOOTujeme"); 74 | 75 | if (! sht4.begin()) 76 | { 77 | Serial.println("SHT4x not found"); 78 | Serial.println("Check the connection"); 79 | while (1) delay(1); 80 | } 81 | 82 | sht4.setPrecision(SHT4X_HIGH_PRECISION); // highest resolution 83 | sht4.setHeater(SHT4X_NO_HEATER); // no heater 84 | 85 | Wire.begin(); 86 | 87 | pixels.begin(); // WS2718 88 | pixels.setBrightness(BRIGHTNESS); 89 | 90 | delay(10); 91 | 92 | /*-------------- RGB adresovatelne LED - zhasni --------------*/ 93 | pixels.setPixelColor(PM_LED, pixels.Color(0, 0, 0)); // R, G, B 94 | pixels.setPixelColor(TEMP_LED, pixels.Color(0, 0, 0)); // R, G, B 95 | pixels.setPixelColor(HUM_LED, pixels.Color(0, 0, 0)); // R, G, B 96 | pixels.show(); // Zaktualizuje barvu 97 | 98 | /*-------------- PM1006 - cidlo prasnosti ---------------*/ 99 | Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2); // cidlo prasnosti PM1006 100 | 101 | /*------------- Wi-Fi -----------*/ 102 | WiFi.begin(ssid, password); 103 | Serial.println("Pripojovani"); 104 | while (WiFi.status() != WL_CONNECTED) { 105 | delay(500); 106 | Serial.print("."); 107 | } 108 | Serial.println(); 109 | Serial.print("Pripojeno do site, IP adresa zarizeni: "); 110 | Serial.println(WiFi.localIP()); 111 | } 112 | 113 | void loop() { 114 | /*------------- LaskaKit SHT40 - teplota, vlhkost -----------*/ 115 | float teplota = 0.0; 116 | int vlhkost = 0; 117 | 118 | sensors_event_t humidity, temp; // temperature and humidity variables 119 | sht4.getEvent(&humidity, &temp); 120 | 121 | teplota = temp.temperature; 122 | vlhkost = humidity.relative_humidity; 123 | 124 | // odeslani hodnot pres UART 125 | Serial.print("Teplota: "); 126 | Serial.print(teplota); 127 | Serial.println(" degC"); 128 | Serial.print("Vlhkost: "); 129 | Serial.print(vlhkost); 130 | 131 | /*-------------- PM1006 - cidlo prasnosti ---------------*/ 132 | uint16_t pm2_5; 133 | digitalWrite(PIN_FAN, HIGH); 134 | Serial.println("Fan ON"); 135 | delay(30000); 136 | 137 | while (!pm1006.read_pm25(&pm2_5)) { 138 | delay(1); 139 | } 140 | 141 | delay(100); 142 | digitalWrite(PIN_FAN, LOW); 143 | Serial.println("Fan OFF"); 144 | 145 | // odeslani hodnot pres UART 146 | Serial.print("PM2.5: "); 147 | Serial.print(pm2_5); 148 | Serial.println(" ppm"); 149 | 150 | /*-------------- RGB adresovatelne LED ---------------*/ 151 | 152 | // teplota LED 153 | if(teplota < 20.0){ 154 | pixels.setPixelColor(TEMP_LED, pixels.Color(0, 0, 255)); // R, G, B 155 | } else if (teplota < 23.0){ 156 | pixels.setPixelColor(TEMP_LED, pixels.Color(0, 255, 0)); // R, G, B 157 | } else { 158 | pixels.setPixelColor(TEMP_LED, pixels.Color(255, 0, 0)); // R, G, B 159 | } 160 | 161 | // vlhkost LED 162 | if (vlhkost < 20.0) { 163 | pixels.setPixelColor(HUM_LED, pixels.Color(0, 0, 50)); // R, G, B 164 | } else if (vlhkost < 40.0) { 165 | pixels.setPixelColor(HUM_LED, pixels.Color(0, 0, 100)); // R, G, B 166 | } else if (vlhkost < 60.0) { 167 | pixels.setPixelColor(HUM_LED, pixels.Color(0, 0, 150)); // R, G, B 168 | } else if (vlhkost < 80.0) { 169 | pixels.setPixelColor(HUM_LED, pixels.Color(0, 0, 200)); // R, G, B 170 | } else { 171 | pixels.setPixelColor(HUM_LED, pixels.Color(0, 0, 255)); // R, G, B 172 | } 173 | 174 | // PM LED 175 | if (pm2_5 < 30) { 176 | pixels.setPixelColor(PM_LED, pixels.Color(0, 255, 0)); // R, G, B 177 | } else if (pm2_5 < 40) { 178 | pixels.setPixelColor(PM_LED, pixels.Color(128, 255, 0)); // R, G, B 179 | } else if (pm2_5 < 80) { 180 | pixels.setPixelColor(PM_LED, pixels.Color(255, 255, 0)); // R, G, B 181 | } else if (pm2_5 < 90) { 182 | pixels.setPixelColor(PM_LED, pixels.Color(255, 128, 0)); // R, G, B 183 | } else { 184 | pixels.setPixelColor(PM_LED, pixels.Color(255, 0, 0)); // R, G, B 185 | } 186 | pixels.show(); // Zaktualizuje barvu 187 | 188 | /*------------ Odeslani hodnot na TMEP.cz ------------------*/ 189 | if (WiFi.status() == WL_CONNECTED) { 190 | // GUID, nasleduje hodnota teploty, vlhkosti a pranosti 191 | String serverPath = serverName + "" + GUID_TEPLOTA + "=" + teplota + "&" + GUID_VLHKOST + "=" + vlhkost + "&" + GUID_PRASNOST + "=" + pm2_5; 192 | sendhttpGet(serverPath); 193 | 194 | } else { 195 | Serial.println("Wi-Fi odpojeno"); 196 | } 197 | 198 | esp_sleep_enable_timer_wakeup(900 * 1000000); // uspani na 15 minut 199 | Serial2.flush(); 200 | Serial.flush(); 201 | delay(100); 202 | esp_deep_sleep_start(); 203 | } 204 | 205 | // funcke pro odeslani dat na TMEP.cz 206 | void sendhttpGet(String httpGet) { 207 | HTTPClient http; 208 | 209 | // odeslani dat 210 | String serverPath = httpGet; 211 | 212 | // zacatek http spojeni 213 | http.begin(serverPath.c_str()); 214 | 215 | // http get request 216 | int httpResponseCode = http.GET(); 217 | 218 | if (httpResponseCode > 0) { 219 | Serial.print("HTTP odpoved: "); 220 | Serial.println(httpResponseCode); 221 | String payload = http.getString(); 222 | Serial.println(payload); 223 | } else { 224 | Serial.print("Error kod: "); 225 | Serial.println(httpResponseCode); 226 | } 227 | // Free resources 228 | http.end(); 229 | } 230 | -------------------------------------------------------------------------------- /SW/ESP-Vindriktning_OLED/ESP-Vindriktning_OLED.ino: -------------------------------------------------------------------------------- 1 | #include "pm1006.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define PIN_FAN 12 10 | #define PIN_LED 25 11 | #define RXD2 16 12 | #define TXD2 17 13 | #define SCREEN_WIDTH 128 // OLED display width, in pixels 14 | #define SCREEN_HEIGHT 32 // OLED display height, in pixels 15 | #define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) 16 | #define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32 17 | Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); 18 | 19 | 20 | #define BRIGHTNESS 10 21 | #define SLEEP_SEC 5*60 22 | 23 | #define PM_LED 1 24 | #define TEMP_LED 2 25 | #define CO2_LED 3 26 | 27 | static PM1006 pm1006(&Serial2); 28 | Adafruit_NeoPixel rgbWS = Adafruit_NeoPixel(3, PIN_LED, NEO_GRB + NEO_KHZ800); 29 | SensirionI2CScd4x scd4x; 30 | 31 | void setup() { 32 | pinMode(PIN_FAN, OUTPUT); // Fan 33 | rgbWS.begin(); // WS2718 34 | rgbWS.setBrightness(BRIGHTNESS); 35 | 36 | Serial.begin(115200); 37 | Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2); 38 | 39 | // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally 40 | if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { 41 | Serial.println(F("SSD1306 allocation failed")); 42 | for(;;); // Don't proceed, loop forever 43 | } 44 | 45 | 46 | 47 | // Clear the buffer 48 | display.clearDisplay(); 49 | 50 | Wire.begin(); 51 | uint16_t error; 52 | char errorMessage[256]; 53 | scd4x.begin(Wire); 54 | 55 | Serial.println("Start..."); 56 | delay(500); 57 | Serial.println("1. LED Green"); 58 | setColorWS(0, 255, 0, PM_LED); 59 | delay(1000); 60 | Serial.println("2. LED Green"); 61 | setColorWS(0, 255, 0, 2); 62 | delay(1000); 63 | Serial.println("3. LED Green"); 64 | setColorWS(0, 255, 0, 3); 65 | delay(1000); 66 | setColorWS(0, 0, 0, 1); 67 | setColorWS(0, 0, 0, 2); 68 | setColorWS(0, 0, 0, 3); 69 | 70 | // stop potentially previously started measurement 71 | error = scd4x.stopPeriodicMeasurement(); 72 | if (error) { 73 | Serial.print("SCD41 Error trying to execute stopPeriodicMeasurement(): "); 74 | errorToString(error, errorMessage, 256); 75 | Serial.println(errorMessage); 76 | alert(CO2_LED); 77 | } 78 | 79 | uint16_t serial0; 80 | uint16_t serial1; 81 | uint16_t serial2; 82 | error = scd4x.getSerialNumber(serial0, serial1, serial2); 83 | if (error) { 84 | Serial.print("SCD41 Error trying to execute getSerialNumber(): "); 85 | errorToString(error, errorMessage, 256); 86 | Serial.println(errorMessage); 87 | alert(CO2_LED); 88 | } else { 89 | printSerialNumber(serial0, serial1, serial2); 90 | } 91 | 92 | // Start Measurement 93 | error = scd4x.startPeriodicMeasurement(); 94 | if (error) { 95 | Serial.print("SCD41 Error trying to execute startPeriodicMeasurement(): "); 96 | errorToString(error, errorMessage, 256); 97 | Serial.println(errorMessage); 98 | alert(CO2_LED); 99 | } 100 | 101 | Serial.println("Waiting for first measurement... (5 sec)"); 102 | } 103 | 104 | void loop() { 105 | uint16_t error; 106 | char errorMessage[256]; 107 | 108 | digitalWrite(PIN_FAN, HIGH); 109 | Serial.println("Fan ON"); 110 | delay(10000); 111 | 112 | uint16_t pm2_5; 113 | if (pm1006.read_pm25(&pm2_5)) { 114 | printf("PM2.5 = %u\n", pm2_5); 115 | } else { 116 | Serial.println("Measurement failed!"); 117 | alert(PM_LED); 118 | } 119 | 120 | delay(1000); 121 | digitalWrite(PIN_FAN, LOW); 122 | Serial.println("Fan OFF"); 123 | 124 | uint16_t co2; 125 | float temperature; 126 | float humidity; 127 | error = scd4x.readMeasurement(co2, temperature, humidity); 128 | if (error) { 129 | Serial.print("SCD41 Error trying to execute readMeasurement(): "); 130 | errorToString(error, errorMessage, 256); 131 | Serial.println(errorMessage); 132 | alert(CO2_LED); 133 | } else if (co2 == 0) { 134 | Serial.println("Invalid sample detected, skipping."); 135 | } else { 136 | //temperature = temperature -4.0; 137 | 138 | Serial.print("Co2:"); 139 | Serial.print(co2); 140 | Serial.print("\t"); 141 | Serial.print(" Temperature:"); 142 | Serial.print(temperature); 143 | Serial.print("\t"); 144 | Serial.print(" Humidity:"); 145 | Serial.println(humidity); 146 | 147 | if(co2 < 1000){ 148 | setColorWS(0, 255, 0, CO2_LED); 149 | } 150 | 151 | if((co2 >= 1000) && (co2 < 1200)){ 152 | setColorWS(128, 255, 0, CO2_LED); 153 | } 154 | 155 | if((co2 >= 1200) && (co2 < 1500)){ 156 | setColorWS(255, 255, 0, CO2_LED); 157 | } 158 | 159 | if((co2 >= 1500) && (co2 < 2000)){ 160 | setColorWS(255, 128, 0, CO2_LED); 161 | } 162 | 163 | if(co2 >= 2000){ 164 | setColorWS(255, 0, 0, CO2_LED); 165 | } 166 | 167 | if(temperature < 23.0){ 168 | setColorWS(0, 0, 255, TEMP_LED); 169 | } 170 | 171 | if((temperature >= 23.0) && (temperature < 28.0)){ 172 | setColorWS(0, 255, 0, TEMP_LED); 173 | } 174 | 175 | if(temperature >= 28.0){ 176 | setColorWS(255, 0, 0, TEMP_LED); 177 | } 178 | } 179 | 180 | // PM LED 181 | if(pm2_5 < 30){ 182 | setColorWS(0, 255, 0, PM_LED); 183 | } 184 | 185 | if((pm2_5 >= 30) && (pm2_5 < 40)){ 186 | setColorWS(128, 255, 0, PM_LED); 187 | } 188 | 189 | if((pm2_5 >= 40) && (pm2_5 < 80)){ 190 | setColorWS(255, 255, 0, PM_LED); 191 | } 192 | 193 | if((pm2_5 >= 80) && (pm2_5 < 90)){ 194 | setColorWS(255, 128, 0, PM_LED); 195 | } 196 | 197 | if(pm2_5 >= 90){ 198 | setColorWS(255, 0, 0, PM_LED); 199 | } 200 | 201 | display.clearDisplay(); 202 | 203 | display.setTextSize(1); // Normal 1:1 pixel scale 204 | display.setTextColor(SSD1306_WHITE); // Draw white text 205 | display.setCursor(5,5); // Start at top-left corner 206 | display.println(String(co2) + "ppm"); 207 | display.setCursor(5,21); // Start at top-left corner 208 | display.println(String(pm2_5) + "mg/m3"); 209 | display.setCursor(69,5); // Start at top-left corner 210 | display.println(String(temperature) + "degC"); 211 | display.setCursor(85,21); // Start at top-left corner 212 | display.println(String(humidity) + "%"); 213 | 214 | display.display(); 215 | 216 | delay(1); 217 | // ESP Deep Sleep 218 | Serial.println("ESP in sleep mode"); 219 | esp_sleep_enable_timer_wakeup(SLEEP_SEC * 1000000); 220 | esp_deep_sleep_start(); 221 | } 222 | 223 | void alert(int id){ 224 | int i = 0; 225 | while (1){ 226 | if (i > 10){ 227 | Serial.println("Maybe need Reboot..."); 228 | //ESP.restart(); 229 | break; 230 | } 231 | rgbWS.setBrightness(255); 232 | setColorWS(255, 0, 0, id); 233 | delay(200); 234 | rgbWS.setBrightness(BRIGHTNESS); 235 | setColorWS(0, 0, 0, id); 236 | delay(200); 237 | i++; 238 | } 239 | } 240 | 241 | void setColorWS(byte r, byte g, byte b, int id) { // r = hodnota cervene, g = hodnota zelene, b = hodnota modre, id = cislo LED v poradi, kterou budeme nastavovat(1 = 1. LED, 2 = 2. LED atd.) 242 | uint32_t rgb; 243 | rgb = rgbWS.Color(r, g, b); // Konverze vstupnich hodnot R, G, B do pomocne promenne 244 | rgbWS.setPixelColor(id - 1, rgb); // Nastavi pozadovanou barvu pro konkretni led = pozice LED zacinaji od nuly 245 | rgbWS.show(); // Zaktualizuje barvu 246 | } 247 | 248 | void printUint16Hex(uint16_t value) { 249 | Serial.print(value < 4096 ? "0" : ""); 250 | Serial.print(value < 256 ? "0" : ""); 251 | Serial.print(value < 16 ? "0" : ""); 252 | Serial.print(value, HEX); 253 | } 254 | 255 | void printSerialNumber(uint16_t serial0, uint16_t serial1, uint16_t serial2) { 256 | Serial.print("SCD41 Serial: 0x"); 257 | printUint16Hex(serial0); 258 | printUint16Hex(serial1); 259 | printUint16Hex(serial2); 260 | Serial.println(); 261 | } 262 | -------------------------------------------------------------------------------- /SW/ESP-Vindriktning_UART_TMEPcz/ESP-Vindriktning_UART_TMEPcz_SCD41.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Vzorovy kod od laskakit.cz pro LaskaKit ESP-VINDRIKTNING a cidlem CO2, teploty a vlhkosti SCD41 3 | * Kod posle pres seriovy port (UART) a zaroven na server TMEP.cz 4 | * 5 | * TMEP.cz momentalne neumi zobrazovat ctyri veliciny najednou - prasnost, CO2, teplota, vlhkost. 6 | * Proto jsou zaregistrovana dve cidla zvlast - prasnost (cidlo PM1006) a CO2/teplota/vlhkost (cidlo SCD41) 7 | * 8 | * LaskaKit ESP-VINDRIKTNING (https://www.laskakit.cz/laskakit-esp-vindriktning-esp-32-i2c/) 9 | * LaskaKit SCD41 Senzor CO2, teploty a vlhkosti vzduchu (https://www.laskakit.cz/laskakit-scd41-senzor-co2--teploty-a-vlhkosti-vzduchu/) 10 | * 11 | * Vytvoreno (c) laskakit.cz 2022 12 | * 13 | * Potrebne knihovny: 14 | * https://github.com/sparkfun/SparkFun_SCD4x_Arduino_Library //SCD41 15 | * https://github.com/bertrik/pm1006 //PM1006 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | /* LaskaKit ESP-VINDRIKTNING - cidlo prasnosti PM1006 */ 23 | #include "pm1006.h" 24 | /* LaskaKit ESP-VINDRIKTNING s čidlem CO2/teploty/vlhkosti SCD41 */ 25 | #include "SparkFun_SCD4x_Arduino_Library.h" 26 | 27 | /* RGB adresovatelne LED */ 28 | #include 29 | 30 | /* LaskaKit ESP-VINDRIKTNING - cidlo prasnosti PM1006 */ 31 | #define PIN_FAN 12 // spinani ventilatoru 32 | #define RXD2 16 // UART - RX 33 | #define TXD2 17 // UART - TX 34 | 35 | /* Nastaveni RGB LED */ 36 | #define BRIGHTNESS 10 37 | #define PIN_LED 25 38 | #define PM_LED 0 39 | #define TEMP_LED 1 40 | #define CO2_LED 2 41 | 42 | /*--------------------- UPRAV NASTAVENI ---------------------*/ 43 | const char* ssid = "SSID"; 44 | const char* password = "PASSWORD"; 45 | 46 | // vypln tvou domenu cidla, kterou sis zaregistroval na tmep.cz PRO PRASNOST (cidlo PM1006) 47 | String serverNamePM = "http://TVOJE_DOMENA_pro_cidlo_PRASNOSTI.tmep.cz/index.php?"; 48 | // vypln tve GUID cidla pro prasnost 49 | String GUID_PM = "TEBOU_ZVOLENE_GUID_PRO_PM1006"; 50 | 51 | // vypln tvou domenu cidla, kterou sis zaregistroval na tmep.cz PRO CO2 (pokud mas, cidlo SCD41) 52 | String serverNameCO2 = "http://TVOJE_DOMENA_pro_cidlo_CO2_teplota_vlhkost.tmep.cz/index.php?"; 53 | // vypln tve GUID cidla pro SCD41 - CO2, teplota, tlak 54 | String GUID_CO2 = "TEBOU_ZVOLENE_GUID_PRO_SCD41"; 55 | /*------------------------ KONEC UPRAV -----------------------*/ 56 | 57 | /* LaskaKit ESP-VINDRIKTNING - cidlo prasnosti PM1006, nastaveni UART2 */ 58 | static PM1006 pm1006(&Serial2); 59 | 60 | /* LaskaKit ESP-VINDRIKTNING s čidlem CO2/teploty/vlhkosti SCD41 */ 61 | SCD4x SCD41; 62 | 63 | /* RGB adresovatelne LED */ 64 | Adafruit_NeoPixel pixels = Adafruit_NeoPixel(3, PIN_LED, NEO_GRB + NEO_KHZ800); 65 | 66 | void setup() { 67 | pinMode(PIN_FAN, OUTPUT); // Ventilator pro cidlo prasnosti PM1006 68 | Serial.begin(115200); 69 | Wire.begin(); 70 | 71 | pixels.begin(); // WS2718 72 | pixels.setBrightness(BRIGHTNESS); 73 | 74 | delay(10); 75 | 76 | /*-------------- RGB adresovatelne LED - zhasni --------------*/ 77 | pixels.setPixelColor(PM_LED, pixels.Color(0, 0, 0)); // R, G, B 78 | pixels.setPixelColor(TEMP_LED, pixels.Color(0, 0, 0)); // R, G, B 79 | pixels.setPixelColor(CO2_LED, pixels.Color(0, 0, 0)); // R, G, B 80 | pixels.show(); // Zaktualizuje barvu 81 | 82 | /*-------------- PM1006 - cidlo prasnosti ---------------*/ 83 | Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2); // cidlo prasnosti PM1006 84 | 85 | /*------------- SCD41 - CO2, teplota, vlhkost -----------*/ 86 | // inicializace 87 | // begin, autokalibrace 88 | // | | 89 | if (SCD41.begin(false, true) == false) 90 | { 91 | Serial.println("SCD41 nenalezen."); 92 | Serial.println("Zkontroluj propojeni."); 93 | while(1) 94 | ; 95 | } 96 | 97 | // prepnuti do low power modu 98 | if (SCD41.startLowPowerPeriodicMeasurement() == true) 99 | { 100 | Serial.println("Low power mod povolen."); 101 | } 102 | 103 | /*------------- Wi-Fi -----------*/ 104 | WiFi.begin(ssid, password); 105 | Serial.println("Pripojovani"); 106 | while(WiFi.status() != WL_CONNECTED) { 107 | delay(500); 108 | Serial.print("."); 109 | } 110 | Serial.println(); 111 | Serial.print("Pripojeno do site, IP adresa zarizeni: "); 112 | Serial.println(WiFi.localIP()); 113 | } 114 | 115 | void loop() { 116 | /*------------- SCD41 - CO2, teplota, vlhkost -----------*/ 117 | int co2 = 0; 118 | float teplota = 0.0; 119 | int vlhkost = 0; 120 | 121 | while (!SCD41.readMeasurement()) // cekani na nova data (zhruba 30s) 122 | { 123 | delay(1); 124 | } 125 | 126 | co2 = SCD41.getCO2(); 127 | teplota = SCD41.getTemperature(); 128 | vlhkost = SCD41.getHumidity(); 129 | 130 | // odeslani hodnot pres UART 131 | Serial.print("Teplota: "); Serial.print(teplota); Serial.println(" degC"); 132 | Serial.print("Vlhkost: "); Serial.print(vlhkost); Serial.println("% rH"); 133 | Serial.print("CO2: "); Serial.print(co2); Serial.println(" ppm"); 134 | 135 | /*-------------- PM1006 - cidlo prasnosti ---------------*/ 136 | uint16_t pm2_5; 137 | digitalWrite(PIN_FAN, HIGH); 138 | Serial.println("Fan ON"); 139 | delay(30000); 140 | 141 | while (!pm1006.read_pm25(&pm2_5)) 142 | { 143 | delay(1); 144 | } 145 | 146 | delay(100); 147 | digitalWrite(PIN_FAN, LOW); 148 | Serial.println("Fan OFF"); 149 | 150 | // odeslani hodnot pres UART 151 | Serial.print("PM2.5: "); Serial.print(pm2_5); Serial.println(" ppm"); 152 | 153 | 154 | /*-------------- RGB adresovatelne LED ---------------*/ 155 | // CO2 LED 156 | if(co2 < 1000){ 157 | pixels.setPixelColor(CO2_LED, pixels.Color(0, 255, 0)); // R, G, B 158 | } 159 | 160 | if((co2 >= 1000) && (co2 < 1200)){ 161 | pixels.setPixelColor(CO2_LED, pixels.Color(128, 255, 0)); // R, G, B 162 | } 163 | 164 | if((co2 >= 1200) && (co2 < 1500)){ 165 | pixels.setPixelColor(CO2_LED, pixels.Color(255, 255, 0)); // R, G, B 166 | } 167 | 168 | if((co2 >= 1500) && (co2 < 2000)){ 169 | pixels.setPixelColor(CO2_LED, pixels.Color(255, 128, 0)); // R, G, B 170 | } 171 | 172 | if(co2 >= 2000){ 173 | pixels.setPixelColor(CO2_LED, pixels.Color(255, 0, 0)); // R, G, B 174 | } 175 | 176 | // teplota LED 177 | if(teplota < 20.0){ 178 | pixels.setPixelColor(TEMP_LED, pixels.Color(0, 0, 255)); // R, G, B 179 | } 180 | 181 | if((teplota >= 20.0) && (teplota < 23.0)){ 182 | pixels.setPixelColor(TEMP_LED, pixels.Color(0, 255, 0)); // R, G, B 183 | } 184 | 185 | if(teplota >= 23.0){ 186 | pixels.setPixelColor(TEMP_LED, pixels.Color(255, 0, 0)); // R, G, B 187 | } 188 | 189 | // PM LED 190 | if(pm2_5 < 30){ 191 | pixels.setPixelColor(PM_LED, pixels.Color(0, 255, 0)); // R, G, B 192 | } 193 | 194 | if((pm2_5 >= 30) && (pm2_5 < 40)){ 195 | pixels.setPixelColor(PM_LED, pixels.Color(128, 255, 0)); // R, G, B 196 | } 197 | 198 | if((pm2_5 >= 40) && (pm2_5 < 80)){ 199 | pixels.setPixelColor(PM_LED, pixels.Color(255, 255, 0)); // R, G, B 200 | } 201 | 202 | if((pm2_5 >= 80) && (pm2_5 < 90)){ 203 | pixels.setPixelColor(PM_LED, pixels.Color(255, 128, 0)); // R, G, B 204 | } 205 | 206 | if(pm2_5 >= 90){ 207 | pixels.setPixelColor(PM_LED, pixels.Color(255, 0, 0)); // R, G, B 208 | } 209 | pixels.show(); // Zaktualizuje barvu 210 | 211 | /*------------ Odeslani hodnot na TMEP.cz ------------------*/ 212 | if(WiFi.status()== WL_CONNECTED) 213 | { 214 | //GUID, nasleduje hodnota teploty, pro vlhkost "humV", pro CO2 "CO2" cidla SCD41 215 | String serverPathCO2 = serverNameCO2 + "" + GUID_CO2 + "=" + teplota + "&humV=" + vlhkost + "&CO2=" + co2; 216 | sendhttpGet(serverPathCO2); 217 | 218 | delay(100); 219 | //GUID, nasleduje hodnota cidla prasnosti PM1006 a odeslani na druhou domenu 220 | String serverPathPM = serverNamePM + "" + GUID_PM + "=" + pm2_5; 221 | sendhttpGet(serverPathPM); 222 | 223 | } 224 | else 225 | { 226 | Serial.println("Wi-Fi odpojeno"); 227 | } 228 | 229 | esp_sleep_enable_timer_wakeup(900 * 1000000); // uspani na 15 minut 230 | Serial2.flush(); 231 | Serial.flush(); 232 | delay(100); 233 | esp_deep_sleep_start(); 234 | } 235 | 236 | // funcke pro odeslani dat na TMEP.cz 237 | void sendhttpGet(String httpGet) 238 | { 239 | HTTPClient http; 240 | 241 | // odeslani dat 242 | String serverPath = httpGet; 243 | 244 | // zacatek http spojeni 245 | http.begin(serverPath.c_str()); 246 | 247 | // http get request 248 | int httpResponseCode = http.GET(); 249 | 250 | if (httpResponseCode>0) 251 | { 252 | Serial.print("HTTP odpoved: "); 253 | Serial.println(httpResponseCode); 254 | String payload = http.getString(); 255 | Serial.println(payload); 256 | } 257 | else 258 | { 259 | Serial.print("Error kod: "); 260 | Serial.println(httpResponseCode); 261 | } 262 | // Free resources 263 | http.end(); 264 | } 265 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The TAPR Open Hardware License 2 | Version 1.0 (May 25, 2007) 3 | Copyright 2007 TAPR - http://www.tapr.org/OHL 4 | 5 | PREAMBLE 6 | 7 | Open Hardware is a thing - a physical artifact, either electrical or 8 | mechanical - whose design information is available to, and usable by, 9 | the public in a way that allows anyone to make, modify, distribute, and 10 | use that thing. In this preface, design information is called 11 | "documentation" and things created from it are called "products." 12 | 13 | The TAPR Open Hardware License ("OHL") agreement provides a legal 14 | framework for Open Hardware projects. It may be used for any kind of 15 | product, be it a hammer or a computer motherboard, and is TAPR's 16 | contribution to the community; anyone may use the OHL for their Open 17 | Hardware project. 18 | 19 | Like the GNU General Public License, the OHL is designed to guarantee 20 | your freedom to share and to create. It forbids anyone who receives 21 | rights under the OHL to deny any other licensee those same rights to 22 | copy, modify, and distribute documentation, and to make, use and 23 | distribute products based on that documentation. 24 | 25 | Unlike the GPL, the OHL is not primarily a copyright license. While 26 | copyright protects documentation from unauthorized copying, modification, 27 | and distribution, it has little to do with your right to make, distribute, 28 | or use a product based on that documentation. For better or worse, patents 29 | play a significant role in those activities. Although it does not prohibit 30 | anyone from patenting inventions embodied in an Open Hardware design, and 31 | of course cannot prevent a third party from enforcing their patent rights, 32 | those who benefit from an OHL design may not bring lawsuits claiming that 33 | design infringes their patents or other intellectual property. 34 | 35 | The OHL addresses unique issues involved in the creation of tangible, 36 | physical things, but does not cover software, firmware, or code loaded 37 | into programmable devices. A copyright-oriented license such as the GPL 38 | better suits these creations. 39 | 40 | How can you use the OHL, or a design based upon it? While the terms and 41 | conditions below take precedence over this preamble, here is a summary: 42 | 43 | * You may modify the documentation and make products based upon it. 44 | 45 | * You may use products for any legal purpose without limitation. 46 | 47 | * You may distribute unmodified documentation, but you must include the 48 | complete package as you received it. 49 | 50 | * You may distribute products you make to third parties, if you either 51 | include the documentation on which the product is based, or make it 52 | available without charge for at least three years to anyone who requests 53 | it. 54 | 55 | * You may distribute modified documentation or products based on it, if 56 | you: 57 | * License your modifications under the OHL. 58 | * Include those modifications, following the requirements stated 59 | below. 60 | * Attempt to send the modified documentation by email to any of the 61 | developers who have provided their email address. This is a good 62 | faith obligation - if the email fails, you need do nothing more 63 | and may go on with your distribution. 64 | 65 | * If you create a design that you want to license under the OHL, you 66 | should: 67 | * Include this document in a file named LICENSE (with the appropriate 68 | extension) that is included in the documentation package. 69 | * If the file format allows, include a notice like "Licensed under 70 | the TAPR Open Hardware License (www.tapr.org/OHL)" in each 71 | documentation file. While not required, you should also include 72 | this notice on printed circuit board artwork and the product 73 | itself; if space is limited the notice can be shortened or 74 | abbreviated. 75 | * Include a copyright notice in each file and on printed circuit 76 | board artwork. 77 | * If you wish to be notified of modifications that others may make, 78 | include your email address in a file named "CONTRIB.TXT" or 79 | something similar. 80 | 81 | * Any time the OHL requires you to make documentation available to 82 | others, you must include all the materials you received from the 83 | upstream licensors. In addition, if you have modified the 84 | documentation: 85 | * You must identify the modifications in a text file (preferably 86 | named "CHANGES.TXT") that you include with the documentation. 87 | That file must also include a statement like "These modifications 88 | are licensed under the TAPR Open Hardware License." 89 | * You must include any new files you created, including any 90 | manufacturing files (such as Gerber files) you create in the 91 | course of making products. 92 | * You must include both "before" and "after" versions of all files 93 | you modified. 94 | * You may include files in proprietary formats, but you must also 95 | include open format versions (such as Gerber, ASCII, Postscript, 96 | or PDF) if your tools can create them. 97 | 98 | TERMS AND CONDITIONS 99 | 100 | 1. Introduction 101 | 1.1 This Agreement governs how you may use, copy, modify, and 102 | distribute Documentation, and how you may make, have made, and 103 | distribute Products based on that Documentation. As used in this 104 | Agreement, to "distribute" Documentation means to directly or indirectly 105 | make copies available to a third party, and to "distribute" Products 106 | means to directly or indirectly give, loan, sell or otherwise transfer 107 | them to a third party. 108 | 109 | 1.2 "Documentation" includes: 110 | (a) schematic diagrams; 111 | (b) circuit or circuit board layouts, including Gerber and other 112 | data files used for manufacture; 113 | (c) mechanical drawings, including CAD, CAM, and other data files 114 | used for manufacture; 115 | (d) flow charts and descriptive text; and 116 | (e) other explanatory material. 117 | Documentation may be in any tangible or intangible form of expression, 118 | including but not limited to computer files in open or proprietary 119 | formats and representations on paper, film, or other media. 120 | 121 | 1.3 "Products" include: 122 | (a) circuit boards, mechanical assemblies, and other physical parts 123 | and components; 124 | (b) assembled or partially assembled units (including components 125 | and subassemblies); and 126 | (c) parts and components combined into kits intended for assembly 127 | by others; 128 | which are based in whole or in part on the Documentation. 129 | 130 | 1.4 This Agreement applies to any Documentation which contains a 131 | notice stating it is subject to the TAPR Open Hardware License, and to 132 | all Products based in whole or in part on that Documentation. If 133 | Documentation is distributed in an archive (such as a "zip" file) which 134 | includes this document, all files in that archive are subject to this 135 | Agreement unless they are specifically excluded. Each person who 136 | contributes content to the Documentation is referred to in this 137 | Agreement as a "Licensor." 138 | 139 | 1.5 By (a) using, copying, modifying, or distributing the 140 | Documentation, or (b) making or having Products made or distributing 141 | them, you accept this Agreement, agree to comply with its terms, and 142 | become a "Licensee." Any activity inconsistent with this Agreement will 143 | automatically terminate your rights under it (including the immunities 144 | from suit granted in Section 2), but the rights of others who have 145 | received Documentation, or have obtained Products, directly or 146 | indirectly from you will not be affected so long as they fully comply 147 | with it themselves. 148 | 149 | 1.6 This Agreement does not apply to software, firmware, or code 150 | loaded into programmable devices which may be used in conjunction with 151 | Documentation or Products. Such software is subject to the license 152 | terms established by its copyright holder(s). 153 | 154 | 2. Patents 155 | 2.1 Each Licensor grants you, every other Licensee, and every 156 | possessor or user of Products a perpetual, worldwide, and royalty-free 157 | immunity from suit under any patent, patent application, or other 158 | intellectual property right which he or she controls, to the extent 159 | necessary to make, have made, possess, use, and distribute Products. 160 | This immunity does not extend to infringement arising from modifications 161 | subsequently made by others. 162 | 163 | 2.2 If you make or have Products made, or distribute Documentation 164 | that you have modified, you grant every Licensor, every other Licensee, 165 | and every possessor or user of Products a perpetual, worldwide, and 166 | royalty-free immunity from suit under any patent, patent application, or 167 | other intellectual property right which you control, to the extent 168 | necessary to make, have made, possess, use, and distribute Products. 169 | This immunity does not extend to infringement arising from modifications 170 | subsequently made by others. 171 | 172 | 2.3 To avoid doubt, providing Documentation to a third party for the 173 | sole purpose of having that party make Products on your behalf is not 174 | considered "distribution,"\" and a third party's act of making Products 175 | solely on your behalf does not cause that party to grant the immunity 176 | described in the preceding paragraph. 177 | 178 | 2.4 These grants of immunity are a material part of this Agreement, 179 | and form a portion of the consideration given by each party to the 180 | other. If any court judgment or legal agreement prevents you from 181 | granting the immunity required by this Section, your rights under this 182 | Agreement will terminate and you may no longer use, copy, modify or 183 | distribute the Documentation, or make, have made, or distribute 184 | Products. 185 | 186 | 3. Modifications 187 | You may modify the Documentation, and those modifications will become 188 | part of the Documentation. They are subject to this Agreement, as are 189 | Products based in whole or in part on them. If you distribute the 190 | modified Documentation, or Products based in whole or in part upon it, 191 | you must email the modified Documentation in a form compliant with 192 | Section 4 to each Licensor who has provided an email address with the 193 | Documentation. Attempting to send the email completes your obligations 194 | under this Section and you need take no further action if any address 195 | fails. 196 | 197 | 4. Distributing Documentation 198 | 4.1 You may distribute unmodified copies of the Documentation in its 199 | entirety in any medium, provided that you retain all copyright and other 200 | notices (including references to this Agreement) included by each 201 | Licensor, and include an unaltered copy of this Agreement. 202 | 4.2 You may distribute modified copies of the Documentation if you 203 | comply with all the requirements of the preceding paragraph and: 204 | (a) include a prominent notice in an ASCII or other open format 205 | file identifying those elements of the Documentation that you 206 | changed, and stating that the modifications are licensed under 207 | the terms of this Agreement; 208 | (b) include all new documentation files that you create, as well as 209 | both the original and modified versions of each file you change 210 | (files may be in your development tool's native file format, 211 | but if reasonably possible, you must also include open format, 212 | such as Gerber, ASCII, Postscript, or PDF, versions); 213 | (c) do not change the terms of this Agreement with respect to 214 | subsequent licensees; and 215 | (d) if you make or have Products made, include in the Documentation 216 | all elements reasonably required to permit others to make 217 | Products, including Gerber, CAD/CAM and other files used for 218 | manufacture. 219 | 220 | 5. Making Products 221 | 5.1 You may use the Documentation to make or have Products made, 222 | provided that each Product retains any notices included by the Licensor 223 | (including, but not limited to, copyright notices on circuit boards). 224 | 5.2 You may distribute Products you make or have made, provided that 225 | you include with each unit a copy of the Documentation in a form 226 | consistent with Section 4. Alternatively, you may include either (i) an 227 | offer valid for at least three years to provide that Documentation, at 228 | no charge other than the reasonable cost of media and postage, to any 229 | person who requests it; or (ii) a URL where that Documentation may be 230 | downloaded, available for at least three years after you last distribute 231 | the Product. 232 | 233 | 6. NEW LICENSE VERSIONS 234 | TAPR may publish updated versions of the OHL which retain the same 235 | general provisions as the present version, but differ in detail to 236 | address new problems or concerns, and carry a distinguishing version 237 | number. If the Documentation specifies a version number which applies 238 | to it and "any later version", you may choose either that version or any 239 | later version published by TAPR. If the Documentation does not specify 240 | a version number, you may choose any version ever published by TAPR. 241 | TAPR owns the copyright to the OHL, but grants permission to any person 242 | to copy, distribute, and use it in unmodified form. 243 | 244 | 7. WARRANTY AND LIABILITY LIMITATIONS 245 | 7.1 THE DOCUMENTATION IS PROVIDED ON AN"AS-IS" BASIS WITHOUT 246 | WARRANTY OF ANY KIND, TO THE EXTENT PERMITTED BY APPLICABLE LAW. ALL 247 | WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY 248 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND 249 | TITLE, ARE HEREBY EXPRESSLY DISCLAIMED. 250 | 7.2 IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW WILL ANY LICENSOR 251 | BE LIABLE TO YOU OR ANY THIRD PARTY FOR ANY DIRECT, INDIRECT, 252 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, OR EXEMPLARY DAMAGES ARISING OUT OF 253 | THE USE OF, OR INABILITY TO USE, THE DOCUMENTATION OR PRODUCTS, 254 | INCLUDING BUT NOT LIMITED TO CLAIMS OF INTELLECTUAL PROPERTY 255 | INFRINGEMENT OR LOSS OF DATA, EVEN IF THAT PARTY HAS BEEN ADVISED OF THE 256 | POSSIBILITY OF SUCH DAMAGES. 257 | 7.3 You agree that the foregoing limitations are reasonable due to 258 | the non-financial nature of the transaction represented by this 259 | Agreement, and acknowledge that were it not for these limitations, the 260 | Licensor(s) would not be willing to make the Documentation available to 261 | you. 262 | 7.4 You agree to defend, indemnify, and hold each Licensor harmless 263 | from any claim brought by a third party alleging any defect in the 264 | design, manufacture, or operation of any Product which you make, have 265 | made, or distribute pursuant to this Agreement. 266 | #### 267 | --------------------------------------------------------------------------------