commit 0e674fe5f80242f48fab185831e601cc02e24804 Author: s3lph Date: Mon Nov 20 01:26:48 2023 +0100 feat: initial commit diff --git a/.forgejo/workflows/build.yml b/.forgejo/workflows/build.yml new file mode 100644 index 0000000..02a0b8a --- /dev/null +++ b/.forgejo/workflows/build.yml @@ -0,0 +1,26 @@ +--- + +on: push + +jobs: + + platformio: + runs-on: docker + steps: + - uses: https://code.forgejo.org/actions/checkout@v4 + - run: | + apt update; apt install --yes python3-pip + pip3 install platformio + cd esp + sed -re 's/put your ssid here/"ssid"' -i src/main.cpp + sed -re 's/put your psk here/"psk"' -i src/main.cpp + pio run + + openscad: + runs-on: docker + steps: + - uses: https://code.forgejo.org/actions/checkout@v4 + - run: | + apt update; apt install --yes openscad + cd case + openscad -o pipeline.stl clock.scad diff --git a/README.md b/README.md new file mode 100644 index 0000000..307df74 --- /dev/null +++ b/README.md @@ -0,0 +1,92 @@ +# Ambient Lighting Clock + +3D model and ESP8266 code for a clock that projects colorful rays of light against the wall: + +* **Hours**: Wide red +* **Minutes**: Blueish green +* **Seconds**: Blue + +## Enclosure + +The directory `case` contains the [OpenSCAD](https://en.wikibooks.org/wiki/OpenSCAD_User_Manual) project and rendered STL file of the clock's enclosure. + +The enclosure is designed to be 3D-printed, and fits an 60x WS2812 LEDs strip with 144 LEDs per meter, such as [this one (AliExpress link)](https://de.aliexpress.com/item/32682015405.html) + +The enclosure also contains mounting holes to which a front plate can be attached (such as in the photo above). + +## ESP8266 + +At the heart of the clock sits an ESP8266, which connects to a WiFi network and fetches the current time of day via NTP. + +To build the code and program the ESP, you need to install the [PlatformIO](https://platformio.org/) framework. + +```shell-session +$ git clone https://git.kabelsalat.ch/s3lph/ambient-lighting-clock.git +$ cd ambient-lighting-clock/esp +$ vim src/main.cpp # Insert the WiFi SSID and PSK +$ pio run # To download dependencies and build without programming the ESP +Processing main (platform: espressif8266; board: nodemcu; framework: arduino) +-------------------------------------------------------------------------------------------------------------------------------------- +Verbose mode can be enabled via `-v, --verbose` option +CONFIGURATION: https://docs.platformio.org/page/boards/espressif8266/nodemcu.html +PLATFORM: Espressif 8266 (2023.4.0) > NodeMCU 0.9 (ESP-12 Module) +HARDWARE: ESP8266 80MHz, 80KB RAM, 4MB Flash +PACKAGES: + - framework-arduinoespressif8266 @ 2.7.4+9 + - tool-esptoolpy @ 1.40501.0 (4.5.1) + - toolchain-xtensa @ 2.40802.200502 (4.8.2) +LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf +LDF Modes: Finder ~ chain, Compatibility ~ soft +Found 31 compatible libraries +Scanning dependencies... +Dependency Graph +|-- Adafruit NeoPixel @ 1.11.0 +|-- NTP @ 1.6.0 +|-- ESP8266WiFi @ 1.0 +Building in release mode +Retrieving maximum program size .pio/build/main/firmware.elf +Checking size .pio/build/main/firmware.elf +Advanced Memory Usage is available via "PlatformIO Home > Project Inspect" +RAM: [==== ] 35.4% (used 28984 bytes from 81920 bytes) +Flash: [=== ] 26.8% (used 279396 bytes from 1044464 bytes) +==================================================== [SUCCESS] Took 1.58 seconds ==================================================== +$ pio run -t upload # To build the code and program it to the ESP8266 +Configuring upload protocol... +AVAILABLE: espota, esptool +CURRENT: upload_protocol = esptool +Looking for upload port... +Auto-detected: /dev/ttyUSB0 +Uploading .pio/build/main/firmware.bin +esptool.py v4.6-dev +Serial port /dev/ttyUSB0 +Connecting.... +Chip is ESP8266EX +Features: WiFi +Crystal is 26MHz +MAC: 2c:3a:e8:01:23:45 +Uploading stub... +Running stub... +Stub running... +Configuring flash size... +Flash will be erased from 0x00000000 to 0x00045fff... +Compressed 283552 bytes to 208462... +Writing at 0x00000000... (7 %) +Writing at 0x00005b34... (15 %) +Writing at 0x0000b2c4... (23 %) +Writing at 0x00010670... (30 %) +Writing at 0x00015f2e... (38 %) +Writing at 0x0001b0d0... (46 %) +Writing at 0x000200a6... (53 %) +Writing at 0x0002523e... (61 %) +Writing at 0x0002a761... (69 %) +Writing at 0x0002fcd8... (76 %) +Writing at 0x00035551... (84 %) +Writing at 0x0003ac1f... (92 %) +Writing at 0x00041169... (100 %) +Wrote 283552 bytes (208462 compressed) at 0x00000000 in 18.3 seconds (effective 124.1 kbit/s)... +Hash of data verified. + +Leaving... +Hard resetting via RTS pin... +=================================================== [SUCCESS] Took 23.76 seconds ==================================================== +``` diff --git a/case/clock.scad b/case/clock.scad new file mode 100644 index 0000000..8ff3f81 --- /dev/null +++ b/case/clock.scad @@ -0,0 +1,88 @@ +$fn=60; + +U=411; +R=U/(2*PI); +H=20; + +module frame() { + rotate([0,0,3]) { + difference() { + union() { + // fins + for (i=[0:59]) { + rotate([0,0,6*i]) { + translate([0,R+2,1]) { + cube([1,13,H]); + } + } + } + // base plate + cylinder(h=2, r=R+15); + // back plate + translate([0,0,H-2]) { + cylinder(h=2, r=R); + } + // outer back plate + translate([0,0,H-1]) { + difference() { + cylinder(h=2, r=R+15); + translate([0,0,-1]) { + cylinder(h=4, r=R+10); + } + } + } + // led mounting cylinder + translate([0,0,1]) { + cylinder(h=H, r=R); + } + } + // middle cutout + translate([0,0,-1]) { + cylinder(h=H+1, r=R-3); + } + // frontplate mounting hole + for (i=[0:30:359]) { + rotate([0,0,i-3]) { + translate([0,R+7,-1]) { + cylinder(h=4, r=2); + } + } + } + // cable feed hole + rotate([0,0,-3]) { + translate([0,-R-1.9,2]) { + rotate([0,0,25]) { + cube([20,2,H-2]); + } + } + } + rotate([0,0,-3]) { + // back plate mounting hole + translate([0,R-10,H-3]) { + hull() { + cylinder(h=4, r=2.5); + translate([0,-10,0]) { + cylinder(h=5, r=7.5); + } + } + } + // back plate button holes + translate([20,0,H-1]) { + cylinder(h=4, r=2.5); + } + translate([-20,0,H-1]) { + cylinder(h=4, r=2.5); + } + // cabling holes + translate([0,R+1, H-2]) { + rotate([90,0,0]) { + cylinder(h=5, r=2); + } + } + } + } + } +} + + +frame(); \ No newline at end of file diff --git a/case/clock.stl b/case/clock.stl new file mode 100644 index 0000000..26178b9 Binary files /dev/null and b/case/clock.stl differ diff --git a/esp/.gitignore b/esp/.gitignore new file mode 100644 index 0000000..03f4a3c --- /dev/null +++ b/esp/.gitignore @@ -0,0 +1 @@ +.pio diff --git a/esp/include/README b/esp/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/esp/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/esp/lib/README b/esp/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/esp/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/esp/platformio.ini b/esp/platformio.ini new file mode 100644 index 0000000..39bbc1a --- /dev/null +++ b/esp/platformio.ini @@ -0,0 +1,20 @@ +; 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 +; https://docs.platformio.org/page/projectconf.html + +[env:main] +platform = espressif8266 +board = nodemcu +framework = arduino +upload_protocol = esptool +lib_deps = + adafruit/Adafruit NeoPixel@^1.11.0 + sstaub/NTP@^1.6 +;board_build.mcu = esp8266 +;board_build.f_cpu = 80000000L \ No newline at end of file diff --git a/esp/src/main.cpp b/esp/src/main.cpp new file mode 100644 index 0000000..f3ec916 --- /dev/null +++ b/esp/src/main.cpp @@ -0,0 +1,51 @@ + +#include +#include +#include +#include + +Adafruit_NeoPixel pixels(60, 5, NEO_GRB + NEO_KHZ800); + + +const char *ssid = put your ssid here; +const char *password = put your psk here; + +WiFiUDP wifiUdp; +NTP ntp(wifiUdp); + +void setup() { + Serial.begin(115200); + WiFi.begin(ssid, password); + while (WiFi.status() != WL_CONNECTED) { + Serial.println("Connecting ..."); + delay(500); + } + Serial.println("Connected"); + ntp.ruleDST("CEST", Last, Sun, Mar, 2, 120); // last sunday in march 2:00, timetone +120min (+1 GMT + 1h summertime offset) + ntp.ruleSTD("CET", Last, Sun, Oct, 3, 60); // last sunday in october 3:00, timezone +60min (+1 GMT) + ntp.begin(); + Serial.println("start NTP"); + pixels.begin(); +} + +uint8_t sixtyToPixel(int8_t sixty) { + return (60 - ((sixty+30) % 60)) % 60; +} + +void loop() { + delay(1000); + ntp.update(); + Serial.println(ntp.formattedTime("%T")); + uint8_t h = sixtyToPixel((ntp.hours() % 12) * 5 + (ntp.minutes() / 12)); + uint8_t hl = sixtyToPixel((ntp.hours() % 12) * 5 + (ntp.minutes() / 12) - 1); + uint8_t hr = sixtyToPixel((ntp.hours() % 12) * 5 + (ntp.minutes() / 12) + 1); + uint8_t m = sixtyToPixel(ntp.minutes()); + uint8_t s = sixtyToPixel(ntp.seconds()); + pixels.clear(); + pixels.setPixelColor(hl, pixels.getPixelColor(hl) | 0x00ff0000); + pixels.setPixelColor(h, pixels.getPixelColor(h) | 0x00ff0000); + pixels.setPixelColor(hr, pixels.getPixelColor(hr) | 0x00ff0000); + pixels.setPixelColor(m, pixels.getPixelColor(m) | 0x0000ff60); + pixels.setPixelColor(s, pixels.getPixelColor(s) | 0x000000ff); + pixels.show(); +} diff --git a/esp/test/README b/esp/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/esp/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html