五、try_update_binary详细流程

本文深入探讨了Android系统的升级过程,特别关注了try_update_binary函数的详细执行流程,包括从升级包中解析并运行updater_binary,以及通过管道在父进程与子进程间的数据交互。此外,还介绍了如何通过调用execv执行updater-binary,进而进入updater.cpp的main方法,执行update-scrypt脚本,完成系统的升级。

五、try_update_binary详细流程

本篇将介绍try_update_binary 执行升级包中的updater_binary可执行文件解析update-scrypt进行升级流程

 

bootable/recovery/install.cpp
static int really_install_package(const std::string& path, bool* wipe_cache, bool needs_mount,
                                  std::vector<std::string>* log_buffer, int retry_count,
                                  int* max_temperature) {
  ......
  ......
  //根据之前install.cpp的流程分析,这里传入升级包的路径,和从内存地址中读出的升级包数据
  int result = try_update_binary(path, zip, wipe_cache, log_buffer, retry_count, max_temperature);
  ......
  ......
}

try_update_binary方法

bootable/recovery/install.cpp
// If the package contains an update binary, extract it and run it.
static int try_update_binary(const std::string& package, ZipArchiveHandle zip, bool* wipe_cache,
                             std::vector<std::string>* log_buffer, int retry_count,
                             int* max_temperature) {
  //将META/com/android/metadata中一些值写入到last_install中
  read_source_target_build(zip, log_buffer);
  //调用pipe在父子进程间使用管道
  //pipefd[0]用于读取数据 ,pipefd[1]用于写入数据
  int pipefd[2];
  pipe(pipefd);

  std::vector<std::string> args;
#ifdef AB_OTA_UPDATER
  int ret = update_binary_command(package, zip, "/sbin/update_engine_sideload", retry_count,
                                  pipefd[1], &args);
#else
  //找到update-binary,放入到tmp路径,和其他一些参数放入args容器中
  int ret = update_binary_command(package, zip, "/tmp/update-binary", retry_count, pipefd[1],
                                  &args);
#endif
  if (ret) {
    close(pipefd[0]);
    close(pipefd[1]);
    log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure));
    return ret;
  }
  // 将args中的参数转换为数组形式
  const char* chr_args[args.size() + 1];
  chr_args[args.size()] = nullptr;
  for (size_t i = 0; i < args.size(); i++) {
    chr_args[i] = args[i].c_str();
  }
  //fork一个子进程
  pid_t pid = fork();

  if (pid == -1) {
    close(pipefd[0]);
    close(pipefd[1]);
    PLOG(ERROR) << "Failed to fork update binary";
    log_buffer->push_back(android::base::StringPrintf("error: %d", kForkUpdateBinaryFailure));
    return INSTALL_ERROR;
  }

  if (pid == 0) {
    umask(022);
    close(pipefd[0]);
    //exevc调用可执行程序 执行updater-binary
    //int execl(const char *path, const char *arg, ...);        
    //path:可执行文件的路径
    //arg:传递的参数(一定要传递NULL)
    execv(chr_args[0], const_cast<char**>(chr_args));
    // Bug: 34769056
    // We shouldn't use LOG/PLOG in the forked process, since they may cause
    // the child process to hang. This deadlock results from an improperly
    // copied mutex in the ui functions.
    fprintf(stdout, "E:Can't run %s (%s)\n", chr_args[0], strerror(errno));
    _exit(EXIT_FAILURE);
  }
  close(pipefd[1]);

  std::atomic<bool> logger_finished(false);
  std::thread temperature_logger(log_max_temperature, max_temperature, std::ref(logger_finished));

  *wipe_cache = false;
  bool retry_update = false;

  char buffer[1024];
  //函数说明:fdopen()会将参数fildes 的文件描述词, 转换为对应的文件指针后返回.
  FILE* from_child = fdopen(pipefd[0], "r");
  //C 库函数 char *fgets(char *str, int n, FILE *stream) 从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。
  //当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。
  //下面的while循环主要为了更新父进程UI,并通过管道与父进程交互
  while (fgets(buffer, sizeof(buffer), from_child) != nullptr) {
    std::string line(buffer);
    size_t space = line.find_first_of(" \n");
    std::string command(line.substr(0, space));
    if (command.empty()) continue;
    ......
    ......
  return INSTALL_SUCCESS;
}

update_binary_command方法

int update_binary_command(const std::string& package, ZipArchiveHandle zip,
                          const std::string& binary_path, int retry_count, int status_fd,
                          std::vector<std::string>* cmd) {
  CHECK(cmd != nullptr);

  // On traditional updates we extract the update binary from the package.
  //constexpr是C++11中新增的关键字,其语义是“常量表达式”,也就是在编译期可求值的表达式。
  //最基础的常量表达式就是字面值或全局变量/函数的地址或sizeof等关键字返回的结果,
  //而其它常量表达式都是由基础表达式通过各种确定的运算得到的。constexpr值可用于enum、switch、数组长度等场合。
  //constexpr所修饰的变量一定是编译期可求值的,所修饰的函数在其所有参数都是constexpr时,一定会返回constexpr。
  static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary";
  //在升级包中找到脚本解释器updater-binary
  ZipString binary_name(UPDATE_BINARY_NAME);
  ZipEntry binary_entry;
  if (FindEntry(zip, binary_name, &binary_entry) != 0) {
    LOG(ERROR) << "Failed to find update binary " << UPDATE_BINARY_NAME;
    return INSTALL_CORRUPT;
  }

  unlink(binary_path.c_str());
  //创建/tmp/update-binary路径,并给到权限
  int fd = open(binary_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0755);
  if (fd == -1) {
    PLOG(ERROR) << "Failed to create " << binary_path;
    return INSTALL_ERROR;
  }
  //将updater-binary放入到文件夹中
  int32_t error = ExtractEntryToFile(zip, &binary_entry, fd);
  close(fd);
  if (error != 0) {
    LOG(ERROR) << "Failed to extract " << UPDATE_BINARY_NAME << ": " << ErrorCodeString(error);
    return INSTALL_ERROR;
  }
  //定义cmd容器中所包含的元素
  *cmd = {
    binary_path,//updater-binary路径
    std::to_string(kRecoveryApiVersion),//recoveryapi版本
    std::to_string(status_fd),//pipefd[1]写入数据
    package,//升级包路径
  };
  if (retry_count > 0) {
    cmd->push_back("retry");
  }
  return 0;
}

execv 执行update-binary,update-binary是由bootable/recovery/updater/ 编译生成的可执行文件,所以执行后,将进入updater.cpp的main方法

int main(int argc, char** argv) {
  // Various things log information to stdout or stderr more or less
  // at random (though we've tried to standardize on stdout).  The
  // log file makes more sense if buffering is turned off so things
  // appear in the right order.
  setbuf(stdout, nullptr);
  setbuf(stderr, nullptr);

  // We don't have logcat yet under recovery. Update logs will always be written to stdout
  // (which is redirected to recovery.log).
  android::base::InitLogging(argv, &UpdaterLogger);
  //我们执行update-binary,首先还是看下时什么参数传进了main方法,以上次的经验,还是放在initlog之后
  fprintf(stderr, "\n============updater.cpp main============\n");
  int i;
  for (i = 0; i < argc; i++){
    fprintf(stderr, "argc && argv we need know how much the argc argv\n");
    fprintf(stderr, "argv %d is %s\n", i, argv[i]);
  }
  printf("\n========================================\n");
  if (argc != 4 && argc != 5) {
    LOG(ERROR) << "unexpected number of arguments: " << argc;
    return 1;
  }

  char* version = argv[1];
  if ((version[0] != '1' && version[0] != '2' && version[0] != '3') || version[1] != '\0') {
    // We support version 1, 2, or 3.
    LOG(ERROR) << "wrong updater binary API; expected 1, 2, or 3; got " << argv[1];
    return 2;
  }

  // Set up the pipe for sending commands back to the parent process.
  int fd = atoi(argv[2]);
  FILE* cmd_pipe = fdopen(fd, "wb");
  setlinebuf(cmd_pipe);

  // Extract the script from the package.
  // 从升级包中提取出updater-scrypt
  const char* package_filename = argv[3];
  //加载内存空间
  MemMapping map;
  if (!map.MapFile(package_filename)) {
    LOG(ERROR) << "failed to map package " << argv[3];
    return 3;
  }
  //获取压缩包
  ZipArchiveHandle za;
  int open_err = OpenArchiveFromMemory(map.addr, map.length, argv[3], &za);
  if (open_err != 0) {
    LOG(ERROR) << "failed to open package " << argv[3] << ": " << ErrorCodeString(open_err);
    CloseArchive(za);
    return 3;
  }
  //获取updater-scrypt脚本,读取到内存中
  ZipString script_name(SCRIPT_NAME);
  ZipEntry script_entry;
  int find_err = FindEntry(za, script_name, &script_entry);
  if (find_err != 0) {
    LOG(ERROR) << "failed to find " << SCRIPT_NAME << " in " << package_filename << ": "
               << ErrorCodeString(find_err);
    CloseArchive(za);
    return 4;
  }

  std::string script;
  script.resize(script_entry.uncompressed_length);
  int extract_err = ExtractToMemory(za, &script_entry, reinterpret_cast<uint8_t*>(&script[0]),
                                    script_entry.uncompressed_length);
  if (extract_err != 0) {
    LOG(ERROR) << "failed to read script from package: " << ErrorCodeString(extract_err);
    CloseArchive(za);
    return 5;
  }
#if 1
    //打印出脚本的内容
    fprintf(stderr, "====== Updater-Script:\n");
    fprintf(stderr, "%s\n\n", script.c_str());
#endif
  // Configure edify's functions.
  //注册脚本中语句处理函数,识别脚本中的命令
  RegisterBuiltins();
  RegisterInstallFunctions();
  RegisterBlockImageFunctions();
  RegisterDeviceExtensions();

  // Parse the script.
  // 智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象, unique_ptr 独占所指向的对象
  // 解析update-scrypt脚本
  std::unique_ptr<Expr> root;
  int error_count = 0;
  int error = parse_string(script.c_str(), &root, &error_count);
  if (error != 0 || error_count > 0) {
    LOG(ERROR) << error_count << " parse errors";
    CloseArchive(za);
    return 6;
  }

  sehandle = selinux_android_file_context_handle();
  selinux_android_set_sehandle(sehandle);

  if (!sehandle) {
    fprintf(cmd_pipe, "ui_print Warning: No file_contexts\n");
  }

  // Evaluate the parsed script. 执行解析后的脚本

  UpdaterInfo updater_info;
  updater_info.cmd_pipe = cmd_pipe;
  updater_info.package_zip = za;
  updater_info.version = atoi(version);
  updater_info.package_zip_addr = map.addr;
  updater_info.package_zip_len = map.length;

  State state(script, &updater_info);

  if (argc == 5) {
    if (strcmp(argv[4], "retry") == 0) {
      state.is_retry = true;
    } else {
      printf("unexpected argument: %s", argv[4]);
    }
  }
  ota_io_init(za, state.is_retry);

  std::string result;
  bool status = Evaluate(&state, root, &result);

  if (have_eio_error) {
    fprintf(cmd_pipe, "retry_update\n");
  }

  if (!status) {
    if (state.errmsg.empty()) {
      LOG(ERROR) << "script aborted (no error message)";
      fprintf(cmd_pipe, "ui_print script aborted (no error message)\n");
    } else {
      LOG(ERROR) << "script aborted: " << state.errmsg;
      const std::vector<std::string> lines = android::base::Split(state.errmsg, "\n");
      for (const std::string& line : lines) {
        // Parse the error code in abort message.
        // Example: "E30: This package is for bullhead devices."
        if (!line.empty() && line[0] == 'E') {
          if (sscanf(line.c_str(), "E%d: ", &state.error_code) != 1) {
            LOG(ERROR) << "Failed to parse error code: [" << line << "]";
          }
        }
        fprintf(cmd_pipe, "ui_print %s\n", line.c_str());
      }
    }

    // Installation has been aborted. Set the error code to kScriptExecutionFailure unless
    // a more specific code has been set in errmsg.
    if (state.error_code == kNoError) {
      state.error_code = kScriptExecutionFailure;
    }
    fprintf(cmd_pipe, "log error: %d\n", state.error_code);
    // Cause code should provide additional information about the abort.
    if (state.cause_code != kNoCause) {
      fprintf(cmd_pipe, "log cause: %d\n", state.cause_code);
      if (state.cause_code == kPatchApplicationFailure) {
        LOG(INFO) << "Patch application failed, retry update.";
        fprintf(cmd_pipe, "retry_update\n");
      }
    }

    if (updater_info.package_zip) {
      CloseArchive(updater_info.package_zip);
    }
    return 7;
  } else {
    fprintf(cmd_pipe, "ui_print script succeeded: result was [%s]\n", result.c_str());
  }

  if (updater_info.package_zip) {
    CloseArchive(updater_info.package_zip);
  }

  return 0;
}

 

