diff --git a/src/tools/flow_control.py b/src/tools/flow_control.py new file mode 100755 index 0000000..0eb26fc --- /dev/null +++ b/src/tools/flow_control.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python2 +# -*- encoding: utf-8 -*- +import optparse +import os, sys +import serial +import numpy as N +import struct +import binascii +import time + +DEBUG = False + +""" +6.2.2 CRC Generation +==================== +The Cyclical Redundancy Checking (CRC) field is two bytes, containing a 16–bit binary value. The CRC value is calculated by the +transmitting device, which appends the CRC to the message. The device that receives recalculates a CRC during receipt of the +message, and compares the calculated value to the actual value it received in the CRC field. If the two values are not equal, an error +results. + +The CRC is started by first preloading a 16–bit register to all 1’s. Then a process begins of applying successive 8–bit bytes of the +message to the current contents of the register. Only the eight bits of data in each character are used for generating the CRC. Start +and stop bits and the parity bit, do not apply to the CRC. +During generation of the CRC, each 8–bit character is exclusive ORed with the register contents. Then the result is shifted in the +direction of the least significant bit (LSB), with a zero filled into the most significant bit (MSB) position. The LSB is extracted and +examined. If the LSB was a 1, the register is then exclusive ORed with a preset, fixed value. If the LSB was a 0, no exclusive OR takes +place. + +This process is repeated until eight shifts have been performed. After the last (eighth) shift, the next 8–bit character is exclusive ORed +with the register’s current value, and the process repeats for eight more shifts as described above. The final content of the register, +after all the characters of the message have been applied, is the CRC value. +A procedure for generating a CRC is: + + 1. Load a 16–bit register with FFFF hex (all 1’s). Call this the CRC register. + 2. Exclusive OR the first 8–bit byte of the message with the low–order byte of the 16–bit CRC register, putting the result in the + CRC register. + 3. Shift the CRC register one bit to the right (toward the LSB), zero–filling the MSB. Extract and examine the LSB. + 4. (If the LSB was 0): Repeat Step 3 (another shift). + (If the LSB was 1): Exclusive OR the CRC register with the polynomial value 0xA001 (1010 0000 0000 0001). + 5. Repeat Steps 3 and 4 until 8 shifts have been performed. When this is done, a complete 8–bit byte will have been + processed. + 6. Repeat Steps 2 through 5 for the next 8–bit byte of the message. Continue doing this until all bytes have been processed. + 7. The final content of the CRC register is the CRC value. + 8. When the CRC is placed into the message, its upper and lower bytes must be swapped as described below. + +Placing the CRC into the Message +When the 16–bit CRC (two 8–bit bytes) is transmitted in the message, the low-order byte will be transmitted first, followed by the high- +order byte. +""" + +crc_test = "\x0d\x01\x00\x62\x00\x33" +crc_expected = 0xddd + + +def crc(message): + crc = 0xffff # step 1 + for byte in message: + if type(byte) == type(str): + crc ^= ord(byte) # step 2: xor with lower byte of crc + else: + crc ^= byte # step 2: xor with lower byte of crc + for i in range(8): # step 5: repeat 2-4 until 8 shifts were performed + if (crc & 0x1) == 0: # step 4: check LSB + mask = 0x0 + else: + mask = 0xa001 + crc >>= 1 # step 3: shift one bit to the right + crc ^= mask + if DEBUG: print "Message:", [i for i in message], " CRC:", hex(crc) + return chr(crc & 0xff) + chr( + (crc >> 8) & 0xff) # return lower byte, upper byte (shift to the right, return last byte) i + + +class Flow: + def __init__(self, device="/dev/ttyUSB0"): + self.s = serial.Serial(port=device, timeout=0.02) + self.s.readline() + self.s.timeout = 0.2 + self.address = bytearray([0x3]) + self.rcommands = { + "flow": [bytearray([0x3, 0x0, 0x0, 0x0, 0x2]), ">f"], + "temperature": [bytearray([0x3, 0x0, 0x2, 0x0, 0x2]), ">f"], + "setflow": [bytearray([0x3, 0x0, 0x6, 0x0, 0x2]), ">f"], + "total": [bytearray([0x3, 0x0, 0x8, 0x0, 0x2]), ">f"], + "alarm": [bytearray([0x3, 0x0, 0xc, 0x0, 0x1]), ">H"], + "hwerror": [bytearray([0x3, 0x0, 0xd, 0x0, 0x1]), ">H"], + "unit_flow": [bytearray([0x3, 0x0, 0x16, 0x0, 0x4]), ">8s"], + "medium": [bytearray([0x3, 0x00, 0x1a, 0x0, 0x4]), ">8s"], + "device": [bytearray([0x3, 0x00, 0x23, 0x0, 0x4]), ">8s"], + "unit_total": [bytearray([0x3, 0x40, 0x48, 0x0, 0x4]), ">8s"], + } + + self.wcommands = { + "flow": [bytearray([0x06, 0x0, 0x6]), ">f"] + } + + def send_data(self, data_and_type): + data, type = data_and_type + if DEBUG: print "send", data_and_type, "data:", binascii.hexlify(data), "type:", type, self.address + msg = self.address + data + msg_crc = msg + crc(msg) + if DEBUG: print "send:", repr(str(msg_crc)) + nbytes_sent = self.s.write(str(msg_crc)) + if DEBUG: print "send:", self.s + if DEBUG: print "Sent bytes:", nbytes_sent + time.sleep(0.1) # this is necessary, otherwise no data can be received + + return msg_crc + + def recv_data(self): + """ + Reading Data + ============ + + Function: 0x3 + Format of the resulting string + + Addr Func NoBytes Byte1...N CRCHi CRCLo + + """ + message = bytearray(self.s.read(3)) + if DEBUG: print "recv_data: header:", binascii.hexlify(message) + if message == "": + return + addr, func, nbytes = message + if func == 0x03: + data = self.s.read(nbytes) + if DEBUG: print "recv_data: data: %i %s" % (nbytes, binascii.hexlify(data)) + message += data + crc_data = self.s.read(2) + if DEBUG: print "recv_data: crc", repr(str(crc_data)) + if crc(message) != crc_data: + print "mismatch", crc(message), crc_data + return data + + def get_var(self, named_type="flow"): + cmd,fmt = self.rcommands[named_type] + if DEBUG: print "get_var", cmd,fmt + self.send_data([cmd,fmt]) + d = self.recv_data() + if d: + d = struct.unpack(fmt, d)[0] + return d + # msg = self.recv_data() + # print str(msg) + + # float struct.unpack('>f', "4byte string") + # ushort struct.unpack('>H', "2byte string") + + + + def current_flow(self): + # start register 0x0000 + # 2 bytes + cmd = self.address + bytearray([0x3, 0x0, 0x0, 0x0, 0x2]) + return cmd + crc(cmd) + + def current_temperature(self): + # start register 0x0002 + # + # + cmd = self.address + bytearray([0x3, 0x0, 0x2, 0x0, 0x2]) + return cmd + crc(cmd) + + def set_flow(self, flow): + """ + write multiple registers: 0x10 + startHi + startLo + numregHi + numregLo + numbytes + """ + _flow = struct.pack(">f", flow) + # cmd = address + bytearray([0x10,0x0,0x6, 0x0, 0x2, 0x2]) + _flow + cmd1 = self.address + bytearray([0x06, 0x0, 0x6]) + _flow + self.s.write(cmd1 + crc(cmd1)) + time.sleep(0.01) + # print self.s.readline() + # self.recv_data() + + def cmd(self, data): + cmds = self.address + data + for i in cmds: + print hex(ord(i)), + return cmds + crc(cmds) + + +if __name__ == "__main__": + conf = os.path.expanduser('~/.flow') + if os.path.exists(conf): + dev = open(conf).readlines()[0].strip() + redy = Flow(device=dev) + print "Device: %6s" % (redy.get_var("device")) + print "Medium: %6s" % (redy.get_var("medium")) + print "Current Flow: %6.2f" % (redy.get_var("flow")) + print "Current Set Flow: %6.2f" % (redy.get_var("setflow")) + print "Flow Unit: %6s" % (redy.get_var("unit_flow")) + print "Total Gas: %6.2f" % (redy.get_var("total")) + print "Total Unit: %6s" % (redy.get_var("unit_total")) + print "Current Temperature: %6.2f C" % (redy.get_var("temperature")) + print "Alarm: %8i" % (redy.get_var("alarm")) + print "HW Error: %8i" % (redy.get_var("hwerror")) + if len(sys.argv) > 1: + try: + float(sys.argv[1]) + except: + raise SyntaxError, "usage: %s # to set flow or empty to get current flow\nfirst line in ~/.flow defines serial tty, default: /dev/ttyUSB0" % (sys.argv[0]) + print "Set flow: %6.2f"%(float(sys.argv[1])) + redy.set_flow(float(sys.argv[1]))