展訊庫侖計驅動

来源:https://www.cnblogs.com/linhaostudy/archive/2020/04/06/12639159.html
-Advertisement-
Play Games

`sprd_27xx_fgu.c`就是展訊SL8541E 庫侖計驅動,用來統計電量的; 還是一樣,從 分析開始: 其中這裡有三個工作隊列: 1. sprdfgu_debug_works工作隊列 2. sprdfgu_qmax_works工作隊列,電量自學習工作隊列 3. 在 函數中的sprdfgu_ ...


sprd_27xx_fgu.c就是展訊SL8541E 庫侖計驅動,用來統計電量的;

還是一樣,從static int sprdfgu_2723_probe(struct platform_device *pdev)分析開始:


static int sprdfgu_2723_probe(struct platform_device *pdev)
{
	int ret = 0, irq = 0;
	u32 value = 0;
	struct device_node *np = pdev->dev.of_node;
	const struct of_device_id *of_id;
	struct power_supply *ret_ptr = NULL;
	struct power_supply_desc *sprdfgu_desc = NULL;

	reg_map = dev_get_regmap(pdev->dev.parent, NULL);
	if (!reg_map)
		dev_err(&pdev->dev, "%s :NULL regmap for fgu 2723\n",
			__func__);
	sprdfgu_data.fgu_pdata = devm_kzalloc(&pdev->dev,
		sizeof(struct sprdfgu_platform_data),
		GFP_KERNEL);
	of_id = of_match_node(sprdfgu_2723_of_match,
		pdev->dev.of_node);
	if (!of_id) {
		pr_err("get 27xx fgu of device id failed!\n");
		return -ENODEV;
	}
	sprdfgu_data.fgu_pdata->fgu_type = (enum fgu_type)of_id->data;
	SPRD_FGU_DEBUG("fgu_type =%d\n",
		sprdfgu_data.fgu_pdata->fgu_type);

    //ocv-type 為0 時,會使用ocv表來統計開機的初始化電量
	ret = of_property_read_u32(np, "ocv-type",
		&sprdfgu_data.fgu_pdata->ocv_type);
	if (ret)
		dev_err(&pdev->dev, "%s :no ocv-type\n",
			__func__);
	irq = platform_get_irq(pdev, 0);
	if (unlikely(irq <= 0))
		dev_err(&pdev->dev, "no irq resource specified\n");
	sprdfgu_data.fgu_pdata->fgu_irq = irq;
	
	//reg是指庫侖計使用的時候的偏移地址
	ret = of_property_read_u32(np, "reg", &value);
	if (ret)
		dev_err(&pdev->dev, "%s :no reg of property for pmic fgu\n",
			__func__);
	sprdfgu_data.fgu_pdata->base_addr = (unsigned long)value;

	sprdfgu_desc = devm_kzalloc(&pdev->dev,
			sizeof(struct power_supply_desc), GFP_KERNEL);
	if (sprdfgu_desc == NULL)
		return -ENOMEM;
		
	//註冊power_supply節點
	sprdfgu_desc->name = "sprdfgu";
	sprdfgu_desc->get_property = sprdfgu_power_get_property;
	ret_ptr = power_supply_register(&pdev->dev, sprdfgu_desc, NULL);
	if (IS_ERR(ret_ptr))
		pr_err("register power supply error!\n");
	else
		sprdfgu_data.sprdfgu = ret_ptr;
	sprdfgu_data.dev = &pdev->dev;
	platform_set_drvdata(pdev, &sprdfgu_data);
	ret = sysfs_create_group(&sprdfgu_data.sprdfgu->dev.kobj,
			&sprd_fgu_group);
	if (ret)
		pr_err("failed to create sprd_fgu sysfs device attributes\n");
	INIT_DELAYED_WORK(&sprdfgu_debug_work, sprdfgu_debug_works);
	mutex_init(&sprdfgu_data.lock);
	mutex_init(&sprdfgu_data.track_lock);
	wake_lock_init(&(sprdfgu_data.low_power_lock), WAKE_LOCK_SUSPEND,
		       "sprdfgu_powerlow_lock");

	sprdfgu_data.is_track = 1;

	INIT_DELAYED_WORK(&sprdfgu_data.fgu_qmax_work,
			  sprdfgu_qmax_works);
	sprdfgu_data.fgu_wqueue =
		create_workqueue("sprdfgu_monitor");
	if (!sprdfgu_data.fgu_wqueue)
		sprdfgu_data.is_track = 0;

	sprdfgu_data.track.vol_buff = devm_kzalloc(&pdev->dev,
		sizeof(int) * CUR_VOL_BUFF_LEN,
		GFP_KERNEL);
	if (!sprdfgu_data.track.vol_buff)
		sprdfgu_data.is_track = 0;

	sprdfgu_data.track.cur_buff = devm_kzalloc(&pdev->dev,
		sizeof(int) * CUR_VOL_BUFF_LEN,
		GFP_KERNEL);
	if (!sprdfgu_data.track.cur_buff)
		sprdfgu_data.is_track = 0;

	SPRD_FGU_DEBUG("sprdfgu_2723_probe ok\n");
	return ret;
}

