store firmwares and add firmware api stub

This commit is contained in:
Laura Klünder 2023-11-05 18:47:20 +01:00
parent a48a3914d3
commit 9e9e41fb3f
5 changed files with 257 additions and 7 deletions

View file

@ -17,6 +17,7 @@ from c3nav.mapdata.api import (AccessRestrictionGroupViewSet, AccessRestrictionV
LocationViewSet, MapViewSet, ObstacleViewSet, POIViewSet, RampViewSet, SourceViewSet, LocationViewSet, MapViewSet, ObstacleViewSet, POIViewSet, RampViewSet, SourceViewSet,
SpaceViewSet, StairViewSet, UpdatesViewSet) SpaceViewSet, StairViewSet, UpdatesViewSet)
from c3nav.mapdata.utils.user import can_access_editor from c3nav.mapdata.utils.user import can_access_editor
from c3nav.mesh.api import FirmwareViewSet
from c3nav.routing.api import RoutingViewSet from c3nav.routing.api import RoutingViewSet
router = SimpleRouter() router = SimpleRouter()
@ -53,6 +54,8 @@ router.register(r'editor', EditorViewSet, basename='editor')
router.register(r'changesets', ChangeSetViewSet) router.register(r'changesets', ChangeSetViewSet)
router.register(r'session', SessionViewSet, basename='session') router.register(r'session', SessionViewSet, basename='session')
router.register(r'firmwares', FirmwareViewSet, basename='firmware')
class APIRoot(GenericAPIView): class APIRoot(GenericAPIView):
""" """

View file

@ -0,0 +1,190 @@
# Generated by Django 4.2.1 on 2023-11-05 17:31
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("mesh", "0007_nodemessage_message_type_new"),
]
operations = [
migrations.CreateModel(
name="FirmwareBuild",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"variant",
models.CharField(max_length=64, verbose_name="variant name"),
),
(
"chip",
models.SmallIntegerField(
choices=[(2, "ESP32-S2"), (5, "ESP32-C3")],
db_index=True,
verbose_name="chip",
),
),
(
"sha256_hash",
models.CharField(
max_length=64, unique=True, verbose_name="SHA256 hash"
),
),
(
"binary",
models.FileField(
null=True, upload_to="", verbose_name="firmware file"
),
),
],
),
migrations.CreateModel(
name="FirmwareBuildBoard",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"board",
models.CharField(
choices=[
("CUSTOM", "CUSTOM"),
("ESP32_C3_DEVKIT_M_1", "ESP32-C3-DevKitM-1"),
("ESP32_C3_32S", "ESP32-C3-32S"),
("C3NAV_UWB_BOARD", "c3nav UWB board"),
("C3NAV_LOCATION_PCB_REV_0_1", "c3nav location PCB rev0.1"),
("C3NAV_LOCATION_PCB_REV_0_2", "c3nav location PCB rev0.2"),
],
db_index=True,
max_length=32,
verbose_name="board",
),
),
(
"build",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="mesh.firmwarebuild",
),
),
],
options={
"unique_together": {("build", "board")},
},
),
migrations.CreateModel(
name="FirmwareVersion",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"project_name",
models.CharField(max_length=32, verbose_name="project name"),
),
(
"version",
models.CharField(
max_length=32, unique=True, verbose_name="firmware version"
),
),
(
"idf_version",
models.CharField(max_length=32, verbose_name="IDF version"),
),
(
"created",
models.DateTimeField(
auto_now_add=True, verbose_name="creation/upload date"
),
),
(
"uploader",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to=settings.AUTH_USER_MODEL,
),
),
],
),
migrations.DeleteModel(
name="Firmware",
),
migrations.AlterField(
model_name="nodemessage",
name="message_type",
field=models.CharField(
choices=[
("NOOP", "noop"),
("ECHO_REQUEST", "echo request"),
("ECHO_RESPONSE", "echo response"),
("MESH_SIGNIN", "mesh signin"),
("MESH_LAYER_ANNOUNCE", "mesh layer announce"),
("MESH_ADD_DESTINATIONS", "mesh add destinations"),
("MESH_REMOVE_DESTINATIONS", "mesh remove destinations"),
("MESH_ROUTE_REQUEST", "mesh route request"),
("MESH_ROUTE_RESPONSE", "mesh route response"),
("MESH_ROUTE_TRACE", "mesh route trace"),
("MESH_ROUTING_FAILED", "mesh routing failed"),
("CONFIG_DUMP", "dump config"),
("CONFIG_HARDWARE", "hardware config"),
("CONFIG_BOARD", "board config"),
("CONFIG_FIRMWARE", "firmware config"),
("CONFIG_UPLINK", "uplink config"),
("CONFIG_POSITION", "position config"),
("OTA_STATUS", "ota status"),
("OTA_REQUEST_STATUS", "ota request status"),
("OTA_START", "ota start"),
("OTA_URL", "ota url"),
("OTA_FRAGMENT", "ota fragment"),
("OTA_REQUEST_FRAGMENT", "ota request fragment"),
("OTA_APPLY", "ota apply"),
("OTA_REBOOT", "ota reboot"),
("LOCATE_REQUEST_RANGE", "locate request range"),
("LOCATE_RANGE_RESULTS", "locate range results"),
("LOCATE_RAW_FTM_RESULTS", "locate raw ftm results"),
],
db_index=True,
max_length=24,
verbose_name="message type",
),
),
migrations.AddField(
model_name="firmwarebuild",
name="version",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="builds",
to="mesh.firmwareversion",
),
),
migrations.AlterUniqueTogether(
name="firmwarebuild",
unique_together={("version", "variant")},
),
]

