This commit is contained in:
		
						commit
						5801910be2
					
				
					 9 changed files with 473 additions and 0 deletions
				
			
		
							
								
								
									
										58
									
								
								.gitea/workflows/docker.yml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								.gitea/workflows/docker.yml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,58 @@
 | 
				
			||||||
 | 
					name: Build Docker image
 | 
				
			||||||
 | 
					run-name: ${{ gitea.actor }} is building a new image 🚀
 | 
				
			||||||
 | 
					on:
 | 
				
			||||||
 | 
					  # schedule:
 | 
				
			||||||
 | 
					  #   - cron: "0 10 * * *"
 | 
				
			||||||
 | 
					  push:
 | 
				
			||||||
 | 
					    branches:
 | 
				
			||||||
 | 
					      - "**"
 | 
				
			||||||
 | 
					    tags:
 | 
				
			||||||
 | 
					      - "v*.*.*"
 | 
				
			||||||
 | 
					  pull_request:
 | 
				
			||||||
 | 
					    branches:
 | 
				
			||||||
 | 
					      - "main"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					jobs:
 | 
				
			||||||
 | 
					  build:
 | 
				
			||||||
 | 
					    runs-on: ubuntu-docker
 | 
				
			||||||
 | 
					    steps:      
 | 
				
			||||||
 | 
					      - name: echo job info
 | 
				
			||||||
 | 
					        run: echo "🎉 This job was automatically triggered by a ${{ gitea.event_name }} event and running on a ${{ runner.os }} repo:branch:${{ gitea.repository }}:${{ gitea.ref }}."
 | 
				
			||||||
 | 
					      - name: Check out repository code
 | 
				
			||||||
 | 
					        uses: actions/checkout@v4
 | 
				
			||||||
 | 
					      - name: Set up Docker Buildx
 | 
				
			||||||
 | 
					        uses: docker/setup-buildx-action@v3
 | 
				
			||||||
 | 
					      - name: Generate image tags
 | 
				
			||||||
 | 
					        # https://docs.docker.com/build/ci/github-actions/manage-tags-labels/
 | 
				
			||||||
 | 
					        id: meta
 | 
				
			||||||
 | 
					        uses: docker/metadata-action@v5
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          # list of Docker images to use as base name for tags
 | 
				
			||||||
 | 
					          images: |
 | 
				
			||||||
 | 
					            git.hollander.online/${{ gitea.repository_owner }}/${{ gitea.event.repository.name }}
 | 
				
			||||||
 | 
					          # generate Docker tags based on the following events/attributes
 | 
				
			||||||
 | 
					          tags: |
 | 
				
			||||||
 | 
					            type=schedule
 | 
				
			||||||
 | 
					            type=ref,event=branch
 | 
				
			||||||
 | 
					            type=ref,event=pr
 | 
				
			||||||
 | 
					            type=semver,pattern={{version}}
 | 
				
			||||||
 | 
					            type=semver,pattern={{major}}.{{minor}}
 | 
				
			||||||
 | 
					            type=semver,pattern={{major}}
 | 
				
			||||||
 | 
					            type=sha
 | 
				
			||||||
 | 
					      - name: Login to registry
 | 
				
			||||||
 | 
					        if: gitea.event_name != 'pull_request'
 | 
				
			||||||
 | 
					        uses: docker/login-action@v3
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          registry: git.hollander.online
 | 
				
			||||||
 | 
					          username: ${{ gitea.repository_owner }}
 | 
				
			||||||
 | 
					          password: ${{ secrets.CI_PACKAGES_RW }}
 | 
				
			||||||
 | 
					      - name: Build and push
 | 
				
			||||||
 | 
					        uses: docker/build-push-action@v5
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          context: .
 | 
				
			||||||
 | 
					          push: ${{ gitea.event_name != 'pull_request' }}
 | 
				
			||||||
 | 
					          tags: ${{ steps.meta.outputs.tags }}
 | 
				
			||||||
 | 
					          labels: ${{ steps.meta.outputs.labels }}
 | 
				
			||||||
 | 
					          platforms: linux/arm64,linux/amd64
 | 
				
			||||||
 | 
					      - name: Cleanup old images
 | 
				
			||||||
 | 
					        run: docker system prune -f
 | 
				
			||||||
							
								
								
									
										28
									
								
								.gitea/workflows/go.yml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								.gitea/workflows/go.yml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,28 @@
 | 
				
			||||||
 | 
					# # https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
 | 
				
			||||||
 | 
					# # https://github.com/goreleaser/goreleaser-action
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# name: Release Go package
 | 
				
			||||||
 | 
					# on: [push]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# jobs:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#   build:
 | 
				
			||||||
 | 
					#     runs-on: ubuntu-docker
 | 
				
			||||||
 | 
					#     strategy:
 | 
				
			||||||
 | 
					#       matrix:
 | 
				
			||||||
 | 
					#         go-version: [ '1.21' ]
 | 
				
			||||||
 | 
					#     steps:
 | 
				
			||||||
 | 
					#       - uses: actions/checkout@v4
 | 
				
			||||||
 | 
					#       # - name: Setup Go ${{ matrix.go-version }}
 | 
				
			||||||
 | 
					#       #   uses: actions/setup-go@v4
 | 
				
			||||||
 | 
					#       #   with:
 | 
				
			||||||
 | 
					#       #     go-version: ${{ matrix.go-version }}
 | 
				
			||||||
 | 
					#       # # You can test your matrix by printing the current Go version
 | 
				
			||||||
 | 
					#       - name: Display Go version
 | 
				
			||||||
 | 
					#         run: go version
 | 
				
			||||||
 | 
					#       - name: Run GoReleaser
 | 
				
			||||||
 | 
					#         uses: goreleaser/goreleaser-action@master
 | 
				
			||||||
 | 
					#         with:
 | 
				
			||||||
 | 
					#           version: latest
 | 
				
			||||||
 | 
					#           args: release --rm-dist
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
							
								
								
									
										72
									
								
								.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,72 @@
 | 
				
			||||||
 | 
					# .env files
 | 
				
			||||||
 | 
					.env
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Nano temporary files
 | 
				
			||||||
 | 
					*.swp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# ---> macOS
 | 
				
			||||||
 | 
					# General
 | 
				
			||||||
 | 
					.DS_Store
 | 
				
			||||||
 | 
					.AppleDouble
 | 
				
			||||||
 | 
					.LSOverride
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Icon must end with two \r
 | 
				
			||||||
 | 
					Icon
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Thumbnails
 | 
				
			||||||
 | 
					._*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Files that might appear in the root of a volume
 | 
				
			||||||
 | 
					.DocumentRevisions-V100
 | 
				
			||||||
 | 
					.fseventsd
 | 
				
			||||||
 | 
					.Spotlight-V100
 | 
				
			||||||
 | 
					.TemporaryItems
 | 
				
			||||||
 | 
					.Trashes
 | 
				
			||||||
 | 
					.VolumeIcon.icns
 | 
				
			||||||
 | 
					.com.apple.timemachine.donotpresent
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Directories potentially created on remote AFP share
 | 
				
			||||||
 | 
					.AppleDB
 | 
				
			||||||
 | 
					.AppleDesktop
 | 
				
			||||||
 | 
					Network Trash Folder
 | 
				
			||||||
 | 
					Temporary Items
 | 
				
			||||||
 | 
					.apdisk
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# ---> Linux
 | 
				
			||||||
 | 
					*~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# temporary files which can be created if a process still has a handle open of a deleted file
 | 
				
			||||||
 | 
					.fuse_hidden*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# KDE directory preferences
 | 
				
			||||||
 | 
					.directory
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Linux trash folder which might appear on any partition or disk
 | 
				
			||||||
 | 
					.Trash-*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# .nfs files are created when an open file is removed but is still being accessed
 | 
				
			||||||
 | 
					.nfs*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# ---> Go
 | 
				
			||||||
 | 
					# Binaries for programs and plugins
 | 
				
			||||||
 | 
					*.exe
 | 
				
			||||||
 | 
					*.exe~
 | 
				
			||||||
 | 
					*.dll
 | 
				
			||||||
 | 
					*.so
 | 
				
			||||||
 | 
					*.dylib
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Test binary, built with `go test -c`
 | 
				
			||||||
 | 
					*.test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Output of the go coverage tool, specifically when used with LiteIDE
 | 
				
			||||||
 | 
					*.out
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Dependency directories (remove the comment below to include it)
 | 
				
			||||||
 | 
					# vendor/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Go workspace file
 | 
				
			||||||
 | 
					go.work
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# ESPhome
 | 
				
			||||||
 | 
					.esphome
 | 
				
			||||||
							
								
								
									
										53
									
								
								chatgpt prompt.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								chatgpt prompt.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,53 @@
 | 
				
			||||||
 | 
					# ChatGPT prompt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Please write a golang program that subscribes to an mqtt topic which outputs the following payload
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{"t":"231205164749W","dt1":830959,"dt2":729319,"rt1":33727,"rt2":111841,"d":224,"r":0,"f":18,"fl":17,"g":426077,"v1":219,"v2":227,"v3":223,"c1":0,"c2":0,"c3":0,"d1":84,"d2":50,"d3":90,"r1":0,"r2":0,"r3":0}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					T is the Timestamp in Timezone Europe/Amsterdam and format YYMMDDhhmmss. The letter at the end of the timestamp can be either "W" for Winter or "S" for Summer and indicates daylight savings time.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The data should be inserted into a configurable postgres database with structure
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```sql
 | 
				
			||||||
 | 
					CREATE TABLE p1 (
 | 
				
			||||||
 | 
					timestamp TIMESTAMPTZ,
 | 
				
			||||||
 | 
					delivered_tariff1 INT,
 | 
				
			||||||
 | 
					delivered_tariff2 INT,
 | 
				
			||||||
 | 
					returned_tariff1 INT,
 | 
				
			||||||
 | 
					returned_tariff2 INT,
 | 
				
			||||||
 | 
					delivery_all INT,
 | 
				
			||||||
 | 
					returning_all INT,
 | 
				
			||||||
 | 
					failures INT,
 | 
				
			||||||
 | 
					long_failures INT,
 | 
				
			||||||
 | 
					gas INT,
 | 
				
			||||||
 | 
					voltage_l1 INT,
 | 
				
			||||||
 | 
					voltage_l2 INT,
 | 
				
			||||||
 | 
					voltage_l3 INT,
 | 
				
			||||||
 | 
					current_l1 INT,
 | 
				
			||||||
 | 
					current_l2 INT,
 | 
				
			||||||
 | 
					current_l3 INT,
 | 
				
			||||||
 | 
					delivery_l1 INT,
 | 
				
			||||||
 | 
					delivery_l2 INT,
 | 
				
			||||||
 | 
					delivery_l3 INT,
 | 
				
			||||||
 | 
					returning_l1 INT,
 | 
				
			||||||
 | 
					returning_l2 INT,
 | 
				
			||||||
 | 
					returning_l3 INT
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					CREATE EXTENSION IF NOT EXISTS timescaledb;
 | 
				
			||||||
 | 
					SELECT create_hypertable('p1', 'timestamp', if_not_exists => TRUE);
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The connections should be configured using the following environment variables
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```conf
 | 
				
			||||||
 | 
					MQTT_BROKER=tls://mqtt.example.com:8883
 | 
				
			||||||
 | 
					MQTT_TOPIC=p1/#
 | 
				
			||||||
 | 
					MQTT_USERNAME=your_mqtt_username
 | 
				
			||||||
 | 
					MQTT_PASSWORD=your_mqtt_password
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PG_DB='host=localhost port=5432 user=p1 password=secret-replace dbname=p1 sslmode=disable'
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The program should be usable in production and should automatically recover on database or mqtt service interruptions.
 | 
				
			||||||
							
								
								
									
										44
									
								
								compose.timescaledb.grafana.yml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								compose.timescaledb.grafana.yml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,44 @@
 | 
				
			||||||
 | 
					version: '3.8'
 | 
				
			||||||
 | 
					services:
 | 
				
			||||||
 | 
					  timescaledb:
 | 
				
			||||||
 | 
					    image: timescale/timescaledb:latest-pg15
 | 
				
			||||||
 | 
					    environment:
 | 
				
			||||||
 | 
					      POSTGRES_USER: ${PG_USER}
 | 
				
			||||||
 | 
					      POSTGRES_PASSWORD: ${PG_PASSWORD}
 | 
				
			||||||
 | 
					      POSTGRES_DB: ${PG_DB}
 | 
				
			||||||
 | 
					    ports:
 | 
				
			||||||
 | 
					      - "5433:5433"
 | 
				
			||||||
 | 
					    networks:
 | 
				
			||||||
 | 
					      - internal
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - /etc/timezone:/etc/timezone:ro
 | 
				
			||||||
 | 
					      - /etc/localtime:/etc/localtime:ro
 | 
				
			||||||
 | 
					      - /opt/p1-logger/database:/var/lib/postgresql/data
 | 
				
			||||||
 | 
					    healthcheck:
 | 
				
			||||||
 | 
					      test: ["CMD-SHELL", "pg_isready -U ${PG_USER}"]
 | 
				
			||||||
 | 
					      interval: 5s
 | 
				
			||||||
 | 
					      timeout: 5s
 | 
				
			||||||
 | 
					      retries: 20
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  p1-logger:
 | 
				
			||||||
 | 
					    image: git.hollander.online/energy/p1-logger:main
 | 
				
			||||||
 | 
					    environment:
 | 
				
			||||||
 | 
					      MQTT_BROKER: ${MQTT_BROKER}
 | 
				
			||||||
 | 
					      MQTT_TOPIC: ${MQTT_TOPIC}
 | 
				
			||||||
 | 
					      MQTT_USERNAME: ${MQTT_USERNAME}
 | 
				
			||||||
 | 
					      MQTT_PASSWORD: ${MQTT_PASSWORD}
 | 
				
			||||||
 | 
					      PG_DB: ${PG_DB}
 | 
				
			||||||
 | 
					    depends_on:
 | 
				
			||||||
 | 
					      timescaledb:
 | 
				
			||||||
 | 
					        condition: service_healthy
 | 
				
			||||||
 | 
					    networks:
 | 
				
			||||||
 | 
					      - internal
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - /etc/timezone:/etc/timezone:ro
 | 
				
			||||||
 | 
					      - /etc/localtime:/etc/localtime:ro
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					networks:
 | 
				
			||||||
 | 
					  internal:
 | 
				
			||||||
 | 
					    attachable: true
 | 
				
			||||||
 | 
					  proxy:
 | 
				
			||||||
 | 
					    external: {}
 | 
				
			||||||
							
								
								
									
										6
									
								
								example.env
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								example.env
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,6 @@
 | 
				
			||||||
 | 
					MQTT_BROKER=tls://mqtt.example.org:8883
 | 
				
			||||||
 | 
					MQTT_TOPIC=p1/metrics
 | 
				
			||||||
 | 
					MQTT_USERNAME=your_username
 | 
				
			||||||
 | 
					MQTT_PASSWORD=your_password
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PG_DB='host=localhost port=5432 user=postgres password=secret-replace dbname=p1 sslmode=disable'
 | 
				
			||||||
							
								
								
									
										15
									
								
								go.mod
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								go.mod
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,15 @@
 | 
				
			||||||
 | 
					module git.hollander.online/energy/p1-logger
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					go 1.21.4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require (
 | 
				
			||||||
 | 
						github.com/eclipse/paho.mqtt.golang v1.4.3
 | 
				
			||||||
 | 
						github.com/lib/pq v1.10.9
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require (
 | 
				
			||||||
 | 
						github.com/gorilla/websocket v1.5.0 // indirect
 | 
				
			||||||
 | 
						github.com/joho/godotenv v1.5.1 // indirect
 | 
				
			||||||
 | 
						golang.org/x/net v0.8.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/sync v0.1.0 // indirect
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										12
									
								
								go.sum
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								go.sum
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,12 @@
 | 
				
			||||||
 | 
					github.com/eclipse/paho.mqtt.golang v1.4.3 h1:2kwcUGn8seMUfWndX0hGbvH8r7crgcJguQNCyp70xik=
 | 
				
			||||||
 | 
					github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci6dVkLCbo5tTC1RIE=
 | 
				
			||||||
 | 
					github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
 | 
				
			||||||
 | 
					github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 | 
				
			||||||
 | 
					github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
 | 
				
			||||||
 | 
					github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
 | 
				
			||||||
 | 
					github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
 | 
				
			||||||
 | 
					github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 | 
				
			||||||
 | 
					golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
 | 
				
			||||||
 | 
					golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
 | 
				
			||||||
 | 
					golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
 | 
				
			||||||
 | 
					golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
				
			||||||
							
								
								
									
										185
									
								
								main.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								main.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,185 @@
 | 
				
			||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"database/sql"
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mqtt "github.com/eclipse/paho.mqtt.golang"
 | 
				
			||||||
 | 
						"github.com/joho/godotenv"
 | 
				
			||||||
 | 
						_ "github.com/lib/pq"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Payload struct {
 | 
				
			||||||
 | 
						T   string `json:"t"`
 | 
				
			||||||
 | 
						Dt1 int    `json:"dt1"`
 | 
				
			||||||
 | 
						Dt2 int    `json:"dt2"`
 | 
				
			||||||
 | 
						Rt1 int    `json:"rt1"`
 | 
				
			||||||
 | 
						Rt2 int    `json:"rt2"`
 | 
				
			||||||
 | 
						D   int    `json:"d"`
 | 
				
			||||||
 | 
						R   int    `json:"r"`
 | 
				
			||||||
 | 
						F   int    `json:"f"`
 | 
				
			||||||
 | 
						Fl  int    `json:"fl"`
 | 
				
			||||||
 | 
						G   int    `json:"g"`
 | 
				
			||||||
 | 
						V1  int    `json:"v1"`
 | 
				
			||||||
 | 
						V2  int    `json:"v2"`
 | 
				
			||||||
 | 
						V3  int    `json:"v3"`
 | 
				
			||||||
 | 
						C1  int    `json:"c1"`
 | 
				
			||||||
 | 
						C2  int    `json:"c2"`
 | 
				
			||||||
 | 
						C3  int    `json:"c3"`
 | 
				
			||||||
 | 
						D1  int    `json:"d1"`
 | 
				
			||||||
 | 
						D2  int    `json:"d2"`
 | 
				
			||||||
 | 
						D3  int    `json:"d3"`
 | 
				
			||||||
 | 
						R1  int    `json:"r1"`
 | 
				
			||||||
 | 
						R2  int    `json:"r2"`
 | 
				
			||||||
 | 
						R3  int    `json:"r3"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var db *sql.DB
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
						// Load environment variables from .env file if it exists
 | 
				
			||||||
 | 
						err := godotenv.Load()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Println("Error loading .env file:", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Connect to PostgreSQL
 | 
				
			||||||
 | 
						pgConnStr := os.Getenv("PG_DB")
 | 
				
			||||||
 | 
						db, err = sql.Open("postgres", pgConnStr)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer db.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Enable TimescaleDB
 | 
				
			||||||
 | 
						_, err = db.Exec(`
 | 
				
			||||||
 | 
							CREATE EXTENSION IF NOT EXISTS timescaledb;
 | 
				
			||||||
 | 
						`)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatal("Error creating TimescaleDB extension:", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Create table if not exists
 | 
				
			||||||
 | 
						_, err = db.Exec(`
 | 
				
			||||||
 | 
							CREATE TABLE IF NOT EXISTS p1 (
 | 
				
			||||||
 | 
								timestamp TIMESTAMPTZ,
 | 
				
			||||||
 | 
								delivered_tariff1 INT,
 | 
				
			||||||
 | 
								delivered_tariff2 INT,
 | 
				
			||||||
 | 
								returned_tariff1 INT,
 | 
				
			||||||
 | 
								returned_tariff2 INT,
 | 
				
			||||||
 | 
								delivery_all INT,
 | 
				
			||||||
 | 
								returning_all INT,
 | 
				
			||||||
 | 
								failures INT,
 | 
				
			||||||
 | 
								long_failures INT,
 | 
				
			||||||
 | 
								gas INT,
 | 
				
			||||||
 | 
								voltage_l1 INT,
 | 
				
			||||||
 | 
								voltage_l2 INT,
 | 
				
			||||||
 | 
								voltage_l3 INT,
 | 
				
			||||||
 | 
								current_l1 INT,
 | 
				
			||||||
 | 
								current_l2 INT,
 | 
				
			||||||
 | 
								current_l3 INT,
 | 
				
			||||||
 | 
								delivery_l1 INT,
 | 
				
			||||||
 | 
								delivery_l2 INT,
 | 
				
			||||||
 | 
								delivery_l3 INT,
 | 
				
			||||||
 | 
								returning_l1 INT,
 | 
				
			||||||
 | 
								returning_l2 INT,
 | 
				
			||||||
 | 
								returning_l3 INT
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
							SELECT create_hypertable('p1', 'timestamp', if_not_exists => TRUE);
 | 
				
			||||||
 | 
						`)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatal("Error creating table:", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Initialize MQTT options
 | 
				
			||||||
 | 
						opts := mqtt.NewClientOptions()
 | 
				
			||||||
 | 
						opts.AddBroker(os.Getenv("MQTT_BROKER"))
 | 
				
			||||||
 | 
						opts.SetUsername(os.Getenv("MQTT_USERNAME"))
 | 
				
			||||||
 | 
						opts.SetPassword(os.Getenv("MQTT_PASSWORD"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Connect to MQTT broker
 | 
				
			||||||
 | 
						client := mqtt.NewClient(opts)
 | 
				
			||||||
 | 
						if token := client.Connect(); token.Wait() && token.Error() != nil {
 | 
				
			||||||
 | 
							log.Fatal(token.Error())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Subscribe to MQTT topic
 | 
				
			||||||
 | 
						topic := os.Getenv("MQTT_TOPIC")
 | 
				
			||||||
 | 
						if token := client.Subscribe(topic, 0, mqttMessageHandler); token.Wait() && token.Error() != nil {
 | 
				
			||||||
 | 
							log.Fatal(token.Error())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Keep the program running
 | 
				
			||||||
 | 
						select {}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func mqttMessageHandler(client mqtt.Client, msg mqtt.Message) {
 | 
				
			||||||
 | 
						// Parse JSON payload
 | 
				
			||||||
 | 
						var payload Payload
 | 
				
			||||||
 | 
						err := json.Unmarshal(msg.Payload(), &payload)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Println("Error parsing MQTT payload:", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Parse timestamp to time.Time
 | 
				
			||||||
 | 
						timestamp, err := parseTimestamp(payload.T)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Println("Error parsing timestamp:", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Insert data into PostgreSQL
 | 
				
			||||||
 | 
						err = insertData(timestamp, payload)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Println("Error inserting data into PostgreSQL:", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func parseTimestamp(t string) (time.Time, error) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Extract values from timestamp string
 | 
				
			||||||
 | 
						year, month, day := 2000+int(t[0]-'0')*10+int(t[1]-'0'), time.Month(int(t[2]-'0')*10+int(t[3]-'0')), int(t[4]-'0')*10+int(t[5]-'0')
 | 
				
			||||||
 | 
						hour, min, sec := int(t[6]-'0')*10+int(t[7]-'0'), int(t[8]-'0')*10+int(t[9]-'0'), int(t[10]-'0')*10+int(t[11]-'0')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Load location for "Europe/Amsterdam" time zone
 | 
				
			||||||
 | 
						loc, err := time.LoadLocation("Europe/Amsterdam")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return time.Time{}, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Create and return the timestamp
 | 
				
			||||||
 | 
						return time.Date(year, month, day, hour, min, sec, 0, loc), nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func insertData(timestamp time.Time, payload Payload) error {
 | 
				
			||||||
 | 
						// Prepare SQL statement
 | 
				
			||||||
 | 
						stmt := `
 | 
				
			||||||
 | 
							INSERT INTO p1 (
 | 
				
			||||||
 | 
								timestamp, delivered_tariff1, delivered_tariff2, returned_tariff1, returned_tariff2,
 | 
				
			||||||
 | 
								delivery_all, returning_all, failures, long_failures, gas,
 | 
				
			||||||
 | 
								voltage_l1, voltage_l2, voltage_l3,
 | 
				
			||||||
 | 
								current_l1, current_l2, current_l3,
 | 
				
			||||||
 | 
								delivery_l1, delivery_l2, delivery_l3,
 | 
				
			||||||
 | 
								returning_l1, returning_l2, returning_l3
 | 
				
			||||||
 | 
							) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22)
 | 
				
			||||||
 | 
						`
 | 
				
			||||||
 | 
						_, err := db.Exec(
 | 
				
			||||||
 | 
							stmt,
 | 
				
			||||||
 | 
							timestamp,
 | 
				
			||||||
 | 
							payload.Dt1, payload.Dt2,
 | 
				
			||||||
 | 
							payload.Rt1, payload.Rt2,
 | 
				
			||||||
 | 
							payload.D, payload.R,
 | 
				
			||||||
 | 
							payload.F, payload.Fl,
 | 
				
			||||||
 | 
							payload.G,
 | 
				
			||||||
 | 
							payload.V1, payload.V2, payload.V3,
 | 
				
			||||||
 | 
							payload.C1, payload.C2, payload.C3,
 | 
				
			||||||
 | 
							payload.D1, payload.D2, payload.D3,
 | 
				
			||||||
 | 
							payload.R1, payload.R2, payload.R3,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue