def process(self): now = datetime.now() try: vehicle_status = self.psacc.vehicles_list.get_car_by_vin(self.vin).get_status() status = vehicle_status.get_energy('Electric').charging.status level = vehicle_status.get_energy('Electric').level logger.info("charging status of %s is %s, battery level: %d", self.vin, status, level) if status == "InProgress": self.force_update() if level >= self.percentage_threshold and self.retry_count < 2: logger.info("Charge threshold is reached, stop the charge") self.control_charge_with_ack(False) elif self._next_stop_hour is not None: if self._next_stop_hour < now: self._next_stop_hour += timedelta(days=1) logger.info("it's time to stop the charge") self.control_charge_with_ack(False) else: next_in_second = (self._next_stop_hour - now).total_seconds() if next_in_second < self.psacc.info_refresh_rate: periodicity = next_in_second thread = threading.Timer(periodicity, self.process) thread.setDaemon(True) thread.start() else: if self._next_stop_hour is not None and self._next_stop_hour < now: self._next_stop_hour += timedelta(days=1) self.retry_count = 0 except AttributeError: logger.error("Probably can't retrieve all information from API: %s", traceback.format_exc()) except: logger.error(traceback.format_exc())
def getStat(self, results): total = len(results) if total == 0: logger.error('No sample') return 0, 0, 0 succ_results = [result for result in results if result['succ'] is True] total_succ = len(succ_results) latencies = [ task_result['response_time'] - task_result['send_time'] for task_result in succ_results if task_result['succ'] is True ] if len(latencies) == 0: logger.error('No succ sample') return 0, 0, 0 avg_lat = sum(latencies) / len(latencies) wait_times = [ task_result['send_time'] - task_result['create_time'] for task_result in succ_results ] avg_wait_time = sum(wait_times) / len(wait_times) logger.debug('Succ rate: {} / {} = {}'.format( total_succ, total, 100.0 * total_succ / total)) logger.debug('Avg latency: {}'.format(avg_lat)) logger.debug('Avg wait time: {}'.format(avg_wait_time)) return 100.0 * total_succ / total, 1000 * avg_lat, avg_wait_time
def createTask(self, id, taskType): param = {} hostname = self.config.getHostname() char_candidates = string.digits short_id = ''.join(random.choice(char_candidates) for i in range(3)) index_url = self.config.getIndexUrl() convert_url = self.config.getConvertUrl() short_id = str(short_id).rjust(6, '0') url_list = [ 'www.douban.com', 'www.baidu.com', 'www.google.com', 'www.facebook.com' ] long_url = random.choice(url_list) if taskType == 'read': param = { "type": taskType, "hostname": hostname, "index_url": index_url, "short_id": short_id } elif taskType == 'write': param = { "type": taskType, "hostname": hostname, "index_url": index_url, "convert_url": convert_url, "long_url": long_url } else: logger.error('Unsupported task type {}'.format(taskType)) return Task(id, param)
def update_trips(): global trips, chargings, cached_layout logger.info("update_data") try: trips_by_vin = Trips.get_trips(myp.vehicles_list) trips = next(iter(trips_by_vin.values())) # todo handle multiple car chargings = MyPSACC.get_chargings() except StopIteration: logger.debug("No trips yet") return except AssertionError: logger.error("update_trips: %s", traceback.format_exc()) # update for slider global min_date, max_date, min_millis, max_millis, step, marks try: min_date = trips[0].start_at max_date = trips[-1].start_at min_millis = figures.unix_time_millis(min_date) max_millis = figures.unix_time_millis(max_date) step = (max_millis - min_millis) / 100 marks = figures.get_marks_from_start_end(min_date, max_date) cached_layout = None # force regenerate layout except (ValueError, IndexError): logger.error("update_trips (slider): %s", traceback.format_exc()) return
def record_position(self, vin, mileage, latitude, longitude, date, level, level_fuel, moving): conn = get_db() if mileage == 0: # fix a bug of the api logger.error("The api return a wrong mileage for %s : %f", vin, mileage) else: if conn.execute("SELECT Timestamp from position where Timestamp=?", (date, )).fetchone() is None: temp = get_temp(latitude, longitude, self.weather_api) if level_fuel == 0: # fix fuel level not provided when car is off try: level_fuel = conn.execute( "SELECT level_fuel FROM position WHERE level_fuel>0 AND VIN=? ORDER BY Timestamp DESC " "LIMIT 1", (vin, )).fetchone()[0] logger.info( "level_fuel fixed with last real value %f for %s", level_fuel, vin) except TypeError: level_fuel = None logger.info("level_fuel unfixed for %s", vin) conn.execute( "INSERT INTO position(Timestamp,VIN,longitude,latitude,mileage,level,level_fuel,moving," "temperature) VALUES(?,?,?,?,?,?,?,?,?)", (date, vin, longitude, latitude, mileage, level, level_fuel, moving, temp)) conn.commit() logger.info("new position recorded for %s", vin) clean_position(conn) else: logger.debug("position already saved")
def refresh_remote_token(self, force=False): if not force and self.remote_token_last_update is not None: last_update: datetime = self.remote_token_last_update if (datetime.now() - last_update).total_seconds() < MQTT_TOKEN_TTL: return self.manager._refresh_token() res = self.manager.post(remote_url + self.client_id, json={ "grant_type": "refresh_token", "refresh_token": self.remote_refresh_token }, headers=self.headers) data = res.json() logger.debug(f"refresh_remote_token: {data}") if "access_token" in data: self.remote_access_token = data["access_token"] self.remote_refresh_token = data["refresh_token"] self.remote_token_last_update = datetime.now() else: logger.error( f"can't refresh_remote_token: {data}\n Create a new one") self.remote_token_last_update = datetime.now() otp_code = self.getOtpCode() res = self.get_remote_access_token(otp_code) self.mqtt_client.username_pw_set("IMA_OAUTH_ACCESS_TOKEN", self.remote_access_token) return res
def update_trips(): global trips, chargings logger.info("update_data") try: trips = MyPSACC.get_trips() chargings = MyPSACC.get_chargings() except: logger.error("update_trips: " + traceback.format_exc())
def on_mqtt_disconnect(self, client, userdata, rc): try: logger.warn("Disconnected with result code " + str(rc)) # Subscribing in on_connect() means that if we lose the connection and # reconnect then subscriptions will be renewed. logger.warn(mqtt.error_string(rc)) except: logger.error(traceback.format_exc())
def on_mqtt_disconnect(self, client, userdata, rc): try: logger.warn("Disconnected with result code " + str(rc)) if rc == 1: self.refresh_remote_token(force=True) else: logger.warn(mqtt.error_string(rc)) except: logger.error(traceback.format_exc())
def get_vehicles(self): try: res = self.api().get_vehicles_by_device() for vehicle in res.embedded.vehicles: self.vehicles_list.add( Car(vehicle.vin, vehicle.id, vehicle.brand, vehicle.label)) self.vehicles_list.save_cars() except ApiException: logger.error(traceback.format_exc()) return self.vehicles_list
def on_mqtt_disconnect(self, client, userdata, rc): try: logger.warn("Disconnected with result code " + str(rc)) # Subscribing in on_connect() means that if we lose the connection and # reconnect then subscriptions will be renewed. logger.warn(mqtt.error_string(rc)) except: logger.error(traceback.format_exc()) self.mqtt_client.username_pw_set("IMA_OAUTH_ACCESS_TOKEN", self.remote_access_token)
def on_mqtt_connect(self, client, userdata, rc, a): try: logger.info("Connected with result code " + str(rc)) topics = [MQTT_RESP_TOPIC + self.customer_id + "/#"] for vin in self.getVIN(): topics.append(MQTT_EVENT_TOPIC + vin) for topic in topics: client.subscribe(topic) logger.info("subscribe to " + topic) except: logger.error(traceback.format_exc())
def on_mqtt_connect(self, client, userdata, rc, a): try: logger.info("Connected with result code " + str(rc)) topics = ["psa/RemoteServices/to/cid/" + self.customer_id + "/#"] for vin in self.getVIN(): topics.append("psa/RemoteServices/events/MPHRTServices/" + vin + "/#") for topic in topics: client.subscribe(topic) logger.info("subscribe to " + topic) except: logger.error(traceback.format_exc())
def on_mqtt_message(self, client, userdata, msg): logger.info(f"mqtt msg {msg.topic} {str(msg.payload)}") try: data = json.loads(msg.payload) if data["return_code"] == "0": return elif data["return_code"] == "400": self.manager._refresh_token() self.refresh_remote_token() logger.info("retry last request") else: logger.error(f'{data["return_code"]} : {data["reason"]}') except: logger.debug("mqtt msg hasn't return code")
def on_mqtt_message(self, client, userdata, msg): try: logger.info("mqtt msg %s %s", msg.topic, msg.payload) data = json.loads(msg.payload) charge_info = None if msg.topic.startswith(MQTT_RESP_TOPIC): if "return_code" not in data: logger.debug("mqtt msg hasn't return code") elif data["return_code"] == "400": self.refresh_remote_token(force=True) logger.error("retry last request, token was expired") elif data["return_code"] == "300": logger.error('%d', data["return_code"]) elif data["return_code"] != "0": logger.error('%s : %s', data["return_code"], data["reason"]) if msg.topic.endswith("/VehicleState"): charge_info = data["resp_data"]["charging_state"] self.precond_programs[data["vin"]] = data["resp_data"][ "precond_state"]["programs"] elif msg.topic.startswith(MQTT_EVENT_TOPIC): charge_info = data["charging_state"] if charge_info is not None and charge_info[ 'remaining_time'] != 0 and charge_info['rate'] == 0: # fix a psa server bug where charge beginning without status api being properly updated logger.warning("charge begin but API isn't updated") sleep(60) self.wakeup(data["vin"]) except KeyError: logger.error(traceback.format_exc())
def get_co2_per_kw(start: datetime, end: datetime, latitude, longitude): try: location = reverse_geocode.search([(latitude, longitude)])[0] country_code = location["country_code"] except UnicodeDecodeError: logger.error("Can't find country for %s %s", latitude, longitude) country_code = None except IndexError: country_code = None # todo implement other countries if country_code == 'FR': co2_per_kw = Ecomix.get_data_france(start, end) else: co2_per_kw = None return co2_per_kw
def get_charge_hour(self, vin): reg = r"PT([0-9]{1,2})H([0-9]{1,2})?" data = self.get_vehicle_info(vin) hour_str = data.energy[0].charging.next_delayed_time try: hour = re.findall(reg, hour_str)[0] h = int(hour[0]) if hour[1] == '': m = 0 else: m = hour[1] return h, m except IndexError: logger.error(traceback.format_exc()) logger.error(f"Can't get charge hour: {hour_str}")
def get_vehicle_info(self, vin): res = None car = self.vehicles_list.get_car_by_vin(vin) for _ in range(0, 2): try: res = self.api().get_vehicle_status(car.vehicle_id, extension=["odometer"]) if res is not None: if self._record_enabled: self.record_info(vin, res) break except ApiException: logger.error(traceback.format_exc()) car.status = res return res
def get_temp(latitude: str, longitude: str, api_key: str) -> float: try: if not (latitude is None or longitude is None or api_key is None): weather_rep = requests.get("https://api.openweathermap.org/data/2.5/onecall", params={"lat": latitude, "lon": longitude, "exclude": "minutely,hourly,daily,alerts", "appid": api_key, "units": "metric"}) temp = weather_rep.json()["current"]["temp"] logger.debug("Temperature :%fc", temp) return temp except ConnectionError: logger.error("Can't connect to openweathermap :%s", traceback.format_exc()) except KeyError: logger.error("Unable to get temperature from openweathermap :%s", traceback.format_exc()) return None
def start(self): periodicity = ChargeControl.periodicity now = datetime.now() if self._next_stop_hour is not None and self._next_stop_hour < now: stop_charge = True self._next_stop_hour += timedelta(days=1) logger.info("it's time to stop the charge") else: stop_charge = False if self.percentage_threshold != 100 or stop_charge: res = None try: res = self.psacc.get_vehicle_info(self.vin) except ApiException: logger.error(traceback.format_exc()) if res is not None: status = res.energy[0]['charging']['status'] level = res.energy[0]["level"] logger.info( f"charging status of {self.vin} is {status}, battery level: {level}" ) if status == "InProgress": if (level >= self.percentage_threshold and self.retry_count < 2) or stop_charge: self.psacc.charge_now(self.vin, False) self.retry_count += 1 sleep(ChargeControl.MQTT_TIMEOUT) res = self.psacc.get_vehicle_info(self.vin) status = res.energy[0]['charging']['status'] if status == "InProgress": logger.warn( f"retry to stop the charge of {self.vin}") self.psacc.charge_now(self.vin, False) self.retry_count += 1 if self._next_stop_hour is not None: next_in_second = (self._next_stop_hour - now).total_seconds() if next_in_second < periodicity: periodicity = next_in_second else: self.retry_count = 0 else: logger.error(f"error when get vehicle info of {self.vin}") self.thread = threading.Timer(periodicity, self.start) self.thread.start()
def refresh_remote_token(self, force=False): if not force and self.remote_token_last_update is not None: last_update: datetime = self.remote_token_last_update if (datetime.now() - last_update).total_seconds() < MQTT_TOKEN_TTL: return self.manager._refresh_token() res = self.manager.post(remote_url + self.client_id, json={ "grant_type": "refresh_token", "refresh_token": self.remote_refresh_token }, headers=self.headers) data = res.json() logger.debug(f"refresh_remote_token: {data}") if "access_token" in data: self.remote_access_token = data["access_token"] self.remote_refresh_token = data["refresh_token"] self.remote_token_last_update = datetime.now() return data["access_token"], data["refresh_token"] else: logger.error(f"can't refresh_remote_token: {data}")
def execute(self): logger.info('Running task: {}'.format(self.id)) self.send_time = time.time() if self.param['type'] == 'read': result = self.readTask() elif self.param['type'] == 'write': result = self.writeTask() else: logger.error('Unsupported task type {}'.format(self.param['type'])) self.response_time = time.time() Stat.finished_tasks += 1 task_stat = { 'id': self.id, 'type': self.param['type'], 'create_time': self.create_time, 'send_time': self.send_time, 'response_time': self.response_time, 'succ': result['succ'], } Stat.result_queue.put(task_stat)
def record_info(self, vin, status: psac.models.status.Status): mileage = status.timed_odometer.mileage level = status.get_energy('Electric').level level_fuel = status.get_energy('Fuel').level charge_date = status.get_energy('Electric').updated_at try: moving = status.kinetic.moving logger.debug("") except AttributeError: logger.error("kinetic not available from api") moving = None try: longitude = status.last_position.geometry.coordinates[0] latitude = status.last_position.geometry.coordinates[1] date = status.last_position.properties.updated_at except AttributeError: logger.error("last_position not available from api") longitude = latitude = None date = charge_date logger.debug( "vin:%s longitude:%s latitude:%s date:%s mileage:%s level:%s charge_date:%s level_fuel:" "%s moving:%s", vin, longitude, latitude, date, mileage, level, charge_date, level_fuel, moving) self.record_position(vin, mileage, latitude, longitude, date, level, level_fuel, moving) try: charging_status = status.get_energy('Electric').charging.status self.record_charging(vin, charging_status, charge_date, level, latitude, longitude) logger.debug("charging_status:%s ", charging_status) except AttributeError: logger.error("charging status not available from api")
def on_mqtt_message(self, client, userdata, msg): charge_not_detected = False try: logger.info(f"mqtt msg {msg.topic} {str(msg.payload)}") data = json.loads(msg.payload) if msg.topic.startswith(MQTT_RESP_TOPIC): if "return_code" in data: if data["return_code"] == "0": pass elif data["return_code"] == "400": self.refresh_remote_token(force=True) logger.error("retry last request, token was expired") else: logger.error( f'{data["return_code"]} : {data["reason"]}') else: logger.debug("mqtt msg hasn't return code") if msg.topic.startswith(MQTT_EVENT_TOPIC): if data["charging_state"]['remaining_time'] != 0 and data[ "charging_state"]['rate'] == 0: charge_not_detected = True elif msg.topic.endswith("/VehicleState"): if data["resp_data"]["charging_state"]['remaining_time'] != 0 \ and data["resp_data"]["charging_state"]['rate'] == 0: charge_not_detected = True if charge_not_detected: # fix a psa server bug where charge beginning without status api being properly updated logger.info("charge begin") sleep(60) self.wakeup(data["vin"]) except: logger.error(traceback.format_exc())
def on_mqtt_message(self, client, userdata, msg): try: logger.info(f"mqtt msg {msg.topic} {str(msg.payload)}") data = json.loads(msg.payload) if msg.topic.startswith(MQTT_RESP_TOPIC): if "return_code" in data: if data["return_code"] == "0": return elif data["return_code"] == "400": self.refresh_remote_token(force=True) logger.error("retry last request, token was expired") else: logger.error( f'{data["return_code"]} : {data["reason"]}') else: logger.debug("mqtt msg hasn't return code") elif msg.topic.startswith(MQTT_EVENT_TOPIC): # fix charge beginning without status api being updated if data["charging_state"]['remaining_time'] != 0 and data[ "charging_state"]['rate'] == 0: logger.info("charge begin") sleep(60) self.wakeup(data["vin"]) except: logger.error(traceback.format_exc())
def start(self): periodicity = ChargeControl.periodicity now = datetime.now() try: if self._next_stop_hour is not None and self._next_stop_hour < now: stop_charge = True self._next_stop_hour += timedelta(days=1) logger.info("it's time to stop the charge") else: stop_charge = False if self.percentage_threshold != 100 or stop_charge or self.always_check: res = None try: res = self.psacc.get_vehicle_info(self.vin) except ApiException: logger.error(traceback.format_exc()) if res is not None: status = res.energy[0].charging.status level = res.energy[0].level logger.info( f"charging status of {self.vin} is {status}, battery level: {level}" ) if status == "InProgress": # force update if the car doesn't send info during 10 minutes last_update = res.energy[0].updated_at if (datetime.utcnow().replace(tzinfo=pytz.UTC) - last_update).total_seconds() > 60 * 10: self.psacc.wakeup(self.vin) if (level >= self.percentage_threshold and self.retry_count < 2) or stop_charge: self.psacc.charge_now(self.vin, False) self.retry_count += 1 sleep(ChargeControl.MQTT_TIMEOUT) res = self.psacc.get_vehicle_info(self.vin) status = res.energy[0].charging.status if status == "InProgress": logger.warn( f"retry to stop the charge of {self.vin}") self.psacc.charge_now(self.vin, False) self.retry_count += 1 if self._next_stop_hour is not None: next_in_second = (self._next_stop_hour - now).total_seconds() if next_in_second < periodicity: periodicity = next_in_second else: self.retry_count = 0 else: logger.error(f"error when get vehicle info of {self.vin}") except: logger.error(traceback.format_exc()) self.thread = threading.Timer(periodicity, self.start) self.thread.start()
def get_status(self): if self.status is not None: return self.status logger.error("status of %s is None", self.vin) raise ValueError("status of %s is None")
tab_id="battery", id="tab_battery", children=[figures.battery_info]), dbc.Tab(label="Map", tab_id="map", children=[ dcc.Graph(figure=figures.trips_map, id="trips_map", style={"height": '90vh'}) ]), ], id="tabs", active_tab="summary", ), html.Div(id="tab-content", className="p-4"), ]) ]) except (IndexError, TypeError) as e: logger.debug( "Failed to generate figure, there is probably not enough data yet") data_div = dbc.Alert("No data to show", color="danger") except: logger.error( "Failed to generate figure, there is probably not enough data yet") logger.error(traceback.format_exc()) data_div = dbc.Alert("No data to show", color="danger") dash_app.layout = dbc.Container(fluid=True, children=[html.H1('My car info'), data_div])
def record_info(self, vin, status: psac.models.status.Status): longitude = status.last_position.geometry.coordinates[0] latitude = status.last_position.geometry.coordinates[1] date = status.last_position.properties.updated_at mileage = status.timed_odometer.mileage level = status.energy[0].level charging_status = status.energy[0].charging.status moving = status.kinetic.moving conn = get_db() if mileage == 0: # fix a bug of the api logger.error( f"The api return a wrong mileage for {vin} : {mileage}") else: if conn.execute("SELECT Timestamp from position where Timestamp=?", (date, )).fetchone() is None: temp = None if self.weather_api is not None: try: weather_rep = requests.get( "https://api.openweathermap.org/data/2.5/onecall", params={ "lat": latitude, "lon": longitude, "exclude": "minutely,hourly,daily,alerts", "appid": "f8ee4124ea074950b696fd3e956a7069", "units": "metric" }) temp = weather_rep.json()["current"]["temp"] logger.debug(f"Temperature :{temp}c") except Exception as e: logger.error( f"Unable to get temperature from openweathermap :{e}" ) conn.execute( "INSERT INTO position(Timestamp,VIN,longitude,latitude,mileage,level, moving, temperature) VALUES(?,?,?,?,?,?,?,?)", (date, vin, longitude, latitude, mileage, level, moving, temp)) conn.commit() logger.info(f"new position recorded for {vin}") res = conn.execute( "SELECT Timestamp,mileage,level from position ORDER BY Timestamp DESC LIMIT 3;" ).fetchall() # Clean DB if len(res) == 3: if res[0]["mileage"] == res[1]["mileage"] == res[2][ "mileage"]: if res[0]["level"] == res[1]["level"] == res[2][ "level"]: logger.debug("Delete duplicate line") conn.execute( "DELETE FROM position where Timestamp=?;", (res[1]["Timestamp"], )) conn.commit() else: logger.debug("position already saved") # todo handle battery status charge_date = status.energy[0].updated_at if charging_status == "InProgress": try: in_progress = conn.execute( "SELECT stop_at FROM battery WHERE VIN=? ORDER BY start_at DESC limit 1", (vin, )).fetchone()[0] is None except TypeError: in_progress = False if not in_progress: res = conn.execute( "INSERT INTO battery(start_at,start_level,VIN) VALUES(?,?,?)", (charge_date, level, vin)) conn.commit() else: try: start_at, stop_at, start_level = conn.execute( "SELECT start_at, stop_at, start_level from battery WHERE VIN=? ORDER BY start_at " "DESC limit 1", (vin, )).fetchone() in_progress = stop_at is None if in_progress: co2_per_kw = Ecomix.get_co2_per_kw(start_at, charge_date, latitude, longitude) kw = (level - start_level) / 100 * BATTERY_POWER res = conn.execute( "UPDATE battery set stop_at=?, end_level=?, co2=?, kw=? WHERE start_at=? and VIN=?", (charge_date, level, co2_per_kw, kw, start_at, vin)) conn.commit() except TypeError: logger.debug("battery table is empty") except: logger.debug("Error when saving status " + traceback.format_exc()) conn.close()
def activation_finalyze(self, random_bytes=None): R = self.get_r() params = { "action": "ActionFinalize", "mode": self.mode, "id": self.data.iwid, "lastsync": self.data.iwTsync, "version": "Generator-1.0/0.2.11", "lang": "fr", "ack": "", "macid": self.macid } if self.mode == Otp.OTP_MODE: params.update({"keytype": '0', "sid": self.data.iwsecid}) elif self.mode == Otp.ACTIVATE_MODE: kma_crypt = self.cipher.encrypt( bytes.fromhex(self.generate_kma(self.codepin))).hex() pin_crypt = self.cipher.encrypt(self.codepin.encode("utf-8")).hex() params.update({ "serial": self.get_serial(), "code": self.smsCode, "Kma": kma_crypt, "pin": pin_crypt, "name": "Android SDK built for x86_64 / UNKNOWN", }) params.update(R) xml = self.request(params) if xml["err"] != "OK": logger.error("Error during activation: %s", xml) return Otp.NOK self.data.synchro(xml, self.generate_kma(self.codepin)) if self.mode == Otp.OTP_MODE: try: self.defi = str(xml["defi"]) except KeyError: raise ConfigException if "J" in xml: logger.debug("Need another otp request") return Otp.OTP_TWICE return Otp.OK if "ms_n" not in xml or xml["ms_n"] == 0: logger.debug("no ms_n request needed") return Otp.OK if int(xml["ms_n"]) > 1: raise NotImplementedError ms_n = "0" self.challenge = xml["challenge"] self.action = "synchro" res = self.decode_oaep(xml["ms_key"], self.Kfact) temp_key = RSA.construct((int(res, 16), self.exponent)) temp_cipher = oaep.new(temp_key, hash_algo=Hash.SHA256) if random_bytes is None: random_bytes = token_bytes(16) kpub_encode = temp_cipher.encrypt(random_bytes) aes_cipher = AES.new(bytes.fromhex(self.generate_kma(self.codepin)), AES.MODE_ECB) encode_aes_from_hex = aes_cipher.encrypt(random_bytes).hex() self.data.iwsecval = encode_aes_from_hex self.data.iwsecid = xml["s_id"] self.data.iwsecn = 1 req_param = { "action": "ActionFinalize", "mode": Otp.MS_MODE, "ms_id" + ms_n: xml["ms_id"], "ms_val" + ms_n: kpub_encode.hex(), "macid": self.macid } req_param.update({ "id": self.data.iwid, "lastsync": self.data.iwTsync, "ms_n": 1 }) req_param.update(self.get_r()) xml = self.request(req_param) self.data.synchro(xml, self.generate_kma(self.codepin)) return Otp.OK