-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for G&G JJ100B scale #7
base: develop
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,13 +25,13 @@ class Units(enum.Enum): | |
|
||
UNIT_MAP = { | ||
'GN': Units.GRAINS, | ||
'g': Units.GRAMS, | ||
'G': Units.GRAMS, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This change and the one below would break support for AND scales if committed. The problem is that I designed it around AND scales and need to refactor this sort of thing to allow other brands with different data formats and unit names to be separated. I'll handle the separation in this other PR (#5 ), which I'll need to merge first so that it will make it more clear what you'll need to do for your scale. |
||
} | ||
|
||
|
||
UNIT_REVERSE_MAP = { | ||
Units.GRAINS: 'GN', | ||
Units.GRAMS: 'g', | ||
Units.GRAMS: 'G', | ||
} | ||
|
||
|
||
|
@@ -40,6 +40,133 @@ def noop(*args, **kwargs): | |
return | ||
|
||
|
||
class GGjj100b: | ||
"""Class for controlling a G&G JJ100B scale.""" | ||
|
||
class ScaleStatusV1(enum.Enum): | ||
"""Supports the first version of OpenTrickler software.""" | ||
STABLE = 0 | ||
UNSTABLE = 1 | ||
OVERLOAD = 2 | ||
ERROR = 3 | ||
MODEL_NUMBER = 4 | ||
SERIAL_NUMBER = 5 | ||
ACKNOWLEDGE = 6 | ||
|
||
class ScaleStatusV2(enum.Enum): | ||
"""New version avoids zero (0) which can be confused with null/None.""" | ||
STABLE = 1 | ||
UNSTABLE = 2 | ||
OVERLOAD = 3 | ||
ERROR = 4 | ||
MODEL_NUMBER = 5 | ||
SERIAL_NUMBER = 6 | ||
ACKNOWLEDGE = 7 | ||
|
||
def __init__(self, memcache, port='/dev/ttyUSB0', baudrate=9600, timeout=0.1, _version=1, **kwargs): | ||
"""Controller.""" | ||
self._memcache = memcache | ||
self._serial = serial.Serial(port=port, baudrate=baudrate, timeout=timeout, **kwargs) | ||
# Set default values, which should be overwritten quickly. | ||
self.raw = b'' | ||
self.unit = Units.GRAINS | ||
self.resolution = decimal.Decimal(0.02) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this resolution correct for your scale? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Negativ, resolution for grains = 0.01 |
||
self.weight = decimal.Decimal('0.00') | ||
|
||
self.StatusMap = self.ScaleStatusV1 | ||
if _version == 2: | ||
self.StatusMap = self.ScaleStatusV2 | ||
|
||
self.status = self.StatusMap.STABLE | ||
self.model_number = None | ||
self.serial_number = None | ||
atexit.register(self._graceful_exit) | ||
|
||
def _graceful_exit(self): | ||
"""Graceful exit, closes serial port.""" | ||
logging.debug('Closing serial port...') | ||
self._serial.close() | ||
|
||
def change_unit(self): | ||
"""Changes the unit of weight on the scale.""" | ||
logging.debug('changing weight unit on scale from: %r', self.unit) | ||
# Send Mode button command. | ||
self._serial.write(b'\x1B\x73') | ||
erichiggins marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# Sleep 1s and wait for change to take effect. | ||
time.sleep(1) | ||
# Run update fn to set latest values. | ||
self.update() | ||
|
||
@property | ||
def is_stable(self): | ||
"""Returns True if the scale is stable, otherwise False.""" | ||
return self.status == self.StatusMap.STABLE | ||
# returns 1 when previous weights match with current | ||
# return len(set(self._previous_reads)) == 1 | ||
|
||
def update(self): | ||
"""Read from the serial port and update an instance of this class with the most recent values.""" | ||
handlers = { | ||
' GN': self._stable, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should other units (such as grams, ounces, etc) be listed here as well? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the trickler project it is not necessary but my scale supports following units so the missing units could be implemented here: |
||
' ': self._unstable, | ||
'F---.-H': self._overload, | ||
None: noop, | ||
} | ||
|
||
# Note: The input buffer can fill up, causing latency. Clear it before reading. | ||
self._serial.reset_input_buffer() | ||
self._serial.write(b'\x1B\x70') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you need to send a command before reading from the scale, please document what is being sent and why it's needed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In order to get a reading from the scale the hardware number (default 27dec = 0x1B) + 0x70 has to be send to the scale There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it. Have you tested this with your scale to see if it works? Is a delay needed after sending before the weight can be read? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I have tested this with my scale. I put a delay for safety reasons but I will test if it works without delay too. Here is a sample code I used for reading and changing units on my scale: import atexit ser = serial.Serial(port='/dev/ttyUSB0', baudrate=9600, timeout=0.1) def change_units(): def read_units(): if name == "main":
|
||
raw = self._serial.readline() | ||
self.raw = raw | ||
logging.debug(raw) | ||
try: | ||
line = raw.strip().decode('utf-8') | ||
except UnicodeDecodeError: | ||
logging.debug('Could not decode bytes to unicode.') | ||
else: | ||
status = line[9:12].strip() | ||
fault = line[2:9].strip() | ||
handler = handlers.get(status, noop) | ||
handler2 = handlers.get(fault, noop) | ||
handler(line) | ||
handler2(line) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There may be a better way to do this than calling both functions when only one is needed. I know that it's a little more complicated because of the data format. I don't have a suggestion off the top of my head. At the very least, let's rename the functions to something more clear instead of |
||
|
||
def _stable_unstable(self, line): | ||
"""Update the scale when status is stable or unstable.""" | ||
weight = line[2:9].strip() | ||
self.weight = decimal.Decimal(weight) | ||
|
||
unit = line[9:12].strip() | ||
self.unit = UNIT_MAP[unit] | ||
|
||
resolution = {} | ||
resolution[Units.GRAINS] = decimal.Decimal(0.02) | ||
resolution[Units.GRAMS] = decimal.Decimal(0.001) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are these resolutions correct for your scale? |
||
|
||
self.resolution = resolution[self.unit] | ||
# Update memcache values. | ||
self._memcache.set(constants.SCALE_STATUS, self.status) | ||
self._memcache.set(constants.SCALE_WEIGHT, self.weight) | ||
self._memcache.set(constants.SCALE_UNIT, self.unit) | ||
self._memcache.set(constants.SCALE_RESOLUTION, self.resolution) | ||
self._memcache.set(constants.SCALE_IS_STABLE, self.is_stable) | ||
|
||
def _stable(self, line): | ||
"""Scale is stable.""" | ||
self.status = self.StatusMap.STABLE | ||
self._stable_unstable(line) | ||
|
||
def _unstable(self, line): | ||
"""Scale is unstable.""" | ||
self.status = self.StatusMap.UNSTABLE | ||
self._stable_unstable(line) | ||
|
||
def _overload(self, line): | ||
"""Scale is overloaded.""" | ||
self.status = self.StatusMap.OVERLOAD | ||
self._memcache.set(constants.SCALE_STATUS, self.status) | ||
|
||
|
||
class ANDFx120: | ||
"""Class for controlling an A&D FX120 scale.""" | ||
|
||
|
@@ -187,6 +314,7 @@ def _serial_number(self, line): | |
|
||
SCALES = { | ||
'and-fx120': ANDFx120, | ||
'jj100b': GGjj100b, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please match the naming convention and change the key to |
||
} | ||
|
||
|
||
|
@@ -196,9 +324,9 @@ def _serial_number(self, line): | |
import helpers | ||
|
||
parser = argparse.ArgumentParser(description='Test scale.') | ||
parser.add_argument('--scale', choices=SCALES.keys(), default='and-fx120') | ||
parser.add_argument('--scale', choices=SCALES.keys(), default='jj100b') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please revert this. |
||
parser.add_argument('--scale_port', default='/dev/ttyUSB0') | ||
parser.add_argument('--scale_baudrate', type=int, default='19200') | ||
parser.add_argument('--scale_baudrate', type=int, default='9600') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please revert this. |
||
parser.add_argument('--scale_timeout', type=float, default='0.1') | ||
parser.add_argument('--scale_version', type=int, default='1') | ||
args = parser.parse_args() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please revert this change. You can keep it locally for testing if you like, but it should not go into the repo.