Update arduinio sources, overhal some basic folder structures, further implementation of plant dashboard/monitor build
|
@ -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
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
# isp
|
||||
|
||||
The ISP programmer code, schematics and more used for programming the ATTiny chips used elsewhere.
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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.
|
|
@ -0,0 +1,3 @@
|
|||
# master_reader
|
||||
|
||||
Sample code used on an Arduino for testing purposes.
|
|
@ -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);
|
||||
}
|
|
@ -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.
|
|
@ -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);
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
# Water Level Sensor
|
||||
|
||||
A water level meter adapted to i2c using an ATTiny85
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
# Extern
|
||||
|
||||
Useful resources pulled from upstream websites that publish details about chips and similar.
|
Before Width: | Height: | Size: 560 KiB After Width: | Height: | Size: 529 KiB |
|
@ -0,0 +1,3 @@
|
|||
# Fonts
|
||||
|
||||
Various open source fonts that are useful when building UIs
|
|
@ -0,0 +1,3 @@
|
|||
# Graphics
|
||||
|
||||
Various icons and graphics from Noun Project.
|
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 9.3 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 16 KiB |
|
@ -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);
|
||||
}
|
||||
};
|
|
@ -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
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Schematic
|
||||
|
||||
The overall hardware hookups needed to re-create the build.
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
};
|
|
@ -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"
|