View file

@ -3,9 +3,12 @@ from functools import cached_property
from operator import attrgetter from operator import attrgetter
from typing import Any, Mapping, Self from typing import Any, Mapping, Self
from django.contrib.auth import get_user_model
from django.db import NotSupportedError, models from django.db import NotSupportedError, models
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from c3nav.mesh.dataformats import BoardType
from c3nav.mesh.messages import ChipType from c3nav.mesh.messages import ChipType
from c3nav.mesh.messages import MeshMessage as MeshMessage from c3nav.mesh.messages import MeshMessage as MeshMessage
from c3nav.mesh.messages import MeshMessageType from c3nav.mesh.messages import MeshMessageType
@ -118,16 +121,67 @@ class NodeMessage(models.Model):
return MeshMessage.fromjson(self.data) return MeshMessage.fromjson(self.data)
class Firmware(models.Model): class FirmwareVersion(models.Model):
CHIPS = [(msgtype.value, msgtype.name.replace('_', '-')) for msgtype in ChipType]
chip = models.SmallIntegerField(_('chip'), db_index=True, choices=CHIPS)
project_name = models.CharField(_('project name'), max_length=32) project_name = models.CharField(_('project name'), max_length=32)
version = models.CharField(_('firmware version'), max_length=32) version = models.CharField(_('firmware version'), max_length=32, unique=True)
idf_version = models.CharField(_('IDF version'), max_length=32) idf_version = models.CharField(_('IDF version'), max_length=32)
uploader = models.ForeignKey(get_user_model(), null=True, on_delete=models.SET_NULL)
created = models.DateTimeField(_('creation/upload date'), auto_now_add=True)
def serialize(self):
return {
'project_name': self.project_name,
'version': self.version,
'idf_version': self.idf_version,
'created': self.created.isoformat(),
'builds': {
build.variant: build.serialize()
for build in self.builds.all().prefetch_related("firmwarebuildboard_set")
}
}
def firmware_upload_path(instance, filename):
# file will be uploaded to MEDIA_ROOT/user_<id>/<filename>
version = slugify(instance.version.version)
variant = slugify(instance.variant)
return f"firmware/{version}/{variant}/{filename}"
class FirmwareBuild(models.Model):
CHIPS = [(chiptype.value, chiptype.pretty_name) for chiptype in ChipType]
version = models.ForeignKey(FirmwareVersion, related_name='builds', on_delete=models.CASCADE)
variant = models.CharField(_('variant name'), max_length=64)
chip = models.SmallIntegerField(_('chip'), db_index=True, choices=CHIPS)
sha256_hash = models.CharField(_('SHA256 hash'), unique=True, max_length=64) sha256_hash = models.CharField(_('SHA256 hash'), unique=True, max_length=64)
binary = models.FileField(_('firmware file'), null=True) project_description = models.JSONField
binary = models.FileField(_('firmware file'), null=True, upload_to=firmware_upload_path)
class Meta: class Meta:
unique_together = [ unique_together = [
('chip', 'project_name', 'version', 'idf_version', 'sha256_hash'), ('version', 'variant'),
]
@property
def boards(self):
return [board.board for board in self.firmwarebuildboard_set.all()]
def serialize(self):
return {
'chip': ChipType(self.chip).name,
'sha256_hash': self.sha256_hash,
'url': self.binary.url,
'boards': self.boards,
}
class FirmwareBuildBoard(models.Model):
BOARDS = [(boardtype.name, boardtype.pretty_name) for boardtype in BoardType]
build = models.ForeignKey(FirmwareBuild, on_delete=models.CASCADE)
board = models.CharField(_('board'), max_length=32, db_index=True, choices=BOARDS)
class Meta:
unique_together = [
('build', 'board'),
] ]

View file

@ -43,6 +43,8 @@ TILES_ROOT = os.path.join(DATA_DIR, 'tiles')
CACHE_ROOT = os.path.join(DATA_DIR, 'cache') CACHE_ROOT = os.path.join(DATA_DIR, 'cache')
STATS_ROOT = os.path.join(DATA_DIR, 'stats') STATS_ROOT = os.path.join(DATA_DIR, 'stats')
MEDIA_URL = '/media/'
if not os.path.exists(DATA_DIR): if not os.path.exists(DATA_DIR):
os.mkdir(DATA_DIR) os.mkdir(DATA_DIR)
if not os.path.exists(LOG_DIR): if not os.path.exists(LOG_DIR):

View file

@ -2,6 +2,7 @@ from contextlib import suppress
from channels.routing import URLRouter from channels.routing import URLRouter
from django.conf import settings from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin from django.contrib import admin
from django.urls import include, path from django.urls import include, path
@ -20,7 +21,7 @@ urlpatterns = [
path('control/', include(c3nav.control.urls)), path('control/', include(c3nav.control.urls)),
path('locales/', include('django.conf.urls.i18n')), path('locales/', include('django.conf.urls.i18n')),
path('', include(c3nav.site.urls)), path('', include(c3nav.site.urls)),
] ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
websocket_urlpatterns = [ websocket_urlpatterns = [
path('mesh/', URLRouter(c3nav.mesh.urls.websocket_urlpatterns)), path('mesh/', URLRouter(c3nav.mesh.urls.websocket_urlpatterns)),