其中這裡有三個工作隊列:

  1. sprdfgu_debug_works工作隊列
  2. sprdfgu_qmax_works工作隊列,電量自學習工作隊列
  3. sprdfgu_int_init函數中的sprdfgu_irq_works函數

1、 函數為空函數,則不予以處理和分析

2、電容自適應函數:

主要在sprdfgu_read_soc函數中讀取:

並且在sprdfgu_qmax_update_monitor函數中當

當電池工作電流足夠小的時候電池工作電壓近似等於電池開路電壓,如果有準確的電量表我們就能準確的定位電池容量我們記錄為 C1,將這時庫侖計計數值容量記錄為Q1。充電滿後電池真實容量接近 100%,這裡我們將庫侖計計數值記錄為 Q2。根據以上幾個數據我們就可以得到電池真實有效容量:

Qmax = (Q2 – Q1) * 100/(100 –C1)

我們一般採用低電量開始作為初始採集點,並嚴格規定了環境溫度即電流、電壓條件,只有符合規定的容量自學習我們才認為是有效的自學習。 自學習功能被抽象為 qmax,結構如下, 各種限制條件需要在初始化的時候寫入。

struct sprdfgu_qmax {
    int state; //自學習狀態
    int cur_cnom; //當前容量
    int s_clbcnt; //開始自學習的庫侖計計數
    int s_soc; //自學習初始點百分比
    int s_vol; //自學習初始電壓限制
    int s_cur; //自學習初始電流限制
    int e_vol; //自學習結束電壓條件
    int e_cur; //自學習結束電流條件
    int force_s_flag; //應用層控制啟動標誌位
    int force_s_soc; //應用層直接寫入容量值
    uint32_t timeout; //設置的超時門限
    __s64 s_time; //開始自學習的時間點
};

2、 電量保存與回讀

自學習狀態 State 有 5 種, DONE 狀態是學習完成狀態, 在 DONE 狀態會將學習完成的容量值寫入/productinfo/.battery_file 這個文件。 productinfo 分區掉電不擦除,重啟後再 INIT狀態會回讀這個數據作為容量基數進行電量積分,並將自學習狀態切換到 IDLE 狀態。 如果滿足了自學習條件就進入了 QMAX_UPDATING, UPDATING 狀態下會檢查自學習後容量, 如果容量檢查失效則恢復 IDLE狀態,需要重新開始學習。 如果手機單體沒有完成過自學習則使用預設的容量配置。

enum QMAX_STATE {
QMAX_INIT, //開機預設狀態
QMAX_IDLE, //自學習準備狀態
QMAX_UPDATING, //電量需要更新
QMAX_DONE, //自學習完成
QMAX_ERR, //異常
};

