Update arduinio sources, overhal some basic folder structures, further implementation of plant dashboard/monitor build

This commit is contained in:
KemoNine 2019-05-27 13:29:03 -04:00
parent 28fc1b72fd
commit 389fdce9aa
51 changed files with 1192 additions and 71 deletions

View File

@ -47,10 +47,10 @@ mqtt:
discovery: true
discovery_prefix: homeassistant
birth_message:
topic: 'hass/status'
topic: 'ha/status'
payload: 'online'
will_message:
topic: 'hass/status'
topic: 'ha/status'
payload: 'offline'
# ZWave integration

View File

@ -0,0 +1,3 @@
# isp
The ISP programmer code, schematics and more used for programming the ATTiny chips used elsewhere.

View File

@ -0,0 +1,639 @@
// ArduinoISP
// Copyright (c) 2008-2011 Randall Bohn
// If you require a license, see
// http://www.opensource.org/licenses/bsd-license.php
//
// This sketch turns the Arduino into a AVRISP using the following Arduino pins:
//
// Put an LED (with resistor) on the following pins:
// 9: Heartbeat - shows the programmer is running
// 8: Error - Lights up if something goes wrong (use red if that makes sense)
// 7: Programming - In communication with the slave
//
#include "Arduino.h"
#undef SERIAL
#define PROG_FLICKER true
// Configure SPI clock (in Hz).
// E.g. for an ATtiny @ 128 kHz: the datasheet states that both the high and low
// SPI clock pulse must be > 2 CPU cycles, so take 3 cycles i.e. divide target
// f_cpu by 6:
// #define SPI_CLOCK (128000/6)
//
// A clock slow enough for an ATtiny85 @ 1 MHz, is a reasonable default:
#define SPI_CLOCK (1000000/6)
// Configure which pins to use:
#define PIN_MOSI 11
#define PIN_MISO 12
#define PIN_SCK 13
#undef USE_HARDWARE_SPI
#define RESET 4
#define LED_HB 7
#define LED_ERR 6
#define LED_PMODE 5
// Configure the serial port to use.
//
// Prefer the USB virtual serial port (aka. native USB port), if the Arduino has one:
// - it does not autoreset (except for the magic baud rate of 1200).
// - it is more reliable because of USB handshaking.
//
// Leonardo and similar have an USB virtual serial port: 'Serial'.
// Due and Zero have an USB virtual serial port: 'SerialUSB'.
//
// On the Due and Zero, 'Serial' can be used too, provided you disable autoreset.
// To use 'Serial': #define SERIAL Serial
#ifdef SERIAL_PORT_USBVIRTUAL
#define SERIAL SERIAL_PORT_USBVIRTUAL
#else
#define SERIAL Serial
#endif
// Configure the baud rate:
#define BAUDRATE 19200
// #define BAUDRATE 115200
// #define BAUDRATE 1000000
#define HWVER 2
#define SWMAJ 1
#define SWMIN 18
// STK Definitions
#define STK_OK 0x10
#define STK_FAILED 0x11
#define STK_UNKNOWN 0x12
#define STK_INSYNC 0x14
#define STK_NOSYNC 0x15
#define CRC_EOP 0x20 //ok it is a space...
void pulse(int pin, int times);
#ifdef USE_HARDWARE_SPI
#include "SPI.h"
#else
#define SPI_MODE0 0x00
class SPISettings {
public:
// clock is in Hz
SPISettings(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) : clock(clock) {
(void) bitOrder;
(void) dataMode;
};
private:
uint32_t clock;
friend class BitBangedSPI;
};
class BitBangedSPI {
public:
void begin() {
digitalWrite(PIN_SCK, LOW);
digitalWrite(PIN_MOSI, LOW);
pinMode(PIN_SCK, OUTPUT);
pinMode(PIN_MOSI, OUTPUT);
pinMode(PIN_MISO, INPUT);
}
void beginTransaction(SPISettings settings) {
pulseWidth = (500000 + settings.clock - 1) / settings.clock;
if (pulseWidth == 0)
pulseWidth = 1;
}
void end() {}
uint8_t transfer (uint8_t b) {
for (unsigned int i = 0; i < 8; ++i) {
digitalWrite(PIN_MOSI, (b & 0x80) ? HIGH : LOW);
digitalWrite(PIN_SCK, HIGH);
delayMicroseconds(pulseWidth);
b = (b << 1) | digitalRead(PIN_MISO);
digitalWrite(PIN_SCK, LOW); // slow pulse
delayMicroseconds(pulseWidth);
}
return b;
}
private:
unsigned long pulseWidth; // in microseconds
};
static BitBangedSPI SPI;
#endif
void setup() {
SERIAL.begin(BAUDRATE);
pinMode(LED_PMODE, OUTPUT);
pulse(LED_PMODE, 2);
pinMode(LED_ERR, OUTPUT);
pulse(LED_ERR, 2);
pinMode(LED_HB, OUTPUT);
pulse(LED_HB, 2);
}
int error = 0;
int pmode = 0;
// address for reading and writing, set by 'U' command
unsigned int here;
uint8_t buff[256]; // global block storage
#define beget16(addr) (*addr * 256 + *(addr+1) )
typedef struct param {
uint8_t devicecode;
uint8_t revision;
uint8_t progtype;
uint8_t parmode;
uint8_t polling;
uint8_t selftimed;
uint8_t lockbytes;
uint8_t fusebytes;
uint8_t flashpoll;
uint16_t eeprompoll;
uint16_t pagesize;
uint16_t eepromsize;
uint32_t flashsize;
}
parameter;
parameter param;
// this provides a heartbeat on pin 9, so you can tell the software is running.
uint8_t hbval = 128;
int8_t hbdelta = 8;
void heartbeat() {
static unsigned long last_time = 0;
unsigned long now = millis();
if ((now - last_time) < 40)
return;
last_time = now;
if (hbval > 192) hbdelta = -hbdelta;
if (hbval < 32) hbdelta = -hbdelta;
hbval += hbdelta;
analogWrite(LED_HB, hbval);
}
static bool rst_active_high;
void reset_target(bool reset) {
digitalWrite(RESET, ((reset && rst_active_high) || (!reset && !rst_active_high)) ? HIGH : LOW);
}
void loop(void) {
// is pmode active?
if (pmode) {
digitalWrite(LED_PMODE, HIGH);
} else {
digitalWrite(LED_PMODE, LOW);
}
// is there an error?
if (error) {
digitalWrite(LED_ERR, HIGH);
} else {
digitalWrite(LED_ERR, LOW);
}
// light the heartbeat LED
heartbeat();
if (SERIAL.available()) {
avrisp();
}
}
uint8_t getch() {
while (!SERIAL.available());
return SERIAL.read();
}
void fill(int n) {
for (int x = 0; x < n; x++) {
buff[x] = getch();
}
}
#define PTIME 30
void pulse(int pin, int times) {
do {
digitalWrite(pin, HIGH);
delay(PTIME);
digitalWrite(pin, LOW);
delay(PTIME);
} while (times--);
}
void prog_lamp(int state) {
if (PROG_FLICKER) {
digitalWrite(LED_PMODE, state);
}
}
uint8_t spi_transaction(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
SPI.transfer(a);
SPI.transfer(b);
SPI.transfer(c);
return SPI.transfer(d);
}
void empty_reply() {
if (CRC_EOP == getch()) {
SERIAL.print((char)STK_INSYNC);
SERIAL.print((char)STK_OK);
} else {
error++;
SERIAL.print((char)STK_NOSYNC);
}
}
void breply(uint8_t b) {
if (CRC_EOP == getch()) {
SERIAL.print((char)STK_INSYNC);
SERIAL.print((char)b);
SERIAL.print((char)STK_OK);
} else {
error++;
SERIAL.print((char)STK_NOSYNC);
}
}
void get_version(uint8_t c) {
switch (c) {
case 0x80:
breply(HWVER);
break;
case 0x81:
breply(SWMAJ);
break;
case 0x82:
breply(SWMIN);
break;
case 0x93:
breply('S'); // serial programmer
break;
default:
breply(0);
}
}
void set_parameters() {
// call this after reading parameter packet into buff[]
param.devicecode = buff[0];
param.revision = buff[1];
param.progtype = buff[2];
param.parmode = buff[3];
param.polling = buff[4];
param.selftimed = buff[5];
param.lockbytes = buff[6];
param.fusebytes = buff[7];
param.flashpoll = buff[8];
// ignore buff[9] (= buff[8])
// following are 16 bits (big endian)
param.eeprompoll = beget16(&buff[10]);
param.pagesize = beget16(&buff[12]);
param.eepromsize = beget16(&buff[14]);
// 32 bits flashsize (big endian)
param.flashsize = buff[16] * 0x01000000
+ buff[17] * 0x00010000
+ buff[18] * 0x00000100
+ buff[19];
// AVR devices have active low reset, AT89Sx are active high
rst_active_high = (param.devicecode >= 0xe0);
}
void start_pmode() {
// Reset target before driving PIN_SCK or PIN_MOSI
// SPI.begin() will configure SS as output, so SPI master mode is selected.
// We have defined RESET as pin 10, which for many Arduinos is not the SS pin.
// So we have to configure RESET as output here,
// (reset_target() first sets the correct level)
reset_target(true);
pinMode(RESET, OUTPUT);
SPI.begin();
SPI.beginTransaction(SPISettings(SPI_CLOCK, MSBFIRST, SPI_MODE0));
// See AVR datasheets, chapter "SERIAL_PRG Programming Algorithm":
// Pulse RESET after PIN_SCK is low:
digitalWrite(PIN_SCK, LOW);
delay(20); // discharge PIN_SCK, value arbitrarily chosen
reset_target(false);
// Pulse must be minimum 2 target CPU clock cycles so 100 usec is ok for CPU
// speeds above 20 KHz
delayMicroseconds(100);
reset_target(true);
// Send the enable programming command:
delay(50); // datasheet: must be > 20 msec
spi_transaction(0xAC, 0x53, 0x00, 0x00);
pmode = 1;
}
void end_pmode() {
SPI.end();
// We're about to take the target out of reset so configure SPI pins as input
pinMode(PIN_MOSI, INPUT);
pinMode(PIN_SCK, INPUT);
reset_target(false);
pinMode(RESET, INPUT);
pmode = 0;
}
void universal() {
uint8_t ch;
fill(4);
ch = spi_transaction(buff[0], buff[1], buff[2], buff[3]);
breply(ch);
}
void flash(uint8_t hilo, unsigned int addr, uint8_t data) {
spi_transaction(0x40 + 8 * hilo,
addr >> 8 & 0xFF,
addr & 0xFF,
data);
}
void commit(unsigned int addr) {
if (PROG_FLICKER) {
prog_lamp(LOW);
}
spi_transaction(0x4C, (addr >> 8) & 0xFF, addr & 0xFF, 0);
if (PROG_FLICKER) {
delay(PTIME);
prog_lamp(HIGH);
}
}
unsigned int current_page() {
if (param.pagesize == 32) {
return here & 0xFFFFFFF0;
}
if (param.pagesize == 64) {
return here & 0xFFFFFFE0;
}
if (param.pagesize == 128) {
return here & 0xFFFFFFC0;
}
if (param.pagesize == 256) {
return here & 0xFFFFFF80;
}
return here;
}
void write_flash(int length) {
fill(length);
if (CRC_EOP == getch()) {
SERIAL.print((char) STK_INSYNC);
SERIAL.print((char) write_flash_pages(length));
} else {
error++;
SERIAL.print((char) STK_NOSYNC);
}
}
uint8_t write_flash_pages(int length) {
int x = 0;
unsigned int page = current_page();
while (x < length) {
if (page != current_page()) {
commit(page);
page = current_page();
}
flash(LOW, here, buff[x++]);
flash(HIGH, here, buff[x++]);
here++;
}
commit(page);
return STK_OK;
}
#define EECHUNK (32)
uint8_t write_eeprom(unsigned int length) {
// here is a word address, get the byte address
unsigned int start = here * 2;
unsigned int remaining = length;
if (length > param.eepromsize) {
error++;
return STK_FAILED;
}
while (remaining > EECHUNK) {
write_eeprom_chunk(start, EECHUNK);
start += EECHUNK;
remaining -= EECHUNK;
}
write_eeprom_chunk(start, remaining);
return STK_OK;
}
// write (length) bytes, (start) is a byte address
uint8_t write_eeprom_chunk(unsigned int start, unsigned int length) {
// this writes byte-by-byte, page writing may be faster (4 bytes at a time)
fill(length);
prog_lamp(LOW);
for (unsigned int x = 0; x < length; x++) {
unsigned int addr = start + x;
spi_transaction(0xC0, (addr >> 8) & 0xFF, addr & 0xFF, buff[x]);
delay(45);
}
prog_lamp(HIGH);
return STK_OK;
}
void program_page() {
char result = (char) STK_FAILED;
unsigned int length = 256 * getch();
length += getch();
char memtype = getch();
// flash memory @here, (length) bytes
if (memtype == 'F') {
write_flash(length);
return;
}
if (memtype == 'E') {
result = (char)write_eeprom(length);
if (CRC_EOP == getch()) {
SERIAL.print((char) STK_INSYNC);
SERIAL.print(result);
} else {
error++;
SERIAL.print((char) STK_NOSYNC);
}
return;
}
SERIAL.print((char)STK_FAILED);
return;
}
uint8_t flash_read(uint8_t hilo, unsigned int addr) {
return spi_transaction(0x20 + hilo * 8,
(addr >> 8) & 0xFF,
addr & 0xFF,
0);
}
char flash_read_page(int length) {
for (int x = 0; x < length; x += 2) {
uint8_t low = flash_read(LOW, here);
SERIAL.print((char) low);
uint8_t high = flash_read(HIGH, here);
SERIAL.print((char) high);
here++;
}
return STK_OK;
}
char eeprom_read_page(int length) {
// here again we have a word address
int start = here * 2;
for (int x = 0; x < length; x++) {
int addr = start + x;
uint8_t ee = spi_transaction(0xA0, (addr >> 8) & 0xFF, addr & 0xFF, 0xFF);
SERIAL.print((char) ee);
}
return STK_OK;
}
void read_page() {
char result = (char)STK_FAILED;
int length = 256 * getch();
length += getch();
char memtype = getch();
if (CRC_EOP != getch()) {
error++;
SERIAL.print((char) STK_NOSYNC);
return;
}
SERIAL.print((char) STK_INSYNC);
if (memtype == 'F') result = flash_read_page(length);
if (memtype == 'E') result = eeprom_read_page(length);
SERIAL.print(result);
}
void read_signature() {
if (CRC_EOP != getch()) {
error++;
SERIAL.print((char) STK_NOSYNC);
return;
}
SERIAL.print((char) STK_INSYNC);
uint8_t high = spi_transaction(0x30, 0x00, 0x00, 0x00);
SERIAL.print((char) high);
uint8_t middle = spi_transaction(0x30, 0x00, 0x01, 0x00);
SERIAL.print((char) middle);
uint8_t low = spi_transaction(0x30, 0x00, 0x02, 0x00);
SERIAL.print((char) low);
SERIAL.print((char) STK_OK);
}
//////////////////////////////////////////
//////////////////////////////////////////
////////////////////////////////////
////////////////////////////////////
void avrisp() {
uint8_t ch = getch();
switch (ch) {
case '0': // signon
error = 0;
empty_reply();
break;
case '1':
if (getch() == CRC_EOP) {
SERIAL.print((char) STK_INSYNC);
SERIAL.print("AVR ISP");
SERIAL.print((char) STK_OK);
}
else {
error++;
SERIAL.print((char) STK_NOSYNC);
}
break;
case 'A':
get_version(getch());
break;
case 'B':
fill(20);
set_parameters();
empty_reply();
break;
case 'E': // extended parameters - ignore for now
fill(5);
empty_reply();
break;
case 'P':
if (!pmode)
start_pmode();
empty_reply();
break;
case 'U': // set address (word)
here = getch();
here += 256 * getch();
empty_reply();
break;
case 0x60: //STK_PROG_FLASH
getch(); // low addr
getch(); // high addr
empty_reply();
break;
case 0x61: //STK_PROG_DATA
getch(); // data
empty_reply();
break;
case 0x64: //STK_PROG_PAGE
program_page();
break;
case 0x74: //STK_READ_PAGE 't'
read_page();
break;
case 'V': //0x56
universal();
break;
case 'Q': //0x51
error = 0;
end_pmode();
empty_reply();
break;
case 0x75: //STK_READ_SIGN 'u'
read_signature();
break;
// expecting a command, not CRC_EOP
// this is how we can get back in sync
case CRC_EOP:
error++;
SERIAL.print((char) STK_NOSYNC);
break;
// anything else we will return STK_UNKNOWN
default:
error++;
if (CRC_EOP == getch())
SERIAL.print((char)STK_UNKNOWN);
else
SERIAL.print((char)STK_NOSYNC);
}
}