import re import os import json import hashlib import time from datetime import datetime, timedelta from collections import defaultdict from typing import List, Dict, Optional, Tuple from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler, FileModifiedEvent, FileCreatedEvent class HexMailParser: """解析FOXMAIL十六进制邮件数据""" def __init__(self): # 定义常见邮件头部的正则表达式模式 self.header_patterns = { 'from': re.compile(rb'From: ([^\r\n]+)'), 'to': re.compile(rb'To: ([^\r\n]+)'), 'subject': re.compile(rb'Subject: ([^\r\n]+)'), 'date': re.compile(rb'Date: ([^\r\n]+)'), 'message_id': re.compile(rb'Message-ID: ([^\r\n]+)') } # 十六进制转储行的正则表达式 self.hex_line_pattern = re.compile(rb'([0-9A-F]+)\s+((?:[0-9A-F]{2}\s+)+)(.*)') def parse_hex_dump(self, hex_data: bytes) -> bytes: """将十六进制转储数据转换为原始二进制数据""" binary_data = bytearray() lines = hex_data.split(b'\n') for line in lines: line = line.strip() if not line: continue # 匹配十六进制转储行格式 match = self.hex_line_pattern.match(line) if match: # 提取十六进制字节部分 hex_part = match.group(2).replace(b' ', b'') try: # 转换为二进制 binary_part = bytes.fromhex(hex_part.decode('ascii')) binary_data.extend(binary_part) except ValueError: # 忽略无法解析的行 continue return bytes(binary_data) def extract_headers(self, binary_data: bytes) -> Dict[str, str]: """从二进制数据中提取邮件头部信息""" headers = {} # 查找邮件头部区域(在空行之前) header_end = binary_data.find(b'\r\n\r\n') if header_end == -1: header_end = binary_data.find(b'\n\n') if header_end != -1: header_data = binary_data[:header_end] # 提取各个头部字段 for field, pattern in self.header_patterns.items(): match = pattern.search(header_data) if match: value = match.group(1).decode('utf-8', errors='ignore') headers[field] = value return headers def extract_body(self, binary_data: bytes) -> str: """从二进制数据中提取邮件正文""" # 查找邮件头部结束位置 header_end = binary_data.find(b'\r\n\r\n') if header_end == -1: header_end = binary_data.find(b'\n\n') if header_end != -1: body_data = binary_data[header_end + 4:] # +4 跳过空行 try: # 尝试多种编码解码 for encoding in ['utf-8', 'gbk', 'latin-1']: try: return body_data.decode(encoding) except UnicodeDecodeError: continue except: pass # 如果无法解码,返回原始字节的十六进制表示 return body_data.hex() def parse(self, hex_data: bytes) -> Dict: """解析十六进制邮件数据并返回结构化信息""" binary_data = self.parse_hex_dump(hex_data) headers = self.extract_headers(binary_data) body = self.extract_body(binary_data) # 解析日期 if 'date' in headers: try: # 尝试常见的日期格式 date_formats = [ '%a, %d %b %Y %H:%M:%S %z', '%a, %d %b %Y %H:%M:%S %Z', '%d %b %Y %H:%M:%S %z', ] parsed_date = None for fmt in date_formats: try: parsed_date = datetime.strptime(headers['date'], fmt) break except ValueError: continue if parsed_date: headers['parsed_date'] = parsed_date.isoformat() except: pass return { 'headers': headers, 'body': body, 'hash': hashlib.sha256(binary_data).hexdigest(), 'file_path': None # 存储邮件文件的原始路径 } class MailIndexer: """邮件索引管理系统""" def __init__(self, index_dir: str, mail_storage_dir: str, important_folders: List[str] = None): self.index_dir = index_dir self.mail_storage_dir = mail_storage_dir self.important_folders = important_folders or [] self.parser = HexMailParser() # 记录上次全量更新时间 self.last_full_update = None # 创建索引目录 os.makedirs(index_dir, exist_ok=True) # 初始化索引 self.index = { 'by_hash': {}, # 按哈希值索引 'by_sender': defaultdict(list), # 按发件人索引 'by_recipient': defaultdict(list), # 按收件人索引 'by_subject': defaultdict(list), # 按主题索引 'by_date': {}, # 按日期索引 'by_message_id': {} # 按Message-ID索引 } # 加载现有索引 self._load_index() def _load_index(self): """从磁盘加载现有索引""" for filename in os.listdir(self.index_dir): if filename.endswith('.json'): filepath = os.path.join(self.index_dir, filename) try: with open(filepath, 'r', encoding='utf-8') as f: mail_data = json.load(f) self._add_to_index(mail_data) except Exception as e: print(f"Error loading index file {filename}: {e}") def _save_mail(self, mail_data: Dict): """保存邮件数据到磁盘""" mail_hash = mail_data['hash'] filename = f"{mail_hash}.json" filepath = os.path.join(self.index_dir, filename) with open(filepath, 'w', encoding='utf-8') as f: json.dump(mail_data, f, ensure_ascii=False, indent=2) def _add_to_index(self, mail_data: Dict): """将邮件添加到内存索引""" mail_hash = mail_data['hash'] headers = mail_data['headers'] self.index['by_hash'][mail_hash] = mail_data # 按发件人索引 if 'from' in headers: self.index['by_sender'][headers['from']].append(mail_hash) # 按收件人索引 if 'to' in headers: recipients = headers['to'].split(',') for recipient in recipients: recipient = recipient.strip() if recipient: self.index['by_recipient'][recipient].append(mail_hash) # 按主题索引 if'subject' in headers: self.index['by_subject'][headers['subject']].append(mail_hash) # 按日期索引 if 'parsed_date' in headers: date = headers['parsed_date'].split('T')[0] # 提取日期部分 if date not in self.index['by_date']: self.index['by_date'][date] = [] self.index['by_date'][date].append(mail_hash) # 按Message-ID索引 if'message_id' in headers: self.index['by_message_id'][headers['message_id']] = mail_hash def index_mail(self, file_path: str) -> Optional[Dict]: """索引单个邮件文件""" try: with open(file_path, 'rb') as f: hex_data = f.read() mail_data = self.parser.parse(hex_data) mail_data['file_path'] = file_path # 记录原始文件路径 # 检查是否已索引 if mail_data['hash'] in self.index['by_hash']: existing_mail = self.index['by_hash'][mail_data['hash']] # 如果文件路径不同,可能是同一邮件的副本 if existing_mail.get('file_path') != file_path: print(f"Found duplicate mail with different path: {file_path}") return None return None # 添加到索引并保存 self._add_to_index(mail_data) self._save_mail(mail_data) print(f"Indexed mail: {mail_data['headers'].get('subject', '(No subject)')}") return mail_data except Exception as e: print(f"Error indexing mail {file_path}: {e}") return None def full_index_update(self) -> int: """执行全量索引更新""" start_time = time.time() count = 0 print(f"Starting full index update...") for root, dirs, files in os.walk(self.mail_storage_dir): for file in files: file_path = os.path.join(root, file) mail_data = self.index_mail(file_path) if mail_data: count += 1 self.last_full_update = datetime.now() duration = time.time() - start_time print(f"Full index update completed: {count} new/updated mails, took {duration:.2f} seconds") return count def is_important_folder(self, file_path: str) -> bool: """检查文件是否在重要文件夹中""" if not self.important_folders: return False for folder in self.important_folders: if folder in file_path: return True return False def search_by_sender(self, sender: str) -> List[Dict]: """按发件人搜索邮件""" mail_hashes = self.index['by_sender'].get(sender, []) return [self.index['by_hash'][h] for h in mail_hashes] def search_by_recipient(self, recipient: str) -> List[Dict]: """按收件人搜索邮件""" mail_hashes = self.index['by_recipient'].get(recipient, []) return [self.index['by_hash'][h] for h in mail_hashes] def search_by_subject(self, subject: str) -> List[Dict]: """按主题搜索邮件""" mail_hashes = self.index['by_subject'].get(subject, []) return [self.index['by_hash'][h] for h in mail_hashes] def search_by_date(self, date: str) -> List[Dict]: """按日期搜索邮件(格式:YYYY-MM-DD)""" mail_hashes = self.index['by_date'].get(date, []) return [self.index['by_hash'][h] for h in mail_hashes] def get_mail_by_hash(self, mail_hash: str) -> Optional[Dict]: """按哈希值获取邮件""" return self.index['by_hash'].get(mail_hash) def get_mail_by_message_id(self, message_id: str) -> Optional[Dict]: """按Message-ID获取邮件""" mail_hash = self.index['by_message_id'].get(message_id) if mail_hash: return self.index['by_hash'].get(mail_hash) return None class MailChangeHandler(FileSystemEventHandler): """处理邮件目录变化的事件处理器""" def __init__(self, indexer: MailIndexer): self.indexer = indexer def on_created(self, event): """处理文件创建事件""" if not event.is_directory: print(f"New file detected: {event.src_path}") if self.indexer.is_important_folder(event.src_path): print("File is in important folder, indexing immediately...") self.indexer.index_mail(event.src_path) def on_modified(self, event): """处理文件修改事件""" if not event.is_directory: print(f"File modified: {event.src_path}") if self.indexer.is_important_folder(event.src_path): print("File is in important folder, reindexing immediately...") self.indexer.index_mail(event.src_path) class MailIndexManager: """邮件索引管理器,整合定期更新和实时监听功能""" def __init__(self, index_dir: str, mail_storage_dir: str, important_folders: List[str] = None, full_update_interval: int = 24): """ 初始化邮件索引管理器 参数: index_dir: 索引存储目录 mail_storage_dir: 邮件存储目录 important_folders: 重要文件夹列表,这些文件夹的变化会被实时监听 full_update_interval: 全量更新间隔(小时) """ self.indexer = MailIndexer(index_dir, mail_storage_dir, important_folders) self.full_update_interval = full_update_interval self.observer = None def start_monitoring(self): """启动文件系统监听""" if not self.indexer.important_folders: print("No important folders specified, skipping real-time monitoring") return print("Starting real-time monitoring for important folders...") event_handler = MailChangeHandler(self.indexer) self.observer = Observer() for folder in self.indexer.important_folders: folder_path = os.path.join(self.indexer.mail_storage_dir, folder) if os.path.exists(folder_path): self.observer.schedule(event_handler, path=folder_path, recursive=True) print(f"Monitoring folder: {folder_path}") else: print(f"Warning: Important folder not found: {folder_path}") self.observer.start() def stop_monitoring(self): """停止文件系统监听""" if self.observer: self.observer.stop() self.observer.join() print("Real-time monitoring stopped") def need_full_update(self) -> bool: """检查是否需要进行全量更新""" if not self.indexer.last_full_update: return True elapsed = datetime.now() - self.indexer.last_full_update return elapsed.total_seconds() >= self.full_update_interval * 3600 def run_scheduled_update(self): """运行计划更新""" if self.need_full_update(): print(f"Scheduled full update triggered (last update: {self.indexer.last_full_update})") self.indexer.full_index_update() else: print(f"Full update not needed yet (last update: {self.indexer.last_full_update})") def run(self, check_interval: int = 3600): """运行管理器主循环 参数: check_interval: 检查间隔(秒),用于检查是否需要全量更新 """ try: # 启动时先进行一次全量更新 self.indexer.full_index_update() # 启动实时监听 self.start_monitoring() print(f"Mail index manager running. Full updates every {self.full_update_interval} hours.") print(f"Checking for updates every {check_interval} seconds.") # 主循环 while True: time.sleep(check_interval) self.run_scheduled_update() except KeyboardInterrupt: print("Shutting down...") finally: # 确保清理资源 self.stop_monitoring() # 使用示例 if __name__ == "__main__": index_dir = r'D:\YOUJIANSUOYIN' mail_storage_dir = r'D:\Foxmail\Storage\sh-op9@isaco-sh.com\Mails' # 指定重要文件夹(相对于mail_storage_dir的路径) important_folders = [ 'Inbox', # 收件箱 'Sent Items', # 已发送邮件 'Important' # 重要文件夹 ] # 创建管理器实例(全量更新间隔为24小时) manager = MailIndexManager( index_dir=index_dir, mail_storage_dir=mail_storage_dir, important_folders=important_folders, full_update_interval=24 ) # 运行管理器(检查间隔为1小时) manager.run(check_interval=3600)
05-11
from maix import touchscreen, display, image import time class GUI: def __init__(self) -> None: self.background = None self.items = list() self.callbacks = list() self.labels = list() self.touch_x = 0 self.touch_y = 0 # 加载字体 try: image.load_font("sourcehansans", "/maixapp/share/font/SourceHansansCN-Regular.otf") image.set_default_font("sourcehansans") print("Font loaded successfully") except Exception as e: print(f"Font loading error: {e}, using default font") image.set_default_font("default") # 初始化触摸屏和显示 try: self._ts = touchscreen.TouchScreen() self._disp = display.Display() print("Touchscreen and display initialized") except Exception as e: print(f"Display/touchscreen init error: {e}") raise self._last_pressed = 0 self._last_state = 0 # 0: release, 1: press def _is_in_item(self, item_id: int, x: int, y: int) -> bool: """检查点(x, y)是否在指定的item内""" if item_id >= len(self.items) or self.background is None: return False item = self.items[item_id] # 计算item在屏幕上的实际位置 screen_width = self._disp.width() screen_height = self._disp.height() bg_width = self.background.width() bg_height = self.background.height() # 计算映射比例 scale_x = screen_width / bg_width scale_y = screen_height / bg_height # 计算item在屏幕上的位置和大小 item_x = item * scale_x item_y = item * scale_y item_w = item * scale_x item_h = item * scale_y # 检查点是否在矩形内 return (item_x <= x <= item_x + item_w and item_y <= y <= item_y + item_h) def createButton(self, x: int, y: int, w: int, h: int) -> int: """创建按钮,返回按钮ID""" self.items.append((x, y, w, h)) self.callbacks.append(None) self.labels.append("") return len(self.items) - 1 def setItemLabel(self, item_id: int, label: str): """设置按钮标签""" if item_id < len(self.labels): self.labels[item_id] = label def setItemCallback(self, item_id: int, callback): """设置按钮回调函数""" if item_id < len(self.callbacks): self.callbacks[item_id] = callback def get_touch(self): """获取当前触摸点坐标(返回图像坐标系)""" # 获取屏幕尺寸 screen_width = self._disp.width() screen_height = self._disp.height() # 计算缩放比例 scale_x = self.background.width() / screen_width scale_y = self.background.height() / screen_height # 返回映射到图像坐标系的触摸点 return int(self.touch_x * scale_x), int(self.touch_y * scale_y) def run(self, img): """更新显示并处理触摸事件""" # 1. 更新背景图像 self.background = img.copy() # 2. 绘制所有按钮 for i, (x, y, w, h) in enumerate(self.items): # 修复:使用draw_rect而不是draw_rectangle self.background.draw_rect(x, y, w, h, image.COLOR_WHITE, thickness=2) # 绘制按钮标签 self.background.draw_string(x + 5, y + h//2 - 8, self.labels[i], image.COLOR_WHITE) # 3. 显示图像 self._disp.show(self.background) # 4. 处理触摸事件 points = self._ts.read() if points: point = points state = point # 触摸状态 x, y = point, point # 更新触摸点坐标 self.touch_x = x self.touch_y = y # 只处理按下事件 if state == 1 and self._last_state == 0: self._last_state = 1 # 检查触摸点是否在按钮内 for i in range(len(self.items)): if self._is_in_item(i, x, y): if self.callbacks[i]: self.callbacks[i](i, state) break elif state == 0: self._last_state = 0 from gui import GUI from maix import camera, image, time, app import math # 屏幕宽度和高度 _image_width = 320 _image_height = 240 _btn_width = _image_width // 6 _btn_height = _image_height // 6 _btn_id_pixel = -1 _btn_id_binary = -1 _to_show_binary = False _to_get_pixel = False def rgb_to_lab(rgb): # RGB到XYZ的转换矩阵 M = [ [0.412453, 0.357580, 0.180423], [0.212671, 0.715160, 0.072169], [0.019334, 0.119193, 0.950227] ] # 归一化RGB值 r, g, b = rgb / 255.0, rgb / 255.0, rgb / 255.0 # 线性化RGB值 r = r / 12.92 if r <= 0.04045 else ((r + 0.055) / 1.055) ** 2.4 g = g / 12.92 if g <= 0.04045 else ((g + 0.055) / 1.055) ** 2.4 b = b / 12.92 if b <= 0.04045 else ((b + 0.055) / 1.055) ** 2.4 # 计算XYZ值 X = M * r + M * g + M * b Y = M * r + M * g + M * b Z = M * r + M * g + M * b # XYZ归一化 X /= 0.95047 Y /= 1.00000 Z /= 1.08883 def f(t): return t ** (1/3) if t > 0.008856 else 7.787 * t + 16/116 L = 116 * f(Y) - 16 a = 500 * (f(X) - f(Y)) b_val = 200 * (f(Y) - f(Z)) return [L, a, b_val] def set_configured_threshold(threshold): """阈值参数信息存入配置文件""" if len(threshold) < 6: return # 阈值范围验证 threshold = max(threshold, 0) # Lmin >= 0 threshold = min(threshold, 100) # Lmax <= 100 threshold = max(threshold, -128) # amin >= -128 threshold = min(threshold, 127) # amax <= 127 threshold = max(threshold, -128) # bmin >= -128 threshold = min(threshold, 127) # bmax <= 127 app.set_app_config_kv('demo_find_line', 'lmin', str(threshold), False) app.set_app_config_kv('demo_find_line', 'lmax', str(threshold), False) app.set_app_config_kv('demo_find_line', 'amin', str(threshold), False) app.set_app_config_kv('demo_find_line', 'amax', str(threshold), False) app.set_app_config_kv('demo_find_line', 'bmin', str(threshold), False) app.set_app_config_kv('demo_find_line', 'bmax', str(threshold), True) def get_configured_threshold(): """获取所有储配置文件中的阈值参数""" threshold = [0, 100, -128, 127, -128, 127] # 默认阈值 value_str = app.get_app_config_kv('demo_find_line', 'lmin', '', False) if value_str: threshold = int(value_str) value_str = app.get_app_config_kv('demo_find_line', 'lmax', '', False) if value_str: threshold = int(value_str) value_str = app.get_app_config_kv('demo_find_line', 'amin', '', False) if value_str: threshold = int(value_str) value_str = app.get_app_config_kv('demo_find_line', 'amax', '', False) if value_str: threshold = int(value_str) value_str = app.get_app_config_kv('demo_find_line', 'bmin', '', False) if value_str: threshold = int(value_str) value_str = app.get_app_config_kv('demo_find_line', 'bmax', '', False) if value_str: threshold = int(value_str) return threshold def btn_pressed(btn_id, state): """界面上按键的状态改变回调函数""" global _to_show_binary, _to_get_pixel, _btn_id_binary, _btn_id_pixel if state == 0: # 只响应触摸指起的动作 return if btn_id == _btn_id_binary: _to_show_binary = not _to_show_binary if _to_get_pixel: _to_get_pixel = False print(f"Binary mode: {'ON' if _to_show_binary else 'OFF'}") elif btn_id == _btn_id_pixel: _to_get_pixel = not _to_get_pixel print(f"Get pixel mode: {'ON' if _to_get_pixel else 'OFF'}") def main(): global _to_show_binary, _to_get_pixel, _btn_id_binary, _btn_id_pixel print("App config path:", app.get_app_config_path()) try: # 初始化摄像头 cam = camera.Camera(_image_width, _image_height) print("Camera initialized") # 初始化GUI gui = GUI() print("GUI initialized") # 创建按钮 _btn_id_pixel = gui.createButton(10, _image_height - _btn_height - 10, _btn_width, _btn_height) gui.setItemLabel(_btn_id_pixel, '取阈值') gui.setItemCallback(_btn_id_pixel, btn_pressed) _btn_id_binary = gui.createButton(_image_width - _btn_width - 10, _image_height - _btn_height - 10, _btn_width, _btn_height) gui.setItemLabel(_btn_id_binary, '二值化') gui.setItemCallback(_btn_id_binary, btn_pressed) last_x = -1 last_y = -1 threshold = get_configured_threshold() print("Initial threshold:", threshold) # FPS计算 start_time = time.ticks_ms() frame_count = 0 fps_value = 0 while not app.need_exit(): frame_count += 1 current_time = time.ticks_ms() elapsed = current_time - start_time # 每秒更新FPS if elapsed >= 1000: fps_value = frame_count * 1000 // elapsed frame_count = 0 start_time = current_time print(f"FPS: {fps_value}") try: # 1. 读取图像 img = cam.read() except Exception as e: print(f"Camera read error: {e}") time.sleep(0.1) # 防止错误循环占用100% CPU continue # 2. 处理图像显示模式 if _to_show_binary: # 二值化显示 try: # 使用正确的参数格式 [lmin, lmax, amin, amax, bmin, bmax] img_bin = img.binary([threshold], invert=False) img = img_bin except Exception as e: print(f"Binary conversion error: {e}") else: # 原始图像显示 try: # 2. 获取触摸点像素值 if _to_get_pixel: x, y = gui.get_touch() if 0 <= x < _image_width and 0 <= y < _image_height: if last_x != x or last_y != y: last_x = x last_y = y rgb = img.get_pixel(x, y, True) lab = rgb_to_lab(rgb) if len(lab) >= 3: # 计算新阈值范围 threshold = max(math.floor(lab) - 30, 0) threshold = min(math.ceil(lab) + 30, 100) threshold = max(math.floor(lab) - 10, -128) threshold = min(math.ceil(lab) + 10, 127) threshold = max(math.floor(lab) - 10, -128) threshold = min(math.ceil(lab) + 10, 127) print("New threshold:", threshold) set_configured_threshold(threshold) img.draw_cross(x, y, image.COLOR_YELLOW, 8, 2) except Exception as e: print(f"Touch processing error: {e}") try: # 3. 线性回归找线 area_threshold = 100 # 使用正确的参数格式 lines = img.get_regression( [threshold], # 阈值列表 area_threshold=area_threshold, pixels_threshold=area_threshold ) if lines: # 修复:get_regression返回的是点坐标元组列表 # 每个元素是((x1, y1), (x2, y2)) line = lines x1, y1 = line x2, y2 = line # 绘制检测到的线段 img.draw_line(x1, y1, x2, y2, image.COLOR_GREEN, 2) # 计算线段参数 magnitude = math.sqrt((x2 - x1)**2 + (y2 - y1)**2) theta = math.degrees(math.atan2(y2 - y1, x2 - x1)) rho = (x1 + x2) / 2 # 简化表示中点x坐标 info = f"mag:{magnitude:.1f} theta:{theta:.1f} rho:{rho:.1f}" img.draw_string(0, 0, info, image.COLOR_RED) except Exception as e: print(f"Line detection error: {e}") # 显示FPS img.draw_string(_image_width - 80, 0, f"FPS:{fps_value:.1f}", image.COLOR_RED) # 4. 更新显示 try: gui.run(img) except Exception as e: print(f"GUI update error: {e}") except Exception as e: print(f"Application error: {e}") finally: # 显式释放资源 try: if 'cam' in locals(): cam.close() print("Camera released") except Exception as e: print(f"Camera release error: {e}") print("Application exited") if __name__ == '__main__': main() 检查代码
07-18
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值