Skip to content
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

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions trickler/scales.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.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,
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Author

Choose a reason for hiding this comment

The 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:
grams (g), grains (GN), ounces (oz), carat (ct), troy ounce(ozt), pennyweight (dwt), pound (lb)

' ': 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')
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Author

Choose a reason for hiding this comment

The 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

Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Author

Choose a reason for hiding this comment

The 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
import serial
import time

ser = serial.Serial(port='/dev/ttyUSB0', baudrate=9600, timeout=0.1)
atexit.register(ser.close)

def change_units():
ser.write(b'\x1B\x73')

def read_units():
ser.write(b'\x1B\x70')
units = ser.readline().decode('utf-8')
units = units[9:12]
print(units)
return units

if name == "main":

while read_units() != " GN":
    change_units()
    time.sleep(1)

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()
check_for_unit = handlers.get(status, noop)
check_for_error = handlers.get(fault, noop)
check_for_unit(line)
check_for_error(line)

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.01)
resolution[Units.GRAMS] = decimal.Decimal(0.001)

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."""

Expand Down Expand Up @@ -187,6 +314,7 @@ def _serial_number(self, line):

SCALES = {
'and-fx120': ANDFx120,
'gg-jj100b': GGjj100b,
}


Expand Down