store firmwares and add firmware api stub
This commit is contained in:
parent
a48a3914d3
commit
9e9e41fb3f
5 changed files with 257 additions and 7 deletions
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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")},
|
||||||
|
),
|
||||||
|
]
|
|
@ -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'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue