Compare commits
6 Commits
0b285b068c
...
bf4817ac5d
Author | SHA1 | Date |
---|---|---|
Dirk Jahnke | bf4817ac5d | |
Dirk Jahnke | b9e9af778e | |
Dirk Jahnke | d0ae1092d9 | |
Dirk Jahnke | 068d13261b | |
Dirk Jahnke | 9247c27278 | |
Dirk Jahnke | 49b79beb18 |
|
@ -11,5 +11,5 @@ router.register(r'products', GtinProductViewSet)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', include(router.urls)),
|
path('', include(router.urls)),
|
||||||
path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
|
path('auth/', include('rest_framework.urls', namespace='rest_framework'))
|
||||||
]
|
]
|
30
api/views.py
30
api/views.py
|
@ -1,6 +1,9 @@
|
||||||
from django.shortcuts import render
|
from rest_framework import viewsets, status
|
||||||
from rest_framework import viewsets
|
|
||||||
from rest_framework import permissions
|
from rest_framework import permissions
|
||||||
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
import asset.gtin_service
|
||||||
from api.serializers import ContainerSerializer, ContainerTypeSerializer, AssetSerializer, GtinProductSerializer
|
from api.serializers import ContainerSerializer, ContainerTypeSerializer, AssetSerializer, GtinProductSerializer
|
||||||
from container.models import Container, ContainerType
|
from container.models import Container, ContainerType
|
||||||
from asset.models import Asset, GtinProduct
|
from asset.models import Asset, GtinProduct
|
||||||
|
@ -36,7 +39,6 @@ class AssetViewSet(viewsets.ModelViewSet):
|
||||||
filterset_fields = ['named_id', 'quantity']
|
filterset_fields = ['named_id', 'quantity']
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class GtinProductViewSet(viewsets.ModelViewSet):
|
class GtinProductViewSet(viewsets.ModelViewSet):
|
||||||
"""
|
"""
|
||||||
API endpoint that allows containers to be viewed or edited.
|
API endpoint that allows containers to be viewed or edited.
|
||||||
|
@ -46,3 +48,25 @@ class GtinProductViewSet(viewsets.ModelViewSet):
|
||||||
permission_classes = [permissions.IsAuthenticated]
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
filterset_fields = ['gtin']
|
filterset_fields = ['gtin']
|
||||||
|
|
||||||
|
def retrieve(self, request, pk=None):
|
||||||
|
if pk:
|
||||||
|
item = GtinProduct.objects.get(id=pk)
|
||||||
|
serializer = GtinProductSerializer(item)
|
||||||
|
return Response({"status": "success", "data": serializer.data}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
items = GtinProduct.objects.all()
|
||||||
|
serializer = GtinProductSerializer(items, many=True)
|
||||||
|
return Response({"status": "success", "data": serializer.data}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@action(detail=True, methods=['GET'])
|
||||||
|
def by_gtin(self, request, gtin=None):
|
||||||
|
if gtin:
|
||||||
|
item = asset.gtin_service.get_by_gtin(gtin=gtin)
|
||||||
|
if item:
|
||||||
|
serializer = GtinProductSerializer(item)
|
||||||
|
return Response({"status": "success", "data": serializer.data}, status=status.HTTP_200_OK)
|
||||||
|
return Response(
|
||||||
|
{"status": "failed", "reason": "Could not find a product for given GTIN", "gtin": gtin},
|
||||||
|
status=status.HTTP_404_NOT_FOUND)
|
||||||
|
return Response({"status": "failed", "reason": "No GTIN given, please provide a valid GTIN"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
|
@ -0,0 +1,226 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block additional_css %}
|
||||||
|
<link rel="preload" as="style" onload="this.rel='stylesheet';this.onload=null" href="https://unpkg.com/normalize.css@8.0.0/normalize.css">
|
||||||
|
<link rel="preload" as="style" onload="this.rel='stylesheet';this.onload=null" href="https://unpkg.com/milligram@1.3.0/dist/milligram.min.css">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block title %}Smartphone Scanner{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container" >
|
||||||
|
|
||||||
|
<div class="row-mb8" id="video-button-content">
|
||||||
|
<a class="button" id="startButton">Start</a>
|
||||||
|
<a class="button-outline" id="resetButton">Reset</a>
|
||||||
|
</div>
|
||||||
|
<div class="row" id="video-content">
|
||||||
|
<div class="col-6">
|
||||||
|
<video id="video" width="450" height="320" style="border: 1px solid gray"></video>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-2" id="sourceSelectPanel" style="display:none">
|
||||||
|
<label for="sourceSelect">Change video source:</label>
|
||||||
|
<select id="sourceSelect" style="max-width:400px">
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-2">
|
||||||
|
<label>Result:</label>
|
||||||
|
<pre><code id="result"></code></pre>
|
||||||
|
<ul id="barcode-list"></ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
function beep() {
|
||||||
|
var audio = new Audio("{% static 'sound/beep.mp3' %}");
|
||||||
|
audio.volume = 0.1
|
||||||
|
console.log("Audio play {% static 'sound/beep.mp3' %}")
|
||||||
|
audio.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
// check compatibility
|
||||||
|
if (!('BarcodeDetector' in window)) {
|
||||||
|
console.log('Barcode Detector is not supported by this browser.');
|
||||||
|
} else {
|
||||||
|
console.log('Barcode Detector supported!');
|
||||||
|
|
||||||
|
// create new detector
|
||||||
|
// var barcodeDetector = new BarcodeDetector({formats: ['code_128', 'code_39', 'code_93', 'codabar', 'data_matrix', 'ean_13', 'ean_8', 'qr_code', 'upc_a', 'upc_e']});
|
||||||
|
var barcodeDetector = new BarcodeDetector({formats: ['code_128', 'ean_13']});
|
||||||
|
console.log('Supported code formats:');
|
||||||
|
BarcodeDetector.getSupportedFormats().then(supportedFormats => {
|
||||||
|
supportedFormats.forEach(format => console.log(format));
|
||||||
|
});
|
||||||
|
console.log('end-of-format-list');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function detect() {
|
||||||
|
const mediaStream = await navigator.mediaDevices.getUserMedia({
|
||||||
|
video: {facingMode: 'environment'}
|
||||||
|
});
|
||||||
|
const video = document.getElementById('video');
|
||||||
|
// const video = document.createElement('video');
|
||||||
|
let itemsFound = [];
|
||||||
|
|
||||||
|
video.srcObject = mediaStream;
|
||||||
|
video.autoplay = true;
|
||||||
|
const list = document.getElementById('barcode-list');
|
||||||
|
// list.before(video);
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
barcodeDetector
|
||||||
|
.detect(video)
|
||||||
|
.then(barcodes => {
|
||||||
|
barcodes.forEach(barcode => {
|
||||||
|
if (!itemsFound.includes(barcode.rawValue)) {
|
||||||
|
itemsFound.push(barcode.rawValue);
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.innerHTML = barcode.rawValue;
|
||||||
|
list.appendChild(li);
|
||||||
|
console.log('Got scan ' + barcode.rawValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// beep();
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
(function renderLoop() {
|
||||||
|
requestAnimationFrame(renderLoop);
|
||||||
|
render();
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('load', function () {
|
||||||
|
detect();
|
||||||
|
document.getElementById('resetButton').addEventListener('click', () => {
|
||||||
|
document.getElementById('result').textContent = '';
|
||||||
|
codeReader.reset();
|
||||||
|
console.log('Reset.')
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-8">
|
||||||
|
<div class="input-group">
|
||||||
|
<input class="form-control" type="text" maxlength="40" id="container_id" placeholder="Container ID">
|
||||||
|
<input class="form-control" type="text" maxlength="40" id="asset_id" placeholder="Asset ID">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<div class="input-group">
|
||||||
|
<input class="btn btn-primary" id="book-in-button" type="button" value="+Book">
|
||||||
|
<input class="btn btn-primary" id="book-out-button" type="button" value="-Book">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-8">
|
||||||
|
<div class="input-group">
|
||||||
|
<textarea class="form-control" readonly="readonly" rows="4" id="container_details"></textarea>
|
||||||
|
<textarea class="form-control" readonly="readonly" rows="4" id="asset_details"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="input-group">
|
||||||
|
<textarea class="form-control mb-2" id="chat-log" cols="100" rows="10"></textarea>
|
||||||
|
<input class="btn btn-secondary" id="clear-chat-area" type="button" value="Clear">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
var containerIsValid = false;
|
||||||
|
var assetIsValid = false;
|
||||||
|
|
||||||
|
async function getContainerInfo(containerId) {
|
||||||
|
document.querySelector('#container_id').value = containerId;
|
||||||
|
url = '/api/containers?named_id=' + containerId;
|
||||||
|
document.querySelector('#container_details').value = 'Trying ' + url;
|
||||||
|
const response = await fetch(url);
|
||||||
|
const container = await response.json();
|
||||||
|
if (container.count >= 1) {
|
||||||
|
document.querySelector('#container_details').value = container.results[0].description;
|
||||||
|
containerIsValid = true;
|
||||||
|
} else {
|
||||||
|
document.querySelector('#container_details').value = document.querySelector('#container_details').value + "\nERROR: No result\n";
|
||||||
|
console.log(response);
|
||||||
|
containerIsValid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAssetInfo(assetId) {
|
||||||
|
document.querySelector('#asset_id').value = assetId;
|
||||||
|
url = '/api/assets?named_id=' + assetId;
|
||||||
|
document.querySelector('#asset_details').value = 'Trying ' + url;
|
||||||
|
const response = await fetch(url);
|
||||||
|
const asset = await response.json();
|
||||||
|
if (asset.count >= 1) {
|
||||||
|
document.querySelector('#asset_details').value = asset.results[0].description;
|
||||||
|
assetIsValid = true;
|
||||||
|
} else {
|
||||||
|
document.querySelector('#asset_details').value = document.querySelector('#asset_details').value + "\nERROR: No result\n";
|
||||||
|
console.log(response);
|
||||||
|
assetIsValid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function useScannedText(msg) {
|
||||||
|
// const data = JSON.parse(e.data);
|
||||||
|
// msg = String(data.message);
|
||||||
|
document.querySelector('#chat-log').value += (msg + '\n');
|
||||||
|
if (msg.startsWith('C-')) {
|
||||||
|
getContainerInfo(data.message);
|
||||||
|
// document.querySelector('#container_id').value = msg;
|
||||||
|
} else if (msg.startsWith('A-')) {
|
||||||
|
getAssetInfo(data.message);
|
||||||
|
// document.querySelector('#asset_id').value = msg;
|
||||||
|
} else {
|
||||||
|
assetId = 'A-' + msg;
|
||||||
|
document.querySelector('#asset_id').value = assetId;
|
||||||
|
getAssetInfo(assetId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.querySelector('#clear-chat-area').onclick = function(e) {
|
||||||
|
document.querySelector('#chat-log').value = "cleared\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelector('#book-in-button').onclick = function(e) {
|
||||||
|
if (!containerIsValid) {
|
||||||
|
msg = 'Cannot book-in as container is invalid';
|
||||||
|
console.log(msg);
|
||||||
|
document.querySelector('#chat-log').value += ('ERROR: ' + msg + '\n');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!assetIsValid) {
|
||||||
|
msg = 'Cannot book-in as asset is invalid';
|
||||||
|
console.log(msg);
|
||||||
|
document.querySelector('#chat-log').value += ('ERROR: ' + msg + '\n');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelector('#book-out-button').onclick = function(e) {
|
||||||
|
if (!containerIsValid) {
|
||||||
|
msg = 'Cannot book-out as container is invalid';
|
||||||
|
console.log(msg);
|
||||||
|
document.querySelector('#chat-log').value += ('ERROR: ' + msg + '\n');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!assetIsValid) {
|
||||||
|
msg = 'Cannot book-out as asset is invalid';
|
||||||
|
console.log(msg);
|
||||||
|
document.querySelector('#chat-log').value += ('ERROR: ' + msg + '\n');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -56,7 +56,8 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<h1>Use your smartphone</h1>
|
<h1>Use your smartphone</h1>
|
||||||
<p>Use your <a class="navbar-brand" href="{% url 'booker:smartphone_scanner' %}">smartphone</a> to scan.</p>
|
<p>Use your <a href="{% url 'booker:smartphone_scanner' %}">smartphone</a> to scan.</p>
|
||||||
|
<p>Use your <a href="{% url 'booker:chrome_scanner' %}">chrome Barcode-Scanner to scan.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -2,118 +2,223 @@
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
{% block additional_css %}
|
{% block additional_css %}
|
||||||
<link rel="preload" as="style" onload="this.rel='stylesheet';this.onload=null" href="https://unpkg.com/normalize.css@8.0.0/normalize.css">
|
|
||||||
<link rel="preload" as="style" onload="this.rel='stylesheet';this.onload=null" href="https://unpkg.com/milligram@1.3.0/dist/milligram.min.css">
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block title %}Smartphone Scanner{% endblock %}
|
{% block title %}Smartphone Scanner{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container" >
|
<style>
|
||||||
|
#interactive canvas { position: absolute; left: 0; }
|
||||||
|
</style>
|
||||||
|
|
||||||
<div class="row-mb8" id="video-button-content">
|
<div class="container" >
|
||||||
<a class="button" id="startButton">Start</a>
|
<div class="row" id="video-button-content">
|
||||||
<a class="button-outline" id="resetButton">Reset</a>
|
<div class="col-1">
|
||||||
|
<a class="btn btn-primary" id="startButton">Start</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-1">
|
||||||
|
<a class="btn btn-outline-primary" id="resetButton">Reset</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-1">
|
||||||
|
<a class="btn btn-outline-primary" id="clearOverlayButton">Clear</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row" id="video-content">
|
<div class="row" id="video-content">
|
||||||
<div class="col-6">
|
<div id="interactive" class="viewport col-12" style="position: relative;">
|
||||||
<video id="video" width="450" height="320" style="border: 1px solid gray"></video>
|
<!-- QuaggaJS will populate this element -->
|
||||||
</div>
|
</div>
|
||||||
|
<!--
|
||||||
<div class="col-2" id="sourceSelectPanel" style="display:none">
|
<div class="col-8">
|
||||||
<label for="sourceSelect">Change video source:</label>
|
<video id="video" autoplay="true" width="640" height="480" style="border: 1px solid gray"></video>
|
||||||
<select id="sourceSelect" style="max-width:400px">
|
</div>-->
|
||||||
</select>
|
</div>
|
||||||
|
<div class="row" id="control-content">
|
||||||
|
<div class="col-6" id="sourceSelectPanel" style="display:none">
|
||||||
|
<div>
|
||||||
|
<label for="sourceSelect">Change video source:</label>
|
||||||
|
<select id="sourceSelect" style="max-width:400px"></select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-6" id="scan-result-panel" style="display:none">
|
||||||
<div class="col-2">
|
<div>
|
||||||
<label>Result:</label>
|
<label>Scan:</label>
|
||||||
<pre><code id="result"></code></pre>
|
<pre><code id="result"></code></pre>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="text/javascript" src="https://unpkg.com/@zxing/library@latest"></script>
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript" src="{% static 'quagga/dist/quagga.js' %}"></script>
|
||||||
function beep() {
|
<script type="text/javascript">
|
||||||
var audio = new Audio("{% static 'sound/beep.mp3' %}");
|
function beep() {
|
||||||
audio.volume = 0.1
|
var audio = new Audio("{% static 'sound/beep.mp3' %}");
|
||||||
console.log("Audio play {% static 'sound/beep.mp3' %}")
|
audio.volume = 0.02;
|
||||||
audio.play();
|
console.log("Audio play {% static 'sound/beep.mp3' %}");
|
||||||
|
audio.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearCanvas() {
|
||||||
|
canvas = document.querySelector('#interactive .drawingBuffer');
|
||||||
|
drawing = (canvas != null) ? canvas.getContext('2d') : null;
|
||||||
|
console.log('Clearing canvas, drawing=' + drawing + ', canvas=' + canvas);
|
||||||
|
if (drawing != null) {
|
||||||
|
drawing.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
console.log('Clearing done');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('detected HW concurrency: ' + self.navigator.hardwareConcurrency);
|
||||||
|
|
||||||
|
window.addEventListener('load', function () {
|
||||||
|
let selectedDeviceId;
|
||||||
|
let lastScanResult = '';
|
||||||
|
let config = {
|
||||||
|
inputStream: {
|
||||||
|
name: "Live",
|
||||||
|
type: "LiveStream",
|
||||||
|
constraints: {
|
||||||
|
facingMode: "environment",
|
||||||
|
},
|
||||||
|
|
||||||
|
target: document.getElementById('interactive'),
|
||||||
|
/*area: {
|
||||||
|
top: '0%',
|
||||||
|
bottom: '0%',
|
||||||
|
left: '0%',
|
||||||
|
right: '0%'
|
||||||
|
},*/
|
||||||
|
},
|
||||||
|
//numOfWorkers: 4,
|
||||||
|
locate: true,
|
||||||
|
debug: true,
|
||||||
|
frequency: 20,
|
||||||
|
decoder: {
|
||||||
|
readers: ["code_128_reader"],
|
||||||
|
debug: {
|
||||||
|
drawScanline: false,
|
||||||
|
drawBoundingBox: false,
|
||||||
|
showPattern: false,
|
||||||
|
showFrequency: false
|
||||||
|
},
|
||||||
|
multiple: false
|
||||||
|
},
|
||||||
|
locator: {
|
||||||
|
halfSample: false,
|
||||||
|
patchSize: "x-large", // x-small, small, medium, large, x-large
|
||||||
|
debug: {
|
||||||
|
/*showCanvas: true,*/
|
||||||
|
showPatches: false,
|
||||||
|
showFoundPatches: false,
|
||||||
|
showSkeleton: false,
|
||||||
|
showLabels: false,
|
||||||
|
showPatchLabels: false,
|
||||||
|
showRemainingPatchLabels: false,
|
||||||
|
boxFromPatches: {
|
||||||
|
showTransformed: false,
|
||||||
|
showTransformedBox: false,
|
||||||
|
showBB: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function quagga_init() {
|
||||||
|
Quagga.init(config, function(err) {
|
||||||
|
if (err) {
|
||||||
|
console.log(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("Initialization finished. Ready to start");
|
||||||
|
Quagga.start();
|
||||||
|
Quagga.onDetected(function(data) {
|
||||||
|
code = data.codeResult.code;
|
||||||
|
console.log('Got scan: ' + code);
|
||||||
|
if (code && lastScanResult !== code) {
|
||||||
|
lastScanResult = code;
|
||||||
|
console.log('Got scan (cont.): ' + lastScanResult);
|
||||||
|
document.querySelector('#result').textContent = lastScanResult;
|
||||||
|
// document.getElementById('result').textContent = lastScanResult
|
||||||
|
beep();
|
||||||
|
useScannedText(lastScanResult);
|
||||||
|
console.log(data);
|
||||||
|
canvas = document.querySelector('#interactive .drawingBuffer');
|
||||||
|
drawing = canvas.getContext('2d');
|
||||||
|
drawing.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
drawing.beginPath();
|
||||||
|
drawing.moveTo(data.line[0].x, data.line[0].y);
|
||||||
|
drawing.lineTo(data.line[1].x, data.line[1].y);
|
||||||
|
drawing.lineWidth = 2;
|
||||||
|
drawing.strokeStyle = 'yellow';
|
||||||
|
drawing.closePath();
|
||||||
|
drawing.stroke();
|
||||||
|
setTimeout(clearCanvas, 3000);
|
||||||
|
} else if (code) {
|
||||||
|
console.log('... scan again');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
/*
|
||||||
|
Quagga.onProcessed(function(data) {
|
||||||
|
canvas = document.querySelector('#interactive .drawingBuffer');
|
||||||
|
if (canvas != null) {
|
||||||
|
context = canvas.getContext('2d');
|
||||||
|
if (context) {
|
||||||
|
context.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});*/
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('load', function () {
|
quagga_init();
|
||||||
let selectedDeviceId;
|
|
||||||
let lastScanResult = '';
|
|
||||||
// const codeReader = new ZXing.BrowserBarcodeReader()
|
|
||||||
const codeReader = new ZXing.BrowserMultiFormatReader()
|
|
||||||
console.log('ZXing code reader initialized')
|
|
||||||
codeReader.getVideoInputDevices()
|
|
||||||
.then((videoInputDevices) => {
|
|
||||||
const sourceSelect = document.getElementById('sourceSelect')
|
|
||||||
selectedDeviceId = videoInputDevices[0].deviceId
|
|
||||||
if (videoInputDevices.length > 1) {
|
|
||||||
videoInputDevices.forEach((element) => {
|
|
||||||
const sourceOption = document.createElement('option')
|
|
||||||
sourceOption.text = element.label
|
|
||||||
sourceOption.value = element.deviceId
|
|
||||||
sourceSelect.appendChild(sourceOption)
|
|
||||||
})
|
|
||||||
|
|
||||||
sourceSelect.onchange = () => {
|
if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
|
||||||
selectedDeviceId = sourceSelect.value;
|
console.log("enumerateDevices() not supported.");
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
const sourceSelectPanel = document.getElementById('sourceSelectPanel')
|
Quagga.CameraAccess.enumerateVideoDevices()
|
||||||
sourceSelectPanel.style.display = 'block'
|
.then(function(devices) {
|
||||||
|
const sourceSelect = document.getElementById('sourceSelect');
|
||||||
|
selectedDeviceId = devices[0].deviceId; // default
|
||||||
|
if (devices.length > 1) {
|
||||||
|
devices.forEach(function(device) {
|
||||||
|
console.log(device.kind + ": " + device.label + " id=" + device.deviceId);
|
||||||
|
const sourceOption = document.createElement('option');
|
||||||
|
sourceOption.text = device.label;
|
||||||
|
sourceOption.value = device.deviceId;
|
||||||
|
sourceSelect.appendChild(sourceOption);
|
||||||
|
});
|
||||||
|
sourceSelect.onchange = () => {
|
||||||
|
selectedDeviceId = sourceSelect.value;
|
||||||
|
config.inputStream.constraints.deviceId = selectedDeviceId;
|
||||||
|
Quagga.stop();
|
||||||
|
quagga_init();
|
||||||
}
|
}
|
||||||
|
const sourceSelectPanel = document.getElementById('sourceSelectPanel');
|
||||||
document.getElementById('startButton').addEventListener('click', () => {
|
sourceSelectPanel.style.display = 'block';
|
||||||
beep()
|
}
|
||||||
codeReader.decodeFromInputVideoDeviceContinuously(selectedDeviceId, 'video', (result) => {
|
document.getElementById('startButton').addEventListener('click', () => {
|
||||||
if (result && lastScanResult !== result.text) {
|
beep();
|
||||||
lastScanResult = result.text
|
Quagga.start();
|
||||||
console.log('Got scan (cont.): ' + result)
|
console.log(`Started scan from camera with id ${selectedDeviceId}`)
|
||||||
document.getElementById('result').textContent = result.text
|
|
||||||
beep()
|
|
||||||
useScannedText(result.text)
|
|
||||||
} else if (result) {
|
|
||||||
console.log('... scan again')
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
codeReader.decodeOnceFromVideoDevice(selectedDeviceId, 'video').then((result) => {
|
|
||||||
if (lastScanResult !== result.text) {
|
|
||||||
lastScanResult = result.text
|
|
||||||
console.log('Got scan: ' + result.text)
|
|
||||||
document.getElementById('result').textContent = result.text
|
|
||||||
useScannedText(result.text)
|
|
||||||
} else {
|
|
||||||
console.log('... scan again')
|
|
||||||
}
|
|
||||||
}).catch((err) => {
|
|
||||||
console.error(err)
|
|
||||||
document.getElementById('result').textContent = err
|
|
||||||
})
|
|
||||||
*/
|
|
||||||
console.log(`Started continous decode from camera with id ${selectedDeviceId}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
document.getElementById('resetButton').addEventListener('click', () => {
|
|
||||||
document.getElementById('result').textContent = '';
|
|
||||||
codeReader.reset();
|
|
||||||
console.log('Reset.')
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
|
||||||
console.error(err)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
document.getElementById('resetButton').addEventListener('click', () => {
|
||||||
|
document.getElementById('result').textContent = '';
|
||||||
|
Quagga.stop();
|
||||||
|
console.log('Reset/stopped.')
|
||||||
|
})
|
||||||
|
|
||||||
|
document.getElementById('clearOverlayButton').addEventListener('click', () => {
|
||||||
|
clearCanvas();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
<hr />
|
<hr />
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-8">
|
<div class="col-8">
|
||||||
|
@ -145,14 +250,22 @@
|
||||||
var containerIsValid = false;
|
var containerIsValid = false;
|
||||||
var assetIsValid = false;
|
var assetIsValid = false;
|
||||||
|
|
||||||
|
async function getObjectInfo(url) {
|
||||||
|
const response = await fetch(url);
|
||||||
|
const object_json = response.json();
|
||||||
|
return object_json;
|
||||||
|
}
|
||||||
|
|
||||||
async function getContainerInfo(containerId) {
|
async function getContainerInfo(containerId) {
|
||||||
document.querySelector('#container_id').value = containerId;
|
document.querySelector('#container_id').value = containerId;
|
||||||
url = '/api/containers?named_id=' + containerId;
|
url = '/api/containers?named_id=' + containerId;
|
||||||
document.querySelector('#container_details').value = 'Trying ' + url;
|
document.querySelector('#container_details').value = 'Trying ' + url;
|
||||||
const response = await fetch(url);
|
const container = await getObjectInfo(url);
|
||||||
const container = await response.json();
|
|
||||||
if (container.count >= 1) {
|
if (container.count >= 1) {
|
||||||
document.querySelector('#container_details').value = container.results[0].description;
|
c = container.results[0];
|
||||||
|
console.log(c);
|
||||||
|
containerType = await getObjectInfo(c.container_type);
|
||||||
|
document.querySelector('#container_details').value = c.description + "\n" + c.named_id + ': ' + c.color + "\n" + containerType.description;
|
||||||
containerIsValid = true;
|
containerIsValid = true;
|
||||||
} else {
|
} else {
|
||||||
document.querySelector('#container_details').value = document.querySelector('#container_details').value + "\nERROR: No result\n";
|
document.querySelector('#container_details').value = document.querySelector('#container_details').value + "\nERROR: No result\n";
|
||||||
|
@ -178,15 +291,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function useScannedText(msg) {
|
function useScannedText(msg) {
|
||||||
// const data = JSON.parse(e.data);
|
|
||||||
// msg = String(data.message);
|
|
||||||
document.querySelector('#chat-log').value += (msg + '\n');
|
document.querySelector('#chat-log').value += (msg + '\n');
|
||||||
if (msg.startsWith('C-')) {
|
if (msg.startsWith('C-')) {
|
||||||
getContainerInfo(data.message);
|
getContainerInfo(msg);
|
||||||
// document.querySelector('#container_id').value = msg;
|
|
||||||
} else if (msg.startsWith('A-')) {
|
} else if (msg.startsWith('A-')) {
|
||||||
getAssetInfo(data.message);
|
getAssetInfo(msg);
|
||||||
// document.querySelector('#asset_id').value = msg;
|
|
||||||
} else {
|
} else {
|
||||||
assetId = 'A-' + msg;
|
assetId = 'A-' + msg;
|
||||||
document.querySelector('#asset_id').value = assetId;
|
document.querySelector('#asset_id').value = assetId;
|
||||||
|
|
|
@ -6,5 +6,7 @@ app_name = 'booker'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.IndexView.as_view(), name='index'),
|
path('', views.IndexView.as_view(), name='index'),
|
||||||
path('smartphone_scanner/', views.SmartphoneScannerView.as_view(), name='smartphone_scanner'),
|
path('smartphone_scanner/', views.SmartphoneScannerView.as_view(), name='smartphone_scanner'),
|
||||||
path('<str:scanner_id>/', views.scanner, name='scanner')
|
path('chrome_scanner/', views.ChromeScannerView.as_view(), name='chrome_scanner'),
|
||||||
|
path('<str:scanner_id>/', views.scanner_view, name='scanner'),
|
||||||
|
path('webscanner/<str:scanner_id>/', views.web_scanner_view, name='web_scanner'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,6 +1,18 @@
|
||||||
|
from datetime import datetime
|
||||||
|
from channels.layers import get_channel_layer
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.views.generic import ListView, TemplateView
|
from django.views.generic import ListView, TemplateView
|
||||||
|
from gmqtt import Client
|
||||||
|
from rest_framework.utils import json
|
||||||
|
|
||||||
|
from homelog import settings
|
||||||
|
from homelog.system_user import get_system_user
|
||||||
from .models import Scanner
|
from .models import Scanner
|
||||||
|
from django.http import HttpResponse
|
||||||
|
import logging
|
||||||
|
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class IndexView(ListView):
|
class IndexView(ListView):
|
||||||
|
@ -8,10 +20,53 @@ class IndexView(ListView):
|
||||||
template_name = 'booker/index.html'
|
template_name = 'booker/index.html'
|
||||||
|
|
||||||
|
|
||||||
def scanner(request, scanner_id):
|
def scanner_view(request, scanner_id):
|
||||||
return render(request, 'booker/scanner.html', {
|
return render(request, 'booker/scanner.html', {
|
||||||
'scanner_id': scanner_id
|
'scanner_id': scanner_id
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
class SmartphoneScannerView(TemplateView):
|
class SmartphoneScannerView(TemplateView):
|
||||||
template_name = 'booker/smartphone_scanner.html'
|
template_name = 'booker/smartphone_scanner.html'
|
||||||
|
|
||||||
|
|
||||||
|
class ChromeScannerView(TemplateView):
|
||||||
|
template_name = 'booker/chrome_scanner.html'
|
||||||
|
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
|
def web_scanner_view(request, scanner_id):
|
||||||
|
def get_or_create_scanner(named_id):
|
||||||
|
scanner, created = Scanner.objects.get_or_create(named_id=named_id, defaults={
|
||||||
|
# 'lwt_topic': topic,
|
||||||
|
'description': 'WebAPI scanner',
|
||||||
|
'last_online_ts': datetime.now(),
|
||||||
|
'created_by': get_system_user(),
|
||||||
|
'changed_by': get_system_user()
|
||||||
|
})
|
||||||
|
if created:
|
||||||
|
LOGGER.debug(f"Created new scanner entry for {named_id}")
|
||||||
|
else:
|
||||||
|
LOGGER.debug(f"Updated scanner entry for {named_id}")
|
||||||
|
return scanner
|
||||||
|
|
||||||
|
print(f"Got {request.POST['scan']} from scanner {scanner_id}")
|
||||||
|
get_or_create_scanner(scanner_id)
|
||||||
|
topic = f"barcodescanner/{scanner_id}/scan"
|
||||||
|
'''
|
||||||
|
client = Client('homelog-webscanner')
|
||||||
|
client.set_auth_credentials(username=settings.MQTT_USER, password=settings.MQTT_PASSWORD)
|
||||||
|
|
||||||
|
client.connect(settings.MQTT_HOST, 1883, keepalive=60, version=settings.MQTT_VERSION)
|
||||||
|
client.publish(topic, request.POST['scan'])
|
||||||
|
client.disconnect()
|
||||||
|
# MqttConsumer().mqtt_publish({'topic': topic, 'payload': request.POST['scan'], 'retain': False})
|
||||||
|
'''
|
||||||
|
channel_layer = get_channel_layer()
|
||||||
|
channel_layer.group_send(
|
||||||
|
'mqtt', # scanner_id,
|
||||||
|
{"type": "distribute", "text": json.dumps({'message': request.POST['scan'], 'topic': topic})}
|
||||||
|
)
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
return HttpResponse(f"<html><body><p>OK, got {request.POST['scan']} from {scanner_id}</p></body></html>")
|
||||||
|
|
|
@ -26,7 +26,7 @@ SECRET_KEY = 'django-insecure-!&87^_vnfn^am1f!w7-a)rrxcx(&5i7)h=bcl4)$8882c2-$na
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
ALLOWED_HOSTS = []
|
ALLOWED_HOSTS = ['192.168.89.45', '127.0.0.1', 'localhost']
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
@ -38,6 +38,7 @@ INSTALLED_APPS = [
|
||||||
'booker',
|
'booker',
|
||||||
'api',
|
'api',
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
|
'django_filters',
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
|
@ -186,5 +187,6 @@ REST_FRAMEWORK = {
|
||||||
'DEFAULT_RENDERER_CLASSES': [
|
'DEFAULT_RENDERER_CLASSES': [
|
||||||
'rest_framework.renderers.JSONRenderer',
|
'rest_framework.renderers.JSONRenderer',
|
||||||
'rest_framework.renderers.BrowsableAPIRenderer',
|
'rest_framework.renderers.BrowsableAPIRenderer',
|
||||||
]
|
],
|
||||||
|
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{% load static i18n %}<!DOCTYPE html>
|
{% load static i18n %}<!DOCTYPE html>
|
||||||
{% get_current_language as LANGUAGE_CODE %}
|
{% get_current_language as LANGUAGE_CODE %}
|
||||||
<html lang="{{ LANGUAGE_CODE }}">
|
<html lang="{{ LANGUAGE_CODE }}" xmlns="http://www.w3.org/1999/html">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
{% block css %}
|
{% block css %}
|
||||||
<!-- Latest compiled and minified Bootstrap CSS -->
|
<!-- Latest compiled and minified Bootstrap CSS -->
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/css/bootstrap.min.css" integrity="sha512-GQGU0fMMi238uA+a/bdWJfpUGKUkBdgfFdgBm72SUQ6BeyWjoY/ton0tEjH+OSH9iP4Dfh+7HM0I9f5eR0L/4w==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
<link rel="stylesheet" href="{% static 'bootstrap/dist/css/bootstrap.min.css' %}" />
|
||||||
|
|
||||||
<!-- Your stuff: Third-party CSS libraries go here -->
|
<!-- Your stuff: Third-party CSS libraries go here -->
|
||||||
<!-- This file stores project-specific CSS -->
|
<!-- This file stores project-specific CSS -->
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
{# Placed at the top of the document so pages load faster with defer #}
|
{# Placed at the top of the document so pages load faster with defer #}
|
||||||
{% block javascript %}
|
{% block javascript %}
|
||||||
<!-- Bootstrap JS -->
|
<!-- Bootstrap JS -->
|
||||||
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/js/bootstrap.min.js" integrity="sha512-OvBgP9A2JBgiRad/mM36mkzXSXaJE9BEIENnVEmeZdITvwT09xnxLtT4twkCa8m/loMbPHsvPl0T8lRGVBwjlQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
<script defer src="{% static 'bootstrap/dist/js/bootstrap.min.js' %}" ></script>
|
||||||
<!-- Your stuff: Third-party javascript libraries go here -->
|
<!-- Your stuff: Third-party javascript libraries go here -->
|
||||||
|
|
||||||
<!-- place project specific Javascript in this file -->
|
<!-- place project specific Javascript in this file -->
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"name": "homelog",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Home Logistics -- manage assets and containers and their locations",
|
||||||
|
"main": "index.js",
|
||||||
|
"directories": {
|
||||||
|
"doc": "doc"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "Dirk Jahnke",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"bootstrap": "^5.1.3",
|
||||||
|
"quagga": "^0.12.1"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
asgiref==3.5.2
|
||||||
|
attrs==21.4.0
|
||||||
|
autobahn==22.4.2
|
||||||
|
Automat==20.2.0
|
||||||
|
cffi==1.15.0
|
||||||
|
channels==3.0.4
|
||||||
|
constantly==15.1.0
|
||||||
|
cryptography==37.0.2
|
||||||
|
daphne==3.0.2
|
||||||
|
Django==4.0.4
|
||||||
|
django-filter==21.1
|
||||||
|
django-npm==1.0.0
|
||||||
|
djangorestframework==3.13.1
|
||||||
|
hyperlink==21.0.0
|
||||||
|
idna==3.3
|
||||||
|
incremental==21.3.0
|
||||||
|
pyasn1==0.4.8
|
||||||
|
pyasn1-modules==0.2.8
|
||||||
|
pycparser==2.21
|
||||||
|
pyOpenSSL==22.0.0
|
||||||
|
pytz==2022.1
|
||||||
|
service-identity==21.1.0
|
||||||
|
six==1.16.0
|
||||||
|
sqlparse==0.4.2
|
||||||
|
Twisted==22.4.0
|
||||||
|
txaio==22.2.1
|
||||||
|
typing_extensions==4.2.0
|
||||||
|
zope.interface==5.4.0
|
Loading…
Reference in New Issue