-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathadafruit_dymoscale.py
152 lines (131 loc) · 4.95 KB
/
adafruit_dymoscale.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# SPDX-FileCopyrightText: 2019 ladyada for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
`adafruit_dymoscale`
================================================================================
CircuitPython interface for DYMO scales.
* Author(s): ladyada
Implementation Notes
--------------------
**Software and Dependencies:**
* Adafruit CircuitPython firmware for the supported boards:
https://github.com/adafruit/circuitpython/releases
"""
try:
import typing # pylint: disable=unused-import
from digitalio import DigitalInOut
import microcontroller
except ImportError:
pass
import time
from pulseio import PulseIn
from micropython import const
OUNCES = const(0x0B) # data in weight is in ounces
GRAMS = const(0x02) # data in weight is in grams
PULSE_WIDTH = 72.5
__version__ = "0.0.0+auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DymoScale.git"
# pylint: disable=too-few-public-methods
class ScaleReading:
"""Dymo Scale Data"""
units = None # what units we're measuring
stable = None # is the measurement stable?
weight = None # the weight!
class DYMOScale:
"""Interface to a DYMO postal scale."""
def __init__(
self,
data_pin: microcontroller.Pin,
units_dio: DigitalInOut,
timeout: float = 1.0,
) -> None:
"""Sets up a DYMO postal scale.
:param ~pulseio.PulseIn data_pin: The data pin from the Dymo scale.
:param ~digitalio.DigitalInOut units_pin: The grams/oz button from the Dymo scale.
:param double timeout: The timeout, in seconds.
"""
self.timeout = timeout
# set up the toggle pin
self.units_dio = units_dio
# set up the dymo data pin
self.dymo = PulseIn(data_pin, maxlen=96, idle_state=True)
@property
def weight(self) -> ScaleReading:
"""Weight in grams"""
reading = self.get_scale_data()
if reading.units == OUNCES:
reading.weight *= 28.35
reading.units = GRAMS
return reading
def toggle_unit_button(self, switch_units: bool = False) -> None:
"""Toggles the unit button on the dymo.
:param bool switch_units: Simulates pressing the units button.
"""
toggle_times = 0
if switch_units: # press the button once
toggle_amt = 2
else: # toggle and preserve current unit state
toggle_amt = 4
while toggle_times < toggle_amt:
self.units_dio.value ^= 1
time.sleep(2)
toggle_times += 1
def _read_pulse(self) -> None:
"""Reads a pulse of SPI data on a pin that corresponds to DYMO scale
output protocol (12 bytes of data at about 14KHz).
"""
timestamp = time.monotonic()
self.dymo.pause()
self.dymo.clear()
self.dymo.resume()
while len(self.dymo) < 35:
if (time.monotonic() - timestamp) > self.timeout:
raise RuntimeError(
"Timed out waiting for data - is the scale turned on?"
)
self.dymo.pause()
def get_scale_data(self) -> ScaleReading:
"""Reads a pulse of SPI data and analyzes the resulting data."""
self._read_pulse()
bits = [0] * 96 # there are 12 bytes = 96 bits of data
bit_idx = 0 # we will count a bit at a time
bit_val = False # first pulses will be LOW
for i in self.dymo:
if i == 65535: # check for the pulse between transmits
break
num_bits = int(i / PULSE_WIDTH + 0.5) # ~14KHz == ~7.5us per clock
bit = 0
while bit < num_bits:
bits[bit_idx] = bit_val
bit_idx += 1
if bit_idx == 96: # we have read all the data we wanted
break
bit += 1
bit_val = not bit_val
data_bytes = [0] * 12 # alllocate data array
for byte_n in range(12):
the_byte = 0
for bit_n in range(8):
the_byte <<= 1
the_byte |= bits[byte_n * 8 + bit_n]
data_bytes[byte_n] = the_byte
# do some very basic data checking
if data_bytes[0] != 3 and data_bytes[0] != 2:
raise RuntimeError("Bad data capture")
if data_bytes[1] != 3 or data_bytes[7] != 4 or data_bytes[8] != 0x1C:
raise RuntimeError("Bad data capture")
if data_bytes[9] != 0 or data_bytes[10] or data_bytes[11] != 0:
raise RuntimeError("Bad data capture")
reading = ScaleReading()
# parse out the data_bytes
reading.stable = data_bytes[2] & 0x4
reading.units = data_bytes[3]
reading.weight = data_bytes[5] + (data_bytes[6] << 8)
if data_bytes[2] & 0x1:
reading.weight *= -1
if reading.units == OUNCES:
if data_bytes[4] & 0x80:
data_bytes[4] -= 0x100
reading.weight *= 10 ** data_bytes[4]
return reading