sys/class/power_supply/sprdfgu/ qmax_force_start
應用層主動開啟自學習功能
sys/class/power_supply/sprdfgu/ qmax_force_s_soc
應用層設置開啟自學習時的容量
sys/class/power_supply/sprdfgu/ qmax_state
讀取當前自學習狀態
sys/class/power_supply/sprdfgu/ qmax_s_vol
設置開啟自學習的電壓條件
sys/class/power_supply/sprdfgu/ qmax_s_cur10
設置開啟自學習的電流條件
sys/class/power_supply/sprdfgu/ qmax_e_vol
設置自學習停止電壓條件
sys/class/power_supply/sprdfgu/ qmax_e_cur
設置自學習停止電流條件
sys/class/power_supply/sprdfgu/ cnom
讀取當前積分容量基數
sys/class/power_supply/sprdfgu/ saved_cnom
應用層直接寫數保存容量

3. 本質上就是用sprdfgu_irq_works計算電量的


static void sprdfgu_irq_works(struct work_struct *work)
{
	uint32_t cur_ocv;
	uint32_t adc;
	int cur_soc;

	wake_lock_timeout(&(sprdfgu_data.low_power_lock), 2 * HZ);

	SPRD_FGU_DEBUG("%s......0x%x.cur vol = %d\n",
		__func__, sprdfgu_data.int_status,
		sprdfgu_read_vbat_vol());

	if (sprdfgu_data.int_status & BIT_VOLT_HIGH_INT)
		power_supply_changed(sprdfgu_data.sprdfgu);

	if (sprdfgu_data.int_status & BIT_VOLT_LOW_INT) {
		cur_soc = sprdfgu_read_soc();
		mutex_lock(&sprdfgu_data.lock);

		cur_ocv = sprdfgu_read_vbat_ocv();
		if (cur_ocv <= sprdfgu_data.shutdown_vol) {
			SPRD_FGU_DEBUG("shutdown_vol .\n");
			sprdfgu_soc_adjust(0);
		} else if (cur_ocv <= sprdfgu_data.pdata->alm_vol) {
			SPRD_FGU_DEBUG("alm_vol %d.\n", cur_soc);
			if (cur_soc > sprdfgu_data.warning_cap) {
				sprdfgu_soc_adjust(sprdfgu_data.warning_cap);
			} else if (cur_soc <= 0) {
				sprdfgu_soc_adjust(sprdfgu_vol2capacity
						   (cur_ocv));
			}
			if (!sprdfgu_data.adp_status) {
				adc = sprdfgu_vol2adc_mv
					(sprdfgu_data.shutdown_vol);
				fgu_adi_write(REG_FGU_LOW_OVER, adc & 0xFFFF,
					      ~0);
			}
		} else {
			SPRD_FGU_DEBUG("need add\n");
		}
		mutex_unlock(&sprdfgu_data.lock);
	}
}

首先第一個函數sprdfgu_read_vbat_vol

讀取vbat上的電壓:

uint32_t sprdfgu_read_vbat_vol(void)
{
	u32 cur_vol_raw;
	uint32_t temp;

	cur_vol_raw = sprdfgu_reg_get(REG_FGU_VOLT_VAL);
	temp = sprdfgu_adc2vol_mv(cur_vol_raw);
	return temp;
}

第二個函數sprdfgu_read_soc函數,也就是電量計的核心:

int sprdfgu_read_soc(void)
{
	int cur_cc, cc_delta, capcity_delta, temp;
	uint32_t cur_ocv;
	int cur;
	union power_supply_propval val;

	sprdfgu_track();
	mutex_lock(&sprdfgu_data.lock);

	sprdfgu_qmax_update_monitor();

	cur_cc = sprdfgu_clbcnt_get();
	cc_delta = cur_cc - sprdfgu_data.init_clbcnt;

	/* 0.1mah */
	temp = DIV_ROUND_CLOSEST(cc_delta,
		(360 * FGU_CUR_SAMPLE_HZ));
	temp = sprdfgu_adc2cur_ma(temp);

	capcity_delta = DIV_ROUND_CLOSEST(temp * 100,
		sprdfgu_data.cur_cnom);

	SPRD_FGU_DEBUG("d cap %d,cnom %d,g %dmAh(0.1mA),init_cc:%d\n",
		capcity_delta, sprdfgu_data.cur_cnom,
		temp, sprdfgu_data.init_clbcnt);

	capcity_delta += sprdfgu_data.init_cap;

	SPRD_FGU_DEBUG("soc %d,init_cap %d,cap:%d\n",
		  (capcity_delta + 9) / 10, sprdfgu_data.init_cap,
		  capcity_delta);

	cur = sprdfgu_read_batcurrent();
	cur_ocv = sprdfgu_read_vbat_ocv();
	if ((cur_ocv >= sprdfgu_data.bat_full_vol)
	    && (abs(cur) <= sprdfgu_data.pdata->chg_end_cur)) {
		SPRD_FGU_DEBUG("cur_ocv %d\n", cur_ocv);
		if (capcity_delta < FULL_CAP) {
			capcity_delta = FULL_CAP;
			sprdfgu_soc_adjust(FULL_CAP);
		}
	}

	if (capcity_delta > FULL_CAP) {
		capcity_delta = FULL_CAP;
		sprdfgu_soc_adjust(FULL_CAP);
	}

	sprdpsy_get_property("battery",
				POWER_SUPPLY_PROP_STATUS,
				&val);
	if (val.intval != POWER_SUPPLY_STATUS_CHARGING) {
		if (capcity_delta <= sprdfgu_data.warning_cap
		    && cur_ocv > sprdfgu_data.pdata->alm_vol) {
			SPRD_FGU_DEBUG("soc low...\n");
			capcity_delta = sprdfgu_data.warning_cap + 10;
			sprdfgu_soc_adjust(capcity_delta);
		} else if (capcity_delta <= 0
		    && cur_ocv > sprdfgu_data.shutdown_vol) {
			SPRD_FGU_DEBUG("soc 0...\n");
			capcity_delta = 10;
			sprdfgu_soc_adjust(capcity_delta);
		} else if (cur_ocv < sprdfgu_data.shutdown_vol) {
			SPRD_FGU_DEBUG("vol 0...\n");
			capcity_delta = 0;
			sprdfgu_soc_adjust(capcity_delta);
		} else if (capcity_delta > sprdfgu_data.warning_cap
			   && cur_ocv < sprdfgu_data.pdata->alm_vol) {
			SPRD_FGU_DEBUG("soc high...\n");
			sprdfgu_soc_adjust(sprdfgu_vol2capacity(cur_ocv));
			capcity_delta = sprdfgu_vol2capacity(cur_ocv);
		}
	}

	if (capcity_delta < 0)
		capcity_delta = 0;

	sprdfgu_record_rawsoc(capcity_delta);

#ifdef SPRDFGU_TEMP_COMP_SOC
	{
		temp = sprdbat_read_temp();
		capcity_delta =
			sprdfgu_temp_comp_soc(capcity_delta, temp / 10);
	}
#endif

	if (sprdfgu_read_vbat_vol() < sprdfgu_data.pdata->soft_vbat_uvlo) {
		SPRD_FGU_DEBUG("trigger soft uvlo vol 0...\n");
		capcity_delta = 0;
		sprdfgu_soc_adjust(capcity_delta);
	}

	mutex_unlock(&sprdfgu_data.lock);
	return capcity_delta;
}
  1. 首先通過sprdfgu_track函數來定位電池開路電壓;

這裡首先介紹電池內阻自適應:

電池開路電壓 OCV 是表徵電池容量的最關鍵因素,在電量顯示、電池容量自學習的時候都可能用到電池開路電壓來定位百分比。因此我們必須找到一個方法定位電池開路電壓。

FGU 可以獲取到電池工作狀態下的電流 I 和電壓 VBAT,根據歐姆定律只要我們得到電池內阻 R 就可以計算出電池開路電壓了: OCV = VBAT – I *R。 電池內阻一般都是動態變化的,不同容量不同溫度都會有不同的電池內阻,所以需要一個動態調整內阻的演算法來保證內阻的準確性, 平臺提供了一套電池內阻自適應的演算法:提取硬體 buff 中的一組電流電壓值(共 8 個)並將這 8 個數據按大小排序,找到最大的壓降 DeltaV 和電流變化DeltaI,根據歐姆定律 R =DeltaV/DeltaI;使用計算出來的 R 就能得到實時 OCV 了。

電池內阻自學習通過 sprdfgu_tracking 來記錄學習狀態,成員包括自學習時的溫度、平均電流、平均電壓、電流 buff、電壓 buff、最小電流、 最小電壓等等。 Chargework 會調用 static void sprdfgu_track(void)來更新 sprdfgu_data.track,使用的時候通過介面 static struct sprdfgu_tracking sprdfgu_get_track_data(void);獲取到實時的電池工作狀態信息sprdfgu_data.track。

struct sprdfgu_tracking {
__s64 r_time;
__s64 query_time; //結構更新時間間隔
int rint; //電池內阻自適應出來的內阻
int r_temp; //獲取電池內阻時候判斷採樣溫度
int c_temp; //容量自適應的時候判斷採樣的溫度
int r_vol; //
int relax_vol; //最接近電池開路電壓
int relax_cur; //最接近零點的電流
int ave_vol; //平均電壓
int ave_cur; //平均電流
int *cur_buff; //電流 buff
int *vol_buff; //電壓 buff
};

sprdfgu_track則是算出上述數據的函數;

  1. sprdfgu_qmax_update_monitor則是電池自學習的過程,所以不再敘述;

  2. sprdfgu_clbcnt_get獲取記錄電流積分數據;

  3. 獲取當前 Coulomb Counter 計數量下次輪詢調用的時候可以計算出兩 次 Coulomb Counter 統計的差值從而得到庫侖量變化 cc_delta 。DIV_ROUND_CLOSEST(cc_delta, (3600 * 2));

  4. DIV_ROUND_CLOSEST(mah * 100, sprdfgu_data.pdata->cnom);通過變化的 mah 計算出與總容量 cnom 的比值,從而得到變化百分比。

  5. capcity_delta += sprdfgu_data.init_cap;計算出這次電量加上初始化的電量;

  6. sprdfgu_read_batcurrent讀取現在電池vbat的容量;

  7. sprdfgu_read_vbat_ocv讀取開路電壓的ocv;

  8. 對一些ocv的條件判定,具體看代碼:

    //開路電壓ocv大於電池滿電的狀態並且充電電流小於充電截止電流
    if ((cur_ocv >= sprdfgu_data.bat_full_vol)
	    && (abs(cur) <= sprdfgu_data.pdata->chg_end_cur)) {
		SPRD_FGU_DEBUG("cur_ocv %d\n", cur_ocv);
		if (capcity_delta < FULL_CAP) {
			capcity_delta = FULL_CAP;
			sprdfgu_soc_adjust(FULL_CAP);
		}
	}
    
    //大於100,電量則設置100
	if (capcity_delta > FULL_CAP) {
		capcity_delta = FULL_CAP;
		sprdfgu_soc_adjust(FULL_CAP);
	}

	sprdpsy_get_property("battery",
				POWER_SUPPLY_PROP_STATUS,
				&val);
	//不在充電狀態的話,
	if (val.intval != POWER_SUPPLY_STATUS_CHARGING) {
		if (capcity_delta <= sprdfgu_data.warning_cap
		    && cur_ocv > sprdfgu_data.pdata->alm_vol) {
			SPRD_FGU_DEBUG("soc low...\n");
			capcity_delta = sprdfgu_data.warning_cap + 10;
			sprdfgu_soc_adjust(capcity_delta);
		} else if (capcity_delta <= 0
		    && cur_ocv > sprdfgu_data.shutdown_vol) {
			SPRD_FGU_DEBUG("soc 0...\n");
			capcity_delta = 10;
			sprdfgu_soc_adjust(capcity_delta);
		} else if (cur_ocv < sprdfgu_data.shutdown_vol) {
			SPRD_FGU_DEBUG("vol 0...\n");
			capcity_delta = 0;
			sprdfgu_soc_adjust(capcity_delta);
		} else if (capcity_delta > sprdfgu_data.warning_cap
			   && cur_ocv < sprdfgu_data.pdata->alm_vol) {
			SPRD_FGU_DEBUG("soc high...\n");
			sprdfgu_soc_adjust(sprdfgu_vol2capacity(cur_ocv));
			capcity_delta = sprdfgu_vol2capacity(cur_ocv);
		}
	}

	if (capcity_delta < 0)
		capcity_delta = 0;
  1. sprdfgu_record_rawsoc記錄soc上raw;

另外:

  • sprdfgu_load_uicap讀取ui的電量,本身由rtc記錄。
  • sprdfgu_load_rawsoc則是記錄實際電量;
  • sprdfgu_soc_adjust調整電量。輸入為電量值

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在上一篇文章abp(net core)+easyui+efcore實現倉儲管理系統——入庫管理之六(四十二)中我們實現了新增入庫單的功能。結合之前的五篇文章,今天我們來測試一下入庫單新增功能。 ...
  • EF Core 數據變更自動審計設計 Intro 有的時候我們需要知道每個數據表的變更記錄以便做一些數據審計,數據恢復以及數據同步等之類的事情, EF 自帶了對象追蹤,使得我們可以很方便的做一些審計工作,每次變更發生了什麼變化都變得很清晰,於是就基於 EF 封裝了一層數據變更自動審計 使用效果 測試 ...
  • IViewLocationExpander API ExpandViewLocations Razor視圖路徑,視圖引擎會搜索該路徑. PopulateValues 每次調用都會填充路由 項目目錄如下所示 創建區域擴展器,其實我並不需要多區域,我目前只需要達到一個區域中有多個文件夾進行存放我的視圖. ...
  • 在這篇文章中,我將帶領大家詳細學習ASP.NET Core 中的Main方法。在這篇文章中,我將向大家詳細介紹下麵幾個問題:ASP.NET Core Main方法的重要性為什麼我們在ASP.NET Core中會有一個Main方法?當你運行一個ASP.NET Core應用程式的時候,背後發生了什麼?為... ...
  • .NET 走向開源,MIT許可協議。 微軟為了推動.NET開源社區的發展,2014年聯合社區成立了.NET基金會。 一年前 .NET 基金會完成第一次全面改選,2014年 .NET基金會的創始成員中有六位創始人,均非微軟公司員工,隨著微軟的收購動作,Miguel 也成了微軟員工,Migel一直在努力 ...
  • varnish的狀態引擎分前端工作線程或者客戶端狀態引擎和後端工作線程或者服務端狀態引擎;客戶端狀態引擎,主要處理客戶端請求和響應相關的處理,比如是否可查緩存,是否命中,是否修剪緩存,是否識別用戶請求的方法有或者直接交給vcl_pass,又或者說怎樣響應客戶端等等,可以看到客戶端狀態引擎vcl_p... ...
  • 性能指標 性能優化核心指標:吞吐和延遲 Linux Performance Tools: 平均負載 System load averages is the average number of processes that are either in a runnable or uninterrupt ...
  • 內核中把物理記憶體的低端區域作為直接映射區,高地址區域定義為高端記憶體,通過一個變數high_memory來界定他們的分界線。high_memory是一個虛擬地址,定義了高端記憶體被允許映射到內核的起始地址。 它在arm平臺上的定義如下: ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...