View File

@ -0,0 +1,15 @@
# i2c_dip
A *simple* i2c based dip switch that can be queried as a sensor.
This build uses an ATTiny 2313 / 4313 for the i2c slave with an 8 pin dip switch. The status of all 8 pins on the dip switch are returned via i2c as a single byte that can be used to determine state of all 8.
## Folders
### slave_sender
The main ATTiny code that reads the dip switch states and sends back a single byte with each dip's state encoded as a ```0``` for ```off``` and ```1``` for on.
### master_reader
Sample code used on an Arduino for testing purposes.

View File

@ -0,0 +1,3 @@
# master_reader
Sample code used on an Arduino for testing purposes.

View File

@ -0,0 +1,30 @@
// Wire Master Reader
// by Nicholas Zambetti <http://www.zambetti.com>
// Demonstrates use of the Wire library
// Reads data from an I2C/TWI slave device
// Refer to the "Wire Slave Sender" example for use with this
// Created 29 March 2006
// This example code is in the public domain.
#include <Wire.h>
void setup() {
Wire.begin(); // join i2c bus (address optional for master)
Serial.begin(115200); // start serial for output
while(!Serial);
}
void loop() {
Wire.requestFrom(0x5F, sizeof(int)); // request 6 bytes from slave device #8
//while (Wire.available()) { // slave may send less than requested
uint8_t value = Wire.read(); // receive a byte as character
Serial.println(value); // print the character
//}
delay(500);
}

