From 7599b3f652b19d733d1902947252a4d04f7c637b Mon Sep 17 00:00:00 2001 From: bdnugget Date: Mon, 16 Jun 2025 20:58:46 +0200 Subject: [PATCH] Add Go server with POST LDR to Sqlite3 and GET graphs of historic data --- server/README.md | 72 +++++++++++++++++ server/cmd/import_history/main.go | 118 ++++++++++++++++++++++++++++ server/go.mod | 5 ++ server/go.sum | 2 + server/ldr_readings.db | Bin 0 -> 69632 bytes server/main.go | 123 +++++++++++++++++++++++++++++ server/web/templates/index.html | 125 ++++++++++++++++++++++++++++++ 7 files changed, 445 insertions(+) create mode 100644 server/README.md create mode 100644 server/cmd/import_history/main.go create mode 100644 server/go.mod create mode 100644 server/go.sum create mode 100644 server/ldr_readings.db create mode 100644 server/main.go create mode 100644 server/web/templates/index.html diff --git a/server/README.md b/server/README.md new file mode 100644 index 0000000..b9bba15 --- /dev/null +++ b/server/README.md @@ -0,0 +1,72 @@ +# Chicken Coop LDR Monitor Server + +This is a Go server that receives and stores LDR (Light Dependent Resistor) readings from the ESP8266 and provides a web interface to visualize the data. + +## Prerequisites + +- Go 1.16 or later +- SQLite3 development libraries + +On Ubuntu/Debian, install SQLite3 development libraries with: +```bash +sudo apt-get install libsqlite3-dev +``` + +## Setup + +1. Install Go dependencies: +```bash +go mod tidy +``` + +2. Update the ESP8266 code with your Go server's IP address: +- Open `ESP8266/ESP8266.ino` +- Change the `goServerHost` value to your Go server's IP address + +## Running the Server + +1. Start the server: +```bash +go run main.go +``` + +2. Access the web interface at `http://localhost:8080` + +The server will: +- Listen for LDR readings on `/api/ldr` endpoint +- Store readings in a SQLite database +- Provide a web interface with charts showing LDR values over time +- Support different time ranges (day, week, month, all time) + +## Importing Historical Data + +If you have a Telegram export file (`tg_export.json`) with historical LDR readings, you can import them into the database: + +1. Make sure the Telegram export file is in the root directory of the project +2. Run the import script: +```bash +cd cmd/import_history +go run main.go +``` + +The script will: +- Read the Telegram export file +- Extract LDR readings from messages +- Import them into the SQLite database +- Show the number of readings imported + +## API Endpoints + +- `POST /api/ldr` - Receive LDR readings + - Body: `{"value": 123, "timestamp": "2024-03-14T12:00:00Z"}` + - Note: timestamp is optional, server will use current time if not provided + +- `GET /api/readings?period=day|week|month|all` - Get LDR readings + - Returns JSON array of readings with timestamps + +## Web Interface + +The web interface provides: +- Line chart of LDR values over time +- Time range selector (24h, week, month, all time) +- Auto-refresh every 5 minutes \ No newline at end of file diff --git a/server/cmd/import_history/main.go b/server/cmd/import_history/main.go new file mode 100644 index 0000000..1c8700f --- /dev/null +++ b/server/cmd/import_history/main.go @@ -0,0 +1,118 @@ +package main + +import ( + "database/sql" + "encoding/json" + "fmt" + "log" + "os" + "strconv" + "strings" + "time" + + _ "github.com/mattn/go-sqlite3" +) + +func main() { + // Open the database + db, err := sql.Open("sqlite3", "../../ldr_readings.db") + if err != nil { + log.Fatal(err) + } + defer db.Close() + + // Create the table if it doesn't exist + createTable := ` + CREATE TABLE IF NOT EXISTS ldr_readings ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + value INTEGER NOT NULL, + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP + );` + + _, err = db.Exec(createTable) + if err != nil { + log.Fatal(err) + } + + // Read the Telegram export file + data, err := os.ReadFile("../../../tg_export.json") + if err != nil { + log.Fatal(err) + } + + // Parse the JSON into a map + var export map[string]interface{} + if err := json.Unmarshal(data, &export); err != nil { + log.Printf("Error unmarshaling JSON: %v", err) + log.Printf("First 100 bytes of data: %s", string(data[:100])) + log.Fatal(err) + } + + // Get the messages array + messages, ok := export["messages"].([]interface{}) + if !ok { + log.Fatal("Could not find messages array in export") + } + + // Prepare the insert statement + stmt, err := db.Prepare("INSERT INTO ldr_readings (value, timestamp) VALUES (?, ?)") + if err != nil { + log.Fatal(err) + } + defer stmt.Close() + + // Process each message + count := 0 + for _, msgInterface := range messages { + msg, ok := msgInterface.(map[string]interface{}) + if !ok { + continue + } + + // Get the text field + text, ok := msg["text"].(string) + if !ok { + continue + } + + // Only process messages containing LDR values + if !strings.HasPrefix(text, "Current LDR value: ") { + continue + } + + // Extract the LDR value + valueStr := strings.TrimPrefix(text, "Current LDR value: ") + value, err := strconv.Atoi(valueStr) + if err != nil { + log.Printf("Error parsing LDR value from message: %s", text) + continue + } + + // Get the date field + date, ok := msg["date"].(string) + if !ok { + continue + } + + // Parse the timestamp + timestamp, err := time.Parse("2006-01-02T15:04:05", date) + if err != nil { + log.Printf("Error parsing timestamp from message: %s", date) + continue + } + + // Insert into database + _, err = stmt.Exec(value, timestamp) + if err != nil { + log.Printf("Error inserting record: %v", err) + continue + } + + count++ + if count%100 == 0 { + fmt.Printf("Imported %d readings...\n", count) + } + } + + fmt.Printf("Successfully imported %d LDR readings\n", count) +} diff --git a/server/go.mod b/server/go.mod new file mode 100644 index 0000000..131f67f --- /dev/null +++ b/server/go.mod @@ -0,0 +1,5 @@ +module chickencoop + +go 1.24.4 + +require github.com/mattn/go-sqlite3 v1.14.28 diff --git a/server/go.sum b/server/go.sum new file mode 100644 index 0000000..42e5bac --- /dev/null +++ b/server/go.sum @@ -0,0 +1,2 @@ +github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= +github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= diff --git a/server/ldr_readings.db b/server/ldr_readings.db new file mode 100644 index 0000000000000000000000000000000000000000..8a75146667c412a406cd735f41cd20fbcea1f6bd GIT binary patch literal 69632 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%0DR(l1aOcgLU9%0hv;qjMIhM1G+@W_GQ0ywU7PG~^ z`F;7F`7QYk`L29Rz9HY6zX15n>;wG%-zC{ovxB+Re*d&r1xW|~J#^(^Zi9r;RxZT0 zR|QE2;=xr>FolW=LDGTvQ1FU~`K?uuG@H9xS0Xd0avh+x3Xx`W&rAf`0U*uhji zPtgG$&E}>Ofp!2#^-t2!NVgxL9H7x`u5KNGjaUURn$6X$fH`dxz@mCnL8@R%TMC-1 zpr|@bFL5TcF%F2TMModk<-yQ2?lRGPIX72<&`MmF2SII|36m}lfL7wVJouT$RT7%3 zz-J|{%Y&Y2T&1A73V2rHx;)sK##I8Et3YQZuFHd*Y25Tbz*&jw^5A9~H~kN6R^qxm zsA=OeF^c&EG%Imk9?VSRrvHJ=N?ex*G1Iu|e*m)**X6;>G;aDIxU9r=dC)SAoBjtZ zD{);OtW4vk|AER%T$cwaZJgD7$4Xq+4>qQ8 z)BiwYC9dlS8PmAy|Ij{wu@cwi!NoN0`ahHh7AtXG9#l-@uKz=MK(P|n<-x=>Zu%cc zti*MB5K+Zv+;}X8h8C4+7BF7;<`L&n8r>21BR72<-_|8EKK93|AE3v zT-Og0rg7K*J-lhI0)&;gE)Nc-ant|6U?r~0gM#Y)PyhSpf3N>qtAL>Xn9d7g2eek< zK>abD2vi1It6-r1%ZpwRo6uT?0<*b&!z4xoDhI7qATXOdFcD}67*KyqCk`qLtyK_E ze@y2E(V@L61URr@>rexsy($E#zJwDGH!baTReMzkaNwV;he}3!RR~c1F12{DLlHfM z0JF=qhkqf2T7&>IP1VfjSlJ66D?)&oUJXN`Qqo=#0?bEjEv!_Ow_STh2rxg?DqKyIf7a4lqRV z@^HVP@Ms(qpdpH-M4>%^!>Wh7vx5hCh~oX$!`<0I0U)A?JL1v~cV`C$h=}5L>*4O~ zpa2q4#5H{Ja8I0|025Kfk?s|El3UW?01Pymc5;`0>`KV=FEkP$^ZCF~X21877M_aj8%@)A72MijrV+aY@i zQ2{ujh%4jb;qp?h(Ow5SqR@+AwAAG#)B-%Bh^x5b;qnp`;3JB-HtH4H1As(f?jZIO zv@Qa^JF3Sbh2 zxeNWwDJZ~66mjQ7YH{NwC;&+{Y5_D+#Qg|b>Ss>y5CoR=^Srrm3B8hs1ZikR)F>70@^Rg-=T0AsSXb&OL;=x)WX9r_T zg+z;oBns`}N%S{Xq4TopNi=>h!?noS!B~qY(fzH58!w?xJ&CSQ6uKRrM9;7alNV2- zrzHyQ;YoCFt1x--B)Tb4Xb(@K2Uvy4i;$@LzD)YVySR9a_O*8GWCgEs8S} zh4wg06mwQ#@^Ypq%xG{&Vbrxe+bOaiTMv_$XNf}3ljXRC^4KPdxROP$vvIGl + + + + + Chicken Coop LDR Monitor + + + + + +
+

Chicken Coop LDR Monitor

+
+ +
+ +
+ + + + \ No newline at end of file