def __init__(self, config_file="config.json", debug=False): self.debug = debug utils.debug_msg(self, "load config start") self.config_file = config_file try: save = open(self.config_file, "r") config = json.load(save) save.close() except IOError: print("No config found at %s, using defaults" % self.config_file) config = self.build_config() except ValueError: print("Config at %s has syntax issues, cannot load" % self.config_file) config = None utils.debug_msg(self, config) self.config = config utils.debug_msg(self, "checking for required values") required = {'display': 'st7735', 'weight_mode': 'as_kg_gross'} for r in required.keys(): if not r in self.config.keys(): utils.debug_msg(self, "adding default: %s %s" % (r, required[r])) self.config[r] = required[r] utils.debug_msg(self, "load config end")
def __init__(self, hoplite, display): self.display = display self.h = hoplite self.debug = self.h.debug resource_package = __name__ resource_path = '' static_path = pkg_resources.resource_filename(resource_package, resource_path) self.font = ImageFont.truetype('%s/font/OpenSans-Regular.ttf' % static_path, 16) if self.display == None: utils.debug_msg(self, "Display not found, using default of st7735") self.display = 'st7735' try: parser = cmdline.create_parser(description='HOPLITE display args') conf = cmdline.load_config('%s/conf/%s.conf' % (static_path, display)) args = parser.parse_args(conf) except FileNotFoundError: conf = ['--display=%s' % display] args = parser.parse_args(conf) try: self.device = cmdline.create_device(args) except error.Error as e: parser.error(e) self.device = None
def add_channel(index, channel, data): if channel != 'A' and channel != 'B': return error(400, 'No such channel %s at index %s' % (channel, index)) required_keys = ('name', 'size', 'tare', 'volume') optional_keys = {'co2': False, 'refunit': 0, 'offset': 0} submission = dict() for key in required_keys: if not key in data.keys(): return error(400, 'Key %s is required' % key) submission[key] = data[key] for key in optional_keys.keys(): if not key in data.keys(): submission[key] = optional_keys[key] else: submission[key] = data[key] try: instance.ShData['config']['hx'][int( index)]['channels'][channel] = submission instance.shmem_write() return response(False, 200, {channel: submission}, '%s successfully created or updated' % channel) except IndexError: utils.debug_msg(instance, traceback.format_exc()) return error(400, 'No existing index %s' % index)
def apply_settings(self, widget): utils.debug_msg(self, "start") self.settings_up = False weight_mode = self.settings_dialog.get_field( 'weight_options').get_value() self.api_write('PUT', 'weight_mode', {'weight_mode': weight_mode}) utils.debug_msg(self, "end")
def edit_keg_channel_handler(self, widget, channel): utils.debug_msg(self, "start") port = self.edit_keg_dialog.get_field( 'port_box').children['1'].get_value() hx_list = self.api_data['hx_list'] index = utils.get_index_from_port(port, hx_list) self.fill_edit_keg(index, channel) utils.debug_msg(self, "end")
def delete_index(index): try: deleted_data = instance.ShData['config']['hx'][int(index)] del instance.ShData['config']['hx'][int(index)] instance.shmem_write() return response(False, 200, {index: deleted_data}, '%s successfully deleted' % index) except IndexError: utils.debug_msg(instance, traceback.format_exc()) return error(400, 'No existing index %s' % index)
def delete_keg(self, widget, index, channel): utils.debug_msg(self, "start") self.delete_keg_up = False self.delete_keg_dialog.hide() if len(self.api_data['hx_list'][index]['channels'].keys()) <= 1: endpoint = 'hx/%s/' % str(index) self.api_delete(endpoint) else: endpoint = 'hx/%s/%s/' % (str(index), channel) self.api_delete(endpoint) utils.debug_msg(self, "end")
def get_port_data(self, dialog): utils.debug_msg(self, "start") new_conf = dict() new_conf['port'] = dialog.get_field( 'port_box').children['1'].get_value() new_conf['pd_sck'] = dialog.get_field( 'hx_pins').children['1'].get_value() new_conf['dout'] = dialog.get_field( 'hx_pins').children['3'].get_value() new_conf['channel'] = dialog.get_field( 'channel_box').children['1'].get_value() utils.debug_msg(self, "end") return new_conf
def delete_channel(index, channel): try: deleted_data = instance.ShData['config']['hx'][int( index)]['channels'][channel] del instance.ShData['config']['hx'][int(index)]['channels'][channel] instance.shmem_write() return response(False, 200, {channel: deleted_data}, '%s successfully deleted' % channel) except KeyError: utils.debug_msg(instance, traceback.format_exc()) return error(400, 'No existing channel %s' % channel) except IndexError: utils.debug_msg(instance, traceback.format_exc()) return error(400, 'No existing index %s' % index)
def set_keg_gui_data(self, dialog, keg_box_id, new_conf): utils.debug_msg(self, "start") utils.debug_msg(self, "new_conf: %s" % new_conf) utils.debug_msg(self, "keg_box_id: %s" % keg_box_id) keg_box = dialog.get_field(keg_box_id) keg_box.children['name'].children['val'].set_value( new_conf.get('name', '')) keg_box.children['size'].children['val'].set_value( new_conf.get('size', '')) keg_box.children['co2_box'].children['1'].set_value( new_conf.get('co2', False)) if new_conf['size'] == 'custom': keg_box.children['custom'].children['1'].set_value( str(new_conf.get('volume', ''))) keg_box.children['custom'].children['3'].set_value( str(new_conf.get('tare', ''))) else: try: pd_sck = utils.keg_data[new_conf['size']][0] dout = utils.keg_data[new_conf['size']][1] except KeyError: pd_sck = '' dout = '' keg_box.children['custom'].children['1'].set_value(str(pd_sck)) keg_box.children['custom'].children['3'].set_value(str(dout)) utils.debug_msg(self, "end")
def set_display(): data = validate_request() if data == None: return error(400, 'Bad Request - invalid JSON') else: try: display = data['display'] except KeyError: utils.debug_msg(instance, traceback.format_exc()) return error(400, 'Bad Request - no display in request') instance.ShData['config']['display'] = display instance.shmem_write() return response(False, 200, {'display': display}, 'display successfully updated')
def idle(self): utils.debug_msg(self, "start") self.api_read() self.co2_list = [] self.container.children['keg_table'] = self.build_keg_table() t = utils.as_degF(self.api_data.get('temp', 0)) try: co2 = self.co2_list[0] #TODO: Handle multiple CO2 sources except IndexError: co2 = "???" self.temp.set_text("%s\nCO2:%s%%" % (t, co2)) utils.debug_msg(self, "end")
def fill_bar(self, x, y, min_w, max_w, w, outline="white", fill=None): net_w = max(w - min_w, 0) max_net_w = max_w - min_w fill_percent = float(net_w) / float(max_net_w) max_y = self.device.height - 21 min_y = y+1 max_bar = max_y - min_y fill_height = max(min_y, min_y + (max_bar - (max_bar * fill_percent))) if fill == None: fill = utils.fill_bar_color(fill_percent) self.draw.rectangle([x,y, x+20,self.device.height-20], outline=outline, fill="black") self.draw.rectangle([x+1,fill_height, x+19,max_y], outline=fill, fill=fill) utils.debug_msg(self, "%s: %s" % (fill_percent, fill))
def __init__(self, debug=False): # debug flag - this should be first self.debug = debug utils.debug_msg(self, "init start") # while true, run update loop self.updating = False # config file location self.config_file = None # temperature sensor output # TODO: evaluate if this can be replaced by read_temp() self.temp = None utils.debug_msg(self, "init end")
def api_delete(self, endpoint): utils.debug_msg(self, "start") headers = {'Content-Type': 'application/json'} dest_url = self.api_url + endpoint response = requests.delete(dest_url, headers=headers) if response.status_code != "200": utils.debug_msg(self, "response: %s" % response.json()) utils.debug_msg(self, dest_url) utils.debug_msg(self, "end")
def add_index(data): new_index = dict() for key in ('pd_sck', 'dout'): if not key in data.keys(): return error(400, 'Key %s is required' % key) new_index[key] = data[key] new_index['channels'] = dict() if not 'channels' in data.keys(): return error( 400, 'channels must be a JSON object containing one or more channels') try: for channel in data['channels'].keys(): if channel not in ('A', 'B'): return error(400, 'Invalid channel %s - must be A or B' % channel) required_keys = ('name', 'size', 'tare', 'volume') optional_keys = {'co2': False, 'refunit': 0, 'offset': 0} submission = dict() for key in required_keys: if not key in data['channels'][channel].keys(): return error( 400, 'Channel %s key %s is required' % (channel, key)) submission[key] = data['channels'][channel][key] for key in optional_keys.keys(): if not key in data['channels'][channel].keys(): submission[key] = optional_keys[key] else: submission[key] = data['channels'][channel][key] new_index['channels'][channel] = submission except AttributeError: utils.debug_msg(instance, traceback.format_exc()) return error( 400, 'channels must be a JSON object containing one or more channels also represented as JSON objects' ) instance.ShData['config']['hx'].append(new_index) instance.shmem_write() last_index = len(instance.ShData['config']['hx']) - 1 return response(False, 200, {last_index: new_index}, 'Index %s successfully created or updated' % last_index)
def fill_port_info(self, index, port): utils.debug_msg(self, "start") try: hx_conf = self.api_data['hx_list'][index] self.edit_keg_dialog.get_field('hx_pins').children['1'].set_value( str(hx_conf.get('pd_sck', ''))) self.edit_keg_dialog.get_field('hx_pins').children['3'].set_value( str(hx_conf.get('dout', ''))) except (KeyError, IndexError, TypeError): if port != "custom": self.edit_keg_dialog.get_field( 'hx_pins').children['1'].set_value( str(utils.breakout_ports[port][0])) self.edit_keg_dialog.get_field( 'hx_pins').children['3'].set_value( str(utils.breakout_ports[port][1])) utils.debug_msg(self, "end")
def init_hx711(self, hx_conf): utils.debug_msg(self, "init hx711 start") dout = int(hx_conf['dout']) pd_sck = int(hx_conf['pd_sck']) try: offset_A = hx_conf['channels']['A']['offset'] refunit_A = hx_conf['channels']['A']['refunit'] except (ValueError, KeyError): offset_A = None refunit_A = None try: offset_B = hx_conf['channels']['B']['offset'] refunit_B = hx_conf['channels']['B']['refunit'] except (ValueError, KeyError): offset_B = None refunit_B = None hx = HX711(dout, pd_sck) hx.set_reading_format("MSB", "MSB") hx.reset() if refunit_A is not None and offset_A is not None: try: hx.set_reference_unit_A(refunit_A) except ValueError: print("Bad channel A reference unit %s, using 1" % refunit_A) hx.set_reference_unit_A(1) hx.set_offset_A(offset_A) else: hx.set_reference_unit_A(1) if refunit_B is not None and offset_B is not None: try: hx.set_reference_unit_B(refunit_B) except ValueError: print("Bad channel B reference unit %s, using 1" % refunit_B) hx.set_reference_unit_B(1) hx.set_offset_B(offset_B) else: hx.set_reference_unit_B(1) utils.debug_msg(self, "init hx711 end") return hx
def api_read(self, force=False): utils.debug_msg(self, "start") since_last_update = int(time.time()) - self.api_last_updated if since_last_update > self.api_update_interval or force: response = requests.get(self.api_url) self.api_data = response.json()['data']['v1'] self.api_last_updated = int(time.time()) utils.debug_msg(self, "api_data: %s" % self.api_data) else: utils.debug_msg( self, "not updating, last update %is ago" % since_last_update) utils.debug_msg(self, "end")
def set_weight_mode(): data = validate_request() if data == None: return error(400, 'Bad Request - invalid JSON') else: try: weight_mode = data['weight_mode'] except KeyError: utils.debug_msg(instance, traceback.format_exc()) return error(400, 'Bad Request - no weight_mode in request') valid_modes = ('as_kg_gross', 'as_kg_net', 'as_pint', 'as_pct') if not weight_mode in valid_modes: return error( 400, 'Bad Request - invalid weight_mode %s' % weight_mode) else: instance.ShData['config']['weight_mode'] = weight_mode instance.shmem_write() return response(False, 200, {'weight_mode': weight_mode}, 'weight_mode successfully updated')
def apply_edit_keg(self, widget): utils.debug_msg(self, "start") self.edit_keg_up = False self.api_read(force=True) hx_list = self.api_data['hx_list'] port_conf = self.get_port_data(self.edit_keg_dialog) port = port_conf['port'] index = utils.get_index_from_port(port, hx_list) channel = port_conf['channel'] if index != None: for attribute in ('pd_sck', 'dout'): endpoint = 'hx/%s/%s' % (str(index), attribute) self.api_write('PUT', endpoint, {attribute: port_conf[attribute]}) try: chan = hx_list[index]['channels'][channel] except KeyError: chan = {} new_conf = self.get_keg_gui_data(self.edit_keg_dialog, 'keg_box') chan['name'] = new_conf['name'] chan['size'] = new_conf['size'] chan['volume'] = new_conf['volume'] chan['tare'] = new_conf['tare'] chan['co2'] = new_conf['co2'] endpoint = 'hx/%s/%s/' % (str(index), channel) self.api_write('POST', endpoint, chan) else: hx = dict() hx['pd_sck'] = port_conf['pd_sck'] hx['dout'] = port_conf['dout'] hx['channels'] = dict() hx['channels'][channel] = self.get_keg_gui_data( self.edit_keg_dialog, 'keg_box') endpoint = 'hx/' self.api_write('POST', endpoint, hx) self.api_read(force=True) utils.debug_msg(self, "end")
def get_all_hx_with_weight(): hxs = list() try: utils.debug_msg(instance, "get keg config") hxs_config = instance.ShData['config']['hx'] utils.debug_msg(instance, "enumerate hx") for index, hx in enumerate(hxs_config): utils.debug_msg(instance, "hx %s" % index) hxs.insert(index, add_weight_to_hx(index, hx)) except (IndexError, KeyError, ValueError): utils.debug_msg(instance, traceback.format_exc()) return hxs
def show_delete_keg_confirm(self, widget, index, channel): utils.debug_msg(self, "start") if self.delete_keg_up == True: return else: self.delete_keg_up = True self.delete_keg_dialog = gui.GenericDialog(title='DELETE Keg?', width=240) warning_label = gui.Label( "Are you sure you want to delete keg at index %s channel %s?" % (index, channel)) self.delete_keg_dialog.append(warning_label) self.delete_keg_dialog.set_on_cancel_dialog_listener( self.cancel_delete_keg) self.delete_keg_dialog.children['buttons_container'].children[ 'confirm_button'].onclick.do(self.delete_keg, index, channel) self.delete_keg_dialog.show(self) utils.debug_msg(self, "end")
def read_co2(self): co2 = list() for index, hx_conf in enumerate(self.config.get('hx')): for channel in ['A', 'B']: try: if hx_conf['channels'][channel]['co2'] == True: local_w = self.hx711_read_ch(self.hx_handles[index], channel) local_max = hx_conf['channels'][channel][ 'volume'] * 1000 local_tare = hx_conf['channels'][channel]['tare'] * 1000 utils.debug_msg( self, "channel %s: %s, %s, %s" % (channel, local_w, local_max, local_tare)) local_net_w = max((local_w - local_tare), 0) local_pct = local_net_w / float(local_max) co2.append(int(local_pct * 100)) except KeyError: pass return co2
def fill_edit_keg(self, index, channel, empty=False): utils.debug_msg(self, "start") new_conf = {} try: hx_conf = self.api_data['hx_list'][index]['channels'][channel] new_conf['volume'] = hx_conf['volume'] new_conf['tare'] = hx_conf['tare'] new_conf['name'] = hx_conf['name'] new_conf['size'] = hx_conf['size'] new_conf['co2'] = hx_conf['co2'] except (KeyError, IndexError, TypeError): new_conf['volume'] = '' new_conf['tare'] = '' new_conf['name'] = '' new_conf['size'] = '' new_conf['co2'] = False self.set_keg_gui_data(self.edit_keg_dialog, 'keg_box', new_conf) self.edit_keg_dialog.set_on_confirm_dialog_listener( self.apply_edit_keg) utils.debug_msg(self, "end")
def edit_keg_port_handler(self, widget, port): utils.debug_msg(self, "start") hx_list = self.api_data['hx_list'] pd_sck = self.edit_keg_dialog.get_field('hx_pins').children['1'] dout = self.edit_keg_dialog.get_field('hx_pins').children['3'] if port == 'custom': pd_sck.set_enabled(True) dout.set_enabled(True) custom_values = (pd_sck.get_value(), dout.get_value()) index = utils.get_index_from_port(port, custom_values) else: pd_sck.set_enabled(False) dout.set_enabled(False) index = utils.get_index_from_port(port, hx_list) channel = self.edit_keg_dialog.get_field( 'channel_box').children['1'].get_value() self.fill_edit_keg(index, channel) self.fill_port_info(index, port) utils.debug_msg(self, "end")
def runtime_init(self): # All the stuff needed for runtime lives here so the Hoplite class # can pe imported into other things for stuff like loading configs # without breaking GPIO access, etc. utils.debug_msg(self, "runtime init start") # dictionary containing current shared memory data self.ShData = dict() # dict containing current config self.config = Config(self.config_file, debug=self.debug) # list of handles for all keg HX711's found in config self.hx_handles = list() # co2 weights are a list now, could be multiple co2 cylinders self.co2_w = list() # REST API class self.api = RestApi(self) # output display try: self.display = Display(self, self.config.get('display')) except KeyError: utils.debug_msg( self, "Display not found in config, using default st7735") self.display = Display(self, 'st7735') utils.debug_msg(self, "runtime init end")
def main(self, config_file='config.json', api_listen=None): self.config_file = config_file self.runtime_init() if self.config.config == None: print("No valid config, bailing out") sys.exit() utils.debug_msg(self, "debug enabled") self.ShData['data'] = dict() self.ShData['data']['weight'] = list() self.ShData['config'] = self.config.config self.setup_all_kegs() utils.debug_msg(self, 'api listener: %s' % api_listen) if api_listen != None: api_listen_split = api_listen.split(':') if len(api_listen_split) == 2: api_host = api_listen_split[0] api_port = api_listen_split[1] elif len(api_listen_split) == 1: api_host = api_listen_split[0] api_port = '5000' else: print( 'Incorrect formatting for API listen address, using defaults' ) api_host = '0.0.0.0' api_port = '5000' else: api_host = '0.0.0.0' api_port = '5000' print('Starting API at %s:%s' % (api_host, api_port)) self.api_process = threading.Thread(None, self.api.worker, 'hoplite REST api', kwargs={ 'host': api_host, 'port': api_port }) self.api_process.daemon = True self.api_process.start() try: self.updating = True self.update_process = threading.Thread(None, self.update, 'hoplite data collection') self.update_process.daemon = True self.update_process.start() while self.updating: time.sleep(1) except (KeyboardInterrupt, SystemExit, RuntimeError): utils.debug_msg(self, "stop updating") self.updating = False self.update_process.join(30) self.cleanup() sys.exit()
def get_keg_gui_data(self, dialog, keg_box_id): utils.debug_msg(self, "start") keg_box = dialog.get_field(keg_box_id) utils.debug_msg(self, "keg_box: %s" % keg_box) new_size = keg_box.children['size'].children['val'].get_value( ) # this takes forever sometimes to actually select, need to investigate utils.debug_msg(self, "new_size: %s" % new_size) if new_size == 'custom': vol = float(keg_box.children['custom'].children['1'].get_value()) tare = float(keg_box.children['custom'].children['3'].get_value()) else: vol = utils.keg_data[new_size][0] tare = utils.keg_data[new_size][1] new_conf = dict() new_conf['name'] = keg_box.children['name'].children['val'].get_value() new_conf['size'] = new_size new_conf['co2'] = keg_box.children['co2_box'].children['1'].get_value() new_conf['volume'] = vol new_conf['tare'] = tare utils.debug_msg(self, "new_conf: %s" % new_conf) utils.debug_msg(self, "end") return new_conf
def validate_request(): try: data = request.json utils.debug_msg(instance, data) except BadRequest: utils.debug_msg(instance, traceback.format_exc()) utils.debug_msg(instance, "request is not valid JSON: %s" % request.get_data()) data = None return data