View File

@ -0,0 +1,3 @@
# slave_sender
The main ATTiny code that reads the dip switch states and sends back a single byte with each dip's state encoded as a ```0``` for ```off``` and ```1``` for on.

View File

@ -0,0 +1,69 @@
// need 0.1uf cap between vcc and ground for proper operation
// need 10k pull up resistor on scl and sda for i2c to work correctly
// Pin Mappings (Per TinyCore)
// ATTiny Pin -- Arduino Pin
// 01 17/PA2/RESET
// 02 0/PD0/RXD
// 03 1/PD1/TXD
// 04 2/PA1/XTAL2
// 05 3/PA0/XTAL1
// 06 4/PD2/INT0
// 07 5/PD3/INT1
// 08 6/PD4
// 09 7/PD5
// 10 GND
// 11 8/PD6
// 12 9/PB0/PCINT0
// 13 10/PB1/PCINT1
// 14 11/PB2/PCINT2/OC0A
// 15 12/PB3/PCINT3/OC1A
// 16 13/PB4/PCINT4/OC1B
// 17 14/PB5/PCINT5/MOSI/DI/SDA
// 18 15/PB6/PCINT6/MISO/DO
// 19 16/PB7/PCINT7/SCK/SCL
// 20 VCC
// i2c ATTiny Pins
// sda: 17
// scl: 19
// DIP Switch ATTiny Pins
// 2 - 9
#define I2C_ADDRESS 0x96
#include <Wire.h>
void setup() {
pinMode(0, INPUT);
pinMode(1, INPUT);
pinMode(2, INPUT);
pinMode(3, INPUT);
pinMode(4, INPUT);
pinMode(5, INPUT);
pinMode(6, INPUT);
pinMode(7, INPUT);
Wire.begin(I2C_ADDRESS);
Wire.onRequest(requestEvent);
}
void loop() {
delay(100);
}
// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
byte dip_switch = 0b00000000;
void requestEvent() {
bitWrite(dip_switch, 0, digitalRead(0));
bitWrite(dip_switch, 1, digitalRead(1));
bitWrite(dip_switch, 2, digitalRead(2));
bitWrite(dip_switch, 3, digitalRead(3));
bitWrite(dip_switch, 4, digitalRead(4));
bitWrite(dip_switch, 5, digitalRead(5));
bitWrite(dip_switch, 6, digitalRead(6));
bitWrite(dip_switch, 7, digitalRead(7));
Wire.write(dip_switch);
}

