From 0d295cc6a413ef6804134030a43e6c1c2467499c Mon Sep 17 00:00:00 2001 From: Derf Null Date: Sat, 22 Apr 2023 19:57:22 +0200 Subject: initial commit --- .gitmodules | 6 ++ case/scd4x-ssd1306.scad | 172 ++++++++++++++++++++++++++++++++++++++++++++++ ext/scd4x | 1 + ext/ssd1306 | 1 + src/framebuffer.lua | 1 + src/init.lua | 177 ++++++++++++++++++++++++++++++++++++++++++++++++ src/scd4x.lua | 1 + src/ssd1306.lua | 1 + src/terminus16.lua | 1 + 9 files changed, 361 insertions(+) create mode 100644 .gitmodules create mode 100644 case/scd4x-ssd1306.scad create mode 160000 ext/scd4x create mode 160000 ext/ssd1306 create mode 120000 src/framebuffer.lua create mode 100644 src/init.lua create mode 120000 src/scd4x.lua create mode 120000 src/ssd1306.lua create mode 120000 src/terminus16.lua diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..c402270 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "ext/scd4x"] + path = ext/scd4x + url = https://github.com/derf/esp8266-nodemcu-scd4x.git +[submodule "ext/ssd1306"] + path = ext/ssd1306 + url = https://github.com/derf/esp8266-nodemcu-ssd1306.git diff --git a/case/scd4x-ssd1306.scad b/case/scd4x-ssd1306.scad new file mode 100644 index 0000000..081711c --- /dev/null +++ b/case/scd4x-ssd1306.scad @@ -0,0 +1,172 @@ +height = 22; +taster_y = 21; +taster_x = 26; + +m3 = 1.55; +nut = [5.4, 2.4, 5.4]; +nutbox = [8, 8, 8]; +m3_zo = 4; + +module battery(top=false) { + liion = [21.8, 81, height]; + wall1 = [2, 2, 2]; + wall2 = [2, 2, 0]; + if (top) { + translate(-wall2) cube([liion.x, liion.y, 0] + wall1 + wall2); + } else { + difference() { + translate(-wall1) cube(liion + wall1 + wall2); + cube(liion); + translate([liion.x, 0, 0]) cube([wall2.x, 4, height]); + translate([liion.x, liion.y-15, 0]) cube([wall2.x, 15, height]); + } + } +} + +module board(top=false) { + $fn=60; + pcb = [59, 71, height]; + wall1 = [0, 2, 2]; + wall2 = [2, 2, 0]; + scdwall = [27, 2, 15]; + scdwall_o = [0, pcb.y - scdwall.y - 25, -scdwall.z]; + espwall = [pcb.x, 2, 12]; + espwall_o = [0, pcb.y - espwall.y - 31, -espwall.z]; + disp = [23.5, 13.5, wall1.z]; + disp_o = [32, pcb.y - disp.y - 8, 0]; + disp_wx = [disp.x + 2, 1, 5.5]; + disp_wy = [1, disp.y + 2, 5.5]; + disp_wx1o = [disp_o.x - 1, disp_o.y - 1, -disp_wx.z]; + disp_wx2o = [disp_o.x - 1, disp_o.y + disp.y, -disp_wx.z]; + disp_wy1o = disp_wx1o; + disp_wy2o = [disp_o.x + disp.x, disp_o.y - 1, -disp_wy.z]; + extra_x = 8; + extra_y = 8; + extra_floor = [extra_x, extra_y, wall1.z]; + m3_1_o = [4, -wall1.y, pcb.z-m3_zo]; + m3_2_o = [4, pcb.y + wall2.y + 10, pcb.z-m3_zo]; + if (top) { + difference() { + translate(-wall2) cube([pcb.x, pcb.y, 0] + wall1 + wall2 + [wall2.x, 0, 0]); + for (x = [0:2]) { + for (y = [0:2]) { + translate([5 + 8*x, pcb.y - wall2.y - 2 - 8*y, 0]) cylinder(h=wall2.y, r=2); + } + } + for (x = [0:6]) { + for (y = [4:7]) { + translate([5 + 8*x, pcb.y - wall2.y - 2 - 8*y, 0]) cylinder(h=wall2.y, r=1.5); + } + } + translate(disp_o - [5, 5, 0]) cube(disp + [10, 10, 0]); + //translate([m3_1_o.x - nut.x/2, 3, 0]) cube(nut); + } + for (i = [0:0.1:6]) { + intersection() { + union() { + translate(disp_wx1o - [i, i, 0]) cube(disp_wx + [2*i, 0, 0]); + translate(disp_wx2o - [i, -i, 0]) cube(disp_wx + [2*i, 0, 0]); + translate(disp_wy1o - [i, i, 0]) cube(disp_wy + [0, 2*i, 0]); + translate(disp_wy2o - [-i, i, 0]) cube(disp_wy + [0, 2*i, 0]); + } + translate(disp_o - [i+2, i+2, 6-i]) cube(disp + [2*i+8, 2*i+8, -0.8]); + } + } + translate([0, pcb.y + wall2.y, 0]) cube([extra_x + wall2.x, extra_y + wall1.y, wall1.z]); + translate(scdwall_o) cube(scdwall); + translate(espwall_o) cube(espwall); + difference() { + translate([m3_1_o.x - nutbox.x/2, 0.1, -nutbox.z]) cube(nutbox); + translate([m3_1_o.x - nut.x/2, 3, -m3_zo -nut.z/2]) cube(nut + [10, 0, 0]); + #translate([m3_1_o.x, 0, -m3_zo]) rotate([-90, 0, 0]) cylinder(h=10, r=m3); + } + difference() { + translate([m3_2_o.x - nutbox.x/2, pcb.y + wall2.y + extra_y - nutbox.y - 0.1, -nutbox.z]) cube(nutbox); + translate([m3_2_o.x - nut.x/2, pcb.y + wall2.y + extra_y - 5.1, -m3_zo -nut.z/2]) cube(nut + [10, 0, 0]); + translate([m3_2_o.x, pcb.y + extra_y - nutbox.y - 0.1, -m3_zo]) rotate([-90, 0, 0]) cylinder(h=10, r=m3); + } + } else { + difference() { + translate(-wall1) cube(pcb + wall1 + wall2); + cube(pcb); + translate([0, pcb.y, 0]) cube([extra_x, wall2.y, height]); + translate([pcb.x, 15, 0]) cube([wall2.x, pcb.y-30, height]); + translate([pcb.x, 30, height/2]) cube([wall2.x, pcb.y-30, height/2]); + translate([8, -wall1.y, 4]) cube([15, wall1.y, height-4]); + translate(m3_1_o) rotate([-90, 0, 0]) cylinder(h=5, r=m3); + translate(m3_1_o) rotate([-90, 0, 0]) cylinder(h=1.8, r1=1.8*m3, r2=m3); + for (x = [0:1]) { + for (z = [0:1]) { + translate([extra_x + 2*wall2.x + 8*x, pcb.y + wall2.y, 10 + 8*z]) rotate([90, 0, 0]) cylinder(h=wall2.y, r=2); + } + } + } + translate([0, pcb.y + wall2.y, -wall1.z]) cube(extra_floor); + difference() { + union() { + translate([0, pcb.y + wall2.y + extra_y, -wall1.z]) cube([extra_x, wall2.y, wall1.z+height]); + translate([extra_x, pcb.y + wall2.y, -wall1.z]) cube([wall2.x, extra_y+wall2.y, wall1.z+height]); + } + translate(m3_2_o) rotate([90, 0, 0]) cylinder(h=10, r=m3); + translate(m3_2_o) rotate([90, 0, 0]) cylinder(h=1.8, r1=1.8*m3, r2=m3); + } + } +} + +module taster(top=false) { + $fn=60; + taster_y = 21; + dim = [26, 71, height]; + wall1 = [0, 2, 2]; + wall2 = [1, 2, 0]; + posl = [dim.x - 0.3, 2, 10]; + posl_o = [0.15, 0.15, -posl.z]; + post = [dim.x - 0.3, 2, 10]; + post_o = [0.15, dim.y - post.y - 0.15, -post.z]; + m3_1_o = [dim.x/2, -wall1.y, dim.z-m3_zo]; + m3_2_o = [dim.x/2, dim.y+wall1.y, dim.z-m3_zo]; + if (top) { + translate(-wall2) cube([dim.x, dim.y, 0] + wall1 + wall2 + [wall2.x, 0, 0]); + difference() { + union() { + translate([m3_1_o.x - nutbox.x/2, 0.1, -nutbox.z]) cube(nutbox); + translate(posl_o) cube(posl); + } + translate([m3_1_o.x - nut.x/2, 3, -m3_zo -nut.z/2]) cube(nut + [10, 0, 0]); + #translate([m3_1_o.x, 0, -m3_zo]) rotate([-90, 0, 0]) cylinder(h=10, r=m3); + } + difference() { + union() { + translate(post_o) cube(post); + translate([m3_2_o.x - nutbox.x/2, dim.y - nutbox.y - 0.1, -nutbox.z]) cube(nutbox); + } + translate([m3_2_o.x - nut.x/2, dim.y - 5.1, -m3_zo -nut.z/2]) cube(nut + [10, 0, 0]); + translate([m3_2_o.x, dim.y - nutbox.y - 0.1, -m3_zo]) rotate([-90, 0, 0]) cylinder(h=10, r=m3); + } + } else { + difference() { + translate(-wall1) cube(dim + wall1 + wall2); + cube(dim); + translate([dim.x, 20, 0]) cube([wall2.x, taster_y, height]); + translate(m3_1_o) rotate([-90, 0, 0]) cylinder(h=5, r=m3); + translate(m3_1_o) rotate([-90, 0, 0]) cylinder(h=1.8, r1=1.8*m3, r2=m3); + translate(m3_2_o) rotate([90, 0, 0]) cylinder(h=5, r=m3); + translate(m3_2_o) rotate([90, 0, 0]) cylinder(h=1.8, r1=1.8*m3, r2=m3); + } + } +} + +module bottom() { + battery(); + translate([23.8, 0, 0]) board(); + translate([23.8+59+2, 0, 0]) taster(); +} + +module top() { + battery(top=true); + translate([23.8, 0, 0]) board(top=true); + translate([23.8+59+2, 0, 0]) taster(top=true); +} + +bottom(); +translate([0, 0, 22+0]) top(); \ No newline at end of file diff --git a/ext/scd4x b/ext/scd4x new file mode 160000 index 0000000..6abca13 --- /dev/null +++ b/ext/scd4x @@ -0,0 +1 @@ +Subproject commit 6abca13a48be662b53ec98377a9e2edbb9aa8c9f diff --git a/ext/ssd1306 b/ext/ssd1306 new file mode 160000 index 0000000..888ad2d --- /dev/null +++ b/ext/ssd1306 @@ -0,0 +1 @@ +Subproject commit 888ad2d273ce8353dc90b3bdc99021a0fefa635b diff --git a/src/framebuffer.lua b/src/framebuffer.lua new file mode 120000 index 0000000..ba71daf --- /dev/null +++ b/src/framebuffer.lua @@ -0,0 +1 @@ +../ext/ssd1306/framebuffer.lua \ No newline at end of file diff --git a/src/init.lua b/src/init.lua new file mode 100644 index 0000000..2286d4e --- /dev/null +++ b/src/init.lua @@ -0,0 +1,177 @@ +station_cfg = {} + +chip_id = string.format("%06X", node.chipid()) +device_id = "esp8266_" .. chip_id + +dofile("config.lua") + +i2c.setup(0, 1, 2, i2c.SLOW) +ssd1306 = require("ssd1306") +fn = require("terminus16") +fb = require("framebuffer") +scd4x = require("scd4x") + +ledpin = 4 +gpio.mode(ledpin, gpio.OUTPUT) +gpio.write(ledpin, 0) + +ssd1306.init(128, 64) +ssd1306.contrast(255) +fb.init(128, 64) + +timestamp = 0 +old_timestamp = 0 +no_wifi_count = 0 +publish_count = 0 +message_queue = {} + +-- cal 2023-01-06 +-- 4.2V -> "4.36V" (raw ~ 948) +-- 4.0V -> "4.16V" (raw ~ 905) +-- 3.8V -> "3.95V" (raw ~ 859) +-- 3.6V -> "3.74V" (raw ~ 814) +function get_battery_mv() + return adc.read(0) * 461 / 104 +end + +function get_battery_percent(bat_mv) + if bat_mv > 4160 then + return 100 + end + if bat_mv < 3360 then + return 0 + end + return (bat_mv - 3360) / 8 +end + +function get_time() + publishing_http = true + http.get("http://arclight:1234/", nil, function(status, body, headers) + publishing_http = false + if timestamp < 604800 then + old_timestamp = timestamp + end + timestamp = tonumber(body) + end) +end + +function connect_wifi() + print("WiFi MAC: " .. wifi.sta.getmac()) + print("Connecting to ESSID " .. station_cfg.ssid) + wifi.eventmon.register(wifi.eventmon.STA_GOT_IP, wifi_connected) + wifi.eventmon.register(wifi.eventmon.STA_DHCP_TIMEOUT, wifi_err) + wifi.eventmon.register(wifi.eventmon.STA_DISCONNECTED, wifi_err) + wifi.setmode(wifi.STATION) + wifi.sta.config(station_cfg) + wifi.sta.connect() +end + +function scd4x_start() + if scd4x.start() then + gpio.write(ledpin, 1) + else + print("SCD4x error") + end + local measure_t = tmr.create() + measure_t:register(5 * 1000, tmr.ALARM_AUTO, measure) + measure_t:start() +end + +function measure() + timestamp = timestamp + 5 + fb.init(128, 64) + local co2, raw_temp, raw_humi = scd4x.read() + local bat_mv = get_battery_mv() + local bat_p = get_battery_percent(bat_mv) + local line1 = "" + local line2 = "" + if co2 == nil then + line1 = "SCD4x error" + fb.print(fn, line1) + ssd1306.show(fb.buf) + return + end + line1 = string.format("%8d ppm\n\n", co2) + line2 = string.format("%8d.%d c\n%8d.%d %%", raw_temp/65536 - 45, (raw_temp%65536)/6554, raw_humi/65536, (raw_humi%65536)/6554) + fb.print(fn, line1) + fb.print(fn, line2) + fb.draw_battery_8(114, 0, bat_p) + if have_wifi then + fb.x = 90 + fb.print(fn, string.format("%02d:%02d", (timestamp / 3600) % 24, (timestamp / 60) % 60)) + elseif no_wifi_count < 120 then + no_wifi_count = no_wifi_count + 1 + else + table.insert(message_queue, {timestamp, co2, raw_temp, raw_humi}) + fb.x = 100 + fb.print(fn, string.format("%d", table.getn(message_queue))) + no_wifi_count = 0 + connect_wifi() + end + ssd1306.show(fb.buf) + fb.init(128, 64) + publish_count = publish_count + 1 + if have_wifi and influx_url and publish_count >= 4 and not publishing_http then + publish_count = 0 + gpio.write(ledpin, 0) + publish_influx(co2, raw_temp, raw_humi, bat_mv) + elseif have_wifi and influx_url and not publishing_http and timestamp > 604800 then + for i, v in ipairs(message_queue) do + if v[1] < 604800 then + v[1] = timestamp - (old_timestamp - v[1]) + end + end + --print(timestamp) + empty_queue(message_queue) + else + collectgarbage() + end +end + +function publish_influx(co2, raw_temp, raw_humi, bat_mv) + publishing_http = true + http.post(influx_url, influx_header, string.format("scd4x%s co2_ppm=%d,temperature_celsius=%d.%d,humidity_relpercent=%d.%d", influx_attr, co2, raw_temp/65536 - 45, (raw_temp%65536)/6554, raw_humi/65536, (raw_humi%65536)/6554), function(code, data) + http.post(influx_url, influx_header, string.format("esp8266%s battery_mv=%d", influx_attr, bat_mv), function(code, data) + publishing_http = false + gpio.write(ledpin, 1) + get_time() + collectgarbage() + end) + end) +end + +function empty_queue(q) + local n = table.getn(q) + if n > 0 then + publishing_http = true + gpio.write(ledpin, 0) + local ts = q[n][1] + local co2 = q[n][2] + local t = q[n][3] + local h = q[n][4] + table.remove(q) + --print(influx_url .. '&precision=s') + --print(string.format("scd4x%s co2_ppm=%d,temperature_celsius=%d.%d,humidity_relpercent=%d.%d %d", influx_attr, co2, t/65536 - 45, (t%65536)/6554, h/65536, (h%65536)/6554, ts)) + http.post(influx_url .. '&precision=s', influx_header, string.format("scd4x%s co2_ppm=%d,temperature_celsius=%d.%d,humidity_relpercent=%d.%d %d", influx_attr, co2, t/65536 - 45, (t%65536)/6554, h/65536, (h%65536)/6554, ts), function(code, data) + --print('Q ' .. n .. ' returned ' .. code .. ' ' .. data) + empty_queue(q) + end) + else + publishing_http = false + gpio.write(ledpin, 1) + end +end + +local init_t = tmr.create() +init_t:register(1 * 1000, tmr.ALARM_SINGLE, scd4x_start) +init_t:start() + +function wifi_connected() + have_wifi = true +end + +function wifi_err() + have_wifi = false +end + +connect_wifi() diff --git a/src/scd4x.lua b/src/scd4x.lua new file mode 120000 index 0000000..6d93ddd --- /dev/null +++ b/src/scd4x.lua @@ -0,0 +1 @@ +../ext/scd4x/scd4x.lua \ No newline at end of file diff --git a/src/ssd1306.lua b/src/ssd1306.lua new file mode 120000 index 0000000..ed7ee52 --- /dev/null +++ b/src/ssd1306.lua @@ -0,0 +1 @@ +../ext/ssd1306/ssd1306.lua \ No newline at end of file diff --git a/src/terminus16.lua b/src/terminus16.lua new file mode 120000 index 0000000..1ee383e --- /dev/null +++ b/src/terminus16.lua @@ -0,0 +1 @@ +../ext/ssd1306/terminus16.lua \ No newline at end of file -- cgit v1.2.3