def blend2list(blend_dict): try: if blend_dict and "label" in blend_dict and "ingredients" in blend_dict and len(blend_dict["ingredients"]) > 1: return [encodeLocal(blend_dict["label"]), [ [encodeLocal(i["coffee"]), i["ratio"]] for i in blend_dict["ingredients"]] ] else: return None except: return None
def extractProfileCropsterXLS(file): res = {} # the interpreted data set book = xlrd.open_workbook(file) sheet_names = book.sheet_names() # extract general profile information general_sh = book.sheet_by_index(0) if general_sh.nrows >= 1: general_data = dict( zip([x.value for x in general_sh.row(0)], general_sh.row(1))) if 'Id-Tag' in general_data: try: id_tag = general_data['Id-Tag'].value batch_prefix = id_tag.rstrip('0123456789') batch_number = int(id_tag[len(batch_prefix):]) res["roastbatchprefix"] = batch_prefix res["roastbatchnr"] = batch_number except: pass for tag, label in zip([ 'Profile', 'Lot name', 'Machine', 'Roast technician', 'Sensorial notes', 'Roasting notes' ], [ 'title', 'beans', 'roastertype', 'operator', 'cuppingnotes', 'roastingnotes' ]): if tag in general_data: try: value = str(general_data[tag].value) res[label] = value except: pass if 'Date' in general_data: try: raw_date = general_data['Date'].value date_tuple = xlrd.xldate_as_tuple(raw_date, book.datemode) date = QDateTime(*date_tuple) res["roastdate"] = encodeLocal(date.date().toString()) res["roastisodate"] = encodeLocal(date.date().toString( Qt.ISODate)) res["roasttime"] = encodeLocal(date.time().toString()) res["roastepoch"] = int(date.toTime_t()) res["roasttzoffset"] = libtime.timezone except: pass if 'Ambient temp.' in general_data: try: value = general_data['Ambient temp.'].value res['ambientTemp'] = float(value) except: pass if 'Start weight' in general_data or 'End weight' in general_data: cropster_weight_units = ["G", "KG", "LBS", "OZ"] artisan_weight_units = ["g", "Kg", "lb", "oz"] weight = [0, 0, artisan_weight_units[0]] try: value = general_data['End weight unit'].value idx = cropster_weight_units.index(value) weight[2] = artisan_weight_units[idx] except: pass try: value = general_data['Start weight unit'].value idx = cropster_weight_units.index(value) weight[2] = artisan_weight_units[idx] except: pass try: value = general_data['Start weight'].value weight[0] = value except: pass try: value = general_data['End weight'].value weight[1] = value except: pass res["weight"] = weight # BT: try: BT_idx = sheet_names.index("Curve - Bean temp.") BT_sh = book.sheet_by_index(BT_idx) if BT_sh.ncols >= 1: time = BT_sh.col(0) temp = BT_sh.col(1) if len(time) > 0 and len(temp) > 0 and len(time) == len(temp): if "FAHRENHEIT" in str(temp[0].value): res["mode"] = 'F' else: res["mode"] = 'C' res["timex"] = [t.value for t in time[1:]] res["temp2"] = [t.value for t in temp[1:]] res["temp1"] = [-1] * len(res["timex"]) res["timeindex"] = [0, 0, 0, 0, 0, 0, len(res["timex"]) - 1, 0] except: pass # ET try: ET_idx = sheet_names.index("Curve - Env. temp.") ET_sh = book.sheet_by_index(ET_idx) if ET_sh.ncols >= 1: time = ET_sh.col(0) temp = ET_sh.col(1) if len(time) > 0 and len(temp) > 0 and len(time) == len(temp): if "FAHRENHEIT" in str(temp[0].value): res["mode"] = 'F' else: res["mode"] = 'C' res["temp1"] = [t.value for t in temp[1:]] if len(res["timex"]) != len(res["temp1"]): res["timex"] = [t.value for t in time[1:]] if "temp2" not in res or len(res["temp2"]) != len( res["timex"]): res["temp2"] = [-1] * len(res["timex"]) res["timeindex"] = [0, 0, 0, 0, 0, 0, len(res["timex"]) - 1, 0] except: pass # extra temperature curves channel = 1 # toggle between channel 1 and 2 to be filled with extra temperature curve data for sn in sheet_names: if sn.startswith( "Curve" ) and "temp." in sn and sn != "Curve - Bean temp." and sn != "Curve - Env. temp.": try: extra_curve_name = sn.split("Curve - ") if len(extra_curve_name) > 1: extra_curve_name = extra_curve_name[1].split("temp.") extra_curve_name = extra_curve_name[0] else: extra_curve_name = extra_curve_name[0] CT_idx = sheet_names.index(sn) CT_sh = book.sheet_by_index(CT_idx) if CT_sh.ncols >= 1: time = CT_sh.col(0) temp = CT_sh.col(1) if len(time) > 0 and len(temp) > 0 and len(time) == len( temp): if "extradevices" not in res: res["extradevices"] = [] if "extraname1" not in res: res["extraname1"] = [] if "extraname2" not in res: res["extraname2"] = [] if "extratimex" not in res: res["extratimex"] = [] if "extratemp1" not in res: res["extratemp1"] = [] if "extratemp2" not in res: res["extratemp2"] = [] if "extramathexpression1" not in res: res["extramathexpression1"] = [] if "extramathexpression2" not in res: res["extramathexpression2"] = [] if channel == 1: channel = 2 res["extradevices"].append(25) res["extraname1"].append(extra_curve_name) res["extratimex"].append( [t.value for t in time[1:]]) res["extratemp1"].append( [t.value for t in temp[1:]]) res["extramathexpression1"].append("") elif (len(time) - 1) == len( res["extratimex"][-1] ): # only if time lengths is same as of channel 1 channel = 1 res["extraname2"].append(extra_curve_name) res["extratemp2"].append( [t.value for t in temp[1:]]) res["extramathexpression2"].append("") except: pass if "extraname1" in res and "extraname2" in res and len( res["extraname1"]) != len(res["extraname2"]): # we add an empty second extra channel if needed res["extraname2"].append("Extra 2") res["extratemp2"].append([-1] * len(res["extratemp1"][-1])) res["extramathexpression2"].append("") # add events try: COMMENTS_idx = sheet_names.index("Comments") COMMENTS_sh = book.sheet_by_index(COMMENTS_idx) gas_event = False # set to True if a Gas event exists airflow_event = False # set to True if an Airflow event exists specialevents = [] specialeventstype = [] specialeventsvalue = [] specialeventsStrings = [] if COMMENTS_sh.ncols >= 4: takeClosest = lambda num, collection: min( collection, key=lambda x: abs(x - num)) for r in range(COMMENTS_sh.nrows): if r > 0: try: time = COMMENTS_sh.cell(r, 0).value comment_type = COMMENTS_sh.cell(r, 2).value if comment_type not in [ "Turning point" ]: # TP is ignored as it is automatically assigned comment_value = COMMENTS_sh.cell(r, 3).value c = takeClosest(time, res["timex"]) timex_idx = res["timex"].index(c) if comment_type == "Color change": res["timeindex"][1] = timex_idx elif comment_type == "First crack": res["timeindex"][2] = timex_idx elif comment_type == "Second crack": res["timeindex"][4] = timex_idx else: specialevents.append(timex_idx) if comment_type == "Airflow": airflow_event = True specialeventstype.append(0) elif comment_type == "Gas": gas_event = True specialeventstype.append(3) else: specialeventstype.append(4) try: v = float(comment_value) v = v / 10. + 1 specialeventsvalue.append(v) except: specialeventsvalue.append(0) if comment_type not in [ "Airflow", "Gas", "Comment" ]: specialeventsStrings.append(comment_type) else: specialeventsStrings.append(comment_value) except: pass if len(specialevents) > 0: res["specialevents"] = specialevents res["specialeventstype"] = specialeventstype res["specialeventsvalue"] = specialeventsvalue res["specialeventsStrings"] = specialeventsStrings if gas_event or airflow_event: # first set etypes to defaults res["etypes"] = [ QApplication.translate("ComboBox", "Air", None), QApplication.translate("ComboBox", "Drum", None), QApplication.translate("ComboBox", "Damper", None), QApplication.translate("ComboBox", "Burner", None), "--" ] # update if airflow_event: res["etypes"][0] = "Airflow" if gas_event: res["etypes"][3] = "Gas" except: pass return res
def extractProfileRoastPathHTML(url): res = {} # the interpreted data set try: s = requests.Session() s.mount('file://', FileAdapter()) page = s.get(url.toString()) tree = html.fromstring(page.content) title = "" date = tree.xpath( '//div[contains(@class, "roast-top")]/*/*[local-name() = "h5"]/text()' ) if date and len(date) > 0: date_str = date[0].strip().split() if len(date_str) > 2: dateQt = QDateTime.fromString(date_str[-2] + date_str[-1], "yyyy-MM-ddhh:mm") if dateQt.isValid(): res["roastdate"] = encodeLocal(dateQt.date().toString()) res["roastisodate"] = encodeLocal(dateQt.date().toString( Qt.ISODate)) res["roasttime"] = encodeLocal(dateQt.time().toString()) res["roastepoch"] = int(dateQt.toTime_t()) res["roasttzoffset"] = libtime.timezone title = "".join(date_str[:-2]).strip() if len(title ) > 0 and title[-1] == "-": # we remove a trailing - title = title[:-1].strip() elif len(date_str) > 0: title = "".join(date_str) coffee = "" for m in [ "Roasted By", "Coffee", "Batch Size", "Notes", "Organization" ]: s = '//*[@class="ml-2" and normalize-space(text())="Details"]/following::table[1]/tbody/tr[*]/td/*[normalize-space(text())="{}"]/following::td[1]/text()'.format( m) value = tree.xpath(s) if len(value) > 0: meta = value[0].strip() try: if m == "Roasted By": res["operator"] = meta elif m == "Coffee": coffee = meta if title == "" or title == "Roast": res["title"] = coffee elif m == "Notes": res["roastingnotes"] = meta elif m == "Batch Size": v, u = meta.split() res["weight"] = [ float(v), 0, ("Kg" if u.strip() == "kg" else "lb") ] elif m == "Organization": res["organization"] = meta except: pass beans = "" for m in [ "Nickname", "Country", "Region", "Farm", "Varietal", "Process" ]: s = '//*[@class="ml-2" and normalize-space(text())="Greens"]/following::table[1]/tbody/tr/td[count(//table/thead/tr/th[normalize-space(text())="{}"]/preceding-sibling::th)+1]/text()'.format( m) value = tree.xpath(s) if len(value) > 0: meta = value[0].strip() if meta != "": if beans == "": beans = meta else: beans += ", " + meta if coffee != "" and title in res: # we did not use the "coffee" as title if beans == "": beans = coffee else: beans = coffee + "\n" + beans if beans != "": res["beans"] = beans if not "title" in res and title != "": res["title"] = title data = {} for e in [ "btData", "etData", "atData", "eventData", "rorData", "noteData" ]: d = re.findall("var {} = JSON\.parse\('(.+?)'\);".format(e), page.content.decode("utf-8"), re.S) bt = [] if d: data[e] = json.loads(d[0]) if "btData" in data and len( data["btData"]) > 0 and "Timestamp" in data["btData"][0]: # BT bt = data["btData"] baseTime = dateutil.parser.parse(bt[0]["Timestamp"]).timestamp() res["mode"] = 'C' res["timex"] = [ dateutil.parser.parse(d["Timestamp"]).timestamp() - baseTime for d in bt ] res["temp2"] = [d["StandardValue"] for d in bt] # ET if "etData" in data and len(data["btData"]) == len(data["etData"]): et = data["etData"] res["temp1"] = [d["StandardValue"] for d in et] else: res["temp1"] = [-1] * len(res["temp2"]) # Events timeindex = [-1, 0, 0, 0, 0, 0, 0, 0] if "eventData" in data: marks = { "Charge": 0, "Green > Yellow": 1, "First Crack": 2, "Second Crack": 4, "Drop": 6 } for d in data["eventData"]: if "EventName" in d and d[ "EventName"] in marks and "Timestamp" in d: tx = dateutil.parser.parse( d["Timestamp"]).timestamp() - baseTime try: # tx_idx = res["timex"].index(tx) # does not cope with dropouts as the next line: tx_idx = next( i for i, item in enumerate(res["timex"]) if item >= tx) timeindex[marks[d["EventName"]]] = tx_idx except: pass res["timeindex"] = timeindex # Notes if "noteData" in data: specialevents = [] specialeventstype = [] specialeventsvalue = [] specialeventsStrings = [] noteData = data["noteData"] for n in noteData: if "Timestamp" in n and "NoteTypeId" in n and "Note" in n: c = dateutil.parser.parse( n["Timestamp"]).timestamp() - baseTime try: timex_idx = res["timex"].index(c) specialevents.append(timex_idx) note_type = n["NoteTypeId"] if note_type == 0: # Fuel specialeventstype.append(3) elif note_type == 1: # Fan specialeventstype.append(0) elif note_type == 2: # Drum specialeventstype.append(1) else: # n == 3: # Notes specialeventstype.append(4) try: v = float(n["Note"]) v = v / 10. + 1 specialeventsvalue.append(v) except: specialeventsvalue.append(0) specialeventsStrings.append(n["Note"]) except: pass if len(specialevents) > 0: res["specialevents"] = specialevents res["specialeventstype"] = specialeventstype res["specialeventsvalue"] = specialeventsvalue res["specialeventsStrings"] = specialeventsStrings if "atData" in data or "rorData" in data: res["extradevices"] = [] res["extratimex"] = [] res["extratemp1"] = [] res["extratemp2"] = [] res["extraname1"] = [] res["extraname2"] = [] res["extramathexpression1"] = [] res["extramathexpression2"] = [] res["extraLCDvisibility1"] = [] res["extraLCDvisibility2"] = [] res["extraCurveVisibility1"] = [] res["extraCurveVisibility2"] = [] res["extraDelta1"] = [] res["extraDelta2"] = [] res["extraFill1"] = [] res["extraFill2"] = [] res["extradevicecolor1"] = [] res["extradevicecolor2"] = [] res["extramarkersizes1"] = [] res["extramarkersizes2"] = [] res["extramarkers1"] = [] res["extramarkers2"] = [] res["extralinewidths1"] = [] res["extralinewidths2"] = [] res["extralinestyles1"] = [] res["extralinestyles2"] = [] res["extradrawstyles1"] = [] res["extradrawstyles2"] = [] # AT if "atData" in data: res["extradevices"].append(25) res["extraname1"].append("AT") res["extraname2"].append("") res["extramathexpression1"].append("") res["extramathexpression2"].append("") res["extraLCDvisibility1"].append(True) res["extraLCDvisibility2"].append(False) res["extraCurveVisibility1"].append(True) res["extraCurveVisibility2"].append(False) res["extraDelta1"].append(False) res["extraDelta2"].append(False) res["extraFill1"].append(False) res["extraFill2"].append(False) res["extradevicecolor1"].append('black') res["extradevicecolor2"].append('black') res["extramarkersizes1"].append(6.0) res["extramarkersizes2"].append(6.0) res["extramarkers1"].append(None) res["extramarkers2"].append(None) res["extralinewidths1"].append(1.0) res["extralinewidths2"].append(1.0) res["extralinestyles1"].append('-') res["extralinestyles2"].append('-') res["extradrawstyles1"].append('default') res["extradrawstyles2"].append('default') at = data["atData"] timex = [ dateutil.parser.parse(d["Timestamp"]).timestamp() - baseTime for d in at ] res["extratimex"].append(timex) res["extratemp1"].append([d["StandardValue"] for d in at]) res["extratemp2"].append([-1] * len(timex)) # BT RoR if "rorData" in data: res["extradevices"].append(25) res["extraname1"].append("RoR") res["extraname2"].append("") res["extramathexpression1"].append("") res["extramathexpression2"].append("") res["extraLCDvisibility1"].append(True) res["extraLCDvisibility2"].append(False) res["extraCurveVisibility1"].append(False) res["extraCurveVisibility2"].append(False) res["extraDelta1"].append(True) res["extraDelta2"].append(False) res["extraFill1"].append(False) res["extraFill2"].append(False) res["extradevicecolor1"].append('black') res["extradevicecolor2"].append('black') res["extramarkersizes1"].append(6.0) res["extramarkersizes2"].append(6.0) res["extramarkers1"].append(None) res["extramarkers2"].append(None) res["extralinewidths1"].append(1.0) res["extralinewidths2"].append(1.0) res["extralinestyles1"].append('-') res["extralinestyles2"].append('-') res["extradrawstyles1"].append('default') res["extradrawstyles2"].append('default') ror = data["rorData"] timex = [ dateutil.parser.parse(d["Timestamp"]).timestamp() - baseTime for d in ror ] res["extratimex"].append(timex) res["extratemp1"].append([d["StandardValue"] for d in ror]) res["extratemp2"].append([-1] * len(timex)) except Exception as e: # import traceback # import sys # traceback.print_exc(file=sys.stdout) pass return res
def extractProfileRoastLog(url, _): res = {} # the interpreted data set try: s = requests.Session() s.mount('file://', FileAdapter()) page = s.get(url.toString(), timeout=(4, 15), headers={"Accept-Encoding": "gzip"}) tree = html.fromstring(page.content) title = "" title_elements = tree.xpath('//h2[contains(@id,"page-title")]/text()') if len(title_elements) > 0: title = title_elements[0].strip() tag_values = {} for tag in [ "Roastable:", "Starting mass:", "Ending mass:", "Roasted on:", "Roasted by:", "Roaster:", "Roast level:", "Roast Notes:" ]: tag_elements = tree.xpath( '//td[contains(@class,"text-rt") and normalize-space(text())="{}"]/following::td[1]/text()' .format(tag)) if len(tag_elements) > 0: tag_values[tag] = "\n".join([e.strip() for e in tag_elements]) # {'Roastable:': '2003000 Diablo FTO BULK', 'Starting mass:': '140.00 lb', 'Ending mass:': '116.80 lb', 'Roasted on:': 'Thu, Jun 6th, 2019 11:11 PM', 'Roasted by:': '*****@*****.**', 'Roaster:': 'Diedrich CR-70'} if "Roasted on:" in tag_values: try: dt = dateutil.parser.parse(tag_values["Roasted on:"]) dateQt = QDateTime.fromTime_t(int(round(dt.timestamp()))) if dateQt.isValid(): res["roastdate"] = encodeLocal(dateQt.date().toString()) res["roastisodate"] = encodeLocal(dateQt.date().toString( Qt.ISODate)) res["roasttime"] = encodeLocal(dateQt.time().toString()) res["roastepoch"] = int(dateQt.toTime_t()) res["roasttzoffset"] = libtime.timezone except: pass w_in = 0 w_out = 0 u = "lb" if "Starting mass:" in tag_values: w_in, u = tag_values["Starting mass:"].strip().split(" ") if "Ending mass:" in tag_values: w_out, u = tag_values["Ending mass:"].strip().split(" ") res["weight"] = [ float(w_in), float(w_out), ("Kg" if u.strip() == "kg" else "lb") ] if "Roasted by:" in tag_values: res["operator"] = tag_values["Roasted by:"] if "Roaster:" in tag_values: res["roastertype"] = tag_values["Roaster:"] if "Roast level:" in tag_values: try: c = int(round(float(tag_values["Roast level:"]))) res["ground_color"] = c except: pass if "Roast Notes:" in tag_values: res["roastingnotes"] = tag_values["Roast Notes:"] if title == "" and "Roastable:" in tag_values: title = tag_values["Roastable:"] if title != "": res["title"] = title # if DROP temp > 300 => F else C source_elements = tree.xpath('//script[contains(@id,"source")]/text()') if len(source_elements) > 0: source_element = source_elements[0].strip() pattern = re.compile(r"\"rid=(\d+)\"") d = pattern.findall(source_element) if len(d) > 0: rid = d[0] url = "https://roastlog.com/roasts/profiles/?rid={}".format( rid) headers = { "X-Requested-With": "XMLHttpRequest", "Accept": "application/json", "Accept-Encoding": "gzip" } response = requests.get(url, timeout=(4, 15), headers=headers) data_json = response.json() timeindex = [-1, 0, 0, 0, 0, 0, 0, 0] specialevents = [] specialeventstype = [] specialeventsvalue = [] specialeventsStrings = [] if "line_plots" in data_json: mode = "F" timex = [] temp1, temp2, temp3, temp4 = [], [], [], [] temp3_label = "TC3" temp4_label = "TC4" # temp1ror = [] for lp in data_json["line_plots"]: if "channel" in lp and "data" in lp: if lp["channel"] == 0: # BT data = lp["data"] timex = [d[0] / 1000 for d in data] temp1 = [d[1] for d in data] elif lp["channel"] == 1: # ET temp2 = [d[1] for d in lp["data"]] elif lp["channel"] == 2: # XT1 temp3 = [d[1] for d in lp["data"]] if "label" in lp: temp3_label = lp["label"] elif lp["channel"] == 3: # XT2 temp4 = [d[1] for d in lp["data"]] if "label" in lp: temp4_label = lp["label"] # elif lp["channel"] == 4: # BT RoR # temp1ror = [d[1] for d in lp["data"]] res["timex"] = timex if len(timex) == len(temp1): res["temp2"] = temp1 else: res["temp2"] = [-1] * len(timex) if len(timex) == len(temp2): res["temp1"] = temp2 else: res["temp1"] = [-1] * len(timex) if len(temp3) == len(timex) or len(temp4) == len(timex): temp3_visibility = True temp4_visibility = True # add one (virtual) extra device res["extradevices"] = [25] res["extratimex"] = [timex] if len(temp3) == len(timex): res["extratemp1"] = [temp3] else: res["extratemp1"] = [[-1] * len(timex)] temp3_visibility = False if len(temp4) == len(timex): res["extratemp2"] = [temp4] else: res["extratemp2"] = [[-1] * len(timex)] temp4_visibility = False res["extraname1"] = [temp3_label] res["extraname2"] = [temp4_label] res["extramathexpression1"] = [""] res["extramathexpression2"] = [""] res["extraLCDvisibility1"] = [temp3_visibility] res["extraLCDvisibility2"] = [temp4_visibility] res["extraCurveVisibility1"] = [temp3_visibility] res["extraCurveVisibility2"] = [temp4_visibility] res["extraDelta1"] = [False] res["extraDelta2"] = [False] res["extraFill1"] = [False] res["extraFill2"] = [False] res["extradevicecolor1"] = ['black'] res["extradevicecolor2"] = ['black'] res["extramarkersizes1"] = [6.0] res["extramarkersizes2"] = [6.0] res["extramarkers1"] = [None] res["extramarkers2"] = [None] res["extralinewidths1"] = [1.0] res["extralinewidths2"] = [1.0] res["extralinestyles1"] = ['-'] res["extralinestyles2"] = ['-'] res["extradrawstyles1"] = ['default'] res["extradrawstyles2"] = ['default'] timex_events = { "Intro temperature": 0, "yellowing": 1, "Yellow": 1, "DRY": 1, "Dry": 1, "dry": 1, "Dry End": 1, "Start 1st crack": 2, "START 1st": 2, "End 1st crack": 3, "Start 2nd crack": 4, "End 2nd crack": 5, "Drop temperature": 6 } if "table_events" in data_json: for te in data_json["table_events"]: if "label" in te and "time" in te: if te["label"] in timex_events: try: timex_idx = res["timex"].index( stringtoseconds(te["time"])) timeindex[timex_events[ te["label"]]] = timex_idx except: pass else: try: timex_idx = res["timex"].index( stringtoseconds(te["time"])) specialeventsStrings.append(te["label"]) specialevents.append(timex_idx) specialeventstype.append(4) specialeventsvalue.append(0) except: pass res["timeindex"] = timeindex mode = "F" if timeindex[6] != 0 and len(temp1) > timeindex[6] and temp1[ timeindex[6]] < 250: mode = "C" if timeindex[0] > -1 and len(temp1) > timeindex[0] and temp1[ timeindex[0]] < 250: mode = "C" res["mode"] = mode if len(specialevents) > 0: res["specialevents"] = specialevents res["specialeventstype"] = specialeventstype res["specialeventsvalue"] = specialeventsvalue res["specialeventsStrings"] = specialeventsStrings except Exception as e: # import traceback # import sys # traceback.print_exc(file=sys.stdout) pass return res
def extractProfileIkawaCSV(file,_): res = {} # the interpreted data set res["samplinginterval"] = 1.0 # set profile date from the file name if it has the format "IKAWA yyyy-mm-dd hhmmss.csv" filename = os.path.basename(file) p = re.compile('IKAWA \d{4,4}-\d{2,2}-\d{2,2} \d{6,6}.csv') if p.match(filename): s = filename[6:-4] # the extracted date time string date = QDateTime.fromString(s,"yyyy-MM-dd HHmmss") res["roastdate"] = encodeLocal(date.date().toString()) res["roastisodate"] = encodeLocal(date.date().toString(Qt.ISODate)) res["roasttime"] = encodeLocal(date.time().toString()) res["roastepoch"] = int(date.toTime_t()) res["roasttzoffset"] = libtime.timezone csvFile = io.open(file, 'r', newline="",encoding='utf-8') data = csv.reader(csvFile,delimiter=',') #read file header header = next(data) fan = None # holds last processed fan event value fan_last = None # holds the fan event value before the last one heater = None # holds last processed heater event value heater_last = None # holds the heater event value before the last one fan_event = False # set to True if a fan event exists heater_event = False # set to True if a heater event exists specialevents = [] specialeventstype = [] specialeventsvalue = [] specialeventsStrings = [] timex = [] temp1 = [] temp2 = [] extra1 = [] extra2 = [] timeindex = [-1,0,0,0,0,0,0,0] #CHARGE index init set to -1 as 0 could be an actal index used i = 0 for row in data: i = i + 1 items = list(zip(header, row)) item = {} for (name, value) in items: item[name] = value.strip() # take i as time in seconds timex.append(i) if 'inlet temp' in item: temp1.append(float(item['inlet temp'])) elif 'temp below' in item: temp1.append(float(item['temp below'])) else: temp1.append(-1) # we map IKAWA Exhaust to BT as main events like CHARGE and DROP are marked on BT in Artisan if 'exaust temp' in item: temp2.append(float(item['exaust temp'])) elif 'temp above' in item: temp2.append(float(item['temp above'])) else: temp2.append(-1) # mark CHARGE if not timeindex[0] > -1 and 'state' in item and item['state'] == 'doser open': timeindex[0] = max(0,i) # mark DROP if timeindex[6] == 0 and 'state' in item and item['state'] == 'cooling': timeindex[6] = max(0,i) # add SET and RPM if 'temp set' in item: extra1.append(float(item['temp set'])) elif 'setpoint' in item: extra1.append(float(item['setpoint'])) else: extra1.append(-1) if 'fan speed (RPM)' in item: rpm = float(item['fan speed (RPM)']) extra2.append(rpm/100) elif 'fan speed' in item: rpm = float(item['fan speed']) extra2.append(rpm/100) else: extra2.append(-1) if "fan set (%)" in item or "fan set" in item: try: if "fan set (%)" in item: v = float(item["fan set (%)"]) elif "fan set" in item: v = float(item["fan set"]) if v != fan: # fan value changed if v == fan_last: # just a fluctuation, we remove the last added fan value again fan_last_idx = next(i for i in reversed(range(len(specialeventstype))) if specialeventstype[i] == 0) del specialeventsvalue[fan_last_idx] del specialevents[fan_last_idx] del specialeventstype[fan_last_idx] del specialeventsStrings[fan_last_idx] fan = fan_last fan_last = None else: fan_last = fan fan = v fan_event = True v = v/10. + 1 specialeventsvalue.append(v) specialevents.append(i) specialeventstype.append(0) specialeventsStrings.append("{}".format(fan) + "%") else: fan_last = None except: pass if "heater power (%)" in item or "heater" in item: try: if "heater power (%)" in item: v = float(item["heater power (%)"]) elif "heater" in item: v = float(item["heater"]) if v != heater: # heater value changed if v == heater_last: # just a fluctuation, we remove the last added heater value again heater_last_idx = next(i for i in reversed(range(len(specialeventstype))) if specialeventstype[i] == 3) del specialeventsvalue[heater_last_idx] del specialevents[heater_last_idx] del specialeventstype[heater_last_idx] del specialeventsStrings[heater_last_idx] heater = heater_last heater_last = None else: heater_last = heater heater = v heater_event = True v = v/10. + 1 specialeventsvalue.append(v) specialevents.append(i) specialeventstype.append(3) specialeventsStrings.append("{}".format(heater) + "%") else: heater_last = None except: pass csvFile.close() res["mode"] = 'C' res["timex"] = timex res["temp1"] = temp1 res["temp2"] = temp2 res["timeindex"] = timeindex res["extradevices"] = [25] res["extratimex"] = [timex[:]] res["extraname1"] = ["SET"] res["extratemp1"] = [extra1] res["extramathexpression1"] = [""] res["extraname2"] = ["RPM"] res["extratemp2"] = [extra2] res["extramathexpression2"] = ["x/100"] if len(specialevents) > 0: res["specialevents"] = specialevents res["specialeventstype"] = specialeventstype res["specialeventsvalue"] = specialeventsvalue res["specialeventsStrings"] = specialeventsStrings if heater_event or fan_event: # first set etypes to defaults res["etypes"] = [QApplication.translate("ComboBox", "Air",None), QApplication.translate("ComboBox", "Drum",None), QApplication.translate("ComboBox", "Damper",None), QApplication.translate("ComboBox", "Burner",None), "--"] # update if fan_event: res["etypes"][0] = "Fan" if heater_event: res["etypes"][3] = "Heater" return res
def extractProfileBulletDict(data, aw): try: res = {} # the interpreted data set if "celsius" in data and not data["celsius"]: res["mode"] = 'F' else: res["mode"] = 'C' if "comments" in data: res["roastingnotes"] = data["comments"] try: if "dateTime" in data: try: dateQt = QDateTime.fromString( data["dateTime"], Qt.ISODate) # RFC 3339 date time except: dateQt = QDateTime.fromMSecsSinceEpoch(data["dateTime"]) if dateQt.isValid(): res["roastdate"] = encodeLocal(dateQt.date().toString()) res["roastisodate"] = encodeLocal(dateQt.date().toString( Qt.ISODate)) res["roasttime"] = encodeLocal(dateQt.time().toString()) res["roastepoch"] = int(dateQt.toTime_t()) res["roasttzoffset"] = time.timezone except: pass try: res["title"] = data["beanName"] except: pass if "roastName" in data: res["title"] = data["roastName"] try: if "roastNumber" in data: res["roastbatchnr"] = int(data["roastNumber"]) except: pass if "beanName" in data: res["beans"] = data["beanName"] elif "bean" in data and "beanName" in data["bean"]: res["beans"] = data["bean"]["beanName"] try: if "weightGreen" in data or "weightRoasted" in data: wunit = aw.qmc.weight_units.index(aw.qmc.weight[2]) if wunit in [1, 3]: # turn Kg into g, and lb into oz wunit = wunit - 1 wgreen = 0 if "weightGreen" in data: wgreen = float(data["weightGreen"]) wroasted = 0 if "weightRoasted" in data: wroasted = float(data["weightRoasted"]) res["weight"] = [wgreen, wroasted, aw.qmc.weight_units[wunit]] except: pass try: if "agtron" in data: res["ground_color"] = int(round(data["agtron"])) res["color_system"] = "Agtron" except: pass try: if "roastMasterName" in data: res["operator"] = data["roastMasterName"] except: pass res["roastertype"] = "Aillio Bullet R1" if "ambient" in data: res['ambientTemp'] = data["ambient"] if "humidity" in data: res["ambient_humidity"] = data["humidity"] if "beanTemperature" in data: bt = data["beanTemperature"] else: bt = [] if "drumTemperature" in data: dt = data["drumTemperature"] else: dt = [] # make dt the same length as bt dt = dt[:len(bt)] dt.extend(-1 for _ in range(len(bt) - len(dt))) if "exitTemperature" in data: et = data["exitTemperature"] else: et = None if et is not None: # make et the same length as bt et = et[:len(bt)] et.extend(-1 for _ in range(len(bt) - len(et))) if "beanDerivative" in data: ror = data["beanDerivative"] else: ror = None if ror is not None: # make et the same length as bt ror = ror[:len(bt)] ror.extend(-1 for _ in range(len(bt) - len(ror))) if "sampleRate" in data: sr = data["sampleRate"] else: sr = 2. res["samplinginterval"] = 1.0 / sr tx = [x / sr for x in range(len(bt))] res["timex"] = tx res["temp1"] = dt res["temp2"] = bt timeindex = [-1, 0, 0, 0, 0, 0, 0, 0] if "roastStartIndex" in data: timeindex[0] = min(max(data["roastStartIndex"], 0), len(tx) - 1) else: timeindex[0] = 0 labels = [ "indexYellowingStart", "indexFirstCrackStart", "indexFirstCrackEnd", "indexSecondCrackStart", "indexSecondCrackEnd" ] for i in range(1, 6): try: idx = data[labels[i - 1]] # RoastTime seems to interpret all index values 1 based, while Artisan takes the 0 based approach. We substruct 1 if idx > 1: timeindex[i] = max(min(idx - 1, len(tx) - 1), 0) except: pass if "roastEndIndex" in data: timeindex[6] = max(0, min(data["roastEndIndex"], len(tx) - 1)) else: timeindex[6] = max(0, len(tx) - 1) res["timeindex"] = timeindex # extract events from newer JSON format specialevents = [] specialeventstype = [] specialeventsvalue = [] specialeventsStrings = [] # extract events from older JSON format try: eventtypes = [ "blowerSetting", "drumSpeedSetting", "--", "inductionPowerSetting" ] for j in range(len(eventtypes)): eventname = eventtypes[j] if eventname != "--": last = None ip = data[eventname] for i in range(len(ip)): v = ip[i] + 1 if last is None or last != v: specialevents.append(i) specialeventstype.append(j) specialeventsvalue.append(v) specialeventsStrings.append("") last = v except: pass # extract events from newer JSON format try: for action in data["actions"]["actionTimeList"]: time_idx = action["index"] - 1 value = action["value"] + 1 if action["ctrlType"] == 0: event_type = 3 elif action["ctrlType"] == 1: event_type = 0 elif action["ctrlType"] == 2: event_type = 1 specialevents.append(time_idx) specialeventstype.append(event_type) specialeventsvalue.append(value) specialeventsStrings.append(str(value)) except: pass if len(specialevents) > 0: res["specialevents"] = specialevents res["specialeventstype"] = specialeventstype res["specialeventsvalue"] = specialeventsvalue res["specialeventsStrings"] = specialeventsStrings if (ror is not None and len(ror) == len(tx)) or (et is not None and len(et) == len(tx)): # add one (virtual) extra device res["extradevices"] = [25] res["extratimex"] = [tx] temp3_visibility = True temp4_visibility = False if et is not None and len(et) == len(tx): res["extratemp1"] = [et] else: res["extratemp1"] = [[-1] * len(tx)] temp3_visibility = False if ror is not None and len(ror) == len(tx): res["extratemp2"] = [ror] else: res["extratemp2"] = [[-1] * len(tx)] temp4_visibility = False res["extraname1"] = ["Exhaust"] res["extraname2"] = ["RoR"] res["extramathexpression1"] = [""] res["extramathexpression2"] = [""] res["extraLCDvisibility1"] = [temp3_visibility] res["extraLCDvisibility2"] = [temp4_visibility] res["extraCurveVisibility1"] = [temp3_visibility] res["extraCurveVisibility2"] = [temp4_visibility] res["extraDelta1"] = [False] res["extraDelta2"] = [True] res["extraFill1"] = [False] res["extraFill2"] = [False] res["extradevicecolor1"] = ['black'] res["extradevicecolor2"] = ['black'] res["extramarkersizes1"] = [6.0] res["extramarkersizes2"] = [6.0] res["extramarkers1"] = [None] res["extramarkers2"] = [None] res["extralinewidths1"] = [1.0] res["extralinewidths2"] = [1.0] res["extralinestyles1"] = ['-'] res["extralinestyles2"] = ['-'] res["extradrawstyles1"] = ['default'] res["extradrawstyles2"] = ['default'] return res except: # import traceback # import sys # traceback.print_exc(file=sys.stdout) return {}
def extractProfileCropsterXLS(file, _): res = {} # the interpreted data set book = xlrd.open_workbook(file) sheet_names = book.sheet_names() id_tag_trans = [ "Id-Tag", # EN "ID-Tag", # DE "Etiqueta de identificaci\u00f3n", # ES "N\u00b0 d'identification", # FR "Codice ID", # IT "ID-Tag", # PT "ID-\u0442ег", # RU "ID-Tag", # KO "ID-\u30bf\u30b0", # JP "ID\uff0d\u6807\u7b7e", # CN simplified "\u7de8\u865f\u0020\u002d\u0020\u6a19\u7c64" ] # CN traditional date_trans = [ "Date", # EN "Datum", # DE "Fecha", # ES "Date", # FR "Data", # IT, PT "\u0414\u0430\u0442\u0430", # RU "\ub0a0\uc9dc", # KO "\u65e5\u4ed8", # JP "\u65e5\u671f", # CN simplified "\u65e5\u671f", # CN traditional ] # list of Artisan tags for strings associated to tuple of (fixed) column nr (or Null) and list of tag translations string_tag_labels_trans = [ ( 'beans', 1, [ "Lot name", # EN "Chargenname", # DE "Nombre del lote", # ES "Nom du lot", # FR "Nome lotto", # IT "Nome do lote", # PT "\u0418\u043c\u044f\u0020\u043b\u043e\u0442\u0430", # RU "Lot \uc774\ub984", # KO "\u30ed\u30c3\u30c8\u540d", # JP "\u6279\u6b21\u540d\u79f0", # CN Simplified "\u6279\u6b21\u540d\u7a31", # CN Traditional ]), ( 'title', 2, [ "Profile", # EN "Profil", # DE, FR "Perfil", # ES, pT "Profilo", # IT "\u041f\u0440\u043e\u0444\u0438\u043b\u044c", # RU "\ud504\ub85c\ud30c\uc77c", # KO "\u30d7\u30ed\u30d5\u30a1\u30a4\u30eb", # JP "\u66f2\u7ebf\u6863\u6848", # CN Simplified "\u66f2\u7dda\u6a94\u6848", # CN Traditional ]), ( 'roastertype', 3, [ "Machine", # EN, FR "Maschine", # DE "Tostador", # ES "Macchina", # IT "M\u00e1quina", # PT "\u041c\u0430\u0448\u0438\u043d\u0430", # RU "\uba38\uc2e0", # KO "\u6a5f\u68b0", # JP "\u70d8\u7119\u673a", # CN Simplified "\u70d8\u7119\u6a5f", # CN Traditional ]), ( 'operator', 4, [ "Roast technician", # EN "R\u00f6sttechniker", # DE "T\u00e9cnico de tueste", # ES "Torr\u00e9facteur", # FR "Addetto alla tostatura", # IT "Mestre de torra", # PT "\u041e\u0431\u0436\u0430\u0440\u0449\u0438\u043a", # RU "\ub85c\uc2a4\ud130", # KO "\u30ed\u30fc\u30b9\u30c8\u30c6\u30af\u30cb\u30b7\u30e3\u30f3", # JP "\u70d8\u7119\u5e08", # CN Simplified "\u70d8\u7119\u5e2b", # CN Traditional ]), ( 'cuppingnotes', None, [ "Sensorial notes", # EN "Sensorische Notizen", # DE "Anotaciones sobre el an\u00e1lisis sensorial", # ES "Commentaires sensoriels", # FR "Note sensoriali", # IT "Observa\u00e7\u00f5es sensoriais", # PT "\u041f\u0440\u0438\u043c\u0435\u0447\u0430\u043d\u0438\u044f\u0020\u043a\u0020\u0430\u043d\u0430\u043b\u0438\u0437\u0443\u0020\u0432\u043a\u0443\u0441\u043e\u0432\u044b\u0445\u0020\u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u0438\u0441\u0442\u0438\u043a", # RU "\uc13c\uc11c\ub9ac\u0020\uba54\ubaa8", # KO "\u77e5\u899a\u30e1\u30e2", # JP "\u611f\u5b98\u7279\u5f81\u9644\u6ce8", # CN Simplified "\u611f\u5b98\u7279\u5fb5\u9644\u8a3b", # CN Traditional ]), ( 'roastingnotes', None, [ "Roasting notes", # EN "R\u00f6st-Notizen", # DE "Anotaciones sobre el tostado", # ES "Commentaires de torr\u00e9faction", # FR "Note sulla tostatura", # IT "Observa\u00e7\u00f5es da torrefa\u00e7\u00e3o", # PT "\u041f\u0440\u0438\u043c\u0435\u0447\u0430\u043d\u0438\u044f\u0020\u043a\u0020\u043e\u0431\u0436\u0430\u0440\u043a\u0435", # RU "\ub85c\uc2a4\ud305\u0020\uba54\ubaa8", # KO "\u30ed\u30fc\u30b9\u30c6\u30a3\u30f3\u30b0\u30ce\u30fc\u30c8", # JP "\u70d8\u7119\u7b14\u8bb0", # CN Simplified "\u70d8\u7119\u7b46\u8a18", # CN Traditional ]) ] ambient_temp_trans = [ "Ambient temp.", # EN "Raumtemp.", # DE "Temperatura ambiente", # ES "Temp. ambiante", # FR, IT, PT "\u0422\u0435\u043c\u043f\u002e\u0020\u043e\u043a\u0440\u0443\u0436\u0430\u044e\u0449\u0435\u0433\u043e\u0020\u0432\u043e\u0437\u0434\u0443\u0445\u0430", # RU "\uc8fc\ubcc0\u0020\uc628\ub3c4", # KO "\u30a2\u30f3\u30d3\u30a8\u30f3\u30c8\u6e29\u5ea6", # JP "\u5ba4\u5185\u6e29\u5ea6", # CN Simplified "\u5ba4\u5167\u6eab\u5ea6", # CN Traditional ] start_weight_trans = [ "Start weight", # EN "Startgewicht", # DE "Peso inicial", # ES, PT "Poids initial", # FR "Peso iniziale", # IT "\u041d\u0430\u0447\u0430\u043b\u044c\u043d\u044b\u0439\u0020\u0432\u0435\u0441", # RU "\uc2dc\uc791\u0020\ubb34\uac8c", # KO "\u958b\u59cb\u91cd\u91cf", # JP "\u5f00\u59cb\u91cd\u91cf", # CN Simplified "\u958b\u59cb\u91cd\u91cf", # CN Traditional ] start_weight_unit_trans = [ "Start weight unit", # EN "Einheit Startgewicht", # DE "Unidad de peso inicial", # ES "Unit\u00e9 poids initial", # FR "Unit\u00e0 peso iniziale", # IT "Unidade do peso inicial", # PT "\u0415\u0434\u0438\u043d\u0438\u0446\u0430\u0020\u0438\u0437\u043c\u0435\u0440\u0435\u043d\u0438\u044f\u0020\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u0433\u043e\u0020\u0432\u0435\u0441\u0430", # RU "\uc2dc\uc791\u0020\ubb34\uac8c\u0020\ub2e8\uc704", # KO "\u958b\u59cb\u91cd\u91cf\u30e6\u30cb\u30c3\u30c8", # JP "\u5f00\u59cb\u91cd\u91cf\u5355\u4f4d", # CN Simplified "\u958b\u59cb\u91cd\u91cf\u55ae\u4f4d", # CN Traditional ] end_weight_trans = [ "End weight", # EN "Endgewicht", # DE "Peso final", # ES, PT "Poids final", # FR "Peso finale", # IT "\u041a\u043e\u043d\u0435\u0447\u043d\u044b\u0439\u0020\u0432\u0435\u0441", # RU "\uc885\ub8cc\u0020\ubb34\uac8c", # KO "\u958b\u59cb\u91cd\u91cf", # JP "\u7ed3\u675f\u91cd\u91cf", # CN Simplified "\u7d50\u675f\u91cd\u91cf", # CN Traditional ] end_weight_unit_trans = [ "End weight unit", # EN "Einheit Endgewicht", # DE "Unidad de peso final", # ES "Unit\u00e9 poids final", # FR "Unit\u00e0 peso finale", # IT "Unidade de peso final", # PT "\u0415\u0434\u0438\u043d\u0438\u0446\u0430\u0020\u0438\u0437\u043c\u0435\u0440\u0435\u043d\u0438\u044f\u0020\u043a\u043e\u043d\u0435\u0447\u043d\u043e\u0433\u043e\u0020\u0432\u0435\u0441\u0430", # RU "\uc885\ub8cc\u0020\ubb34\uac8c\u0020\ub2e8\uc704", # KO "\u958b\u59cb\u91cd\u91cf\u30e6\u30cb\u30c3\u30c8", # JP "\u7ed3\u675f\u91cd\u91cf\u5355\u4f4d", # CN Simplified "\u7d50\u675f\u91cd\u91cf\u55ae\u4f4d", # CN Traditional ] turning_point_trans = [ "Turning point", # EN "Wendepunkt", # DE "Temperatura de fondo", # ES "Point de balance", # FR "Punto di flesso", # IT "Temperatura de fundo", # PT "\u041f\u043e\u0432\u043e\u0440\u043e\u0442\u043d\u0430\u044f\u0020\u0442\u043e\u0447\u043a\u0430", # RU "\ud130\ub2dd\u0020\ud3ec\uc778\ud2b8", # KO "\u30bf\u30fc\u30cb\u30f3\u30b0\u30dd\u30a4\u30f3\u30c8", # JP "\u56de\u6e29\u70b9", # CN Simplified "\u56de\u6e29\u9ede", # CN Trraditional ] color_change_trans = [ "Color change", # EN "Farb\u00e4nderung", # DE "Cambio de color", # ES "Changement de couleur", # FR "Cambiamento di colore", # IT "Mudan\u00e7a de cor", # PT "\u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435\u0020\u0446\u0432\u0435\u0442\u0430", # RU "\uc0c9\u0020\ubcc0\ud654", # KO "\u8272\u306e\u5909\u5316", # JP "\u989c\u8272\u53d8\u5316", # CN Simplified "\u984f\u8272\u8b8a\u5316", # CN Traditional ] first_crack_trans = [ "First crack", # EN "1. Crack", # DE "Primer crac", # ES "Premier crack", # FR "Primo Crack", # IT "Primeiro crack", # PT "\u041f\u0435\u0440\u0432\u044b\u0439\u0020\u0442\u0440\u0435\u0441\u043a", # RU "\u0031\ucc28\u0020\ud06c\ub799", # KO "\uff11\u30cf\u30bc", # JP "\u4e00\u7206", # CN Simplified, Traditional ] second_crack_trans = [ "Second crack", # EN, FR "2. Crack", # DE "Segundo crac", # ES "Secondo Crack", # IT "Segundo crack", # PT "\u0412\u0442\u043e\u0440\u043e\u0439\u0020\u0442\u0440\u0435\u0441\u043a", # RU "\u0032\ucc28\u0020\ud06c\ub799", # KO "\uff12\u30cf\u30bc", # JP "\u4e8c\u7206", # CN Simplified, Traditional ] gas_trans = [ "Gas", # EN, DE, ES, IT "Gaz", # FR "G\u00e1s", # PT "\u0413\u0430\u0437", # RU "\uac00\uc2a4", # KO "\u30ac\u30b9", # JP "\u706b\u529b", # CN Timplified, Traditional ] airflow_trans = [ "Airflow", # EN, DE "Flujo de aire", # ES "Arriv\u00e9e d'air", # FR "Flusso d'aria", # IT "Fluxo de ar", # PT "\u0412\u043e\u0437\u0434\u0443\u0448\u043d\u044b\u0439\u0020\u043f\u043e\u0442\u043e\u043a", # RU "\uacf5\uae30\u0020\ud750\ub984", # KO "\u7a7a\u6c17\u306e\u6d41\u308c", # JP "\u98ce\u95e8", # CN Simplified "\u98a8\u9580", # CN Traditional ] comment_trans = [ "Comment", # EN "Kommentar", # DE "Comentar", # ES "Commentaire", # FR "Commento", # IT "Coment\u00e1rio", # PT "\u041a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439", # RU "\ucf54\uba58\ud2b8", # KO "\u30b3\u30e1\u30f3\u30c8", # JP "\u5907\u6ce8", # CN Simplified "\u5099\u8a3b", # CN Traditional ] # standard curves curve_bt_trans = [ "Curve - Bean temp.", # EN "Kurve - Bohnentemp.", # DE "Curva - Temp. del grano", # ES (potentially wrong) "Courbe Temp. grain", # FR "Curva - Temp. chicco", # IT "Curva - Temp. do gr\u00e3o", # PT "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0422\u0435\u043c\u043f\u002e\u0020\u0437\u0435\u0440\u0435\u043d", # RU "\ucee4\ube0c\u0020\u002d\u0020\ube48\u0020\uc628\ub3c4", # KO "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u8c46\u306e\u6e29\u5ea6\u3002", # JP "\u66f2\u7ebf\u0020\u002d\u0020\u8c46\u6e29", # CN Simplified "\u66f2\u7dda\u0020\u002d\u0020\u8c46\u6eab", # CH Traditional ] curve_et_trans = [ # not that we map Exhaust to ET and not Env. Temp. as it is available more often; Env. Temp. is mapped to an extra device curve if available "Curve - Exhaust temp.", # EN "Kurve - Ablufttemp.", # DE "Curva - Temp. de salida", # ES (potentially wrong) "Courbe Temp. \u00e9chappement", # FR "Curva - Temp. fumi", # IT "Curva - Temp. de exaust\u00e3o", # PT "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0422\u0435\u043c\u043f\u002e\u0020\u043d\u0430\u0020\u0432\u044b\u0445\u043e\u0434\u0435", # RU "\ucee4\ube0c\u0020\u002d\u0020\ubc30\uae30\u0020\uc628\ub3c4", # KO "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u6392\u6c17\u6e29\u5ea6\u3002", # JP "\u66f2\u7ebf\u0020\u002d\u0020\u6392\u98ce\u6e29", # CN Simplified, Traditional ] # extra temperature curves (C-F conversion applicable) curve_env_temp_trans = [ "Curve - Env. temp.", # EN "Kurve - Umgebungstemp.", # DE "Curva - Temp. ambiente", # ES "Courbe Temp. env.", # FR "Curva - Temp. aria in tamburo", # IT "Curva - Temp. ambiente", # PT "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0422\u0435\u043c\u043f\u002e\u0020\u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f", # RU "\ucee4\ube0c\u0020\u002d\u0020\uc8fc\ubcc0\u0020\uc628\ub3c4", # KO "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u5468\u56f2\u6e29\u5ea6", # JP "\u66f2\u7ebf\u0020\u002d\u0020\u7089\u6e29", # CN Simplified "\u66f2\u7dda\u0020\u002d\u0020\u7210\u6eab", # CN Traditional ] curve_burner_temp_trans = [ "Curve - Burner temp.", # EN "Kurve - Brennertemp.", # DE "Curva - Temp. del quemador", # ES "Courbe Temp. br\u00fbleur", # FR "Curva - Temp. bruciatore", # IT "Curva - Temp. do queimador", # PT "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0422\u0435\u043c\u043f\u002e\u0020\u0433\u043e\u0440\u0435\u043b\u043a\u0438", # RU "\ucee4\ube0c\u0020\u002d\u0020\ubc84\ub108\u0020\uc628\ub3c4", # KO "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u30d0\u30fc\u30ca\u30fc\u6e29\u5ea6", # JP "\u66f2\u7ebf\u0020\u002d\u0020\u71c3\u70df\u5668\u6e29\u5ea6", # CN Simplified "\u66f2\u7dda\u0020\u002d\u0020\u71c3\u7159\u5668\u6eab\u5ea6", # CN Traditional ] curve_other_temp_trans = [ "Curve - Other temp.", # EN "Kurve - Andere Temp.", # DE "Curva - Otras temperaturas", # ES "Courbe Temp. autre", # FR "Curva - Altra temp.", # IT "Curva - Outra temp.", # PT "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0414\u0440\u0443\u0433\u0430\u044f\u0020\u0442\u0435\u043c\u043f.", # RU "\ucee4\ube0c\u0020\u002d\u0020\uae30\ud0c0\u0020\uc628\ub3c4\u002e", # KO "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u4ed6\u306e\u6e29\u5ea6", # JP "\u66f2\u7ebf\u0020\u002d\u0020\u5176\u5b83\u6e29\u5ea6", # CN Simplified "\u66f2\u7dda\u0020\u002d\u0020\u5176\u5b83\u6eab\u5ea6", # CN Traditional ] curve_stack_temp_trans = [ "Curve - Stack temp.", # EN "Kurve - Schornsteintemp.", # DE "Curva - Temp. del tiro", # ES "Courbe Temp. broche", # FR "Curva - Temp. camino", # IT "Curva - Temp. do escoamento de", # PT "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0422\u0435\u043c\u043f\u002e\u0020\u0432\u0020\u0434\u044b\u043c\u043e\u0432\u043e\u0439\u0020\u0442\u0440\u0443\u0431\u0435", # RU "\ucee4\ube0c\u0020\u002d\u0020\uc2a4\ud0dd\u0020\uc628\ub3c4", # KO "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u30b9\u30bf\u30c3\u30af\u6e29\u5ea6", # JP "\u66f2\u7ebf\u0020\u002d\u0020\u70df\u56f1\u6e29\u5ea6", # CN Simplified "\u66f2\u7dda\u0020\u002d\u0020\u7159\u56ea\u6eab\u5ea6", # CN Traditional ] curve_return_temp_trans = [ "Curve - Return temp.", # EN "Kurve - R\u00fccklauftemp.", # DE "Curva - Temp. de retorno", # ES "Courbe Temp. retour", # FR "Curva - Temp. di ritorno", # IT "Curva - Temperatura de Retorno", # PT "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0422\u0435\u043c\u043f\u002e\u0020\u0432\u043e\u0437\u0432\u0440\u0430\u0442\u0430", # RU "\ucee4\ube0c\u0020\u002d\u0020\ubc30\uae30\u0020\uc628\ub3c4", # KO "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u623b\u308a\u6e29\u5ea6\u3002", # JP "\u66f2\u7ebf\u0020\u002d\u0020\u7a7a\u6c14\u56de\u7089\u6e29\u5ea6", # CN Simplified "\u66f2\u7dda\u0020\u002d\u0020\u7a7a\u6c23\u56de\u7210\u6eab\u5ea6", # CN Traditional ] curve_inlet_temp_trans = [ "Curve - Inlet temp.", # EN "Kurve - Einlasstemp.", # DE "Curva - Temp. de entrada", # ES "Courbe Temp. entr\u00e9e", # FR "Curva - Temp. in ingresso", # IT "Curva - Temp. do ar de entrada", # PT "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0422\u0435\u043c\u043f\u002e\u0020\u043d\u0430\u0020\u0432\u0445\u043e\u0434\u0435", # RU "\ucee4\ube0c\u0020\u002d\u0020\ud761\uae30\u0020\uc628\ub3c4", # KO "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u5438\u6c17\u53e3\u6e29\u5ea6", # JP "\u66f2\u7ebf\u0020\u002d\u0020\u8fdb\u98ce\u6e29", # CN Simplified "\u66f2\u7dda\u0020\u002d\u0020\u9032\u98a8\u6eab", # CN Traditional ] curve_afterburner_temp_trans = [ "Curve - Afterburner temp.", # EN "Kurve - Nachbrennertemp.", # DE "Curva - Temp. del posquemador", # ES "Courbe Temp. post-combustion", # FR "Curva - Temp. bruciafumi", # IT "Curva - Temp. p\u00f3s-combust\u00e3o", # PT "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0422\u0435\u043c\u043f\u002e\u0020\u0432\u0020\u0444\u043e\u0440\u0441\u0430\u0436\u043d\u043e\u0439\u0020\u043a\u0430\u043c\u0435", # RU "\ucee4\ube0c\u0020\u002d\u0020\uc560\ud504\ud130\ubc84\ub108\u0020\uc628\ub3c4", # KO "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u30a2\u30d5\u30bf\u30fc\u30d0\u30fc\u30ca\u30fc\u6e29\u5ea6\u3002", # JP "\u66f2\u7ebf\u0020\u002d\u0020\u540e\u7f6e\u71c3\u70df\u5668\u6e29\u5ea6", # CN Simplified "\u66f2\u7dda\u0020\u002d\u0020\u5f8c\u7f6e\u71c3\u7159\u5668\u6eab\u5ea6", # CN Traditional ] curve_drum_temp_trans = [ "Curve - Drum temp.", # EN "Kurve - Trommeltemp.", # DE "Curva - Temp. del Tambor", # EE "Courbe Temp. tambour", # FR "Curva - Temp. tamburo", # IT "Curva - Temperatura do Tambor", # PT "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0422\u0435\u043c\u043f\u002e\u0020\u0431\u0430\u0440\u0430\u0431\u0430\u043d\u0430", # RU "\ucee4\ube0c\u0020\u002d\u0020\ub4dc\ub7fc\u0020\uc628\ub3c4", # KO "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u30c9\u30e9\u30e0\u6e29\u5ea6\u3002", # JP "\u66f2\u7ebf\u0020\u002d\u0020\u9505\u7089\u6e29\u5ea6", # CN Simplified "\u66f2\u7dda\u0020\u002d\u0020\u934b\u7210\u6eab\u5ea6", # CN Traditional ] # extra non-temperature curves (no temperature conversion) curve_gas_control_trans = [ "Curve - Gas control", # EN "Kurve - Gas-Kontrolle", # DE "Curva - Control del gas", # ES "Courbe R\u00e9gulation du d\u00e9bit de g", # FR "Curva - Controllo gas", # IT "Curva - Controle de g\u00e1s", # PT "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u0020\u0433\u0430\u0437\u043e\u043c", # RU "\ucee4\ube0c\u0020\u002d\u0020\uac00\uc2a4\u0020\uc81c\uc5b4", # KO "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u30ac\u30b9\u30b3\u30f3\u30c8\u30ed\u30fc\u30eb", # JP "\u66f2\u7ebf\u0020\u002d\u0020\u706b\u529b\u63a7\u5236", # CN Simplified "\u66f2\u7dda\u0020\u002d\u0020\u706b\u529b\u63a7\u5236", # CN Traditional ] curve_drum_speed_trans = [ "Curve - Drum speed", # EN "Kurve - Trommelgeschw.", # DE "Curva - Velocidad del tambor", # ES "Courbe Vitesse du tambour", # FR "Curva - Velocit\u00e0 tamburo", # IT "Curva - Velocidade do tambor", # PT "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0421\u043a\u043e\u0440\u043e\u0441\u0442\u044c\u0020\u0431\u0430\u0440\u0430\u0431\u0430\u043d\u0430", # RU "\ucee4\ube0c\u0020\u002d\u0020\ub4dc\ub7fc\u0020\uc18d\ub3c4", # KO "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u30c9\u30e9\u30e0\u30b9\u30d4\u30fc\u30c9", # JP "\u66f2\u7ebf\u0020\u002d\u0020\u8f6c\u901f", # CN Simplified "\u66f2\u7dda\u0020\u002d\u0020\u8f49\u901f", # CN Traditional ] curve_airflow_trans = [ "Curve - Airflow", # EN "Kurve - Airflow", # DE "Curva - Flujo de aire", # ES "Courbe Arriv\u00e9e d'air", # FR "Curva - Flusso d'aria", # IT "Curva - Fluxo de ar", # PT "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0412\u043e\u0437\u0434\u0443\u0448\u043d\u044b\u0439\u0020\u043f\u043e\u0442\u043e\u043a", # RU "\ucee4\ube0c\u0020\u002d\u0020\uacf5\uae30\u0020\ud750\ub984", # KO "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u7a7a\u6c17\u306e\u6d41\u308c", # JP "\u66f2\u7ebf\u0020\u002d\u0020\u98ce\u95e8", # CN Simplified "\u66f2\u7dda\u0020\u002d\u0020\u98a8\u9580", # CN Traditional ] curve_gas_trans = [ "Curve - Gas", # EN "Kurve - Gas", # DE "Curva - Gas", # ES "Courbe Gaz", # FR "Curva - Gas", # IT "Curva - G\u00e1s", # PT "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0413\u0430\u0437", # RU "\ucee4\ube0c\u0020\u002d\u0020\uac00\uc2a4", # KO "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u30ac\u30b9", # JP "\u66f2\u7ebf\u0020\u002d\u0020\u706b\u529b", # CN Simplified "\u66f2\u7dda\u0020\u002d\u0020\u706b\u529b", # CN Traditional ] # curve_gas_comments_trans = [ # "Curve - Gas comments", # EN # "Kurve - Kommentare Gas", # DE # "Curva - Comentarios sobre el gas", # ES # "Courbe Commentaires de type Gaz", # FR # "Curva - Commenti sul Gas", # IT # "Curva - Coment\u00e1rios do g\u00e1s", # PT # "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u041a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438\u0020\u043f\u043e\u0020\u043f\u043e\u0432\u043e\u0434\u0443", # RU # "\ucee4\ube0c\u0020\u002d\u0020\uac00\uc2a4\u0020\ucf54\uba58\ud2b8", # KO # "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u30ac\u30b9\u306b\u3064\u3044\u3066\u306e\u30b3\u30e1\u30f3\u30c8", # JP # "\u66f2\u7ebf\u0020\u002d\u0020\u706b\u529b\u5907\u6ce8", # CN Simplified # "\u66f2\u7dda\u0020\u002d\u0020\u706b\u529b\u5099\u8a3b", # CN Traditional # ] curve_drum_pressure_trans = [ "Curve - Drum pressure", # EN "Kurve - Trommeldruck", # DE "Curva - Presi\u00f3n del tambor", # ES "Courbe Pression du tambour", # FR "Curva - Pressione tamburo", # IT "Curva - Press\u00e3o do tambor", # PT "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0414\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u0020\u0432\u0020\u0431\u0430\u0440\u0430\u0431\u0430\u043d\u0435", # RU "\ucee4\ube0c\u0020\u002d\u0020\ub4dc\ub7fc\u0020\uc555\ub825", # KO "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u30c9\u30e9\u30e0\u5727\u529b", # JP "\u66f2\u7ebf\u0020\u002d\u0020\u7089\u538b", # CN Simplified "\u66f2\u7dda\u0020\u002d\u0020\u7210\u58d3", # CN Traditional ] curve_airflow_control_trans = [ "Curve - Airflow control", # EN "Kurve - Airflow-Steuerung", # DE "Curva - Regulaci\u00f3n del caudal de aire", # ES "Courbe Contr\u00f4le de la ventilati", # FR "Curva - Controllo del flusso d'", # IT: "Curva - Controllo del flusso d'aria" "Curva - Controle de fluxo de ar", # PT "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u041a\u043e\u043d\u0442\u0440\u043e\u043b\u044c\u0020\u0432\u043e\u0437\u0434\u0443\u0448\u043d\u043e\u0433\u043e\u0020\u043f\u043e", # RU "\ucee4\ube0c\u0020\u002d\u0020\u0041\u0069\u0072\u0066\u006c\u006f\u0077\u0020\u0063\u006f\u006e\u0074\u0072\u006f\u006c", # KO "\u30ab\u30fc\u30d6\u0020\u002d\u0020\u7a7a\u6c17\u306e\u6d41\u308c\u306e\u7ba1\u7406", # JP "\u66f2\u7ebf\u0020\u002d\u0020\u98ce\u95e8\u63a7\u5236", # SN Simplified "\u66f2\u7dda\u0020\u002d\u0020\u0041\u0069\u0072\u0066\u006c\u006f\u0077\u0020\u0063\u006f\u006e\u0074\u0072\u006f\u006c", # SN Traditional ] curve_fan_speed_trans = [ "Curve - fanSpeed", # EN "Kurve - fanSpeed", # DE #"Curva - Velocidad del ventilador", # ES "Courbe fanSpeed", # FR "Curva - fanSpeed", # IT, ES "Curva - fanSpeed", # PT "\u041a\u0440\u0438\u0432\u0430\u044f - fanSpeed", # RU "\ucee4\ube0c - fanSpeed", # KO "\u30ab\u30fc\u30d6 - fanSpeed", # JP "\u66f2\u7ebf - fanSpeed", # CN Simplified "\u66f2\u7dda - fanSpeed", # CN Traditional ] #### extra_temp_curve_trans = \ curve_env_temp_trans + \ curve_burner_temp_trans + \ curve_other_temp_trans + \ curve_stack_temp_trans + \ curve_return_temp_trans + \ curve_inlet_temp_trans + \ curve_afterburner_temp_trans + \ curve_drum_temp_trans extra_nontemp_curve_trans = \ curve_gas_control_trans + \ curve_drum_speed_trans + \ curve_airflow_trans + \ curve_gas_trans + \ curve_drum_pressure_trans + \ curve_airflow_control_trans + \ curve_fan_speed_trans #curve_gas_comments_trans curve_prefixa = [ # Curve + Temp "Curva - Temp.", # ES (potentially wrong) "Courbe Temp.", # FR "Curva - Temp.", # IT "Curva - Temp.", # PT "\u041a\u0440\u0438\u0432\u0430\u044f\u0020\u002d\u0020\u0422\u0435\u043c\u043f\u002e\u0020", # RU # just Curve "Curve -", # EN "Kurve -", # DE "Curva -", # ES, IT, PT "Courbe", # FR "\u041a\u0440\u0438\u0432\u0430\u044f -", # RU "\ucee4\ube0c -", # KO "\u30ab\u30fc\u30d6 -", # JP "\u66f2\u7ebf -", # CN Simplified "\u66f2\u7dda -", # CN Traditional ] curve_postfixa = [ "temp.", # EN "temp.", # DE "\u0020\uc628\ub3c4", # KO "\u6e29\u5ea6\u3002", # JP "\u6e29\u5ea6", # CN Simplified & Tranitional ] ########## # extract general profile information general_sh = book.sheet_by_index(0) if general_sh.nrows >= 1: row1 = general_sh.row(1) general_data = dict(zip([x.value for x in general_sh.row(0)], row1)) res["samplinginterval"] = 1.0 try: id_tag_value = None # try to find the "Id-Tag" value # 1. test the column name in all known translations for tag in id_tag_trans: if tag in general_data: id_tag_value = general_data[tag].value break # 2. take the first value of row1 if id_tag_value is None and len(row1) > 0: id_tag_value = row1[0].value if id_tag_value is not None: batch_prefix = id_tag_value.rstrip('0123456789') batch_number = int(id_tag_value[len(batch_prefix):]) res["roastbatchprefix"] = batch_prefix res["roastbatchnr"] = batch_number except: pass for (tag, pos, trans) in string_tag_labels_trans: value = None try: # 1. test the column name in all known translations for tr in trans: if tr in general_data: value = general_data[tr].value break # 2. take the pos column value of row1 if value is None and pos is not None and len(row1) > pos: value = row1[pos].value if value is not None: res[tag] = encodeLocal(value) except: pass try: date_tag_value = None # try to find the "Date" value # 1. test the column name in all known translations for tag in date_trans: if tag in general_data: date_tag_value = general_data[tag].value break # 2. take the first value of row1 if date_tag_value is None: date_tag_value = row1[6].value if date_tag_value is not None: date_tuple = xlrd.xldate_as_tuple(date_tag_value, book.datemode) date = QDateTime(*date_tuple) if date.isValid(): res["roastdate"] = encodeLocal(date.date().toString()) res["roastisodate"] = encodeLocal(date.date().toString( Qt.ISODate)) res["roasttime"] = encodeLocal(date.time().toString()) res["roastepoch"] = int(date.toTime_t()) res["roasttzoffset"] = libtime.timezone except: pass try: ambient_tag_value = None # try to find the "Date" value # test the column name in all known translations for tag in ambient_temp_trans: if tag in general_data: ambient_tag_value = general_data[tag].value break if ambient_tag_value is None and len(row1) > 27: ambient_tag_value = row1[27] if ambient_tag_value is not None: res['ambientTemp'] = float(ambient_tag_value) except: pass try: start_weight_tag_value = None start_weight_unit_tag_value = None end_weight_tag_value = None end_weight_unit_tag_value = None # try to find the "Start weight" value # test the column name in all known translations for tag in start_weight_trans: if tag in general_data: start_weight_tag_value = general_data[tag].value break if start_weight_tag_value is None and len(row1) > 9: start_weight_tag_value = row1[9] # try to find the "Start weight unit" value # test the column name in all known translations for tag in start_weight_unit_trans: if tag in general_data: start_weight_unit_tag_value = general_data[tag].value break if start_weight_unit_tag_value is None and len(row1) > 10: start_weight_unit_tag_value = row1[10] # try to find the "End weight" value # test the column name in all known translations for tag in end_weight_trans: if tag in general_data: end_weight_tag_value = general_data[tag].value break if end_weight_tag_value is None and len(row1) > 11: end_weight_tag_value = row1[11] # try to find the "End weight unit" value # test the column name in all known translations for tag in end_weight_unit_trans: if tag in general_data: end_weight_unit_tag_value = general_data[tag].value break if end_weight_unit_tag_value is None and len(row1) > 12: end_weight_unit_tag_value = row1[12] if start_weight_tag_value is not None and end_weight_tag_value is not None: cropster_weight_units = ["G", "KG", "LBS", "OZ"] artisan_weight_units = ["g", "Kg", "lb", "oz"] weight = [0, 0, artisan_weight_units[0]] try: if end_weight_unit_tag_value is not None: idx = cropster_weight_units.index( end_weight_unit_tag_value) weight[2] = artisan_weight_units[idx] except: pass try: if start_weight_unit_tag_value: idx = cropster_weight_units.index( start_weight_unit_tag_value) weight[2] = artisan_weight_units[idx] except: pass try: if start_weight_tag_value is not None: weight[0] = start_weight_tag_value except: pass try: if end_weight_tag_value is not None: weight[1] = end_weight_tag_value except: pass res["weight"] = weight except: pass res["timex"] = [] res["temp1"] = [] res["temp2"] = [] # BT: BT_idx = None ET_idx = None try: for i, sn in enumerate(sheet_names): if sn in curve_bt_trans: BT_idx = i BT_sh = book.sheet_by_index(BT_idx) if BT_sh.ncols >= 1: time = BT_sh.col(0) temp = BT_sh.col(1) if len(time) > 0 and len(temp) > 0 and len(time) == len( temp): if "FAHRENHEIT" in str(temp[0].value): res["mode"] = 'F' else: res["mode"] = 'C' res["timex"] = [t.value for t in time[1:]] res["temp2"] = [t.value for t in temp[1:]] res["temp1"] = [-1] * len(res["timex"]) res["timeindex"] = [ 0, 0, 0, 0, 0, 0, len(res["timex"]) - 1, 0 ] break except: pass # ET try: for et_trans in [ curve_env_temp_trans, curve_et_trans, curve_return_temp_trans, curve_burner_temp_trans ]: for i, sn in enumerate(sheet_names): if sn in et_trans: ET_idx = i ET_sh = book.sheet_by_index(ET_idx) if ET_sh.ncols >= 1: time = ET_sh.col(0) temp = ET_sh.col(1) if len(time) > 0 and len(temp) > 0 and len( time) == len(temp): if "FAHRENHEIT" in str(temp[0].value): res["mode"] = 'F' else: res["mode"] = 'C' res["temp1"] = [t.value for t in temp[1:]] if len(res["timex"]) != len(res["temp1"]): res["timex"] = [t.value for t in time[1:]] if "temp2" not in res or len(res["temp2"]) != len( res["timex"]): res["temp2"] = [-1] * len(res["timex"]) res["timeindex"] = [ 0, 0, 0, 0, 0, 0, len(res["timex"]) - 1, 0 ] break if ET_idx is not None: break except: pass # extra temperature curves (only if ET or BT and its corresponding timex was already parsed successfully) if len(res["timex"]) > 0: channel = 1 # toggle between channel 1 and 2 to be filled with extra temperature curve data for CT_idx, sn in enumerate(sheet_names): sn = sn.strip() if CT_idx != BT_idx and CT_idx != ET_idx: # all but temp and non-temp curves but for the already processed ET and BT curves if sn in extra_temp_curve_trans: temp_curve = True elif sn in extra_nontemp_curve_trans: temp_curve = False else: continue try: extra_curve_name = sn # we split of the "Curve -" prefix for px in curve_prefixa: if extra_curve_name.startswith(px): sp = extra_curve_name.split(px) if len(sp) > 1: extra_curve_name = sp[1] else: extra_curve_name = sp[0] extra_curve_name = extra_curve_name.strip() break # we split of also the "temp." postfix for px in curve_postfixa: if extra_curve_name.endswith(px): extra_curve_name = extra_curve_name.split( px)[0].strip() break CT_sh = book.sheet_by_index(CT_idx) if CT_sh.ncols >= 1: time = CT_sh.col(0) temp = CT_sh.col(1) if len(time) > 0 and len(temp) > 0 and len( time) == len(temp): if "extradevices" not in res: res["extradevices"] = [] if "extraname1" not in res: res["extraname1"] = [] if "extraname2" not in res: res["extraname2"] = [] if "extratimex" not in res: res["extratimex"] = [] if "extratemp1" not in res: res["extratemp1"] = [] if "extratemp2" not in res: res["extratemp2"] = [] if "extramathexpression1" not in res: res["extramathexpression1"] = [] if "extramathexpression2" not in res: res["extramathexpression2"] = [] if "extraNoneTempHint1" not in res: res["extraNoneTempHint1"] = [] if "extraNoneTempHint2" not in res: res["extraNoneTempHint2"] = [] if channel == 1: channel = 2 if temp_curve: # apply temp conversion res["extraNoneTempHint1"].append(False) else: # no temp conversion res["extraNoneTempHint1"].append(True) res["extradevices"].append( 25) # Virtual Device res["extraname1"].append( encodeLocal(extra_curve_name)) res["extratimex"].append( [t.value for t in time[1:]]) res["extratemp1"].append( [t.value for t in temp[1:]]) res["extramathexpression1"].append("") elif (len(time) - 1) == len( res["extratimex"][-1] ): # only if time lengths is same as of channel 1 channel = 1 if temp_curve: # apply temp conversion res["extraNoneTempHint2"].append(False) else: # no temp conversion res["extraNoneTempHint2"].append(True) res["extraname2"].append( encodeLocal(extra_curve_name)) res["extratemp2"].append( [t.value for t in temp[1:]]) res["extramathexpression2"].append("") except: pass if "extraname1" in res and "extraname2" in res and len( res["extraname1"]) != len(res["extraname2"]): # we add an empty second extra channel if needed res["extraname2"].append("Extra 2") res["extratemp2"].append([-1] * len(res["extratemp1"][-1])) res["extraNoneTempHint2"].append(True) res["extramathexpression2"].append("") # add events try: COMMENTS_idx = 1 try: sheet_names.index("Comments") except: pass COMMENTS_sh = book.sheet_by_index(COMMENTS_idx) gas_event = False # set to True if a Gas event exists airflow_event = False # set to True if an Airflow event exists specialevents = [] specialeventstype = [] specialeventsvalue = [] specialeventsStrings = [] if COMMENTS_sh.ncols >= 4: takeClosest = lambda num, collection: min( collection, key=lambda x: abs(x - num)) for r in range(COMMENTS_sh.nrows): if r > 0: try: time = COMMENTS_sh.cell(r, 0).value comment_type = COMMENTS_sh.cell(r, 2).value.strip() if comment_type not in turning_point_trans: # TP is ignored as it is automatically assigned comment_value = COMMENTS_sh.cell(r, 3).value c = takeClosest(time, res["timex"]) timex_idx = res["timex"].index(c) if comment_type in color_change_trans: res["timeindex"][1] = timex_idx elif comment_type in first_crack_trans: res["timeindex"][2] = timex_idx elif comment_type == "First crack end": res["timeindex"][3] = timex_idx elif comment_type in second_crack_trans: res["timeindex"][4] = timex_idx elif comment_type == "Second crack end": res["timeindex"][5] = timex_idx elif comment_type == "Duration": res["timeindex"][6] = timex_idx else: specialevents.append(timex_idx) ae = False ge = False if comment_type in airflow_trans: ae = True airflow_event = True specialeventstype.append(0) elif comment_type in gas_trans: ge = True gas_event = True specialeventstype.append(3) else: specialeventstype.append(4) try: v = float(comment_value) v = v / 10. + 1 specialeventsvalue.append(v) except: specialeventsvalue.append(0) if not ae and not ge and comment_type not in comment_trans: specialeventsStrings.append( encodeLocal(comment_type)) else: specialeventsStrings.append( encodeLocal(comment_value)) except: pass if len(specialevents) > 0: res["specialevents"] = specialevents res["specialeventstype"] = specialeventstype res["specialeventsvalue"] = specialeventsvalue res["specialeventsStrings"] = specialeventsStrings if gas_event or airflow_event: # first set etypes to defaults res["etypes"] = [ QApplication.translate("ComboBox", "Air", None), QApplication.translate("ComboBox", "Drum", None), QApplication.translate("ComboBox", "Damper", None), QApplication.translate("ComboBox", "Burner", None), "--" ] # update if airflow_event: res["etypes"][0] = "Airflow" if gas_event: res["etypes"][3] = "Gas" except: # import traceback # import sys # traceback.print_exc(file=sys.stdout) pass return res
def extractProfilePetronciniCSV(file, _): res = {} # the interpreted data set res["samplinginterval"] = 1.0 csvFile = io.open(file, 'r', newline="", encoding='utf-8') data = csv.reader(csvFile, delimiter=';') #read file header next(data) # skip "Export path" next(data) # skip path header = [i.strip() for i in next(data)] roast_date = None power = None # holds last processed heater event value power_last = None # holds the heater event value before the last one power_event = False # set to True if a heater event exists specialevents = [] specialeventstype = [] specialeventsvalue = [] specialeventsStrings = [] timex = [] temp1 = [] # outlet temperature as ET temp2 = [] # bean temperature extra1 = [] # inlet temperature extra2 = [] # burner percentage timeindex = [ -1, 0, 0, 0, 0, 0, 0, 0 ] #CHARGE index init set to -1 as 0 could be an actal index used i = 0 for row in data: if row == []: continue i = i + 1 items = list(zip(header, row)) item = {} for (name, value) in items: item[name] = value.strip() # take i as time in seconds timex.append(i) # extract roast_date if roast_date is None and 'Year' in item and 'Month' in item and 'Day' in item and 'Hour' in item and 'Minute' in item and 'Second' in item: try: date = QDate(int(item['Year']), int(item['Month']), int(item['Day'])) time = QTime(int(item['Hour']), int(item['Minute']), int(item['Second'])) roast_date = QDateTime(date, time) except: passs # if 'Outlet Temperature' in item: temp1.append(float(item['Outlet Temperature'])) else: temp1.append(-1) if 'Beans Temperature' in item: temp2.append(float(item['Beans Temperature'].replace(",", "."))) else: temp2.append(-1) # mark CHARGE if not timeindex[0] > -1: timeindex[0] = i # mark DROP if timeindex[0] > -1 and i > 0: timeindex[6] = i - 1 # add ror, power, speed and pressure if 'Inlet Temperature' in item: extra1.append(float(item['Inlet Temperature'])) else: extra1.append(-1) if 'Burner Percentage' in item: extra2.append(float(item['Burner Percentage'])) else: extra2.append(-1) if "Burner Percentage" in item: try: v = float(item["Burner Percentage"]) if v != power: # power value changed if v == power_last: # just a fluctuation, we remove the last added power value again power_last_idx = next( i for i in reversed(range(len(specialeventstype))) if specialeventstype[i] == 3) del specialeventsvalue[power_last_idx] del specialevents[power_last_idx] del specialeventstype[power_last_idx] del specialeventsStrings[power_last_idx] power = power_last power_last = None else: power_last = power power = v power_event = True v = v / 10. + 1 specialeventsvalue.append(v) specialevents.append(i) specialeventstype.append(3) specialeventsStrings.append(item["power"] + "%") else: power_last = None except Exception: pass csvFile.close() res["timex"] = timex res["temp1"] = replace_duplicates(temp1) res["temp2"] = replace_duplicates(temp2) res["timeindex"] = timeindex res["extradevices"] = [25] res["extratimex"] = [timex[:]] res["extraname1"] = ["IT"] res["extratemp1"] = [extra1] res["extramathexpression1"] = [""] res["extraname2"] = ["burner"] res["extratemp2"] = [replace_duplicates(extra2)] res["extramathexpression2"] = [""] # set date if roast_date is not None and roast_date.isValid(): res["roastdate"] = encodeLocal(roast_date.date().toString()) res["roastisodate"] = encodeLocal(roast_date.date().toString( Qt.ISODate)) res["roasttime"] = encodeLocal(roast_date.time().toString()) res["roastepoch"] = int(roast_date.toTime_t()) res["roasttzoffset"] = libtime.timezone if len(specialevents) > 0: res["specialevents"] = specialevents res["specialeventstype"] = specialeventstype res["specialeventsvalue"] = specialeventsvalue res["specialeventsStrings"] = specialeventsStrings if power_event or speed_event: # first set etypes to defaults res["etypes"] = [ QApplication.translate("ComboBox", "Air", None), QApplication.translate("ComboBox", "Drum", None), QApplication.translate("ComboBox", "Damper", None), QApplication.translate("ComboBox", "Burner", None), "--" ] return res