View File

@ -0,0 +1,3 @@
# Water Level Sensor
A water level meter adapted to i2c using an ATTiny85

View File

@ -0,0 +1,38 @@
// need 0.1uf cap between vcc and ground for proper operation
// need 10k pull up resistor on scl and sda for i2c to work correctly
// Pin Mappings (Per TinyCore)
// ATTiny Pin -- Arduino Pin
// 01 RESET
// 02 ADC3/PB3
// 03 ADC2/PB4
// 04 GND
// 05 PB0/AIN0/SDA
// 06 PB1/AIN1
// 07 PB2/ADC1/SCL
// 08 VCC
// i2c ATTiny Pins
// sda: 5
// scl: 7
#define I2C_ADDRESS 0x5F
#include <Wire.h>
void setup() {
pinMode(6, INPUT);
Wire.begin(I2C_ADDRESS);
Wire.onRequest(requestEvent);
}
void loop() {
delay(100);
}
// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent() {
Wire.write((uint8_t) analogRead(6)); // Only get 0 - 1023 back so move this into a 1 byte int
}

View File

@ -2,41 +2,43 @@
A SparkFun ```Thing Plus ESP32 WROOM``` based plant monitor and dashboard. This hardware build leverages the ```esphome``` platform to monitor up to 8 plants with a Waveshare 2.9" e-ink display to show status information.
Long-term the dashboard will also support monitoring remote plants that are not directly tied into the main board via MQTT subscriptions (see ```Future Mini Build``` for hardware ideas).
Long-term the dashboard will also support monitoring remote plants that are not directly tied into the main board via MQTT subscriptions (see ```plant_monitor``` folder for additional ideas).
The sensors and data are tied back to a home-assistant.io deployment using MQTT for tracking information inside Home Assistant. The local dashboard is meant to help remind the user (me) that plants need watering when close to the plants themselves.
Please note: *this is an indoor build*. Despite using parts that are safe for use outdoors it's assumed this build will live indoors. The e-ink display is *not* rated for outdoor use. Other components may not be viable for outdoor use as well.
## Current Status
Please be aware the information contained in this repo isn't always 100% presently. This is very much a work in progress build and integration. I will do my best to keep the information here current but that may not always be the case.
## Hardware / Bill Of Materials
| Quantity | Hardware | Product Page |
| -------- | --------------------------------------- | ----------------------------------------------------------------------------------------- |
| 1 | SparkFun Thing Plus - ESP32 WROOM | https://www.sparkfun.com/products/14689 |
| 1 | Adafruit HUZZAH32 | https://www.adafruit.com/product/4172 |
| 1 | SparkFun Qwiic Mux Breakout - 8 Channel | https://www.sparkfun.com/products/14685 |
| 8 | SparkFun Qwiic Adapter | https://www.sparkfun.com/products/14495 |
| 1 | Qwiic Cable - 50mm | https://www.sparkfun.com/products/14426 |
| 2 | Qwiic Cable - 50mm | https://www.sparkfun.com/products/14426 |
| 8 | Qwiic Cable - 500mm | https://www.sparkfun.com/products/14429 |
| 1 | Waveshare 2.9" e-ink display | https://www.waveshare.com/product/modules/oleds-lcds/e-paper/2.9inch-e-paper-module.htm |
| 1 | 18650 Battery Holder | https://www.sparkfun.com/products/12899 |
| 15 | 4 pin 2.54mm pitch screw terminals | N/A |
| 1 | DF Robot Solar Charger (MQTT) | https://www.dfrobot.com/product-1712.html |
| 1 | Adafruit Large Solar Panel | https://www.adafruit.com/product/500 |
| 1 | SparkFun LiPo Fuel Gauge | https://www.sparkfun.com/products/10617 |
| 1 | SparkFun UV Sensor (VEML6075) | https://www.sparkfun.com/products/15089 |
| 2 | SparkFun MOSTFET Power Controller | https://www.sparkfun.com/products/11214 |
## Software
- [https://esphome.io/components/ota.html](https://esphome.io/components/ota.html)
- See deep sleep docs for a way to publish via mqtt to keep alive for updates
- [https://community.home-assistant.io/t/hadashboard-publishing-an-mqtt-message/58614](https://community.home-assistant.io/t/hadashboard-publishing-an-mqtt-message/58614)
- [https://community.home-assistant.io/t/mqtt-publish-in-scripts-yaml/29776](https://community.home-assistant.io/t/mqtt-publish-in-scripts-yaml/29776)
- [https://thenounproject.com/term/update/452099/](https://thenounproject.com/term/update/452099/)
- [https://thenounproject.com/term/download/356100/](https://thenounproject.com/term/download/356100/)
- [https://github.com/ageir/chirp-rpi](https://github.com/ageir/chirp-rpi)
- [https://github.com/Miceuz/i2c-moisture-sensor/blob/master/README.md](https://github.com/Miceuz/i2c-moisture-sensor/blob/master/README.md)
- [https://github.com/Apollon77/I2CSoilMoistureSensor](https://github.com/Apollon77/I2CSoilMoistureSensor)
| 1 | SparkFun Momentary Button (Red) | https://www.sparkfun.com/products/11992 |
| 1 | SparkFun Momentary Button (Blue) | https://www.sparkfun.com/products/11994 |
## Future Hardware
- [https://www.adafruit.com/product/3931](https://www.adafruit.com/product/3931)
- [https://www.tindie.com/products/miceuz/i2c-soil-moisture-sensor/](https://www.tindie.com/products/miceuz/i2c-soil-moisture-sensor/)
## Future/Posible Software
- [https://github.com/ageir/chirp-rpi](https://github.com/ageir/chirp-rpi)
- [https://github.com/Miceuz/i2c-moisture-sensor/blob/master/README.md](https://github.com/Miceuz/i2c-moisture-sensor/blob/master/README.md)
- [https://github.com/Apollon77/I2CSoilMoistureSensor](https://github.com/Apollon77/I2CSoilMoistureSensor)

View File

@ -0,0 +1,3 @@
# Extern
Useful resources pulled from upstream websites that publish details about chips and similar.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 560 KiB

After

Width:  |  Height:  |  Size: 529 KiB

View File

@ -0,0 +1,3 @@
# Fonts
Various open source fonts that are useful when building UIs

View File

@ -0,0 +1,3 @@
# Graphics
Various icons and graphics from Noun Project.

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,72 @@
#include "esphome.h"
#include "tca9548a.h"
#include <Wire.h>
#include <Adafruit_seesaw.h>
class LocalPlantsSensor : public PollingComponent, public sensor::Sensor {
private:
Adafruit_seesaw plant_1_ss;
public:
sensor::Sensor *plant_1_sensor = new sensor::Sensor();
sensor::Sensor *plant_2_sensor = new sensor::Sensor();
sensor::Sensor *plant_3_sensor = new sensor::Sensor();
sensor::Sensor *plant_4_sensor = new sensor::Sensor();
sensor::Sensor *plant_5_sensor = new sensor::Sensor();
sensor::Sensor *plant_6_sensor = new sensor::Sensor();
sensor::Sensor *plant_7_sensor = new sensor::Sensor();
sensor::Sensor *plant_8_sensor = new sensor::Sensor();
// 4294967295UL == never per sources
LocalPlantsSensor() : PollingComponent(60*60*1000) { }
void setup() override {
// Plant 1 : Adafruit STEMMA Soil Sensor
enableMuxPort(1);
plant_1_ss.begin(0x36);
disableMuxPort(1);
}
void update() override {
enableMuxPort(0);
double plant_1_value = -1;
disableMuxPort(0);
plant_1_sensor->publish_state(plant_1_value);
enableMuxPort(1);
double plant_2_value = plant_1_ss.touchRead(0);
disableMuxPort(1);
plant_2_sensor->publish_state(plant_2_value);
enableMuxPort(2);
double plant_3_value = -1;
disableMuxPort(2);
plant_3_sensor->publish_state(plant_3_value);
enableMuxPort(3);
double plant_4_value = -1;
disableMuxPort(3);
plant_4_sensor->publish_state(plant_4_value);
enableMuxPort(4);
double plant_5_value = -1;
disableMuxPort(4);
plant_5_sensor->publish_state(plant_5_value);
enableMuxPort(5);
double plant_6_value = -1;
disableMuxPort(5);
plant_6_sensor->publish_state(plant_6_value);
enableMuxPort(6);
double plant_7_value = -1;
disableMuxPort(6);
plant_7_sensor->publish_state(plant_7_value);
enableMuxPort(7);
double plant_8_value = -1;
disableMuxPort(7);
plant_8_sensor->publish_state(plant_8_value);
}
};

View File

@ -5,6 +5,8 @@
# GxEPD2 - 5869 - https://platformio.org/lib/show/5869/GxEPD2
# AsyncMqttClient - 346 - https://platformio.org/lib/show/346/AsyncMqttClient/
# Adafruit GFX Library - 13 - https://platformio.org/lib/show/13/Adafruit GFX Library/
# SparkFun VEML6075 Library - 5452 - https://platformio.org/lib/show/5452/SparkFun%20VEML6075%20Arduino%20Library
# Adafruit Seesaw - 1890 - https://platformio.org/lib/show/1890/Adafruit%20seesaw%20Library
# Node Config
esphome:
@ -16,45 +18,98 @@ esphome:
includes:
- sht1x.h
- max1704xsensor.h
- veml6075.h
- local_plants.h
libraries:
- SPI
- Wire
- "346"
- "86"
- "5812"
- "5452"
- "1890"
on_boot:
- priority: 90.0 # 100 is highest where early init happens, 50.0 is where sensors come up
then:
- lambda: |-
id(early_boot) = true;
pinMode(13, OUTPUT);
digitalWrite(13, HIGH);
delay(10);
delay(100);
- priority: -100
then:
- lambda: |-
ESP_LOGD("on_boot: -10:", "Starting sensor updates");
id(early_boot) = false;
ESP_LOGD("on_boot: -10:", "Starting sensor updates");
//////////////////////////////////////////////////
ESP_LOGD("on_boot: -10:", "WiFi");
id(sensor_wifi_signal)->update();
ESP_LOGD("on_boot: -10:", "END WiFi");
//////////////////////////////////////////////////
//ESP_LOGD("on_boot: -10:", "UV");
//id(ptr_component_veml6075)->update();
//ESP_LOGD("on_boot: -10:", "END UV");
//////////////////////////////////////////////////
//ESP_LOGD("on_boot: -10:", "delay(500)");
delay(500);
ESP_LOGD("on_boot: -10:", "END delay(500)");
//////////////////////////////////////////////////
ESP_LOGD("on_boot: -10:", "shtx1");
((SHT1xSensor*)id(ptr_component_shtx1))->update();
ESP_LOGD("on_boot: -10:", "END shtx1");
//////////////////////////////////////////////////
ESP_LOGD("on_boot: -10:", "delay(500)");
delay(500);
ESP_LOGD("on_boot: -10:", "END delay(500)");
//////////////////////////////////////////////////
ESP_LOGD("on_boot: -10:", "max1704x");
id(ptr_component_max1704x)->update();
ESP_LOGD("on_boot: -10:", "END max1704x");
//////////////////////////////////////////////////
ESP_LOGD("on_boot: -10:", "delay(500)");
delay(500);
ESP_LOGD("on_boot: -10:", "END delay(500)");
//////////////////////////////////////////////////
ESP_LOGD("on_boot: -10:", "LocalPlants");
id(ptr_component_local_plants)->update();
ESP_LOGD("on_boot: -10:", "END local_plants");
//////////////////////////////////////////////////
ESP_LOGD("on_boot: -10:", "delay(500)");
delay(500);
ESP_LOGD("on_boot: -10:", "END delay(500)");
//////////////////////////////////////////////////
ESP_LOGD("on_boot: -10:", "Value sensor_wifi_signal: %.1f", id(sensor_wifi_signal).state);
ESP_LOGD("on_boot: -10:", "Value Temperature_C: %.1f", id(Temperature_C).state);
ESP_LOGD("on_boot: -10:", "Value Temperature_F: %.1f", id(Temperature_F).state);
ESP_LOGD("on_boot: -10:", "Value Humidity: %.1f", id(Humidity).state);
ESP_LOGD("on_boot: -10:", "Value uva: %.1f", id(uva).state);
ESP_LOGD("on_boot: -10:", "Value uvb: %.1f", id(uvb).state);
ESP_LOGD("on_boot: -10:", "Value uv_index: %.1f", id(uv_index).state);
ESP_LOGD("on_boot: -10:", "Value Plant_1: %.1f", id(Plant_1).state);
ESP_LOGD("on_boot: -10:", "Value Plant_2: %.1f", id(Plant_2).state);
ESP_LOGD("on_boot: -10:", "Value Plant_3: %.1f", id(Plant_3).state);
ESP_LOGD("on_boot: -10:", "Value Plant_4: %.1f", id(Plant_4).state);
ESP_LOGD("on_boot: -10:", "Value Plant_5: %.1f", id(Plant_5).state);
ESP_LOGD("on_boot: -10:", "Value Plant_6: %.1f", id(Plant_6).state);
ESP_LOGD("on_boot: -10:", "Value Plant_7: %.1f", id(Plant_7).state);
ESP_LOGD("on_boot: -10:", "Value Plant_8: %.1f", id(Plant_8).state);
ESP_LOGD("on_boot: -10:", "Value Battery_Voltage: %.1f", id(Battery_Voltage).state);
ESP_LOGD("on_boot: -10:", "Value Battery_Level: %.1f", id(Battery_Level).state);
ESP_LOGD("on_boot: -10:", "Value Battery_Sleeping: %.1f", id(Battery_Sleeping).state);
ESP_LOGD("on_boot: -10:", "Value Battery_Alert: %.1f", id(Battery_Alert).state);
ESP_LOGD("on_boot: -10:", "Value Battery_Threshold: %.1f", id(Battery_Threshold).state);
//////////////////////////////////////////////////
ESP_LOGD("on_boot: -10:", "Display");
id(display_main).clear();
id(display_main).update();
ESP_LOGD("on_boot: -10:", "END Display");
//////////////////////////////////////////////////
ESP_LOGD("on_boot: -10:", "END custom");
delay(500);
- deep_sleep.enter: deep_sleep_main
// Go to deep sleep if NOT in OTA mode
if (! id(ota_mode)) {
id(deep_sleep_main)->begin_sleep();
}
on_shutdown:
then:
- lambda: |-
@ -107,19 +162,32 @@ mqtt:
username: !secret mqtt_client_username
password: !secret mqtt_client_password
birth_message:
topic: 'hass/status'
topic: 'ha/status'
payload: 'online'
will_message:
topic: 'hass/status'
topic: 'ha/status'
payload: 'offline'
globals:
- id: ptr_component_shtx1
type: "SHT1xSensor*"
restore_value: no
- id: ptr_component_veml6075
type: "VEML6075Sensor*"
restore_value: no
- id: ptr_component_max1704x
type: "MAX1704xSensor*"
restore_value: no
- id: ptr_component_local_plants
type: "LocalPlantsSensor*"
restore_value: no
- id: early_boot
type: bool
restore_value: no
- id: ota_mode
type: bool
restore_value: no
initial_value: "false"
time:
- platform: sntp
@ -129,6 +197,18 @@ time:
binary_sensor:
- platform: status
name: "Status"
- platform: gpio
name: "OTA Button"
id: button_ota
internal: true
pin:
number: A3
mode: INPUT_PULLDOWN
on_release:
then:
- lambda: |-
id(ota_mode) = true;
- deep_sleep.prevent: deep_sleep_main
sensor:
- platform: wifi_signal
@ -160,6 +240,64 @@ sensor:
unit_of_measurement: "%"
accuracy_decimals: 1
expire_after: 4h
- platform: custom
lambda: |-
VEML6075Sensor* veml6075 = new VEML6075Sensor();
id(ptr_component_veml6075) = veml6075;
App.register_component(veml6075);
return {veml6075->uva_sensor, veml6075->uvb_sensor, veml6075->uv_index_sensor};
sensors:
- name: "UVA"
id: uva
accuracy_decimals: 2
expire_after: 4h
- name: "UVB"
id: uvb
accuracy_decimals: 2
expire_after: 4h
- name: "UV Index"
id: uv_index
accuracy_decimals: 2
expire_after: 4h
- platform: custom
lambda: |-
LocalPlantsSensor* local_plants = new LocalPlantsSensor();
id(ptr_component_local_plants) = local_plants;
App.register_component(local_plants);
return {local_plants->plant_1_sensor, local_plants->plant_2_sensor, local_plants->plant_3_sensor, local_plants->plant_4_sensor, local_plants->plant_5_sensor, local_plants->plant_6_sensor, local_plants->plant_7_sensor, local_plants->plant_8_sensor};
sensors:
- name: "Plant 1"
id: Plant_1
accuracy_decimals: 2
expire_after: 4h
- name: "Plant 2"
id: Plant_2
accuracy_decimals: 2
expire_after: 4h
- name: "Plant 3"
id: Plant_3
accuracy_decimals: 2
expire_after: 4h
- name: "Plant 4"
id: Plant_4
accuracy_decimals: 2
expire_after: 4h
- name: "Plant 5"
id: Plant_5
accuracy_decimals: 2
expire_after: 4h
- name: "Plant 6"
id: Plant_6
accuracy_decimals: 2
expire_after: 4h
- name: "Plant 7"
id: Plant_7
accuracy_decimals: 2
expire_after: 4h
- name: "Plant 8"
id: Plant_8
accuracy_decimals: 2
expire_after: 4h
- platform: custom
lambda: |-
MAX1704xSensor* max1704x = new MAX1704xSensor();
@ -201,58 +339,66 @@ display:
rotation: 90
update_interval: 1h
lambda: |-
delay(60*1000);
// Clear
it.clear();
// Battery
if (id(Battery_Level).state > 90) {
it.image(0, 0, id(battery_100));
}
else if (id(Battery_Level).state > 75) {
it.image(0, 0, id(battery_75));
}
else if (id(Battery_Level).state > 50) {
it.image(0, 0, id(battery_50));
}
else if (id(Battery_Level).state > 25) {
it.image(0, 0, id(inverted_battery_25));
if (id(early_boot)) {
it.clear();
it.image(84, 0, id(rocket_ship));
}
else {
it.image(0, 0, id(inverted_battery_0));
delay(60*1000);
// Clear
it.clear();
// Battery
if (id(Battery_Level).state > 90) {
it.image(0, 0, id(battery_100));
}
else if (id(Battery_Level).state > 75) {
it.image(0, 0, id(battery_75));
}
else if (id(Battery_Level).state > 50) {
it.image(0, 0, id(battery_50));
}
else if (id(Battery_Level).state > 25) {
it.image(0, 0, id(inverted_battery_25));
}
else {
it.image(0, 0, id(inverted_battery_0));
}
if (id(Battery_Alert).state != 0) {
it.image(32, 0, id(alert_filled));
}
else {
it.printf(32, 16, id(deja_vu_mono), "%.1f%%", id(Battery_Level).state);
}
// Temperature
it.image(116, 0, id(thermometer)); // 148, 0
it.printf(153, 16, id(deja_vu_mono), "%.1f°F", id(Temperature_F).state);
// OTA Mode
if (id(ota_mode)) {
it.image(259, 0, id(ota_mode_enabled));
}
// Last update
it.image(0, 32, id(last_sync_icon));
it.strftime(32, 39, id(deja_vu_mono), "%Y-%m-%d %H:%M", id(sntp_time).now());
// Plants
it.image(0, 64, id(inverted_cat_nip));
it.image(37, 64, id(inverted_christmas_cactus));
it.image(74, 64, id(inverted_cat_grass));
it.image(111, 64, id(inverted_tarragon));
it.image(148, 64, id(inverted_tulip));
it.image(185, 64, id(inverted_poinsettia));
it.image(222, 64, id(inverted_bamboo));
it.image(259, 64, id(inverted_vase));
it.image(0, 96, id(cat_nip));
it.image(37, 96, id(christmas_cactus));
it.image(74, 96, id(cat_grass));
it.image(111, 96, id(tarragon));
it.image(148, 96, id(tulip));
it.image(185, 96, id(poinsettia));
it.image(222, 96, id(bamboo));
it.image(259, 96, id(vase));
it.image(222, 32, id(disabled_tulip));
it.image(259, 32, id(disabled_vase));
}
if (id(Battery_Alert).state != 0) {
it.image(32, 0, id(alert_filled));
}
else {
it.printf(32, 16, id(deja_vu_mono), "%.1f%%", id(Battery_Level).state);
}
// Temperature
it.image(116, 0, id(thermometer)); // 148, 0
it.printf(153, 16, id(deja_vu_mono), "%.1f°F", id(Temperature_F).state);
// OTA Mode
it.image(259, 0, id(ota_mode_enabled));
// Last update
it.image(0, 32, id(last_sync_icon));
it.strftime(32, 39, id(deja_vu_mono), "%Y-%m-%d %H:%M", id(sntp_time).now());
// Plants
it.image(0, 64, id(inverted_cat_nip));
it.image(37, 64, id(inverted_christmas_cactus));
it.image(74, 64, id(inverted_cat_grass));
it.image(111, 64, id(inverted_tarragon));
it.image(148, 64, id(inverted_tulip));
it.image(185, 64, id(inverted_poinsettia));
it.image(222, 64, id(inverted_bamboo));
it.image(259, 64, id(inverted_vase));
it.image(0, 96, id(cat_nip));
it.image(37, 96, id(christmas_cactus));
it.image(74, 96, id(cat_grass));
it.image(111, 96, id(tarragon));
it.image(148, 96, id(tulip));
it.image(185, 96, id(poinsettia));
it.image(222, 96, id(bamboo));
it.image(259, 96, id(vase));
it.image(222, 32, id(disabled_tulip));
it.image(259, 32, id(disabled_vase));
font:
- file: "fonts/DejaVuSansMono.ttf"
@ -353,4 +499,7 @@ image:
- file: graphics/noun_Settings Download_1854756.png # ota mode enabled
id: ota_mode_enabled
resize: 32x32
- file: graphics/noun_startup_1946229.png # rocket ship / startup
id: rocket_ship
resize: 128x128

View File

@ -0,0 +1,3 @@
# Schematic
The overall hardware hookups needed to re-create the build.

View File

@ -0,0 +1,51 @@
// Adapted from https://github.com/sparkfun/Qwiic_Mux_TCA9548A/blob/master/Firmware/Example1-BasicReadings/Mux_Control.ino
// Also See https://platformio.org/lib/show/2087/TI%20TCA9548A
/*
Basic control and commands for the PCA9548A/TCA9548A I2C multiplexer
*/
#include "esphome.h"
#define MUX_ADDR 0x70 //7-bit unshifted default I2C Address
//Enables a specific port number
boolean enableMuxPort(byte portNumber)
{
if(portNumber > 7) portNumber = 7;
//Read the current mux settings
Wire.requestFrom(MUX_ADDR, 1);
if(!Wire.available()) return(false); //Error
byte settings = Wire.read();
//Set the wanted bit to enable the port
settings |= (1 << portNumber);
Wire.beginTransmission(MUX_ADDR);
Wire.write(settings);
Wire.endTransmission();
return(true);
}
//Disables a specific port number
boolean disableMuxPort(byte portNumber)
{
if(portNumber > 7) portNumber = 7;
//Read the current mux settings
Wire.requestFrom(MUX_ADDR, 1);
if(!Wire.available()) return(false); //Error
byte settings = Wire.read();
//Clear the wanted bit to disable the port
settings &= ~(1 << portNumber);
Wire.beginTransmission(MUX_ADDR);
Wire.write(settings);
Wire.endTransmission();
return(true);
}

View File

@ -0,0 +1,29 @@
#include "esphome.h"
#include <SparkFun_VEML6075_Arduino_Library.h>
class VEML6075Sensor : public PollingComponent, public sensor::Sensor {
private:
VEML6075 _uv;
public:
sensor::Sensor *uva_sensor = new sensor::Sensor();
sensor::Sensor *uvb_sensor = new sensor::Sensor();
sensor::Sensor *uv_index_sensor = new sensor::Sensor();
// 4294967295UL == never per sources
VEML6075Sensor() : PollingComponent(60*60*1000) { }
void setup() override {
_uv.begin();
}
void update() override {
double uva_value = _uv.uva();
double uvb_value = _uv.uvb();
double uv_index_value = _uv.index();
uva_sensor->publish_state(uva_value);
uvb_sensor->publish_state(uvb_value);
uv_index_sensor->publish_state(uv_index_value);
}
};

View File

@ -28,3 +28,6 @@
- platform: mqtt
name: plant_dashboard_battery_alert
state_topic: "plant_dashboard/sensor/battery_alert/state"
- platform: mqtt
name: plant_dashboard_plant_2
state_topic: "plant_dashboard/sensor/plant_2/state"