Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
GGCE
GGCE Web
Commits
6d568a8f
Commit
6d568a8f
authored
May 26, 2022
by
Matija Obreza
Browse files
BT: Electronic balance reader
Use Bluetooth-enabled electronic balances to capture weights directly into GGCE!
parent
1fd641e4
Changes
4
Hide whitespace changes
Inline
Side-by-side
workspaces/ui-express/locales/en/express.json
View file @
6d568a8f
...
...
@@ -539,6 +539,14 @@
"loading"
:
"Loading inventory data"
,
"scheduleInventoryAction"
:
"Schedule inventory action"
},
"balance"
:
{
"deviceSelected"
:
"Bluetooth device {{name}} selected."
,
"connecting"
:
"Connecting {{name}}..."
,
"connected"
:
"Connected to {{name}}."
,
"disconnecting"
:
"Disconnecting from {{name}}..."
,
"disconnected"
:
"Disconnected."
,
"error"
:
"Bluetooth error: {{message}}"
},
"public"
:
{
"p"
:
{
"actions"
:
{
...
...
workspaces/ui-express/src/inventory/translations.json
View file @
6d568a8f
...
...
@@ -16,6 +16,14 @@
"loading"
:
"Loading inventory data"
,
"scheduleInventoryAction"
:
"Schedule inventory action"
},
"balance"
:
{
"deviceSelected"
:
"Bluetooth device {{name}} selected."
,
"connecting"
:
"Connecting {{name}}..."
,
"connected"
:
"Connected to {{name}}."
,
"disconnecting"
:
"Disconnecting from {{name}}..."
,
"disconnected"
:
"Disconnected."
,
"error"
:
"Bluetooth error: {{message}}"
},
"public"
:
{
"p"
:
{
"actions"
:
{
...
...
workspaces/ui-express/src/inventory/ui/AdjustQuantity.tsx
View file @
6d568a8f
...
...
@@ -26,6 +26,7 @@ import { withUrlNavigation } from '_core/routing/withPageNavigation';
import
{
compose
}
from
'
redux
'
;
import
BarcodeScannerButton
from
'
ui/barcode/BarcodeScanner
'
;
import
{
Accession
}
from
'
@gringlobal-ce/client/model/gringlobal
'
;
import
BalanceReader
from
'
./c/BalanceReader
'
;
export
const
InventoryTableConfig
=
new
TableConfiguration
({
defaultColumns
:
[
...
...
@@ -78,9 +79,15 @@ const calculator = createDecorator(
},
);
const
Condition
=
({
when
,
is
,
children
}:
{
when
:
string
,
is
:
string
}
&
React
.
ComponentProps
<
any
>
)
=>
(
const
Condition
=
({
when
,
is
,
match
,
children
}:
{
when
:
string
}
&
({
is
:
string
,
match
:
never
}
|
{
is
:
never
,
match
:
RegExp
})
&
React
.
ComponentProps
<
any
>
)
=>
(
<
Field
name
=
{
when
}
subscription
=
{
{
value
:
true
}
}
>
{
({
input
:
{
value
}
})
=>
(
value
===
is
?
children
:
null
)
}
{
({
input
:
{
value
}
})
=>
is
!==
undefined
?
(
value
===
is
?
children
:
null
)
:
(
value
.
match
(
match
)
?
children
:
null
)
}
</
Field
>
);
const
ConditionNot
=
({
when
,
isNot
,
notMatch
,
children
}:
{
when
:
string
}
&
({
isNot
:
string
,
notMatch
:
never
}
|
{
isNot
:
never
,
notMatch
:
RegExp
})
&
React
.
ComponentProps
<
any
>
)
=>
(
<
Field
name
=
{
when
}
subscription
=
{
{
value
:
true
}
}
>
{
({
input
:
{
value
}
})
=>
isNot
!==
undefined
?
(
value
===
isNot
?
null
:
children
)
:
(
value
.
match
(
notMatch
)
?
null
:
children
)
}
</
Field
>
);
...
...
@@ -257,16 +264,16 @@ class AdjustQuantity extends React.Component<{ showSnackbar: (msg: string) => vo
<
Grid
container
spacing
=
{
2
}
>
<
Condition
when
=
"quantityOnHandUnitCode"
is
=
{
'
sd
'
}
>
<
Grid
item
xs
=
{
12
}
sm
=
{
8
}
>
<
Field
disabled
=
{
quantityDisabled
}
<
BalanceReader
label
=
{
t
(
'
inventory.adjust.currentWeight
'
)
}
placeholder
=
{
t
(
'
inventory.adjust.currentWeight
'
)
}
name
=
"weight"
type
=
"
text
"
type
=
"
number
"
tabIndex
=
"1"
autoFocus
validate
=
{
validatePositiveNumber
}
component
=
{
TextFiel
d
}
label
=
{
t
(
'
inventory.adjust.currentWeight
'
)
}
disabled
=
{
quantityDisable
d
}
// component={ TextField
}
/>
</
Grid
>
<
Grid
item
xs
=
{
12
}
sm
=
{
4
}
>
...
...
@@ -283,18 +290,33 @@ class AdjustQuantity extends React.Component<{ showSnackbar: (msg: string) => vo
</
Grid
>
</
Condition
>
<
Grid
item
xs
=
{
12
}
sm
=
{
8
}
>
<
Field
placeholder
=
{
t
([
'
client:model.Inventory.quantityOnHand
'
,
'
client:model._.quantityOnHand
'
])
}
disabled
=
{
quantityDisabled
}
name
=
"quantityOnHand"
type
=
"text"
tabIndex
=
"2"
component
=
{
TextField
}
inputRef
=
{
this
.
inputQuantity
}
required
validate
=
{
validatePositiveNumber
}
label
=
{
t
([
'
client:model.Inventory.quantityOnHand
'
,
'
client:model._.quantityOnHand
'
])
}
/>
<
Condition
when
=
"quantityOnHandUnitCode"
match
=
{
/^
(
gm|g
)
$/
}
>
<
BalanceReader
placeholder
=
{
t
([
'
client:model.Inventory.quantityOnHand
'
,
'
client:model._.quantityOnHand
'
])
}
disabled
=
{
quantityDisabled
}
name
=
"quantityOnHand"
type
=
"text"
tabIndex
=
"2"
inputRef
=
{
this
.
inputQuantity
}
required
validate
=
{
validatePositiveNumber
}
label
=
{
t
([
'
client:model.Inventory.quantityOnHand
'
,
'
client:model._.quantityOnHand
'
])
}
/>
</
Condition
>
<
ConditionNot
when
=
"quantityOnHandUnitCode"
notMatch
=
{
/^
(
gm|g
)
$/
}
>
<
Field
placeholder
=
{
t
([
'
client:model.Inventory.quantityOnHand
'
,
'
client:model._.quantityOnHand
'
])
}
disabled
=
{
quantityDisabled
}
name
=
"quantityOnHand"
type
=
"text"
tabIndex
=
"2"
component
=
{
TextField
}
inputRef
=
{
this
.
inputQuantity
}
required
validate
=
{
validatePositiveNumber
}
label
=
{
t
([
'
client:model.Inventory.quantityOnHand
'
,
'
client:model._.quantityOnHand
'
])
}
/>
</
ConditionNot
>
</
Grid
>
<
Grid
item
xs
=
{
12
}
sm
=
{
4
}
>
<
Field
...
...
workspaces/ui-express/src/inventory/ui/c/BalanceReader.tsx
0 → 100644
View file @
6d568a8f
/**
* A small UI component to read weight from BTLE-connected
* weighing balances.
*/
import
*
as
React
from
'
react
'
;
import
{
Field
,
useField
}
from
'
react-final-form
'
;
import
{
TextField
}
from
'
@gringlobal-ce/client/ui/common/form/TextField
'
;
import
{
useTranslation
}
from
'
react-i18next
'
;
import
{
IconButton
,
InputAdornment
}
from
'
@material-ui/core
'
;
import
BluetoothSearchingIcon
from
'
@material-ui/icons/Bluetooth
'
;
import
BluetoothConnectedIcon
from
'
@material-ui/icons/BluetoothConnected
'
;
import
{
useDispatch
}
from
'
react-redux
'
;
import
{
showSnackbar
}
from
'
@gringlobal-ce/client/action/snackbar
'
;
declare
const
navigator
:
any
;
const
BalanceBTButton
=
({
onReceiveWeight
}:
{
onReceiveWeight
:
(
weight
:
number
|
''
)
=>
unknown
})
=>
{
const
BLUETOOTH_SERIAL_PORT
=
0x1101
;
const
BLUETOOTH_CLASSIC_SPP
=
'
00001101-0000-1000-8000-00805f9b34fb
'
;
const
BLUETOOTH_BTS5_LE_SERVICE
=
'
0000b350-d6d8-c7ec-bdf0-eab1bfc6bcbc
'
;
const
BLUETOOTH_BTS5_R_CHARACTERISTIC
=
'
0000b351-d6d8-c7ec-bdf0-eab1bfc6bcbc
'
;
const
{
t
}
=
useTranslation
();
const
[
balanceDevice
,
setBalanceDevice
]
=
React
.
useState
<
any
>
();
const
myCharacteristic
=
React
.
useRef
(
null
);
const
[
busyIndicator
,
setBusyIndicator
]
=
React
.
useState
(
false
);
const
dec
=
new
TextDecoder
();
// always utf-8
const
dispatch
=
useDispatch
();
const
handleCharacteristicNotification
=
(
event
)
=>
{
// console.log(event.target.value);
const
data
=
dec
.
decode
(
event
.
target
.
value
);
// console.log('Data', data);
const
val
=
data
.
match
(
/^
([
+-
]
*
\d
+
\.\d
+
)
g *$/
);
if
(
val
)
{
// console.log(`Weight: ${val[1]}`);
try
{
const
weight
=
parseFloat
(
val
[
1
].
replace
(
/ +/g
,
''
));
if
(
onReceiveWeight
)
{
onReceiveWeight
(
weight
);
}
}
catch
(
e
)
{
console
.
log
(
`Error reporting weight "
${
val
[
1
]}
":
${
e
}
`
);
}
}
}
const
disconnect
=
()
=>
{
if
(
myCharacteristic
.
current
)
{
return
myCharacteristic
.
current
.
stopNotifications
()
.
then
(
_
=>
{
console
.
log
(
'
Notifications stopped
'
,
_
);
myCharacteristic
.
current
.
removeEventListener
(
'
characteristicvaluechanged
'
,
handleCharacteristicNotification
);
myCharacteristic
.
current
.
service
.
device
.
gatt
.
disconnect
();
})
.
catch
(
error
=>
{
console
.
log
(
'
Argh!
'
+
error
);
})
.
finally
(()
=>
{
myCharacteristic
.
current
=
null
;
})
}
return
Promise
.
resolve
();
}
// effect
React
.
useEffect
(()
=>
{
console
.
log
(
'
Effecting Balance reader
'
);
if
(
typeof
navigator
[
'
bluetooth
'
]
===
'
undefined
'
)
{
console
.
log
(
'
Not in a Chrome browser
'
);
return
;
}
navigator
.
bluetooth
.
getAvailability
().
then
(
available
=>
{
const
preferredBalanceId
=
localStorage
.
getItem
(
'
preferredBalance
'
);
console
.
log
(
`Bluetooth availability:
${
available
}
, looking for
${
preferredBalanceId
}
`
);
navigator
.
bluetooth
.
getDevices
().
then
(
d
=>
{
console
.
log
(
'
We have access to
'
,
d
);
const
preferredDevice
=
d
.
find
(
device
=>
device
.
id
===
preferredBalanceId
);
if
(
preferredDevice
)
{
setBalanceDevice
(
preferredDevice
);
}
else
{
throw
new
Error
(
'
No preferred device!
'
);
}
}).
catch
(
err
=>
{
console
.
log
(
'
Error retrieving preferred balance device
'
,
err
);
});
});
return
()
=>
{
console
.
log
(
'
We are done here!
'
,
myCharacteristic
.
current
);
disconnect
();
}
},
[]);
const
connectBalance
=
()
=>
{
navigator
.
bluetooth
.
requestDevice
({
acceptAllDevices
:
true
,
optionalServices
:
[
BLUETOOTH_SERIAL_PORT
,
BLUETOOTH_CLASSIC_SPP
,
BLUETOOTH_BTS5_LE_SERVICE
]
}).
then
(
device
=>
{
console
.
log
(
'
Got device
'
,
device
);
localStorage
.
setItem
(
'
preferredBalance
'
,
device
.
id
);
setBalanceDevice
(
device
);
dispatch
(
showSnackbar
(
t
(
'
inventory.balance.deviceSelected
'
,
{
name
:
device
.
name
})));
});
}
const
readBalance
=
()
=>
{
const
device
=
balanceDevice
;
if
(
!
device
)
{
return
;
}
if
(
device
.
gatt
.
connected
)
{
console
.
log
(
'
Device is already connected
'
);
dispatch
(
showSnackbar
(
t
(
'
inventory.balance.disconnecting
'
,
{
name
:
device
.
name
})));
setBusyIndicator
(
true
);
disconnect
().
then
(()
=>
{
dispatch
(
showSnackbar
(
t
(
'
inventory.balance.disconnected
'
)));
}).
finally
(()
=>
{
setBusyIndicator
(
false
);
});
return
;
}
dispatch
(
showSnackbar
(
t
(
'
inventory.balance.connecting
'
,
{
name
:
device
.
name
})));
setBusyIndicator
(
true
);
device
.
gatt
.
connect
()
.
then
(
server
=>
{
console
.
log
(
'
GATT server
'
,
server
);
server
.
getPrimaryServices
().
then
(
x
=>
{
console
.
log
(
'
Primary services
'
,
x
);
});
return
server
.
getPrimaryService
(
BLUETOOTH_BTS5_LE_SERVICE
);
}).
then
(
service
=>
{
console
.
log
(
'
Got service
'
,
service
);
service
.
getCharacteristics
({}).
then
(
characteristics
=>
{
console
.
log
(
'
Service characteristic
'
,
characteristics
);
});
return
service
.
getCharacteristic
(
BLUETOOTH_BTS5_R_CHARACTERISTIC
);
}).
then
(
readCharacteristic
=>
{
console
.
log
(
'
Connected to BT characteristic
'
,
readCharacteristic
);
myCharacteristic
.
current
=
readCharacteristic
;
return
readCharacteristic
.
startNotifications
().
then
(
_
=>
{
console
.
log
(
'
> Notifications started
'
,
_
);
dispatch
(
showSnackbar
(
t
(
'
inventory.balance.connected
'
,
{
name
:
readCharacteristic
.
service
.
device
.
name
})));
readCharacteristic
.
addEventListener
(
'
characteristicvaluechanged
'
,
handleCharacteristicNotification
);
});
}).
catch
(
err
=>
{
console
.
log
(
`Error:
${
err
}
`
,
err
);
dispatch
(
showSnackbar
(
t
([
'
inventory.balance.error
'
],
{
message
:
`
${
err
}
`
})));
setBalanceDevice
(
null
);
myCharacteristic
.
current
=
null
;
}).
finally
(()
=>
{
setBusyIndicator
(
false
);
});
}
if
(
typeof
navigator
[
'
bluetooth
'
]
===
'
undefined
'
)
{
return
null
;
}
if
(
!
balanceDevice
)
{
return
(
<
InputAdornment
position
=
"end"
><
IconButton
onClick
=
{
connectBalance
}
><
BluetoothSearchingIcon
/></
IconButton
></
InputAdornment
>
)
}
// We have a device!
return
(
<
InputAdornment
position
=
"end"
><
IconButton
disabled
=
{
busyIndicator
}
onClick
=
{
readBalance
}
><
BluetoothConnectedIcon
style
=
{
{
color
:
myCharacteristic
.
current
!==
null
?
'
green
'
:
'
red
'
}
}
/></
IconButton
></
InputAdornment
>
)
}
const
BalanceReader
=
({
...
rest
}:
{
component
:
never
}
&
any
)
=>
{
// const { t } = useTranslation();
// const inputRef = React.useRef(null);
const
field
=
useField
(
rest
.
name
);
const
updateWeight
=
(
weight
)
=>
{
// console.log(`Received weight ${weight} for field "${rest.name}"`, field);
field
.
input
.
onChange
(
weight
);
// inputRef.current.value = weight;
}
return
(
<
Field
// inputRef={ inputRef }
component
=
{
TextField
}
endAdornment
=
{
<
BalanceBTButton
onReceiveWeight
=
{
updateWeight
}
/>
}
{
...
rest
}
/>
)
}
export
{
BalanceReader
as
default
};
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment