diff --git a/.gitignore b/.gitignore deleted file mode 100644 index b02f414..0000000 --- a/.gitignore +++ /dev/null @@ -1,115 +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 - 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/README.md b/README.md index 9ddc370..102d30c 100644 --- a/README.md +++ b/README.md @@ -30,5 +30,90 @@ TTN LoRa frequency / region 3. Build with ESP-IDF extension in VSCodium +## MQTT Endpoint ## +`pip3 install paho-mqtt` +- `mkdir /opt/msv-clubhouse-backend/` +- `cd /opt/msv-clubhouse-backend/` +- import `msv_clubhouse_backend.py` and `config.py` +- Set the constants in `config.py` +- `chmod +x /opt/msv-clubhouse-backend/msv_clubhouse_backend.py` +- `chown -R prometheus /opt/msv-clubhouse-backend/` +- `nano /etc/systemd/system/msv-clubhouse-backend.service` +- `systemctl daemon-reload && systemctl enable --now msv-clubhouse-backend.service` + +JS Payload Formatter: + +``` +function Decoder(bytes, port) { + var decoded = {}; + + // temperature + rawTemp = bytes[0] + bytes[1] * 256; + decoded.degreesC = sflt162f(rawTemp) * 100; + + // pressure + rawPressure = bytes[2] + bytes[3] * 256; + decoded.pressure = sflt162f(rawPressure) * 100; + + // windspeed + rawWindspeed = bytes[4] + bytes[5] * 256; + decoded.windspeed = sflt162f(rawWindspeed) * 100; + + // winddirection + rawWinddirection = bytes[6] + bytes[7] * 256; + decoded.winddirection = sflt162f(rawWinddirection) * 100; + + if(bytes[8] === 0){ + decoded.dooropen = false; + }else{ + decoded.dooropen = true; + } + + + return decoded; +} + +function sflt162f(rawSflt16) + { + // rawSflt16 is the 2-byte number decoded from wherever; + // it's in range 0..0xFFFF + // bit 15 is the sign bit + // bits 14..11 are the exponent + // bits 10..0 are the the mantissa. Unlike IEEE format, + // the msb is transmitted; this means that numbers + // might not be normalized, but makes coding for + // underflow easier. + // As with IEEE format, negative zero is possible, so + // we special-case that in hopes that JavaScript will + // also cooperate. + // + // The result is a number in the open interval (-1.0, 1.0); + // + + // throw away high bits for repeatability. + rawSflt16 &= 0xFFFF; + + // special case minus zero: + if (rawSflt16 == 0x8000) + return -0.0; + + // extract the sign. + var sSign = ((rawSflt16 & 0x8000) !== 0) ? -1 : 1; + + // extract the exponent + var exp1 = (rawSflt16 >> 11) & 0xF; + + // extract the "mantissa" (the fractional part) + var mant1 = (rawSflt16 & 0x7FF) / 2048.0; + + // convert back to a floating point number. We hope + // that Math.pow(2, k) is handled efficiently by + // the JS interpreter! If this is time critical code, + // you can replace by a suitable shift and divide. + var f_unscaled = sSign * mant1 * Math.pow(2, exp1 - 15); + + return f_unscaled; + } + ``` \ No newline at end of file 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/components/ttn-esp32 b/components/ttn-esp32 deleted file mode 160000 index 8777846..0000000 --- a/components/ttn-esp32 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 877784640d7976aab84398de6a5b376033fd56d1 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/CMakeLists.txt b/node/CMakeLists.txt similarity index 100% rename from CMakeLists.txt rename to node/CMakeLists.txt diff --git a/node/components/.gitkeep b/node/components/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/main/CMakeLists.txt b/node/main/CMakeLists.txt similarity index 100% rename from main/CMakeLists.txt rename to node/main/CMakeLists.txt diff --git a/main/Kconfig.projbuild b/node/main/Kconfig.projbuild similarity index 100% rename from main/Kconfig.projbuild rename to node/main/Kconfig.projbuild diff --git a/main/font8x8_basic.h b/node/main/font8x8_basic.h similarity index 100% rename from main/font8x8_basic.h rename to node/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/main/main.cpp b/node/main/main.cpp similarity index 100% rename from main/main.cpp rename to node/main/main.cpp diff --git a/main/main.h b/node/main/main.h similarity index 100% rename from main/main.h rename to node/main/main.h 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/main/ssd1366.h b/node/main/oled/ssd1366.h similarity index 100% rename from main/ssd1366.h rename to node/main/oled/ssd1366.h diff --git a/main/ssd1366.cpp b/node/main/ssd1366.cpp similarity index 100% rename from main/ssd1366.cpp rename to node/main/ssd1366.cpp 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