commit abe13843b485607e3e25b6787fac51f5569bf5ac Author: Mark van Renswoude Date: Thu Mar 23 19:45:29 2017 +0100 Initial commit, very much a work in progress diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..425213f --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.pioenvs +.piolibdeps +src/credentials.h +*.sublime-workspace diff --git a/TODO b/TODO new file mode 100644 index 0000000..69998eb --- /dev/null +++ b/TODO @@ -0,0 +1,3 @@ +1. Add an "ease" option to the static mode + other mode to static: from 0 + static to static: depends on current brightness \ No newline at end of file diff --git a/Thermostat.sublime-project b/Thermostat.sublime-project new file mode 100644 index 0000000..7d06507 --- /dev/null +++ b/Thermostat.sublime-project @@ -0,0 +1,22 @@ +{ + "folders": + [ + { + "path": ".", + "file_exclude_patterns": ["*.sublime-project"] + } + ]/*, + "build_systems": + [ + { + "name": "PlatformIO - Build", + "cmd": ["platformio", "run"], + "working_dir": "$project_path" + }, + { + "name": "PlatformIO - Upload", + "cmd": ["platformio", "run", "--target", "upload"], + "working_dir": "$project_path" + } + ]*/ +} \ No newline at end of file diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..e63c79f --- /dev/null +++ b/build.bat @@ -0,0 +1 @@ +@platformio run \ No newline at end of file diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..36408bd --- /dev/null +++ b/platformio.ini @@ -0,0 +1,15 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; http://docs.platformio.org/page/projectconf.html + +[env:esp12e] +platform = espressif8266_stage +board = esp12e +framework = arduino +upload_speed = 115200 \ No newline at end of file diff --git a/src/components/PCA9685.cpp b/src/components/PCA9685.cpp new file mode 100644 index 0000000..eff6ad3 --- /dev/null +++ b/src/components/PCA9685.cpp @@ -0,0 +1,193 @@ +/* + * PCA9685 library for ESP8266 + * Copyright 2017 (c) Mark van Renswoude + * + * Uses brzo I2C library. +*/ +#include +#include +#include "PCA9685.h" + +#include + + + +void PCA9685::setAddress(uint8_t address, uint16_t sclFrequency, uint8_t pinSDA, uint8_t pinSCL, uint32_t timeoutUSec) +{ +} + + +void PCA9685::setAddress(uint8_t address, uint16_t sclFrequency) +{ +} + + +uint8_t PCA9685::read(uint8_t registerAddress) +{ + return 0; +} + + +void PCA9685::write(uint8_t registerAddress, uint8_t value) +{ +} + + +inline void PCA9685::write(uint8_t data) +{ +} + + +void PCA9685::setPWMFrequency(float frequency) +{ +} + + +void PCA9685::setPWM(uint8_t pin, uint16_t value) +{ + this->setAll(value); +} + + +void PCA9685::setPWM(uint8_t pin, uint16_t on, uint16_t off) +{ +} + + + +void PCA9685::setAll(uint16_t value) +{ + analogWrite(9, map(value, 0, 4096, 0, 255)); +} + + +void PCA9685::setAll(uint16_t on, uint16_t off) +{ +} + +/* +void PCA9685::setAddress(uint8_t address, uint16_t sclFrequency, uint8_t pinSDA, uint8_t pinSCL, uint32_t timeoutUSec) +{ + brzo_i2c_setup(pinSDA, pinSCL, timeoutUSec); + this->setAddress(address, sclFrequency); +} + + +void PCA9685::setAddress(uint8_t address, uint16_t sclFrequency) +{ + this->address = address; + this->sclFrequency = sclFrequency; +} + + +uint8_t PCA9685::read(uint8_t registerAddress) +{ + uint8_t result; + + brzo_i2c_write(®isterAddress, 1, true); + brzo_i2c_read(&result, 1, false); + + return result; +} + + +void PCA9685::write(uint8_t registerAddress, uint8_t value) +{ + uint8_t buffer[2]; + + buffer[0] = registerAddress; + buffer[1] = value; + brzo_i2c_write(buffer, 2, false); +} + + +inline void PCA9685::write(uint8_t data) +{ + brzo_i2c_write(&data, 1, false); +} + + +#define prescaleValue 25000000 / 4096 + +void PCA9685::setPWMFrequency(float frequency) +{ + // Credit to the Adafruit PWM Servo Driver library for these calculations + // https://github.com/adafruit/Adafruit-PWM-Servo-Driver-Library/ + uint8_t prescale = floor(prescaleValue / (frequency * 0.9) - 0.5); + + brzo_i2c_start_transaction(address, sclFrequency); + + // Sleep while changing the frequency + uint8_t oldMode = this->read(PCA9685::RegisterMode1); + uint8_t newMode = (oldMode & !PCA9685::Mode1Restart) | PCA9685::Mode1Sleep; + + this->write(PCA9685::RegisterMode1, newMode); + this->write(PCA9685::RegisterPrescale, prescale); + this->write(PCA9685::RegisterMode1, oldMode); + + // According to the datasheet: + // It takes 500 us max. for the oscillator to be up and running once + // SLEEP bit has been set to logic 0 + // + // The Adafruit library uses 5 milliseconds, so I'll stick to that as well. + delay(5); + + // Restart and turn on auto-increment (required for SetPWM) + this->write(PCA9685::RegisterMode1, oldMode | PCA9685::Mode1Restart | PCA9685::Mode1AI | PCA9685::Mode1AllCall); + + brzo_i2c_end_transaction(); +} + + +void PCA9685::setPWM(uint8_t pin, uint16_t value) +{ + if (value < 0) value = 0; + if (value > 4095) value = 4095; + + if (value == 4095) + this->setPWM(pin, 4096, 0); + else if (value == 0) + this->setPWM(pin, 0, 4096); + else + this->setPWM(pin, 0, value); +} + + +void PCA9685::setPWM(uint8_t pin, uint16_t on, uint16_t off) +{ + brzo_i2c_start_transaction(address, sclFrequency); + this->write(PCA9685::RegisterLED0OnL + (4 * pin)); + this->write(on); + this->write(on >> 8); + this->write(off); + this->write(off >> 8); + brzo_i2c_end_transaction(); +} + + + +void PCA9685::setAll(uint16_t value) +{ + if (value < 0) value = 0; + if (value > 4095) value = 4095; + + if (value == 4095) + this->setAll(4096, 0); + else if (value == 0) + this->setAll(0, 4096); + else + this->setAll(0, value); +} + + +void PCA9685::setAll(uint16_t on, uint16_t off) +{ + brzo_i2c_start_transaction(address, sclFrequency); + this->write(PCA9685::RegisterAllLEDOnL); + this->write(on); + this->write(on >> 8); + this->write(off); + this->write(off >> 8); + brzo_i2c_end_transaction(); +} +*/ \ No newline at end of file diff --git a/src/components/PCA9685.h b/src/components/PCA9685.h new file mode 100644 index 0000000..23d43b9 --- /dev/null +++ b/src/components/PCA9685.h @@ -0,0 +1,53 @@ +#ifndef __PCA9685 +#define __PCA9685 + +/* + * PCA9685 library for ESP8266 + * Copyright 2017 (c) Mark van Renswoude + * + * Uses brzo I2C library. +*/ +#include + +class PCA9685 +{ + private: + uint8_t address; + uint16_t sclFrequency; + + protected: + uint8_t read(uint8_t registerAddress); + void write(uint8_t registerAddress, uint8_t value); + inline void write(uint8_t data); + + public: + static const uint16_t Off = 0; + static const uint16_t On = 4096; + + static const uint8_t RegisterMode1 = 0x0; + static const uint8_t RegisterPrescale = 0xFE; + + static const uint8_t RegisterLED0OnL = 0x6; + static const uint8_t RegisterAllLEDOnL = 0xFA; + + static const uint8_t Mode1Restart = 0x80; + static const uint8_t Mode1AI = 0x20; + static const uint8_t Mode1Sleep = 0x10; + static const uint8_t Mode1AllCall = 0x01; + + // Call this to initializes the brzo I2C library + void setAddress(uint8_t address, uint16_t sclFrequency, uint8_t pinSDA, uint8_t pinSCL, uint32_t timeoutUSec); + + // Call this if you already initialized the brzo I2C library + void setAddress(uint8_t address, uint16_t sclFrequency); + + void setPWMFrequency(float frequency); + + void setPWM(uint8_t pin, uint16_t value); + void setPWM(uint8_t pin, uint16_t on, uint16_t off); + + void setAll(uint16_t value); + void setAll(uint16_t on, uint16_t off); +}; + +#endif \ No newline at end of file diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..c587ae1 --- /dev/null +++ b/src/config.h @@ -0,0 +1,27 @@ +#ifndef __Config +#define __Config + +#include +#include "credentials.h" + + +// The number of steps (assumed to be <= 16, as the code currently only controls 1 PCA9685 board) +static const uint8_t StepCount = 14; + + +// The port number on which the UDP server listens +static const uint16_t UDPPort = 3126; + + +// Pins for the I2C bus +static const uint8_t PinSDA = 12; +static const uint8_t PinSCL = 13; + +static const uint32_t I2CTimeout = 2000; + +// I2C address and clock frequency of the PCA9685 board +static const uint8_t PWMDriverAddress = 0x60; +static const uint16_t PWMDriverSCLFrequency = 400; +static const uint16_t PWMDriverPWMFrequency = 1600; + +#endif \ No newline at end of file diff --git a/src/credentials.example.h b/src/credentials.example.h new file mode 100644 index 0000000..b77597e --- /dev/null +++ b/src/credentials.example.h @@ -0,0 +1,3 @@ +// Create a copy of this file called "credentials.h" +static const char* WiFiSSID = "example"; +static const char* WiFiPassword = "example"; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..ee0126f --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,184 @@ +/* + * Stairs lighting + * Copyright 2017 (c) Mark van Renswoude +*/ +#include +#include +#include +#include + +#include "config.h" +#include "protocol.h" +#include "components\PCA9685.h" +//#include "modes\adc.h" +#include "modes\alternate.h" +#include "modes\custom.h" +#include "modes\slide.h" +#include "modes\static.h" +#include "stairs.h" + + +PCA9685* pwmDriver; +Stairs* stairs; +WiFiUDP udpServer; +uint8_t currentModeIdentifier; +IMode* currentMode; + + +// Forward declarations +void checkRequest(); +void handleRequest(uint8_t* packet); +IMode* createMode(uint8_t identifier); +void setCurrentMode(IMode *mode, uint8_t identifier); +void handleCurrentMode(); + + +void setup() +{ + pwmDriver = new PCA9685(); + pwmDriver->setAddress(PWMDriverAddress, PWMDriverSCLFrequency, PinSDA, PinSCL, I2CTimeout); + pwmDriver->setPWMFrequency(PWMDriverPWMFrequency); + + stairs = new Stairs(); + stairs->init(pwmDriver); + + WiFi.begin(WiFiSSID, WiFiPassword); + + + // Run a little startup test sequence + pwmDriver->setPWM(0, PCA9685::On); + delay(300); + + for (int step = 1; step < StepCount; step++) + { + pwmDriver->setPWM(step - 1, PCA9685::Off); + pwmDriver->setPWM(step, PCA9685::On); + delay(300); + } + + pwmDriver->setPWM(StepCount - 1, PCA9685::Off); + + + // Pulsate the bottom step while WiFi is connecting + uint16_t brightness = 0; + uint16_t speed = 16; + + while (WiFi.status() != WL_CONNECTED) + { + brightness += speed; + if (brightness <= 0 || brightness >= 1024) + speed = -speed; + + pwmDriver->setPWM(0, brightness); + delay(16); + } + + setCurrentMode(new StaticMode(), Mode::Static); + + + // Start the UDP server + udpServer.begin(UDPPort); +} + + +uint32_t currentTime; +uint8_t packet[51]; +uint8_t* packetRef; + +void loop() +{ + currentTime = millis(); + + checkRequest(); + handleCurrentMode(); +} + + +void checkRequest() +{ + int packetSize = udpServer.parsePacket(); + if (packetSize) + { + int length = udpServer.read(packet, 50); + if (length && packet[0]) + { + packet[length] = 0; + packetRef = packet; + + handleRequest(packetRef); + } + } +} + + +void handleRequest(uint8_t* packet) +{ + // Every request will result in a reply, either containing the + // requested data or a copy of the input parameters for verification. + // + // Apparantly this also makes the ESP8266 more stable, as reports + // have been made that UDP communication can stall if no replies are sent. + udpServer.beginPacket(udpServer.remoteIP(), udpServer.remotePort()); + udpServer.write(Command::Reply); + + switch (*packet) + { + case Command::GetMode: + udpServer.write(currentModeIdentifier); + break; + + case Command::SetMode: + { + packet++; + uint8_t newIdentifier = *packet; + packet++; + + IMode* newMode = createMode(newIdentifier); + + if (newMode != NULL) + { + newMode->read(packet); + newMode->write(&udpServer); + setCurrentMode(newMode, newIdentifier); + } + else + udpServer.write((uint8_t)0); + + break; + } + } + + udpServer.endPacket(); +} + + +IMode* createMode(uint8_t identifier) +{ + if (identifier == currentModeIdentifier) + return currentMode; + + switch (identifier) + { + case Mode::Static: return new StaticMode(); + //case Mode::Custom: return new CustomMode(); + case Mode::Alternate: return new AlternateMode(); + //case Mode::Slide: return new SlideMode(); + //case Mode::ADC: return new ADCInputMode(); + } + + return NULL; +} + + +void setCurrentMode(IMode* mode, uint8_t identifier) +{ + currentModeIdentifier = identifier; + currentMode = mode; + currentMode->init(stairs, currentTime); +} + + +void handleCurrentMode() +{ + currentMode->tick(stairs, currentTime); +} \ No newline at end of file diff --git a/src/mode.h b/src/mode.h new file mode 100644 index 0000000..79b700f --- /dev/null +++ b/src/mode.h @@ -0,0 +1,25 @@ +#ifndef __Mode +#define __Mode + +#include +#include + + +class IStairs +{ + public: + virtual void setStep(uint8_t step, uint16_t value) = 0; + virtual void setAll(uint16_t value) = 0; +}; + +class IMode +{ + public: + virtual void read(uint8_t* data) = 0; + virtual void write(Stream* stream) = 0; + + virtual void init(IStairs* stairs, uint32_t currentTime) = 0; + virtual void tick(IStairs* stairs, uint32_t currentTime) = 0; +}; + +#endif \ No newline at end of file diff --git a/src/modes/alternate.cpp b/src/modes/alternate.cpp new file mode 100644 index 0000000..29e119a --- /dev/null +++ b/src/modes/alternate.cpp @@ -0,0 +1,27 @@ +#include "alternate.h" +#include + + +void AlternateMode::init(IStairs* stairs, uint32_t currentTime) +{ + stairs->setAll(0); + + this->lastChange = currentTime; + this->even = false; +} + + +void AlternateMode::tick(IStairs* stairs, uint32_t currentTime) +{ + if (currentTime - this->lastChange < this->parameters.interval) + return; + + this->lastChange = currentTime; + this->even = !this->even; + + uint8_t stepCount = stairs->getCount(); + for (uint8_t step = 0; step < stepCount; step++) + { + stairs->set(step, ((step % 2) == 0) == this->even ? this->parameters.brightness : 0); + } +} \ No newline at end of file diff --git a/src/modes/alternate.h b/src/modes/alternate.h new file mode 100644 index 0000000..bf4efde --- /dev/null +++ b/src/modes/alternate.h @@ -0,0 +1,33 @@ +#ifndef __AlternateMode +#define __AlternateMode + +#include +#include "base.h" +#include "../config.h" + + +struct AlternateModeParameters +{ + uint16_t interval; + uint16_t brightness; +}; + + +class AlternateMode : public BaseMode +{ + private: + uint32_t lastChange; + bool even = false; + + public: + AlternateMode() + { + parameters.interval = 500; + parameters.brightness = 4096; + } + + void init(IStairs* stairs, uint32_t currentTime); + void tick(IStairs* stairs, uint32_t currentTime); +}; + +#endif \ No newline at end of file diff --git a/src/modes/base.h b/src/modes/base.h new file mode 100644 index 0000000..f25e0ed --- /dev/null +++ b/src/modes/base.h @@ -0,0 +1,27 @@ +#ifndef __BaseMode +#define __BaseMode + +#include +#include +#include "../mode.h" + + +template +class BaseMode : public IMode +{ + protected: + T parameters; + + public: + virtual void read(uint8_t* data) + { + this->parameters = *reinterpret_cast(data); + } + + virtual void write(Stream* stream) + { + stream->write(reinterpret_cast(&this->parameters), sizeof(T)); + } +}; + +#endif \ No newline at end of file diff --git a/src/modes/custom.h b/src/modes/custom.h new file mode 100644 index 0000000..8b228d7 --- /dev/null +++ b/src/modes/custom.h @@ -0,0 +1,14 @@ +#ifndef __CustomMode +#define __CustomMode + +#include +#include "base.h" +#include "../config.h" + +class CustomMode : public IMode +{ + private: + uint16_t values[StepCount]; +}; + +#endif \ No newline at end of file diff --git a/src/modes/slide.h b/src/modes/slide.h new file mode 100644 index 0000000..26885ad --- /dev/null +++ b/src/modes/slide.h @@ -0,0 +1,15 @@ +#ifndef __SlideMode +#define __SlideMode + +#include +#include "base.h" +#include "../config.h" + +class SlideMode : public IMode +{ + private: + uint16_t interval; + bool up; +}; + +#endif \ No newline at end of file diff --git a/src/modes/static.cpp b/src/modes/static.cpp new file mode 100644 index 0000000..7b5fc88 --- /dev/null +++ b/src/modes/static.cpp @@ -0,0 +1,11 @@ +#include "static.h" + +void StaticMode::init(IStairs* stairs, uint32_t currentTime) +{ + stairs->setAll(this->parameters.brightness); +} + + +void StaticMode::tick(IStairs* stairs, uint32_t currentTime) +{ +} \ No newline at end of file diff --git a/src/modes/static.h b/src/modes/static.h new file mode 100644 index 0000000..c06edc3 --- /dev/null +++ b/src/modes/static.h @@ -0,0 +1,27 @@ +#ifndef __StaticMode +#define __StaticMode + +#include +#include "base.h" +#include "../config.h" + + +struct StaticModeParameters +{ + uint16_t brightness = 0; +}; + + +class StaticMode : public BaseMode +{ + public: + StaticMode() + { + parameters.brightness = 0; + } + + void init(IStairs* stairs, uint32_t currentTime); + void tick(IStairs* stairs, uint32_t currentTime); +}; + +#endif \ No newline at end of file diff --git a/src/protocol.h b/src/protocol.h new file mode 100644 index 0000000..f0b9e06 --- /dev/null +++ b/src/protocol.h @@ -0,0 +1,25 @@ +#ifndef __Protocol +#define __Protocol + +#include + +class Command +{ + public: + static const uint8_t Reply = 0x01; + static const uint8_t GetMode = 0x02; + static const uint8_t SetMode = 0x03; +}; + + +class Mode +{ + public: + static const uint8_t Static = 0x01; + static const uint8_t Custom = 0x02; + static const uint8_t Alternate = 0x03; + static const uint8_t Slide = 0x04; + //static const uint8_t ADC = 0x05; +}; + +#endif \ No newline at end of file diff --git a/src/stairs.cpp b/src/stairs.cpp new file mode 100644 index 0000000..c1e539c --- /dev/null +++ b/src/stairs.cpp @@ -0,0 +1,25 @@ +#include "stairs.h" +#include "config.h" + +void Stairs::init(PCA9685* pwmDriver) +{ + this->pwmDriver = pwmDriver; +} + + +uint8_t Stairs::getCount() +{ + return StepCount; +} + + +void Stairs::set(uint8_t step, uint16_t value) +{ + pwmDriver->setPWM(step, value); +} + + +void Stairs::setAll(uint16_t value) +{ + pwmDriver->setAll(value); +} \ No newline at end of file diff --git a/src/stairs.h b/src/stairs.h new file mode 100644 index 0000000..309f92a --- /dev/null +++ b/src/stairs.h @@ -0,0 +1,20 @@ +#ifndef __Stairs +#define __Stairs + +#include "components/PCA9685.h" +#include "modes/base.h" + +class Stairs : public IStairs +{ + private: + PCA9685* pwmDriver; + + public: + void init(PCA9685* pwmDriver); + + uint8_t getCount(); + void set(uint8_t step, uint16_t value); + void setAll(uint16_t value); +}; + +#endif \ No newline at end of file diff --git a/upload.bat b/upload.bat new file mode 100644 index 0000000..73e7389 --- /dev/null +++ b/upload.bat @@ -0,0 +1 @@ +@platformio run --target upload \ No newline at end of file