From e4a36849517a88a1ca96df5ac710b54f780284a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniele=20Vigan=C3=B2?= Date: Wed, 3 Jun 2020 18:53:33 +0200 Subject: [PATCH] Enable NTP (#6) * Add ntpd server to the stack * Enable NTP updates on Arduino * Fix telemetry when clock is 0 --- arduino/eeprom_prog/eeprom_prog.ino | 2 +- arduino/tempLightSensor/tempLightSensor.ino | 91 +++++++++++++------ docker/docker-compose.yml | 8 ++ docker/ntpd/Dockerfile | 8 ++ docker/ntpd/chrony.conf | 4 + freedcs/api/admin.py | 4 + freedcs/telemetry/admin.py | 5 +- .../migrations/0005_telemetry_clock.py | 19 ++++ .../migrations/0006_auto_20200603_1317.py | 17 ++++ freedcs/telemetry/models.py | 8 +- freedcs/telemetry/serializers.py | 2 +- 11 files changed, 134 insertions(+), 34 deletions(-) create mode 100644 docker/ntpd/Dockerfile create mode 100644 docker/ntpd/chrony.conf create mode 100644 freedcs/telemetry/migrations/0005_telemetry_clock.py create mode 100644 freedcs/telemetry/migrations/0006_auto_20200603_1317.py diff --git a/arduino/eeprom_prog/eeprom_prog.ino b/arduino/eeprom_prog/eeprom_prog.ino index 2efacbf..6923565 100644 --- a/arduino/eeprom_prog/eeprom_prog.ino +++ b/arduino/eeprom_prog/eeprom_prog.ino @@ -9,7 +9,7 @@ const char serial[] = "abcd1234"; struct netConfig { IPAddress address; - int port; + unsigned int port; }; netConfig config = { diff --git a/arduino/tempLightSensor/tempLightSensor.ino b/arduino/tempLightSensor/tempLightSensor.ino index 6d9c1e3..9bde4c4 100644 --- a/arduino/tempLightSensor/tempLightSensor.ino +++ b/arduino/tempLightSensor/tempLightSensor.ino @@ -1,23 +1,32 @@ #include #include +#include +#include #include #define DEBUG_TO_SERIAL 1 +#define USE_INTERNAL_NTP 0 // use default ntp server or the internal one #define AREF_VOLTAGE 3.3 -const String serverName = "sensor.server.domain"; +// const String serverName = "sensor.server.domain"; -const size_t capacity = JSON_OBJECT_SIZE(1) + 2*JSON_OBJECT_SIZE(6); -DynamicJsonDocument doc(capacity); -JsonObject payload = doc.createNestedObject("payload"); +const size_t capacity = JSON_OBJECT_SIZE(2) + 2 * JSON_OBJECT_SIZE(3) + 110; + +DynamicJsonDocument json(capacity); +JsonObject payload = json.createNestedObject("payload"); JsonObject temp = payload.createNestedObject("temperature"); +unsigned int counter = 0; int tempPin = A0; int photocellPin = A1; +EthernetUDP ntpUDP; +NTPClient timeClient(ntpUDP); +bool NTPValid = false; + struct netConfig { IPAddress address; - int port; + unsigned int port; }; netConfig config; @@ -26,37 +35,32 @@ const int postDelay = 10 * 1000; void setup(void) { Serial.begin(9600); - + analogReference(EXTERNAL); byte mac[6]; char serial[9]; - + int eeAddress = 0; EEPROM.get(eeAddress, mac); eeAddress += sizeof(mac); EEPROM.get(eeAddress, serial); eeAddress += sizeof(serial); - - Serial.println("Initialize Ethernet with DHCP:"); + if (Ethernet.begin(mac) == 0) { - Serial.println("Failed to configure Ethernet using DHCP"); if (Ethernet.hardwareStatus() == EthernetNoHardware) { - Serial.println("Ethernet shield was not found. Sorry, can't run without hardware. :("); - } else if (Ethernet.linkStatus() == LinkOFF) { - Serial.println("Ethernet cable is not connected."); + Serial.println("ERROR: ethernet shield was not found."); } - // no point in carrying on, so do nothing forevermore: while (true) { delay(1); } } EEPROM.get(eeAddress, config); - + Serial.print("IoT #"); Serial.print(serial); - Serial.println(" started at address:"); + Serial.println(" at address:"); Serial.println(Ethernet.localIP()); Serial.println(); Serial.println("Connecting to:"); @@ -64,43 +68,72 @@ void setup(void) { Serial.print(":"); Serial.println(config.port); - doc["device"] = serial; // FIXME - payload["id"] = serverName; +#if USE_INTERNAL_NTP + timeClient.setPoolServerIP(config.address); +#endif + timeClient.begin(); + if (timeClient.update()) { + NTPValid = true; + } + +#if DEBUG_TO_SERIAL + Serial.println("DEBUG: clock updated via NTP."); +#endif + + json["device"] = 1; // FIXME + // payload["id"] = serverName; } void loop(void) { - - int photocellReading = analogRead(photocellPin); - int tempReading = analogRead(tempPin); + + unsigned int photocellReading = analogRead(photocellPin); + unsigned int tempReading = analogRead(tempPin); float tempVoltage = tempReading * AREF_VOLTAGE / 1024.0; float tempC = (tempVoltage - 0.5) * 100 ; + if (NTPValid) { + json["clock"] = timeClient.getEpochTime(); + } else { + json["clock"] = NULL; // converted into 0 + } payload["light"] = photocellReading; temp["celsius"] = tempC; temp["raw"] = tempReading; temp["volts"] = tempVoltage; - + if (EthernetClient client = client.connect(config.address, config.port)) { client.print("POST "); client.print(URL); client.println(" HTTP/1.1"); client.print("Host: "); - printAddr(config.address, &client); + client.print(config.address); client.print(":"); client.println(config.port); client.println("Content-Type: application/json"); client.print("Content-Length: "); - client.println(measureJsonPretty(doc)); + client.println(measureJsonPretty(json)); client.println("Connection: close"); client.println(); - serializeJson(doc, client); + serializeJson(json, client); client.stop(); - - #if DEBUG_TO_SERIAL - serializeJsonPretty(doc, Serial); - #endif + +#if DEBUG_TO_SERIAL + Serial.println("DEBUG: >>>"); + serializeJsonPretty(json, Serial); + Serial.println("\n<<<"); +#endif + } + + if (counter == 6 * 120) { // Update clock every 6 times * 10 sec * 120 minutes = 2 hrs + timeClient.update(); + counter = 0; +#if DEBUG_TO_SERIAL + Serial.println("DEBUG: clock updated via NTP."); +#endif + } else { + counter++; } delay(postDelay); diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 0293715..d6917b8 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -11,6 +11,14 @@ x-op-service-default: &service_default init: true services: + ntpd: + <<: *service_default + image: daniviga/ntpd + networks: + - net + ports: + - "127.0.0.1:123:123/udp" + timescale: <<: *service_default image: timescale/timescaledb:latest-pg12 diff --git a/docker/ntpd/Dockerfile b/docker/ntpd/Dockerfile new file mode 100644 index 0000000..859f3c6 --- /dev/null +++ b/docker/ntpd/Dockerfile @@ -0,0 +1,8 @@ +FROM alpine:3.9 + +RUN apk update && apk add chrony && \ + chown -R chrony:chrony /var/lib/chrony +COPY ./chrony.conf /etc/chrony/chrony.conf + +EXPOSE 123/udp +ENTRYPOINT ["chronyd", "-d", "-s", "-x"] diff --git a/docker/ntpd/chrony.conf b/docker/ntpd/chrony.conf new file mode 100644 index 0000000..c02db03 --- /dev/null +++ b/docker/ntpd/chrony.conf @@ -0,0 +1,4 @@ +pool pool.ntp.org iburst +initstepslew 10 pool.ntp.org +driftfile /var/lib/chrony/chrony.drift +allow all diff --git a/freedcs/api/admin.py b/freedcs/api/admin.py index b566819..0d27b99 100644 --- a/freedcs/api/admin.py +++ b/freedcs/api/admin.py @@ -5,6 +5,8 @@ from api.models import Device, WhiteList @admin.register(Device) class DeviceAdmin(admin.ModelAdmin): readonly_fields = ('creation_time', 'updated_time',) + list_filter = ('serial',) + search_fields = ('serial',) fieldsets = ( (None, { @@ -20,6 +22,8 @@ class DeviceAdmin(admin.ModelAdmin): @admin.register(WhiteList) class WhiteListAdmin(admin.ModelAdmin): readonly_fields = ('creation_time', 'updated_time',) + list_filter = ('serial',) + search_fields = ('serial',) fieldsets = ( (None, { diff --git a/freedcs/telemetry/admin.py b/freedcs/telemetry/admin.py index 4ab6ccc..6f229f5 100644 --- a/freedcs/telemetry/admin.py +++ b/freedcs/telemetry/admin.py @@ -4,4 +4,7 @@ from telemetry.models import Telemetry @admin.register(Telemetry) class TelemetryAdmin(admin.ModelAdmin): - readonly_fields = ('device', 'time', 'payload',) + readonly_fields = ('device', 'time', 'clock', 'payload',) + list_display = ('__str__', 'device') + list_filter = ('time', 'device__serial') + search_fields = ('device__serial',) diff --git a/freedcs/telemetry/migrations/0005_telemetry_clock.py b/freedcs/telemetry/migrations/0005_telemetry_clock.py new file mode 100644 index 0000000..476e345 --- /dev/null +++ b/freedcs/telemetry/migrations/0005_telemetry_clock.py @@ -0,0 +1,19 @@ +# Generated by Django 3.0.6 on 2020-06-03 13:08 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('telemetry', '0004_auto_20200602_2132'), + ] + + operations = [ + migrations.AddField( + model_name='telemetry', + name='clock', + field=models.IntegerField(null=True, validators=[django.core.validators.MinValueValidator(0)]), + ), + ] diff --git a/freedcs/telemetry/migrations/0006_auto_20200603_1317.py b/freedcs/telemetry/migrations/0006_auto_20200603_1317.py new file mode 100644 index 0000000..6ca695a --- /dev/null +++ b/freedcs/telemetry/migrations/0006_auto_20200603_1317.py @@ -0,0 +1,17 @@ +# Generated by Django 3.0.6 on 2020-06-03 13:17 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('telemetry', '0005_telemetry_clock'), + ] + + operations = [ + migrations.AlterModelOptions( + name='telemetry', + options={'ordering': ['-time', 'device'], 'verbose_name_plural': 'Telemetry'}, + ), + ] diff --git a/freedcs/telemetry/models.py b/freedcs/telemetry/models.py index ce95be9..2c8a04b 100644 --- a/freedcs/telemetry/models.py +++ b/freedcs/telemetry/models.py @@ -1,4 +1,5 @@ from django.db import models +from django.core.validators import MinValueValidator from django.contrib.postgres.fields import JSONField from api.models import Device @@ -7,11 +8,14 @@ from api.models import Device class Telemetry(models.Model): device = models.ForeignKey(Device, on_delete=models.CASCADE) time = models.DateTimeField(primary_key=True, auto_now_add=True) + clock = models.IntegerField( + validators=[MinValueValidator(0)], + null=True) payload = JSONField() class Meta: - ordering = ['time', 'device'] + ordering = ['-time', 'device'] verbose_name_plural = "Telemetry" def __str__(self): - return "%s - %s" % (self.time, self.device.serial) + return str(self.time) diff --git a/freedcs/telemetry/serializers.py b/freedcs/telemetry/serializers.py index 337012d..5bd8ab7 100644 --- a/freedcs/telemetry/serializers.py +++ b/freedcs/telemetry/serializers.py @@ -11,7 +11,7 @@ class TelemetrySerializer(serializers.ModelSerializer): class Meta: model = Telemetry - fields = ('device', 'time', 'payload',) + fields = ('time', 'device', 'clock', 'payload',) # class WhiteListSerializer(serializers.ModelSerializer):