diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 9fa915b..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,269 +0,0 @@
-# ---> esp-idf
-# gitignore template for esp-idf, the official development framework for ESP32
-# https://github.com/espressif/esp-idf
-
-build/
-sdkconfig
-sdkconfig.old
-
-# ---> VisualStudioCode
-.vscode/*
-!.vscode/settings.json
-!.vscode/tasks.json
-!.vscode/launch.json
-!.vscode/extensions.json
-!.vscode/*.code-snippets
-
-# Local History for Visual Studio Code
-.history/
-
-# Built Visual Studio Code Extensions
-*.vsix
-
-# ---> Kate
-# Swap Files #
-.*.kate-swp
-.swp.*
-
-# ---> C
-# Prerequisites
-*.d
-
-# Object files
-*.o
-*.ko
-*.obj
-*.elf
-
-# Linker output
-*.ilk
-*.map
-*.exp
-
-# Precompiled Headers
-*.gch
-*.pch
-
-# Libraries
-*.lib
-*.a
-*.la
-*.lo
-
-# Shared objects (inc. Windows DLLs)
-*.dll
-*.so
-*.so.*
-*.dylib
-
-# Executables
-*.exe
-*.out
-*.app
-*.i*86
-*.x86_64
-*.hex
-
-# Debug files
-*.dSYM/
-*.su
-*.idb
-*.pdb
-
-# Kernel Module Compile Results
-*.mod*
-*.cmd
-.tmp_versions/
-modules.order
-Module.symvers
-Mkfile.old
-dkms.conf
-
-# ---> C++
-# Prerequisites
-*.d
-
-# Compiled Object files
-*.slo
-*.lo
-*.o
-*.obj
-
-# Precompiled Headers
-*.gch
-*.pch
-
-# Compiled Dynamic libraries
-*.so
-*.dylib
-*.dll
-
-# Fortran module files
-*.mod
-*.smod
-
-# Compiled Static libraries
-*.lai
-*.la
-*.a
-*.lib
-
-# Executables
-*.exe
-*.out
-*.app
-
-# ---> Python
-# Byte-compiled / optimized / DLL files
-__pycache__/
-*.py[cod]
-*$py.class
-
-# C extensions
-*.so
-
-# Distribution / packaging
-.Python
-build/
-develop-eggs/
-dist/
-downloads/
-eggs/
-.eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-wheels/
-share/python-wheels/
-*.egg-info/
-.installed.cfg
-*.egg
-MANIFEST
-
-# PyInstaller
-# Usually these files are written by a python script from a template
-# before PyInstaller builds the exe, so as to inject date/other infos into it.
-*.manifest
-*.spec
-
-# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
-
-# Unit test / coverage reports
-htmlcov/
-.tox/
-.nox/
-.coverage
-.coverage.*
-.cache
-nosetests.xml
-coverage.xml
-*.cover
-*.py,cover
-.hypothesis/
-.pytest_cache/
-cover/
-
-# Translations
-*.mo
-*.pot
-
-# Django stuff:
-*.log
-local_settings.py
-db.sqlite3
-db.sqlite3-journal
-
-# Flask stuff:
-instance/
-.webassets-cache
-
-# Scrapy stuff:
-.scrapy
-
-# Sphinx documentation
-docs/_build/
-
-# PyBuilder
-.pybuilder/
-target/
-
-# Jupyter Notebook
-.ipynb_checkpoints
-
-# IPython
-profile_default/
-ipython_config.py
-
-# pyenv
-# For a library or package, you might want to ignore these files since the code is
-# intended to run in multiple environments; otherwise, check them in:
-# .python-version
-
-# pipenv
-# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
-# However, in case of collaboration, if having platform-specific dependencies or dependencies
-# having no cross-platform support, pipenv may install dependencies that don't work, or not
-# install all needed dependencies.
-#Pipfile.lock
-
-# poetry
-# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
-# This is especially recommended for binary packages to ensure reproducibility, and is more
-# commonly ignored for libraries.
-# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
-#poetry.lock
-
-# pdm
-# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
-#pdm.lock
-# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
-# in version control.
-# https://pdm.fming.dev/#use-with-ide
-.pdm.toml
-
-# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
-__pypackages__/
-
-# Celery stuff
-celerybeat-schedule
-celerybeat.pid
-
-# SageMath parsed files
-*.sage.py
-
-# Environments
-.env
-.venv
-env/
-venv/
-ENV/
-env.bak/
-venv.bak/
-
-# Spyder project settings
-.spyderproject
-.spyproject
-
-# Rope project settings
-.ropeproject
-
-# mkdocs documentation
-/site
-
-# mypy
-.mypy_cache/
-.dmypy.json
-dmypy.json
-
-# Pyre type checker
-.pyre/
-
-# pytype static type analyzer
-.pytype/
-
-# Cython debug symbols
-cython_debug/
diff --git a/.gitmodules b/.gitmodules
index 651e526..67e1810 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,3 @@
-[submodule "components/ttn-esp32"]
- path = components/ttn-esp32
+[submodule "node/components/ttn-esp32"]
+ path = node/components/ttn-esp32
url = https://git.mosad.xyz/localhorst/ttn-esp32.git
diff --git a/__pycache__/config.cpython-310.pyc b/__pycache__/config.cpython-310.pyc
deleted file mode 100644
index e5bda93..0000000
Binary files a/__pycache__/config.cpython-310.pyc and /dev/null differ
diff --git a/backend/config.py b/backend/config.py
new file mode 100644
index 0000000..087069b
--- /dev/null
+++ b/backend/config.py
@@ -0,0 +1,12 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+""" Author: Hendrik Schutter, mail@hendrikschutter.com
+"""
+
+hostName = "127.0.0.1"
+serverPort = 9101
+exporter_prefix = "msv_clubhouse_"
+
+ttn_user = "USER@ttn"
+ttn_key = "TTN KEY"
+ttn_region = "EU1"
\ No newline at end of file
diff --git a/backend/msv-clubhouse-backend.service b/backend/msv-clubhouse-backend.service
new file mode 100644
index 0000000..a56b568
--- /dev/null
+++ b/backend/msv-clubhouse-backend.service
@@ -0,0 +1,15 @@
+[Unit]
+Description=MSV-Clubhouse-Backend
+After=syslog.target
+After=network.target
+
+[Service]
+RestartSec=2s
+Type=oneshot
+User=prometheus
+Group=prometheus
+WorkingDirectory=/opt/msv-clubhouse-backend/
+ExecStart=/usr/bin/python3 /opt/msv-clubhouse-backend/msv_clubhouse_backend.py
+
+[Install]
+WantedBy=multi-user.target
\ No newline at end of file
diff --git a/backend/msv_clubhouse_backend.py b/backend/msv_clubhouse_backend.py
new file mode 100644
index 0000000..8e4c433
--- /dev/null
+++ b/backend/msv_clubhouse_backend.py
@@ -0,0 +1,159 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+""" Author: Hendrik Schutter, mail@hendrikschutter.com
+"""
+
+from http.server import BaseHTTPRequestHandler, HTTPServer
+import paho.mqtt.client as mqtt
+from datetime import datetime
+import threading
+import time
+import json
+import config
+
+scrape_healthy = True
+startTime = datetime.now()
+node_metrics = list()
+mutex = threading.Lock()
+request_count = 0
+
+class RequestHandler(BaseHTTPRequestHandler):
+
+ def get_metrics(self):
+ global request_count
+ global node_metrics
+ global mutex
+ mutex.acquire()
+ self.send_response(200)
+ self.send_header("Content-type", "text/html")
+ self.end_headers()
+ self.wfile.write(bytes(config.exporter_prefix + "expoter_duration_seconds_sum " + str(int((datetime.now() - startTime).total_seconds())) + "\n", "utf-8"))
+ self.wfile.write(bytes(config.exporter_prefix + "exporter_request_count " + str(request_count) + "\n", "utf-8"))
+ self.wfile.write(bytes(config.exporter_prefix + "exporter_scrape_healthy " + str(int(scrape_healthy)) + "\n", "utf-8"))
+
+ for metric in node_metrics:
+ #print(metric)
+ self.wfile.write(bytes(config.exporter_prefix + metric + "\n", "utf-8"))
+
+ mutex.release()
+
+ def do_GET(self):
+ global request_count
+ request_count = request_count + 1
+ print("Request: " + self.path)
+ if (self.path.startswith("/metrics")):
+ self.get_metrics()
+ else:
+ self.send_response(200)
+ self.send_header("Content-type", "text/html")
+ self.end_headers()
+ self.wfile.write(bytes("", "utf-8"))
+ self.wfile.write(bytes("
MSV Clubhouse exporter", "utf-8"))
+ self.wfile.write(bytes("", "utf-8"))
+ self.wfile.write(bytes('msv-clubhouse exporter based on data from LoRaWAN TTN node.
', "utf-8"))
+ self.wfile.write(bytes('Metrics
', "utf-8"))
+ self.wfile.write(bytes("", "utf-8"))
+ self.wfile.write(bytes("", "utf-8"))
+
+def update_metrics(payload, metadata):
+ #print("Payload: "+ str(payload))
+ #print("Metadata: "+ str(metadata))
+
+ global node_metrics
+ global mutex
+ global scrape_healthy
+ mutex.acquire()
+ scrape_healthy = True
+ node_metrics.clear()
+
+ if "degreesC" in payload:
+ print("set degree: " + str(float(payload["degreesC"])))
+ node_metrics.append("temperature " + str(float(payload["degreesC"])))
+
+ if "pressure" in payload:
+ print("set pressure: " + str(float(payload["pressure"])))
+ node_metrics.append("pressure " + str(float(payload["pressure"])))
+
+ if "winddirection" in payload:
+ print("set winddirection: " + str(float(payload["winddirection"])))
+ node_metrics.append("winddirection " + str(float(payload["winddirection"])))
+
+ if "windspeed" in payload:
+ print("set windspeed: " + str(float(payload["windspeed"])))
+ node_metrics.append("windspeed " + str(float(payload["windspeed"])))
+
+ if "dooropen" in payload:
+ print("set dooropen: " + str(bool(payload["dooropen"])))
+ node_metrics.append("dooropen " + str(int(payload["dooropen"])))
+
+ # if "gateway_id" in metadata[0]["gateway_ids"]:
+ # print("set gateway_id: " + str(metadata[0]["gateway_ids"]["gateway_id"]))
+ # node_metrics.append("gateway_id " + str(metadata[0]["gateway_ids"]["gateway_id"]))
+
+ if "rssi" in metadata[0]:
+ print("set rssi: " + str(int(metadata[0]["rssi"])))
+ node_metrics.append("rssi " + str(int(metadata[0]["rssi"])))
+
+ if "channel_rssi" in metadata[0]:
+ print("set channel_rssi: " + str(int(metadata[0]["channel_rssi"])))
+ node_metrics.append("channel_rssi " + str(int(metadata[0]["channel_rssi"])))
+
+ if "snr" in metadata[0]:
+ print("set snr: " + str(float(metadata[0]["snr"])))
+ node_metrics.append("snr " + str(float(metadata[0]["snr"])))
+
+
+ #scrape_healthy = False
+
+ mutex.release()
+
+def on_connect(mqttc, obj, flags, rc):
+ print("\nConnected to MQTT: rc = " + str(rc))
+
+def on_message(mqttc, obj, msg):
+ #print("\nMessage: " + msg.topic + " " + str(msg.qos))
+ parsedJSON = json.loads(msg.payload)
+ #print(json.dumps(parsedJSON, indent=4))
+
+ try:
+ uplink_message = parsedJSON["uplink_message"];
+ update_metrics(uplink_message["decoded_payload"], uplink_message["rx_metadata"])
+ except:
+ print("Unable to parse uplink")
+
+def on_subscribe(mqttc, obj, mid, granted_qos):
+ print("\nSubscribed to MQTT: " + str(mid) + " " + str(granted_qos))
+
+def poll_mqtt(mqttc):
+ while True:
+ mqttc.loop(10) # seconds timeout
+
+def main():
+ print("starting ...")
+
+ mqttc = mqtt.Client()
+ mqttc.on_connect = on_connect
+ mqttc.on_subscribe = on_subscribe
+ mqttc.on_message = on_message
+ mqttc.username_pw_set(config.ttn_user, config.ttn_key)
+ mqttc.tls_set()
+ mqttc.connect(config.ttn_region.lower() + ".cloud.thethings.network", 8883, 60)
+ mqttc.subscribe("#", 0) # all device uplinks
+
+ # run mqtt in thread forever
+ poll_mqtt_thread = threading.Thread(target=poll_mqtt, args=((mqttc,)))
+ poll_mqtt_thread.start()
+
+ webServer = HTTPServer((config.hostName, config.serverPort), RequestHandler)
+ print("Server started http://%s:%s" % (config.hostName, config.serverPort))
+ try:
+ webServer.serve_forever()
+ except KeyboardInterrupt:
+ pass
+
+ webServer.server_close()
+ print("Server stopped.")
+ poll_mqtt_thread.join()
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/frontend/grafana_dashboard.json b/frontend/grafana_dashboard.json
new file mode 100644
index 0000000..b34c076
--- /dev/null
+++ b/frontend/grafana_dashboard.json
@@ -0,0 +1,766 @@
+{
+ "annotations": {
+ "list": [
+ {
+ "builtIn": 1,
+ "datasource": {
+ "type": "grafana",
+ "uid": "-- Grafana --"
+ },
+ "enable": true,
+ "hide": true,
+ "iconColor": "rgba(0, 211, 255, 1)",
+ "name": "Annotations & Alerts",
+ "target": {
+ "limit": 100,
+ "matchAny": false,
+ "tags": [],
+ "type": "dashboard"
+ },
+ "type": "dashboard"
+ }
+ ]
+ },
+ "editable": true,
+ "fiscalYearStartMonth": 0,
+ "graphTooltip": 0,
+ "id": 16,
+ "links": [],
+ "liveNow": false,
+ "panels": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "x08D2Hlnz"
+ },
+ "description": "Wenn die Türe offen, dann ist wohl jemand am Platz.",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [
+ {
+ "options": {
+ "0": {
+ "color": "red",
+ "index": 1,
+ "text": "Türe geschlossen"
+ },
+ "1": {
+ "color": "green",
+ "index": 0,
+ "text": "Türe offen"
+ }
+ },
+ "type": "value"
+ }
+ ],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 6,
+ "w": 5,
+ "x": 0,
+ "y": 0
+ },
+ "id": 2,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "9.3.6",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "x08D2Hlnz"
+ },
+ "editorMode": "builder",
+ "expr": "msv_clubhouse_dooropen",
+ "legendFormat": "__auto",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "Türe zur Vereinshütte",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "x08D2Hlnz"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "decimals": 2,
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "pressurehpa"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 6,
+ "w": 5,
+ "x": 5,
+ "y": 0
+ },
+ "id": 5,
+ "options": {
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true
+ },
+ "pluginVersion": "9.3.6",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "x08D2Hlnz"
+ },
+ "editorMode": "builder",
+ "expr": "msv_clubhouse_pressure",
+ "legendFormat": "Luftdruck Außen",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "Aktueller Luftdruck Außen ",
+ "type": "gauge"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "x08D2Hlnz"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "decimals": 2,
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "celsius"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 6,
+ "w": 5,
+ "x": 10,
+ "y": 0
+ },
+ "id": 6,
+ "options": {
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true
+ },
+ "pluginVersion": "9.3.6",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "x08D2Hlnz"
+ },
+ "editorMode": "builder",
+ "expr": "msv_clubhouse_temperature",
+ "legendFormat": "Temperatur Außen",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "Aktuelle Temperatur Außen ",
+ "type": "gauge"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "x08D2Hlnz"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "decimals": 2,
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "degree"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 6,
+ "w": 4,
+ "x": 15,
+ "y": 0
+ },
+ "id": 8,
+ "options": {
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true
+ },
+ "pluginVersion": "9.3.6",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "x08D2Hlnz"
+ },
+ "editorMode": "builder",
+ "expr": "msv_clubhouse_winddirection",
+ "legendFormat": "Windrichtung",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "Aktuelle Windrichtung",
+ "type": "gauge"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "x08D2Hlnz"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "decimals": 2,
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "velocityms"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 6,
+ "w": 4,
+ "x": 19,
+ "y": 0
+ },
+ "id": 9,
+ "options": {
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showThresholdLabels": false,
+ "showThresholdMarkers": true
+ },
+ "pluginVersion": "9.3.6",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "x08D2Hlnz"
+ },
+ "editorMode": "builder",
+ "expr": "msv_clubhouse_winddirection",
+ "legendFormat": "Windstärke",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "Aktuelle Windstärke",
+ "type": "gauge"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "x08D2Hlnz"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "decimals": 2,
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "pressurehpa"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 12,
+ "x": 0,
+ "y": 6
+ },
+ "id": 4,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "x08D2Hlnz"
+ },
+ "editorMode": "builder",
+ "expr": "msv_clubhouse_pressure",
+ "legendFormat": "Luftdruck Außen",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "Luftdruck Außen",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "x08D2Hlnz"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "decimals": 2,
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "celsius"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 12,
+ "x": 12,
+ "y": 6
+ },
+ "id": 7,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "x08D2Hlnz"
+ },
+ "editorMode": "builder",
+ "expr": "msv_clubhouse_temperature",
+ "legendFormat": "Luftdruck Außen",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "Tempertur Außen",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "x08D2Hlnz"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "decimals": 2,
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "dB"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 12,
+ "x": 0,
+ "y": 15
+ },
+ "id": 10,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "x08D2Hlnz"
+ },
+ "editorMode": "builder",
+ "expr": "msv_clubhouse_rssi",
+ "legendFormat": "RSSI",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "RSSI",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "x08D2Hlnz"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "decimals": 2,
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "dB"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 12,
+ "x": 12,
+ "y": 15
+ },
+ "id": 12,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "x08D2Hlnz"
+ },
+ "editorMode": "builder",
+ "expr": "msv_clubhouse_snr",
+ "legendFormat": "SNR",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "SNR",
+ "type": "timeseries"
+ }
+ ],
+ "schemaVersion": 37,
+ "style": "dark",
+ "tags": [],
+ "templating": {
+ "list": []
+ },
+ "time": {
+ "from": "now-6h",
+ "to": "now"
+ },
+ "timepicker": {},
+ "timezone": "",
+ "title": "MSV Moos Modellflugplatz",
+ "uid": "nuH5csxVz",
+ "version": 6,
+ "weekStart": ""
+}
\ No newline at end of file
diff --git a/node/CMakeLists.txt b/node/CMakeLists.txt
new file mode 100644
index 0000000..a917f70
--- /dev/null
+++ b/node/CMakeLists.txt
@@ -0,0 +1,4 @@
+cmake_minimum_required(VERSION 3.5)
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+
+project(msv_node)
diff --git a/node/components/.gitkeep b/node/components/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/node/main/CMakeLists.txt b/node/main/CMakeLists.txt
new file mode 100644
index 0000000..6970ecf
--- /dev/null
+++ b/node/main/CMakeLists.txt
@@ -0,0 +1,4 @@
+idf_component_register(
+ SRCS "ssd1366.cpp" "main.cpp"
+ INCLUDE_DIRS "."
+ REQUIRES ttn-esp32)
diff --git a/node/main/Kconfig.projbuild b/node/main/Kconfig.projbuild
new file mode 100644
index 0000000..f6aab17
--- /dev/null
+++ b/node/main/Kconfig.projbuild
@@ -0,0 +1,13 @@
+menu "MSV Node"
+
+ config APPEUI
+ string "AppEUI"
+ default "0000000000000000"
+ config DEVEUI
+ string "DevEUI"
+ default "0000000000000000"
+ config APPKEY
+ string "AppKey"
+ default "00000000000000000000000000000000"
+
+endmenu
\ No newline at end of file
diff --git a/node/main/font8x8_basic.h b/node/main/font8x8_basic.h
new file mode 100644
index 0000000..1d39023
--- /dev/null
+++ b/node/main/font8x8_basic.h
@@ -0,0 +1,174 @@
+/*
+ * font8x8_basic.h
+ *
+ * Created on: 2017/05/03
+ * Author: yanbe
+ */
+
+#ifndef MAIN_FONT8X8_BASIC_H_
+#define MAIN_FONT8X8_BASIC_H_
+
+#include "freertos/FreeRTOS.h"
+
+/*
+ Constant: font8x8_basic_tr
+ Contains an 90 digree transposed 8x8 font map for unicode points
+ U+0000 - U+007F (basic latin)
+
+ To make it easy to use with SSD1306's GDDRAM mapping and API,
+ this constant is an 90 degree transposed.
+ The original version written by Marcel Sondaar is availble at:
+ https://github.com/dhepper/font8x8/blob/master/font8x8_basic.h
+ Conversion is done via following procedure:
+
+ for (int code = 0; code < 128; code++) {
+ uint8_t trans[8];
+ for (int w = 0; w < 8; w++) {
+ trans[w] = 0x00;
+ for (int b = 0; b < 8; b++) {
+ trans[w] |= ((font8x8_basic[code][b] & (1 << w)) >> w) << b;
+ }
+ }
+
+ for (int w = 0; w < 8; w++) {
+ if (w == 0) { printf(" { "); }
+ printf("0x%.2X", trans[w]);
+ if (w < 7) { printf(", "); }
+ if (w == 7) { printf(" }, // U+00%.2X (%c)\n", code, code); }
+ }
+ }
+*/
+
+uint8_t font8x8_basic_tr[128][8] = {
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0000 (nul)
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0001
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0002
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0003
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0004
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0005
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0006
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0007
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0008
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0009
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+000A
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+000B
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+000C
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+000D
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+000E
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+000F
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0010
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0011
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0012
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0013
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0014
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0015
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0016
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0017
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0018
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0019
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+001A
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+001B
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+001C
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+001D
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+001E
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+001F
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0020 (space)
+ { 0x00, 0x00, 0x06, 0x5F, 0x5F, 0x06, 0x00, 0x00 }, // U+0021 (!)
+ { 0x00, 0x03, 0x03, 0x00, 0x03, 0x03, 0x00, 0x00 }, // U+0022 (")
+ { 0x14, 0x7F, 0x7F, 0x14, 0x7F, 0x7F, 0x14, 0x00 }, // U+0023 (#)
+ { 0x24, 0x2E, 0x6B, 0x6B, 0x3A, 0x12, 0x00, 0x00 }, // U+0024 ($)
+ { 0x46, 0x66, 0x30, 0x18, 0x0C, 0x66, 0x62, 0x00 }, // U+0025 (%)
+ { 0x30, 0x7A, 0x4F, 0x5D, 0x37, 0x7A, 0x48, 0x00 }, // U+0026 (&)
+ { 0x04, 0x07, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0027 (')
+ { 0x00, 0x1C, 0x3E, 0x63, 0x41, 0x00, 0x00, 0x00 }, // U+0028 (()
+ { 0x00, 0x41, 0x63, 0x3E, 0x1C, 0x00, 0x00, 0x00 }, // U+0029 ())
+ { 0x08, 0x2A, 0x3E, 0x1C, 0x1C, 0x3E, 0x2A, 0x08 }, // U+002A (*)
+ { 0x08, 0x08, 0x3E, 0x3E, 0x08, 0x08, 0x00, 0x00 }, // U+002B (+)
+ { 0x00, 0x80, 0xE0, 0x60, 0x00, 0x00, 0x00, 0x00 }, // U+002C (,)
+ { 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00 }, // U+002D (-)
+ { 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00 }, // U+002E (.)
+ { 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00 }, // U+002F (/)
+ { 0x3E, 0x7F, 0x71, 0x59, 0x4D, 0x7F, 0x3E, 0x00 }, // U+0030 (0)
+ { 0x40, 0x42, 0x7F, 0x7F, 0x40, 0x40, 0x00, 0x00 }, // U+0031 (1)
+ { 0x62, 0x73, 0x59, 0x49, 0x6F, 0x66, 0x00, 0x00 }, // U+0032 (2)
+ { 0x22, 0x63, 0x49, 0x49, 0x7F, 0x36, 0x00, 0x00 }, // U+0033 (3)
+ { 0x18, 0x1C, 0x16, 0x53, 0x7F, 0x7F, 0x50, 0x00 }, // U+0034 (4)
+ { 0x27, 0x67, 0x45, 0x45, 0x7D, 0x39, 0x00, 0x00 }, // U+0035 (5)
+ { 0x3C, 0x7E, 0x4B, 0x49, 0x79, 0x30, 0x00, 0x00 }, // U+0036 (6)
+ { 0x03, 0x03, 0x71, 0x79, 0x0F, 0x07, 0x00, 0x00 }, // U+0037 (7)
+ { 0x36, 0x7F, 0x49, 0x49, 0x7F, 0x36, 0x00, 0x00 }, // U+0038 (8)
+ { 0x06, 0x4F, 0x49, 0x69, 0x3F, 0x1E, 0x00, 0x00 }, // U+0039 (9)
+ { 0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00 }, // U+003A (:)
+ { 0x00, 0x80, 0xE6, 0x66, 0x00, 0x00, 0x00, 0x00 }, // U+003B (;)
+ { 0x08, 0x1C, 0x36, 0x63, 0x41, 0x00, 0x00, 0x00 }, // U+003C (<)
+ { 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x00, 0x00 }, // U+003D (=)
+ { 0x00, 0x41, 0x63, 0x36, 0x1C, 0x08, 0x00, 0x00 }, // U+003E (>)
+ { 0x02, 0x03, 0x51, 0x59, 0x0F, 0x06, 0x00, 0x00 }, // U+003F (?)
+ { 0x3E, 0x7F, 0x41, 0x5D, 0x5D, 0x1F, 0x1E, 0x00 }, // U+0040 (@)
+ { 0x7C, 0x7E, 0x13, 0x13, 0x7E, 0x7C, 0x00, 0x00 }, // U+0041 (A)
+ { 0x41, 0x7F, 0x7F, 0x49, 0x49, 0x7F, 0x36, 0x00 }, // U+0042 (B)
+ { 0x1C, 0x3E, 0x63, 0x41, 0x41, 0x63, 0x22, 0x00 }, // U+0043 (C)
+ { 0x41, 0x7F, 0x7F, 0x41, 0x63, 0x3E, 0x1C, 0x00 }, // U+0044 (D)
+ { 0x41, 0x7F, 0x7F, 0x49, 0x5D, 0x41, 0x63, 0x00 }, // U+0045 (E)
+ { 0x41, 0x7F, 0x7F, 0x49, 0x1D, 0x01, 0x03, 0x00 }, // U+0046 (F)
+ { 0x1C, 0x3E, 0x63, 0x41, 0x51, 0x73, 0x72, 0x00 }, // U+0047 (G)
+ { 0x7F, 0x7F, 0x08, 0x08, 0x7F, 0x7F, 0x00, 0x00 }, // U+0048 (H)
+ { 0x00, 0x41, 0x7F, 0x7F, 0x41, 0x00, 0x00, 0x00 }, // U+0049 (I)
+ { 0x30, 0x70, 0x40, 0x41, 0x7F, 0x3F, 0x01, 0x00 }, // U+004A (J)
+ { 0x41, 0x7F, 0x7F, 0x08, 0x1C, 0x77, 0x63, 0x00 }, // U+004B (K)
+ { 0x41, 0x7F, 0x7F, 0x41, 0x40, 0x60, 0x70, 0x00 }, // U+004C (L)
+ { 0x7F, 0x7F, 0x0E, 0x1C, 0x0E, 0x7F, 0x7F, 0x00 }, // U+004D (M)
+ { 0x7F, 0x7F, 0x06, 0x0C, 0x18, 0x7F, 0x7F, 0x00 }, // U+004E (N)
+ { 0x1C, 0x3E, 0x63, 0x41, 0x63, 0x3E, 0x1C, 0x00 }, // U+004F (O)
+ { 0x41, 0x7F, 0x7F, 0x49, 0x09, 0x0F, 0x06, 0x00 }, // U+0050 (P)
+ { 0x1E, 0x3F, 0x21, 0x71, 0x7F, 0x5E, 0x00, 0x00 }, // U+0051 (Q)
+ { 0x41, 0x7F, 0x7F, 0x09, 0x19, 0x7F, 0x66, 0x00 }, // U+0052 (R)
+ { 0x26, 0x6F, 0x4D, 0x59, 0x73, 0x32, 0x00, 0x00 }, // U+0053 (S)
+ { 0x03, 0x41, 0x7F, 0x7F, 0x41, 0x03, 0x00, 0x00 }, // U+0054 (T)
+ { 0x7F, 0x7F, 0x40, 0x40, 0x7F, 0x7F, 0x00, 0x00 }, // U+0055 (U)
+ { 0x1F, 0x3F, 0x60, 0x60, 0x3F, 0x1F, 0x00, 0x00 }, // U+0056 (V)
+ { 0x7F, 0x7F, 0x30, 0x18, 0x30, 0x7F, 0x7F, 0x00 }, // U+0057 (W)
+ { 0x43, 0x67, 0x3C, 0x18, 0x3C, 0x67, 0x43, 0x00 }, // U+0058 (X)
+ { 0x07, 0x4F, 0x78, 0x78, 0x4F, 0x07, 0x00, 0x00 }, // U+0059 (Y)
+ { 0x47, 0x63, 0x71, 0x59, 0x4D, 0x67, 0x73, 0x00 }, // U+005A (Z)
+ { 0x00, 0x7F, 0x7F, 0x41, 0x41, 0x00, 0x00, 0x00 }, // U+005B ([)
+ { 0x01, 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x00 }, // U+005C (\)
+ { 0x00, 0x41, 0x41, 0x7F, 0x7F, 0x00, 0x00, 0x00 }, // U+005D (])
+ { 0x08, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x08, 0x00 }, // U+005E (^)
+ { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, // U+005F (_)
+ { 0x00, 0x00, 0x03, 0x07, 0x04, 0x00, 0x00, 0x00 }, // U+0060 (`)
+ { 0x20, 0x74, 0x54, 0x54, 0x3C, 0x78, 0x40, 0x00 }, // U+0061 (a)
+ { 0x41, 0x7F, 0x3F, 0x48, 0x48, 0x78, 0x30, 0x00 }, // U+0062 (b)
+ { 0x38, 0x7C, 0x44, 0x44, 0x6C, 0x28, 0x00, 0x00 }, // U+0063 (c)
+ { 0x30, 0x78, 0x48, 0x49, 0x3F, 0x7F, 0x40, 0x00 }, // U+0064 (d)
+ { 0x38, 0x7C, 0x54, 0x54, 0x5C, 0x18, 0x00, 0x00 }, // U+0065 (e)
+ { 0x48, 0x7E, 0x7F, 0x49, 0x03, 0x02, 0x00, 0x00 }, // U+0066 (f)
+ { 0x98, 0xBC, 0xA4, 0xA4, 0xF8, 0x7C, 0x04, 0x00 }, // U+0067 (g)
+ { 0x41, 0x7F, 0x7F, 0x08, 0x04, 0x7C, 0x78, 0x00 }, // U+0068 (h)
+ { 0x00, 0x44, 0x7D, 0x7D, 0x40, 0x00, 0x00, 0x00 }, // U+0069 (i)
+ { 0x60, 0xE0, 0x80, 0x80, 0xFD, 0x7D, 0x00, 0x00 }, // U+006A (j)
+ { 0x41, 0x7F, 0x7F, 0x10, 0x38, 0x6C, 0x44, 0x00 }, // U+006B (k)
+ { 0x00, 0x41, 0x7F, 0x7F, 0x40, 0x00, 0x00, 0x00 }, // U+006C (l)
+ { 0x7C, 0x7C, 0x18, 0x38, 0x1C, 0x7C, 0x78, 0x00 }, // U+006D (m)
+ { 0x7C, 0x7C, 0x04, 0x04, 0x7C, 0x78, 0x00, 0x00 }, // U+006E (n)
+ { 0x38, 0x7C, 0x44, 0x44, 0x7C, 0x38, 0x00, 0x00 }, // U+006F (o)
+ { 0x84, 0xFC, 0xF8, 0xA4, 0x24, 0x3C, 0x18, 0x00 }, // U+0070 (p)
+ { 0x18, 0x3C, 0x24, 0xA4, 0xF8, 0xFC, 0x84, 0x00 }, // U+0071 (q)
+ { 0x44, 0x7C, 0x78, 0x4C, 0x04, 0x1C, 0x18, 0x00 }, // U+0072 (r)
+ { 0x48, 0x5C, 0x54, 0x54, 0x74, 0x24, 0x00, 0x00 }, // U+0073 (s)
+ { 0x00, 0x04, 0x3E, 0x7F, 0x44, 0x24, 0x00, 0x00 }, // U+0074 (t)
+ { 0x3C, 0x7C, 0x40, 0x40, 0x3C, 0x7C, 0x40, 0x00 }, // U+0075 (u)
+ { 0x1C, 0x3C, 0x60, 0x60, 0x3C, 0x1C, 0x00, 0x00 }, // U+0076 (v)
+ { 0x3C, 0x7C, 0x70, 0x38, 0x70, 0x7C, 0x3C, 0x00 }, // U+0077 (w)
+ { 0x44, 0x6C, 0x38, 0x10, 0x38, 0x6C, 0x44, 0x00 }, // U+0078 (x)
+ { 0x9C, 0xBC, 0xA0, 0xA0, 0xFC, 0x7C, 0x00, 0x00 }, // U+0079 (y)
+ { 0x4C, 0x64, 0x74, 0x5C, 0x4C, 0x64, 0x00, 0x00 }, // U+007A (z)
+ { 0x08, 0x08, 0x3E, 0x77, 0x41, 0x41, 0x00, 0x00 }, // U+007B ({)
+ { 0x00, 0x00, 0x00, 0x77, 0x77, 0x00, 0x00, 0x00 }, // U+007C (|)
+ { 0x41, 0x41, 0x77, 0x3E, 0x08, 0x08, 0x00, 0x00 }, // U+007D (})
+ { 0x02, 0x03, 0x01, 0x03, 0x02, 0x03, 0x01, 0x00 }, // U+007E (~)
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } // U+007F
+};
+
+#endif /* MAIN_FONT8X8_BASIC_H_ */
+
diff --git a/node/main/i2c.cpp b/node/main/i2c.cpp
new file mode 100644
index 0000000..12d5908
--- /dev/null
+++ b/node/main/i2c.cpp
@@ -0,0 +1,22 @@
+
+
+#include "i2c.h"
+
+#include "esp_log.h"
+
+
+void i2c_init()
+{
+ i2c_config_t i2c_config;
+
+ i2c_config.mode = I2C_MODE_MASTER;
+ i2c_config.sda_io_num = I2C_SDA_PIN;
+ i2c_config.scl_io_num = I2C_SCL_PIN;
+ i2c_config.sda_pullup_en = GPIO_PULLUP_ENABLE;
+ i2c_config.scl_pullup_en = GPIO_PULLUP_ENABLE;
+ i2c_config.master.clk_speed = I2C_MASTER_FREQ_HZ;
+
+
+ ESP_ERROR_CHECK(i2c_param_config(I2C_NUM, &i2c_config));
+ ESP_ERROR_CHECK(i2c_driver_install(I2C_NUM, I2C_MODE_MASTER, 0, 0, 0));
+}
\ No newline at end of file
diff --git a/node/main/i2c.h b/node/main/i2c.h
new file mode 100644
index 0000000..7d705ec
--- /dev/null
+++ b/node/main/i2c.h
@@ -0,0 +1,24 @@
+#ifndef _I2C_H_
+#define _I2C_H_
+
+#define I2C_SDA_PIN GPIO_NUM_4
+#define I2C_SCL_PIN GPIO_NUM_15
+
+#define I2C_NUM I2C_NUM_0
+//#define I2C_NUM I2C_NUM
+
+#define I2C_MASTER_FREQ_HZ 400000U /*!< I2C clock of SSD1306 can run at 400 kHz max. */
+
+
+class I2C_Driver
+{
+
+
+};
+
+
+
+void i2c_init();
+
+
+#endif /* _I2C_H_ */
\ No newline at end of file
diff --git a/node/main/main.cpp b/node/main/main.cpp
new file mode 100644
index 0000000..ffd6f3e
--- /dev/null
+++ b/node/main/main.cpp
@@ -0,0 +1,90 @@
+#include "main.h"
+
+#include "ssd1366.h"
+
+void sendMessages(void* pvParameter)
+{
+ while (1) {
+ printf("Sending message...\n");
+ ssd1306_display_clear();
+ const char *pcTask1 = "Sending message...\n";
+ xTaskCreate(&task_ssd1306_display_text, "task_ssd1306_display_text", 4096, (void *) pcTask1, 5, NULL);
+ TTNResponseCode res = ttn.transmitMessage(msgData, sizeof(msgData) - 1);
+
+ if(res == kTTNSuccessfulTransmission){
+ printf("Message sent.\n");
+ ssd1306_display_clear();
+ const char *pcTask1 = "Message sent.\n";
+ xTaskCreate(&task_ssd1306_display_text, "task_ssd1306_display_text", 4096, (void *) pcTask1, 5, NULL);
+ }else{
+ printf("Transmission failed.\n");
+ ssd1306_display_clear();
+ const char *pcTask1 = "Transmission failed.\n";
+ xTaskCreate(&task_ssd1306_display_text, "task_ssd1306_display_text", 4096, (void *) pcTask1, 5, NULL);
+ }
+
+ vTaskDelay(TX_INTERVAL * pdMS_TO_TICKS(1000));
+ }
+}
+
+void messageReceived(const uint8_t* message, size_t length, ttn_port_t port)
+{
+ printf("Message of %d bytes received on port %d:", length, port);
+ for (int i = 0; i < length; i++)
+ printf(" %02x", message[i]);
+ printf("\n");
+}
+
+extern "C" void app_main(void)
+{
+ esp_err_t err;
+ // Initialize the GPIO ISR handler service
+ err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
+ ESP_ERROR_CHECK(err);
+
+ // Initialize the NVS (non-volatile storage) for saving and restoring the keys
+ err = nvs_flash_init();
+ ESP_ERROR_CHECK(err);
+
+ // Initialize SPI bus
+ spi_bus_config_t spi_bus_config;
+ spi_bus_config.miso_io_num = TTN_PIN_SPI_MISO;
+ spi_bus_config.mosi_io_num = TTN_PIN_SPI_MOSI;
+ spi_bus_config.sclk_io_num = TTN_PIN_SPI_SCLK;
+ spi_bus_config.quadwp_io_num = -1;
+ spi_bus_config.quadhd_io_num = -1;
+ spi_bus_config.max_transfer_sz = 0;
+ err = spi_bus_initialize(TTN_SPI_HOST, &spi_bus_config, TTN_SPI_DMA_CHAN);
+ ESP_ERROR_CHECK(err);
+
+ // Configure the SX127x pins
+ ttn.configurePins(TTN_SPI_HOST, TTN_PIN_NSS, TTN_PIN_RXTX, TTN_PIN_RST, TTN_PIN_DIO0, TTN_PIN_DIO1);
+
+ // The below line can be commented after the first run as the data is saved in NVS
+ ttn.provision(CONFIG_DEVEUI, CONFIG_APPEUI, CONFIG_APPKEY);
+
+ // Register callback for received messages
+ ttn.onMessage(messageReceived);
+
+
+ i2c_master_init();
+ ssd1306_init();
+
+ printf("Joining...\n");
+ ssd1306_display_clear();
+ const char *pcTask1 = "Joining...\n";
+ xTaskCreate(&task_ssd1306_display_text, "task_ssd1306_display_text", 4096, (void *) pcTask1, 5, NULL);
+ if (ttn.join())
+ {
+ printf("Joined.\n");
+ ssd1306_display_clear();
+ const char *pcTask1 = "Joined.\n";
+ xTaskCreate(&task_ssd1306_display_text, "task_ssd1306_display_text", 4096, (void *) pcTask1, 5, NULL);
+ xTaskCreate(sendMessages, "send_messages", 1024 * 4, (void* )0, 3, nullptr);
+ }
+ else
+ {
+ printf("Join failed. Goodbye\n");
+ esp_restart();
+ }
+}
diff --git a/node/main/main.h b/node/main/main.h
new file mode 100644
index 0000000..6032db7
--- /dev/null
+++ b/node/main/main.h
@@ -0,0 +1,33 @@
+#ifndef MAIN_H
+#define MAIN_H
+
+#include "freertos/FreeRTOS.h"
+#include "esp_event.h"
+#include "driver/gpio.h"
+#include "nvs_flash.h"
+#include "driver/i2c.h"
+
+#include "TheThingsNetwork.h"
+
+#define SDA_PIN GPIO_NUM_4
+#define SCL_PIN GPIO_NUM_15
+
+// Pins and other resources
+#define TTN_SPI_HOST HSPI_HOST
+#define TTN_SPI_DMA_CHAN 1
+#define TTN_PIN_SPI_SCLK 5
+#define TTN_PIN_SPI_MOSI 27
+#define TTN_PIN_SPI_MISO 19
+#define TTN_PIN_NSS 18
+#define TTN_PIN_RXTX TTN_NOT_CONNECTED
+#define TTN_PIN_RST 14
+#define TTN_PIN_DIO0 26
+#define TTN_PIN_DIO1 35
+
+static TheThingsNetwork ttn;
+
+const unsigned TX_INTERVAL = 5;
+static uint8_t msgData[] = "0xBADC0DED";
+
+
+#endif /* MAIN_H */
\ No newline at end of file
diff --git a/node/main/oled/font8x8_basic.h b/node/main/oled/font8x8_basic.h
new file mode 100644
index 0000000..1d39023
--- /dev/null
+++ b/node/main/oled/font8x8_basic.h
@@ -0,0 +1,174 @@
+/*
+ * font8x8_basic.h
+ *
+ * Created on: 2017/05/03
+ * Author: yanbe
+ */
+
+#ifndef MAIN_FONT8X8_BASIC_H_
+#define MAIN_FONT8X8_BASIC_H_
+
+#include "freertos/FreeRTOS.h"
+
+/*
+ Constant: font8x8_basic_tr
+ Contains an 90 digree transposed 8x8 font map for unicode points
+ U+0000 - U+007F (basic latin)
+
+ To make it easy to use with SSD1306's GDDRAM mapping and API,
+ this constant is an 90 degree transposed.
+ The original version written by Marcel Sondaar is availble at:
+ https://github.com/dhepper/font8x8/blob/master/font8x8_basic.h
+ Conversion is done via following procedure:
+
+ for (int code = 0; code < 128; code++) {
+ uint8_t trans[8];
+ for (int w = 0; w < 8; w++) {
+ trans[w] = 0x00;
+ for (int b = 0; b < 8; b++) {
+ trans[w] |= ((font8x8_basic[code][b] & (1 << w)) >> w) << b;
+ }
+ }
+
+ for (int w = 0; w < 8; w++) {
+ if (w == 0) { printf(" { "); }
+ printf("0x%.2X", trans[w]);
+ if (w < 7) { printf(", "); }
+ if (w == 7) { printf(" }, // U+00%.2X (%c)\n", code, code); }
+ }
+ }
+*/
+
+uint8_t font8x8_basic_tr[128][8] = {
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0000 (nul)
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0001
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0002
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0003
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0004
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0005
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0006
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0007
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0008
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0009
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+000A
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+000B
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+000C
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+000D
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+000E
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+000F
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0010
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0011
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0012
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0013
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0014
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0015
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0016
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0017
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0018
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0019
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+001A
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+001B
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+001C
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+001D
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+001E
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+001F
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0020 (space)
+ { 0x00, 0x00, 0x06, 0x5F, 0x5F, 0x06, 0x00, 0x00 }, // U+0021 (!)
+ { 0x00, 0x03, 0x03, 0x00, 0x03, 0x03, 0x00, 0x00 }, // U+0022 (")
+ { 0x14, 0x7F, 0x7F, 0x14, 0x7F, 0x7F, 0x14, 0x00 }, // U+0023 (#)
+ { 0x24, 0x2E, 0x6B, 0x6B, 0x3A, 0x12, 0x00, 0x00 }, // U+0024 ($)
+ { 0x46, 0x66, 0x30, 0x18, 0x0C, 0x66, 0x62, 0x00 }, // U+0025 (%)
+ { 0x30, 0x7A, 0x4F, 0x5D, 0x37, 0x7A, 0x48, 0x00 }, // U+0026 (&)
+ { 0x04, 0x07, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 }, // U+0027 (')
+ { 0x00, 0x1C, 0x3E, 0x63, 0x41, 0x00, 0x00, 0x00 }, // U+0028 (()
+ { 0x00, 0x41, 0x63, 0x3E, 0x1C, 0x00, 0x00, 0x00 }, // U+0029 ())
+ { 0x08, 0x2A, 0x3E, 0x1C, 0x1C, 0x3E, 0x2A, 0x08 }, // U+002A (*)
+ { 0x08, 0x08, 0x3E, 0x3E, 0x08, 0x08, 0x00, 0x00 }, // U+002B (+)
+ { 0x00, 0x80, 0xE0, 0x60, 0x00, 0x00, 0x00, 0x00 }, // U+002C (,)
+ { 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00 }, // U+002D (-)
+ { 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00 }, // U+002E (.)
+ { 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00 }, // U+002F (/)
+ { 0x3E, 0x7F, 0x71, 0x59, 0x4D, 0x7F, 0x3E, 0x00 }, // U+0030 (0)
+ { 0x40, 0x42, 0x7F, 0x7F, 0x40, 0x40, 0x00, 0x00 }, // U+0031 (1)
+ { 0x62, 0x73, 0x59, 0x49, 0x6F, 0x66, 0x00, 0x00 }, // U+0032 (2)
+ { 0x22, 0x63, 0x49, 0x49, 0x7F, 0x36, 0x00, 0x00 }, // U+0033 (3)
+ { 0x18, 0x1C, 0x16, 0x53, 0x7F, 0x7F, 0x50, 0x00 }, // U+0034 (4)
+ { 0x27, 0x67, 0x45, 0x45, 0x7D, 0x39, 0x00, 0x00 }, // U+0035 (5)
+ { 0x3C, 0x7E, 0x4B, 0x49, 0x79, 0x30, 0x00, 0x00 }, // U+0036 (6)
+ { 0x03, 0x03, 0x71, 0x79, 0x0F, 0x07, 0x00, 0x00 }, // U+0037 (7)
+ { 0x36, 0x7F, 0x49, 0x49, 0x7F, 0x36, 0x00, 0x00 }, // U+0038 (8)
+ { 0x06, 0x4F, 0x49, 0x69, 0x3F, 0x1E, 0x00, 0x00 }, // U+0039 (9)
+ { 0x00, 0x00, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00 }, // U+003A (:)
+ { 0x00, 0x80, 0xE6, 0x66, 0x00, 0x00, 0x00, 0x00 }, // U+003B (;)
+ { 0x08, 0x1C, 0x36, 0x63, 0x41, 0x00, 0x00, 0x00 }, // U+003C (<)
+ { 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x00, 0x00 }, // U+003D (=)
+ { 0x00, 0x41, 0x63, 0x36, 0x1C, 0x08, 0x00, 0x00 }, // U+003E (>)
+ { 0x02, 0x03, 0x51, 0x59, 0x0F, 0x06, 0x00, 0x00 }, // U+003F (?)
+ { 0x3E, 0x7F, 0x41, 0x5D, 0x5D, 0x1F, 0x1E, 0x00 }, // U+0040 (@)
+ { 0x7C, 0x7E, 0x13, 0x13, 0x7E, 0x7C, 0x00, 0x00 }, // U+0041 (A)
+ { 0x41, 0x7F, 0x7F, 0x49, 0x49, 0x7F, 0x36, 0x00 }, // U+0042 (B)
+ { 0x1C, 0x3E, 0x63, 0x41, 0x41, 0x63, 0x22, 0x00 }, // U+0043 (C)
+ { 0x41, 0x7F, 0x7F, 0x41, 0x63, 0x3E, 0x1C, 0x00 }, // U+0044 (D)
+ { 0x41, 0x7F, 0x7F, 0x49, 0x5D, 0x41, 0x63, 0x00 }, // U+0045 (E)
+ { 0x41, 0x7F, 0x7F, 0x49, 0x1D, 0x01, 0x03, 0x00 }, // U+0046 (F)
+ { 0x1C, 0x3E, 0x63, 0x41, 0x51, 0x73, 0x72, 0x00 }, // U+0047 (G)
+ { 0x7F, 0x7F, 0x08, 0x08, 0x7F, 0x7F, 0x00, 0x00 }, // U+0048 (H)
+ { 0x00, 0x41, 0x7F, 0x7F, 0x41, 0x00, 0x00, 0x00 }, // U+0049 (I)
+ { 0x30, 0x70, 0x40, 0x41, 0x7F, 0x3F, 0x01, 0x00 }, // U+004A (J)
+ { 0x41, 0x7F, 0x7F, 0x08, 0x1C, 0x77, 0x63, 0x00 }, // U+004B (K)
+ { 0x41, 0x7F, 0x7F, 0x41, 0x40, 0x60, 0x70, 0x00 }, // U+004C (L)
+ { 0x7F, 0x7F, 0x0E, 0x1C, 0x0E, 0x7F, 0x7F, 0x00 }, // U+004D (M)
+ { 0x7F, 0x7F, 0x06, 0x0C, 0x18, 0x7F, 0x7F, 0x00 }, // U+004E (N)
+ { 0x1C, 0x3E, 0x63, 0x41, 0x63, 0x3E, 0x1C, 0x00 }, // U+004F (O)
+ { 0x41, 0x7F, 0x7F, 0x49, 0x09, 0x0F, 0x06, 0x00 }, // U+0050 (P)
+ { 0x1E, 0x3F, 0x21, 0x71, 0x7F, 0x5E, 0x00, 0x00 }, // U+0051 (Q)
+ { 0x41, 0x7F, 0x7F, 0x09, 0x19, 0x7F, 0x66, 0x00 }, // U+0052 (R)
+ { 0x26, 0x6F, 0x4D, 0x59, 0x73, 0x32, 0x00, 0x00 }, // U+0053 (S)
+ { 0x03, 0x41, 0x7F, 0x7F, 0x41, 0x03, 0x00, 0x00 }, // U+0054 (T)
+ { 0x7F, 0x7F, 0x40, 0x40, 0x7F, 0x7F, 0x00, 0x00 }, // U+0055 (U)
+ { 0x1F, 0x3F, 0x60, 0x60, 0x3F, 0x1F, 0x00, 0x00 }, // U+0056 (V)
+ { 0x7F, 0x7F, 0x30, 0x18, 0x30, 0x7F, 0x7F, 0x00 }, // U+0057 (W)
+ { 0x43, 0x67, 0x3C, 0x18, 0x3C, 0x67, 0x43, 0x00 }, // U+0058 (X)
+ { 0x07, 0x4F, 0x78, 0x78, 0x4F, 0x07, 0x00, 0x00 }, // U+0059 (Y)
+ { 0x47, 0x63, 0x71, 0x59, 0x4D, 0x67, 0x73, 0x00 }, // U+005A (Z)
+ { 0x00, 0x7F, 0x7F, 0x41, 0x41, 0x00, 0x00, 0x00 }, // U+005B ([)
+ { 0x01, 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x00 }, // U+005C (\)
+ { 0x00, 0x41, 0x41, 0x7F, 0x7F, 0x00, 0x00, 0x00 }, // U+005D (])
+ { 0x08, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x08, 0x00 }, // U+005E (^)
+ { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, // U+005F (_)
+ { 0x00, 0x00, 0x03, 0x07, 0x04, 0x00, 0x00, 0x00 }, // U+0060 (`)
+ { 0x20, 0x74, 0x54, 0x54, 0x3C, 0x78, 0x40, 0x00 }, // U+0061 (a)
+ { 0x41, 0x7F, 0x3F, 0x48, 0x48, 0x78, 0x30, 0x00 }, // U+0062 (b)
+ { 0x38, 0x7C, 0x44, 0x44, 0x6C, 0x28, 0x00, 0x00 }, // U+0063 (c)
+ { 0x30, 0x78, 0x48, 0x49, 0x3F, 0x7F, 0x40, 0x00 }, // U+0064 (d)
+ { 0x38, 0x7C, 0x54, 0x54, 0x5C, 0x18, 0x00, 0x00 }, // U+0065 (e)
+ { 0x48, 0x7E, 0x7F, 0x49, 0x03, 0x02, 0x00, 0x00 }, // U+0066 (f)
+ { 0x98, 0xBC, 0xA4, 0xA4, 0xF8, 0x7C, 0x04, 0x00 }, // U+0067 (g)
+ { 0x41, 0x7F, 0x7F, 0x08, 0x04, 0x7C, 0x78, 0x00 }, // U+0068 (h)
+ { 0x00, 0x44, 0x7D, 0x7D, 0x40, 0x00, 0x00, 0x00 }, // U+0069 (i)
+ { 0x60, 0xE0, 0x80, 0x80, 0xFD, 0x7D, 0x00, 0x00 }, // U+006A (j)
+ { 0x41, 0x7F, 0x7F, 0x10, 0x38, 0x6C, 0x44, 0x00 }, // U+006B (k)
+ { 0x00, 0x41, 0x7F, 0x7F, 0x40, 0x00, 0x00, 0x00 }, // U+006C (l)
+ { 0x7C, 0x7C, 0x18, 0x38, 0x1C, 0x7C, 0x78, 0x00 }, // U+006D (m)
+ { 0x7C, 0x7C, 0x04, 0x04, 0x7C, 0x78, 0x00, 0x00 }, // U+006E (n)
+ { 0x38, 0x7C, 0x44, 0x44, 0x7C, 0x38, 0x00, 0x00 }, // U+006F (o)
+ { 0x84, 0xFC, 0xF8, 0xA4, 0x24, 0x3C, 0x18, 0x00 }, // U+0070 (p)
+ { 0x18, 0x3C, 0x24, 0xA4, 0xF8, 0xFC, 0x84, 0x00 }, // U+0071 (q)
+ { 0x44, 0x7C, 0x78, 0x4C, 0x04, 0x1C, 0x18, 0x00 }, // U+0072 (r)
+ { 0x48, 0x5C, 0x54, 0x54, 0x74, 0x24, 0x00, 0x00 }, // U+0073 (s)
+ { 0x00, 0x04, 0x3E, 0x7F, 0x44, 0x24, 0x00, 0x00 }, // U+0074 (t)
+ { 0x3C, 0x7C, 0x40, 0x40, 0x3C, 0x7C, 0x40, 0x00 }, // U+0075 (u)
+ { 0x1C, 0x3C, 0x60, 0x60, 0x3C, 0x1C, 0x00, 0x00 }, // U+0076 (v)
+ { 0x3C, 0x7C, 0x70, 0x38, 0x70, 0x7C, 0x3C, 0x00 }, // U+0077 (w)
+ { 0x44, 0x6C, 0x38, 0x10, 0x38, 0x6C, 0x44, 0x00 }, // U+0078 (x)
+ { 0x9C, 0xBC, 0xA0, 0xA0, 0xFC, 0x7C, 0x00, 0x00 }, // U+0079 (y)
+ { 0x4C, 0x64, 0x74, 0x5C, 0x4C, 0x64, 0x00, 0x00 }, // U+007A (z)
+ { 0x08, 0x08, 0x3E, 0x77, 0x41, 0x41, 0x00, 0x00 }, // U+007B ({)
+ { 0x00, 0x00, 0x00, 0x77, 0x77, 0x00, 0x00, 0x00 }, // U+007C (|)
+ { 0x41, 0x41, 0x77, 0x3E, 0x08, 0x08, 0x00, 0x00 }, // U+007D (})
+ { 0x02, 0x03, 0x01, 0x03, 0x02, 0x03, 0x01, 0x00 }, // U+007E (~)
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } // U+007F
+};
+
+#endif /* MAIN_FONT8X8_BASIC_H_ */
+
diff --git a/node/main/oled/ssd1366.cpp b/node/main/oled/ssd1366.cpp
new file mode 100644
index 0000000..e0318e6
--- /dev/null
+++ b/node/main/oled/ssd1366.cpp
@@ -0,0 +1,180 @@
+
+#include "ssd1366.h"
+#include "font8x8_basic.h"
+#include
+#include "driver/gpio.h"
+
+
+#define SSD1366_RST_PIN GPIO_NUM_16
+#define SSD1366_VEXT_PIN GPIO_NUM_21
+
+#define I2C_NUM I2C_NUM_0
+
+static const char *tag = "oled";
+
+void ssd1306_init()
+{
+ esp_err_t espRc;
+ gpio_set_direction(SSD1366_RST_PIN, GPIO_MODE_OUTPUT);
+ gpio_set_direction(SSD1366_VEXT_PIN, GPIO_MODE_OUTPUT);
+ gpio_set_level(SSD1366_VEXT_PIN, 0); //enable power to oled
+ gpio_set_level(SSD1366_RST_PIN, 0);
+ vTaskDelay(50 / portTICK_PERIOD_MS);
+ gpio_set_level(SSD1366_RST_PIN, 1);
+
+ i2c_cmd_handle_t cmd = i2c_cmd_link_create();
+
+ i2c_master_start(cmd);
+ i2c_master_write_byte(cmd, (OLED_I2C_ADDRESS << 1) | I2C_MASTER_WRITE, true);
+ i2c_master_write_byte(cmd, OLED_CONTROL_BYTE_CMD_STREAM, true);
+
+ i2c_master_write_byte(cmd, OLED_CMD_DISPLAY_OFF, true); // AE
+ i2c_master_write_byte(cmd, OLED_CMD_SET_MUX_RATIO, true); // A8
+ i2c_master_write_byte(cmd, 0x3F, true);
+ i2c_master_write_byte(cmd, OLED_CMD_SET_DISPLAY_OFFSET, true); // D3
+ i2c_master_write_byte(cmd, 0x00, true);
+ i2c_master_write_byte(cmd, OLED_CMD_SET_DISPLAY_START_LINE, true); // 40
+
+ i2c_master_write_byte(cmd, OLED_CMD_SET_SEGMENT_REMAP, true); // reverse left-right mapping
+ i2c_master_write_byte(cmd, OLED_CMD_SET_COM_SCAN_MODE, true); // reverse up-bottom mapping
+
+ i2c_master_write_byte(cmd, OLED_CMD_SET_DISPLAY_CLK_DIV, true); // D5
+ i2c_master_write_byte(cmd, 0x80, true);
+ i2c_master_write_byte(cmd, OLED_CMD_SET_COM_PIN_MAP, true); // DA
+ i2c_master_write_byte(cmd, 0x12, true);
+ i2c_master_write_byte(cmd, OLED_CMD_SET_CONTRAST, true); // 81
+ i2c_master_write_byte(cmd, 0xFF, true);
+ i2c_master_write_byte(cmd, OLED_CMD_DISPLAY_RAM, true); // A4
+ i2c_master_write_byte(cmd, OLED_CMD_SET_VCOMH_DESELCT, true); // DB
+ i2c_master_write_byte(cmd, 0x40, true);
+ i2c_master_write_byte(cmd, OLED_CMD_SET_MEMORY_ADDR_MODE, true); // 20
+ //i2c_master_write_byte(cmd, OLED_CMD_SET_HORI_ADDR_MODE, true); // 00
+ i2c_master_write_byte(cmd, OLED_CMD_SET_PAGE_ADDR_MODE, true); // 02
+ // Set Lower Column Start Address for Page Addressing Mode
+ i2c_master_write_byte(cmd, 0x00, true);
+ // Set Higher Column Start Address for Page Addressing Mode
+ i2c_master_write_byte(cmd, 0x10, true);
+ i2c_master_write_byte(cmd, OLED_CMD_SET_CHARGE_PUMP, true); // 8D
+ i2c_master_write_byte(cmd, 0x14, true);
+ i2c_master_write_byte(cmd, OLED_CMD_DEACTIVE_SCROLL, true); // 2E
+ i2c_master_write_byte(cmd, OLED_CMD_DISPLAY_NORMAL, true); // A6
+ i2c_master_write_byte(cmd, OLED_CMD_DISPLAY_ON, true);
+
+ i2c_master_stop(cmd);
+
+ espRc = i2c_master_cmd_begin(I2C_NUM, cmd, 10/portTICK_PERIOD_MS);
+ if (espRc == ESP_OK)
+ {
+ ESP_LOGI(tag, "OLED configured successfully");
+ }
+ else
+ {
+ ESP_LOGE(tag, "OLED configuration failed. code: 0x%.2X", espRc);
+ }
+ i2c_cmd_link_delete(cmd);
+}
+
+void i2c_contrast( int contrast) {
+ i2c_cmd_handle_t cmd;
+ int _contrast = contrast;
+ if (contrast < 0x0) _contrast = 0;
+ if (contrast > 0xFF) _contrast = 0xFF;
+
+ cmd = i2c_cmd_link_create();
+ i2c_master_start(cmd);
+ i2c_master_write_byte(cmd, (OLED_I2C_ADDRESS << 1) | I2C_MASTER_WRITE, true);
+ i2c_master_write_byte(cmd, OLED_CONTROL_BYTE_CMD_STREAM, true);
+ i2c_master_write_byte(cmd, OLED_CMD_SET_CONTRAST, true); // 81
+ i2c_master_write_byte(cmd, _contrast, true);
+ i2c_master_stop(cmd);
+ i2c_master_cmd_begin(I2C_NUM, cmd, 10/portTICK_PERIOD_MS);
+ i2c_cmd_link_delete(cmd);
+}
+
+void ssd1306_display_clear(void) {
+ i2c_cmd_handle_t cmd;
+ uint8_t zero[128] = {0};
+ for (uint8_t i = 0; i < 8; i++) {
+ cmd = i2c_cmd_link_create();
+ i2c_master_start(cmd);
+ i2c_master_write_byte(cmd, (OLED_I2C_ADDRESS << 1) | I2C_MASTER_WRITE, true);
+ i2c_master_write_byte(cmd, OLED_CONTROL_BYTE_CMD_SINGLE, true);
+ i2c_master_write_byte(cmd, 0xB0 | i, true); // Set GDDRAM page start address
+ i2c_master_write_byte(cmd, OLED_CONTROL_BYTE_DATA_STREAM, true);
+ i2c_master_write(cmd, zero, 128, true);
+ i2c_master_stop(cmd);
+ i2c_master_cmd_begin(I2C_NUM_0, cmd, 10/portTICK_PERIOD_MS);
+ i2c_cmd_link_delete(cmd);
+ }
+}
+
+void ssd1306_display_text(char* text, uint16_t text_len)
+{
+
+ i2c_cmd_handle_t cmd;
+
+ uint8_t cur_page = 0;
+
+ cmd = i2c_cmd_link_create();
+ i2c_master_start(cmd);
+ i2c_master_write_byte(cmd, (OLED_I2C_ADDRESS << 1) | I2C_MASTER_WRITE, true);
+
+ i2c_master_write_byte(cmd, OLED_CONTROL_BYTE_CMD_STREAM, true);
+ i2c_master_write_byte(cmd, 0x00, true); // reset column
+ i2c_master_write_byte(cmd, 0x10, true);
+ i2c_master_write_byte(cmd, 0xB0 | cur_page, true); // reset page
+
+ i2c_master_stop(cmd);
+ i2c_master_cmd_begin(I2C_NUM, cmd, 10/portTICK_PERIOD_MS);
+ i2c_cmd_link_delete(cmd);
+
+ for (uint8_t i = 0; i < text_len; i++)
+ {
+ if (text[i] == '\n')
+ {
+ cmd = i2c_cmd_link_create();
+ i2c_master_start(cmd);
+ i2c_master_write_byte(cmd, (OLED_I2C_ADDRESS << 1) | I2C_MASTER_WRITE, true);
+
+ i2c_master_write_byte(cmd, OLED_CONTROL_BYTE_CMD_STREAM, true);
+ i2c_master_write_byte(cmd, 0x00, true); // reset column
+ i2c_master_write_byte(cmd, 0x10, true);
+ i2c_master_write_byte(cmd, 0xB0 | ++cur_page, true); // increment page
+
+ i2c_master_stop(cmd);
+ i2c_master_cmd_begin(I2C_NUM, cmd, 10/portTICK_PERIOD_MS);
+ i2c_cmd_link_delete(cmd);
+ }
+ else
+ {
+ cmd = i2c_cmd_link_create();
+ i2c_master_start(cmd);
+ i2c_master_write_byte(cmd, (OLED_I2C_ADDRESS << 1) | I2C_MASTER_WRITE, true);
+
+ i2c_master_write_byte(cmd, OLED_CONTROL_BYTE_DATA_STREAM, true);
+ i2c_master_write(cmd, font8x8_basic_tr[(uint8_t)text[i]], 8, true);
+
+ i2c_master_stop(cmd);
+ i2c_master_cmd_begin(I2C_NUM, cmd, 10/portTICK_PERIOD_MS);
+ i2c_cmd_link_delete(cmd);
+ }
+ }
+}
+
+
+void task_ssd1306_display_text(void *pvParameters)
+{
+ char *pcTaskName;
+ pcTaskName = (char *) pvParameters;
+
+ //char text[13] = "Hello world!";
+ //uint8_t text_len = strlen(text);
+
+ ssd1306_display_text(pcTaskName, strlen(pcTaskName));
+
+ vTaskDelete(NULL);
+}
+
+
+
+
diff --git a/node/main/oled/ssd1366.h b/node/main/oled/ssd1366.h
new file mode 100644
index 0000000..13fa6d0
--- /dev/null
+++ b/node/main/oled/ssd1366.h
@@ -0,0 +1,65 @@
+#ifndef MAIN_SSD1366_H_
+#define MAIN_SSD1366_H_
+
+#include "freertos/FreeRTOS.h"
+#include "driver/gpio.h"
+#include "driver/i2c.h"
+#include "esp_log.h"
+
+// SLA (0x3C) + WRITE_MODE (0x00) = 0x78 (0b01111000)
+#define OLED_I2C_ADDRESS 0x3C
+
+// Control byte
+#define OLED_CONTROL_BYTE_CMD_SINGLE 0x80
+#define OLED_CONTROL_BYTE_CMD_STREAM 0x00
+#define OLED_CONTROL_BYTE_DATA_STREAM 0x40
+
+// Fundamental commands (pg.28)
+#define OLED_CMD_SET_CONTRAST 0x81 // follow with 0x7F
+#define OLED_CMD_DISPLAY_RAM 0xA4
+#define OLED_CMD_DISPLAY_ALLON 0xA5
+#define OLED_CMD_DISPLAY_NORMAL 0xA6
+#define OLED_CMD_DISPLAY_INVERTED 0xA7
+#define OLED_CMD_DISPLAY_OFF 0xAE
+#define OLED_CMD_DISPLAY_ON 0xAF
+
+// Addressing Command Table (pg.30)
+#define OLED_CMD_SET_MEMORY_ADDR_MODE 0x20 // follow with 0x00 = HORZ mode = Behave like a KS108 graphic LCD
+#define OLED_CMD_SET_COLUMN_RANGE 0x21 // can be used only in HORZ/VERT mode - follow with 0x00 and 0x7F = COL127
+#define OLED_CMD_SET_PAGE_RANGE 0x22 // can be used only in HORZ/VERT mode - follow with 0x00 and 0x07 = PAGE7
+#define OLED_CMD_SET_PAGE_ADDR_MODE 0x02 // Page Addressing Mode
+
+// Hardware Config (pg.31)
+#define OLED_CMD_SET_DISPLAY_START_LINE 0x40
+#define OLED_CMD_SET_SEGMENT_REMAP 0xA1
+#define OLED_CMD_SET_MUX_RATIO 0xA8 // follow with 0x3F = 64 MUX
+#define OLED_CMD_SET_COM_SCAN_MODE 0xC8
+#define OLED_CMD_SET_DISPLAY_OFFSET 0xD3 // follow with 0x00
+#define OLED_CMD_SET_COM_PIN_MAP 0xDA // follow with 0x12
+#define OLED_CMD_NOP 0xE3 // NOP
+
+// Timing and Driving Scheme (pg.32)
+#define OLED_CMD_SET_DISPLAY_CLK_DIV 0xD5 // follow with 0x80
+#define OLED_CMD_SET_PRECHARGE 0xD9 // follow with 0xF1
+#define OLED_CMD_SET_VCOMH_DESELCT 0xDB // follow with 0x30
+
+// Charge Pump (pg.62)
+#define OLED_CMD_SET_CHARGE_PUMP 0x8D // follow with 0x14
+
+// Scrolling Command
+#define OLED_CMD_HORIZONTAL_RIGHT 0x26
+#define OLED_CMD_HORIZONTAL_LEFT 0x27
+#define OLED_CMD_CONTINUOUS_SCROLL 0x29
+#define OLED_CMD_DEACTIVE_SCROLL 0x2E
+#define OLED_CMD_ACTIVE_SCROLL 0x2F
+#define OLED_CMD_VERTICAL 0xA3
+
+
+void i2c_master_init();
+void ssd1306_init();
+void task_ssd1306_display_text(void *pvParameters);
+void i2c_contrast( int contrast);
+void ssd1306_display_clear(void);
+void ssd1306_display_text(char* text, uint16_t text_len);
+
+#endif /* MAIN_SSD1366_H_ */
\ No newline at end of file
diff --git a/node/main/ssd1366.cpp b/node/main/ssd1366.cpp
new file mode 100644
index 0000000..ba86219
--- /dev/null
+++ b/node/main/ssd1366.cpp
@@ -0,0 +1,204 @@
+
+#include "ssd1366.h"
+#include "font8x8_basic.h"
+#include
+#include "driver/gpio.h"
+
+#define SSD1366_SDA_PIN GPIO_NUM_4
+#define SSD1366_SCL_PIN GPIO_NUM_15
+#define SSD1366_RST_PIN GPIO_NUM_16
+#define SSD1366_VEXT_PIN GPIO_NUM_21
+
+
+
+
+#define I2C_NUM I2C_NUM_0
+//#define I2C_NUM I2C_NUM
+
+#define I2C_MASTER_FREQ_HZ 400000U /*!< I2C clock of SSD1306 can run at 400 kHz max. */
+
+
+#define tag "SSD1306"
+
+void i2c_master_init()
+{
+ i2c_config_t i2c_config;
+
+ i2c_config.mode = I2C_MODE_MASTER;
+ i2c_config.sda_io_num = SSD1366_SDA_PIN;
+ i2c_config.scl_io_num = SSD1366_SCL_PIN;
+ i2c_config.sda_pullup_en = GPIO_PULLUP_ENABLE;
+ i2c_config.scl_pullup_en = GPIO_PULLUP_ENABLE;
+ i2c_config.master.clk_speed = I2C_MASTER_FREQ_HZ;
+
+
+ ESP_ERROR_CHECK(i2c_param_config(I2C_NUM, &i2c_config));
+ ESP_ERROR_CHECK(i2c_driver_install(I2C_NUM, I2C_MODE_MASTER, 0, 0, 0));
+}
+
+void ssd1306_init()
+{
+ esp_err_t espRc;
+ gpio_set_direction(SSD1366_RST_PIN, GPIO_MODE_OUTPUT);
+ gpio_set_direction(SSD1366_VEXT_PIN, GPIO_MODE_OUTPUT);
+ gpio_set_level(SSD1366_VEXT_PIN, 0); //enable power to oled
+ gpio_set_level(SSD1366_RST_PIN, 0);
+ vTaskDelay(50 / portTICK_PERIOD_MS);
+ gpio_set_level(SSD1366_RST_PIN, 1);
+
+ i2c_cmd_handle_t cmd = i2c_cmd_link_create();
+
+ i2c_master_start(cmd);
+ i2c_master_write_byte(cmd, (OLED_I2C_ADDRESS << 1) | I2C_MASTER_WRITE, true);
+ i2c_master_write_byte(cmd, OLED_CONTROL_BYTE_CMD_STREAM, true);
+
+ i2c_master_write_byte(cmd, OLED_CMD_DISPLAY_OFF, true); // AE
+ i2c_master_write_byte(cmd, OLED_CMD_SET_MUX_RATIO, true); // A8
+ i2c_master_write_byte(cmd, 0x3F, true);
+ i2c_master_write_byte(cmd, OLED_CMD_SET_DISPLAY_OFFSET, true); // D3
+ i2c_master_write_byte(cmd, 0x00, true);
+ i2c_master_write_byte(cmd, OLED_CMD_SET_DISPLAY_START_LINE, true); // 40
+
+ i2c_master_write_byte(cmd, OLED_CMD_SET_SEGMENT_REMAP, true); // reverse left-right mapping
+ i2c_master_write_byte(cmd, OLED_CMD_SET_COM_SCAN_MODE, true); // reverse up-bottom mapping
+
+ i2c_master_write_byte(cmd, OLED_CMD_SET_DISPLAY_CLK_DIV, true); // D5
+ i2c_master_write_byte(cmd, 0x80, true);
+ i2c_master_write_byte(cmd, OLED_CMD_SET_COM_PIN_MAP, true); // DA
+ i2c_master_write_byte(cmd, 0x12, true);
+ i2c_master_write_byte(cmd, OLED_CMD_SET_CONTRAST, true); // 81
+ i2c_master_write_byte(cmd, 0xFF, true);
+ i2c_master_write_byte(cmd, OLED_CMD_DISPLAY_RAM, true); // A4
+ i2c_master_write_byte(cmd, OLED_CMD_SET_VCOMH_DESELCT, true); // DB
+ i2c_master_write_byte(cmd, 0x40, true);
+ i2c_master_write_byte(cmd, OLED_CMD_SET_MEMORY_ADDR_MODE, true); // 20
+ //i2c_master_write_byte(cmd, OLED_CMD_SET_HORI_ADDR_MODE, true); // 00
+ i2c_master_write_byte(cmd, OLED_CMD_SET_PAGE_ADDR_MODE, true); // 02
+ // Set Lower Column Start Address for Page Addressing Mode
+ i2c_master_write_byte(cmd, 0x00, true);
+ // Set Higher Column Start Address for Page Addressing Mode
+ i2c_master_write_byte(cmd, 0x10, true);
+ i2c_master_write_byte(cmd, OLED_CMD_SET_CHARGE_PUMP, true); // 8D
+ i2c_master_write_byte(cmd, 0x14, true);
+ i2c_master_write_byte(cmd, OLED_CMD_DEACTIVE_SCROLL, true); // 2E
+ i2c_master_write_byte(cmd, OLED_CMD_DISPLAY_NORMAL, true); // A6
+ i2c_master_write_byte(cmd, OLED_CMD_DISPLAY_ON, true);
+
+ i2c_master_stop(cmd);
+
+ espRc = i2c_master_cmd_begin(I2C_NUM, cmd, 10/portTICK_PERIOD_MS);
+ if (espRc == ESP_OK)
+ {
+ ESP_LOGI(tag, "OLED configured successfully");
+ }
+ else
+ {
+ ESP_LOGE(tag, "OLED configuration failed. code: 0x%.2X", espRc);
+ }
+ i2c_cmd_link_delete(cmd);
+}
+
+void i2c_contrast( int contrast) {
+ i2c_cmd_handle_t cmd;
+ int _contrast = contrast;
+ if (contrast < 0x0) _contrast = 0;
+ if (contrast > 0xFF) _contrast = 0xFF;
+
+ cmd = i2c_cmd_link_create();
+ i2c_master_start(cmd);
+ i2c_master_write_byte(cmd, (OLED_I2C_ADDRESS << 1) | I2C_MASTER_WRITE, true);
+ i2c_master_write_byte(cmd, OLED_CONTROL_BYTE_CMD_STREAM, true);
+ i2c_master_write_byte(cmd, OLED_CMD_SET_CONTRAST, true); // 81
+ i2c_master_write_byte(cmd, _contrast, true);
+ i2c_master_stop(cmd);
+ i2c_master_cmd_begin(I2C_NUM, cmd, 10/portTICK_PERIOD_MS);
+ i2c_cmd_link_delete(cmd);
+}
+
+void ssd1306_display_clear(void) {
+ i2c_cmd_handle_t cmd;
+ uint8_t zero[128] = {0};
+ for (uint8_t i = 0; i < 8; i++) {
+ cmd = i2c_cmd_link_create();
+ i2c_master_start(cmd);
+ i2c_master_write_byte(cmd, (OLED_I2C_ADDRESS << 1) | I2C_MASTER_WRITE, true);
+ i2c_master_write_byte(cmd, OLED_CONTROL_BYTE_CMD_SINGLE, true);
+ i2c_master_write_byte(cmd, 0xB0 | i, true); // Set GDDRAM page start address
+ i2c_master_write_byte(cmd, OLED_CONTROL_BYTE_DATA_STREAM, true);
+ i2c_master_write(cmd, zero, 128, true);
+ i2c_master_stop(cmd);
+ i2c_master_cmd_begin(I2C_NUM_0, cmd, 10/portTICK_PERIOD_MS);
+ i2c_cmd_link_delete(cmd);
+ }
+}
+
+void ssd1306_display_text(char* text, uint16_t text_len)
+{
+
+ i2c_cmd_handle_t cmd;
+
+ uint8_t cur_page = 0;
+
+ cmd = i2c_cmd_link_create();
+ i2c_master_start(cmd);
+ i2c_master_write_byte(cmd, (OLED_I2C_ADDRESS << 1) | I2C_MASTER_WRITE, true);
+
+ i2c_master_write_byte(cmd, OLED_CONTROL_BYTE_CMD_STREAM, true);
+ i2c_master_write_byte(cmd, 0x00, true); // reset column
+ i2c_master_write_byte(cmd, 0x10, true);
+ i2c_master_write_byte(cmd, 0xB0 | cur_page, true); // reset page
+
+ i2c_master_stop(cmd);
+ i2c_master_cmd_begin(I2C_NUM, cmd, 10/portTICK_PERIOD_MS);
+ i2c_cmd_link_delete(cmd);
+
+ for (uint8_t i = 0; i < text_len; i++)
+ {
+ if (text[i] == '\n')
+ {
+ cmd = i2c_cmd_link_create();
+ i2c_master_start(cmd);
+ i2c_master_write_byte(cmd, (OLED_I2C_ADDRESS << 1) | I2C_MASTER_WRITE, true);
+
+ i2c_master_write_byte(cmd, OLED_CONTROL_BYTE_CMD_STREAM, true);
+ i2c_master_write_byte(cmd, 0x00, true); // reset column
+ i2c_master_write_byte(cmd, 0x10, true);
+ i2c_master_write_byte(cmd, 0xB0 | ++cur_page, true); // increment page
+
+ i2c_master_stop(cmd);
+ i2c_master_cmd_begin(I2C_NUM, cmd, 10/portTICK_PERIOD_MS);
+ i2c_cmd_link_delete(cmd);
+ }
+ else
+ {
+ cmd = i2c_cmd_link_create();
+ i2c_master_start(cmd);
+ i2c_master_write_byte(cmd, (OLED_I2C_ADDRESS << 1) | I2C_MASTER_WRITE, true);
+
+ i2c_master_write_byte(cmd, OLED_CONTROL_BYTE_DATA_STREAM, true);
+ i2c_master_write(cmd, font8x8_basic_tr[(uint8_t)text[i]], 8, true);
+
+ i2c_master_stop(cmd);
+ i2c_master_cmd_begin(I2C_NUM, cmd, 10/portTICK_PERIOD_MS);
+ i2c_cmd_link_delete(cmd);
+ }
+ }
+}
+
+
+void task_ssd1306_display_text(void *pvParameters)
+{
+ char *pcTaskName;
+ pcTaskName = (char *) pvParameters;
+
+ //char text[13] = "Hello world!";
+ //uint8_t text_len = strlen(text);
+
+ ssd1306_display_text(pcTaskName, strlen(pcTaskName));
+
+ vTaskDelete(NULL);
+}
+
+
+
+
diff --git a/node/main/ssd1366.h b/node/main/ssd1366.h
new file mode 100644
index 0000000..13fa6d0
--- /dev/null
+++ b/node/main/ssd1366.h
@@ -0,0 +1,65 @@
+#ifndef MAIN_SSD1366_H_
+#define MAIN_SSD1366_H_
+
+#include "freertos/FreeRTOS.h"
+#include "driver/gpio.h"
+#include "driver/i2c.h"
+#include "esp_log.h"
+
+// SLA (0x3C) + WRITE_MODE (0x00) = 0x78 (0b01111000)
+#define OLED_I2C_ADDRESS 0x3C
+
+// Control byte
+#define OLED_CONTROL_BYTE_CMD_SINGLE 0x80
+#define OLED_CONTROL_BYTE_CMD_STREAM 0x00
+#define OLED_CONTROL_BYTE_DATA_STREAM 0x40
+
+// Fundamental commands (pg.28)
+#define OLED_CMD_SET_CONTRAST 0x81 // follow with 0x7F
+#define OLED_CMD_DISPLAY_RAM 0xA4
+#define OLED_CMD_DISPLAY_ALLON 0xA5
+#define OLED_CMD_DISPLAY_NORMAL 0xA6
+#define OLED_CMD_DISPLAY_INVERTED 0xA7
+#define OLED_CMD_DISPLAY_OFF 0xAE
+#define OLED_CMD_DISPLAY_ON 0xAF
+
+// Addressing Command Table (pg.30)
+#define OLED_CMD_SET_MEMORY_ADDR_MODE 0x20 // follow with 0x00 = HORZ mode = Behave like a KS108 graphic LCD
+#define OLED_CMD_SET_COLUMN_RANGE 0x21 // can be used only in HORZ/VERT mode - follow with 0x00 and 0x7F = COL127
+#define OLED_CMD_SET_PAGE_RANGE 0x22 // can be used only in HORZ/VERT mode - follow with 0x00 and 0x07 = PAGE7
+#define OLED_CMD_SET_PAGE_ADDR_MODE 0x02 // Page Addressing Mode
+
+// Hardware Config (pg.31)
+#define OLED_CMD_SET_DISPLAY_START_LINE 0x40
+#define OLED_CMD_SET_SEGMENT_REMAP 0xA1
+#define OLED_CMD_SET_MUX_RATIO 0xA8 // follow with 0x3F = 64 MUX
+#define OLED_CMD_SET_COM_SCAN_MODE 0xC8
+#define OLED_CMD_SET_DISPLAY_OFFSET 0xD3 // follow with 0x00
+#define OLED_CMD_SET_COM_PIN_MAP 0xDA // follow with 0x12
+#define OLED_CMD_NOP 0xE3 // NOP
+
+// Timing and Driving Scheme (pg.32)
+#define OLED_CMD_SET_DISPLAY_CLK_DIV 0xD5 // follow with 0x80
+#define OLED_CMD_SET_PRECHARGE 0xD9 // follow with 0xF1
+#define OLED_CMD_SET_VCOMH_DESELCT 0xDB // follow with 0x30
+
+// Charge Pump (pg.62)
+#define OLED_CMD_SET_CHARGE_PUMP 0x8D // follow with 0x14
+
+// Scrolling Command
+#define OLED_CMD_HORIZONTAL_RIGHT 0x26
+#define OLED_CMD_HORIZONTAL_LEFT 0x27
+#define OLED_CMD_CONTINUOUS_SCROLL 0x29
+#define OLED_CMD_DEACTIVE_SCROLL 0x2E
+#define OLED_CMD_ACTIVE_SCROLL 0x2F
+#define OLED_CMD_VERTICAL 0xA3
+
+
+void i2c_master_init();
+void ssd1306_init();
+void task_ssd1306_display_text(void *pvParameters);
+void i2c_contrast( int contrast);
+void ssd1306_display_clear(void);
+void ssd1306_display_text(char* text, uint16_t text_len);
+
+#endif /* MAIN_SSD1366_H_ */
\ No newline at end of file