上海到三亚:从冬天到夏天

1月26日,今年上海的冬季与往年大有不同。继续连续一个多月都没有降水,让我有着在上海有使用加湿器的冲动;另外今年的冬季气温偏高,老婆还没有叫过冷,并且将此归结于锻炼的结果。大约是前几年,曾经有气象专家对于当年的冬季做出了一些预测,最终的结论是:是否是一个暖冬必须等到冬天结束才能知道。因为这个结论,专家被喷了很多,从此之后再也不见有人对于是否暖冬做出预测了。

飞机的目的是是海口,下机之后有发小接站。这是我到过的祖国最南端。不知道未来是否还有机会前往祖国更远的南方。

起飞的时候因为阴天,落地之后才注意到航班的涂装是“玩具总动员”,机头是巴斯光年:

发小开车带我们从中线横穿海南岛,一路上的景色和内陆大有不同。在东北道路两边通常是高大的白杨树守护着道路,而在这里经常是挺拔的椰子树在充当着道路守护者的责任。

途中休息时发小带我们到本地特色的餐厅,或者说是富有本地特色的餐饮一条街:“会跳舞的牛肉”。道路的一侧是各种牛肉店铺。挂在钩子上的牛肉非常新鲜,许多还在不停的抖动,故此得名。

牛肉店铺对面则是以新鲜牛肉为卖点的餐厅。食客下单后,餐厅老板马上到对面购买牛肉,十分钟之内即可端上餐桌。

店铺类似大排档,是发小经当地人推荐经常光顾的,除了正常的点餐之外,发小还直接委托老板帮着购买了几百块的牛肉在我们吃饭之后带走。海南人对于食材新鲜程度的追求让发小感到惊讶,从要求各种肉类的新鲜屠宰,到绿叶菜必须当日现摘。诸多当地人品尝之后都能准备分辨出食材为当日生产。

牛肉的烹饪也颇具当地特色,灶上的锅是分作两种烹饪方式:中间是涮着吃的(中间),外围一圈则是类似于铁板烧的方式。

发小也分享了区分牛肉是否冷冻过的经验:在煎制过程如果有水出现,那么一定是冷冻过,最新鲜牛肉在这个烹制过程中只会有油析出。

蘸料非常特别,据说每一家都有自己的秘方。蘸料带有了除了酸味之外的所有味道,而刺激食欲的酸味是通过一颗颗小青柠檬来提供的。挤出来的柠檬汁,取代了醋的作用,自己挤压滴入蘸料中。

几刀下去,椰子露出来白色的椰蓉,插入吸管,椰子就变成了现成的饮料。如同茄子有着长短粗细不同品种,不同品种的椰子也有着巨大的差异。据说下面这种椰子只是用来喝的,并不会吃其中的椰蓉。和所有的农产品一样,椰子之间也有不同,我们点了4个椰子,品尝起来彼此之间汁水味道和甜度差别挺大。

我们沿着中线穿越了整个海南岛,路上有着连绵的高山,山顶被云雾覆盖着。据说正是因为山脉的原因,使得整个海南的气候差别很大。比如,有些地方经常阴雨,有些地方没有热带的感觉。同样,截断的水汽,也成为岛上许多河流的源头。

比较有趣的是,即便地处热带的三亚地区,经常出现身着羽绒服和赤足穿着拖鞋的人同框的情形。

路上接近乐东的时候,我们遇到了大雨。据说这样的大雨在这个季节非常少见,在我到来之前已经一月有余没有降水。

开进了三亚,雨水逐渐平息。最终,到达此行的目的地:三亚。推开车门,暖湿的气息铺面而来,仿佛踏进了八九月份的上海。地面和建筑也被这场突如其来的大雨冲刷得愈发干净。

酒店旁边就是大海,虽然无法听到波涛的声音,但是出门10分钟左右就能到达海滩。海滩的一侧都是这样酒店,跟随着海滩延伸而去。

Visual C++ 计算一个函数耗时的代码

下面的代码可以用来计算一个函数的耗时。

   LARGE_INTEGER frequency;
    LARGE_INTEGER start;
    LARGE_INTEGER end;

    // 获取高精度计时器的频率
    QueryPerformanceFrequency(&frequency);

    // 获取开始时间
    QueryPerformanceCounter(&start);

   // EnumerateComPorts();
    EmuCom();

    // 获取结束时间
    QueryPerformanceCounter(&end);

    // 计算耗时(秒)
    double elapsedTime = static_cast<double>(end.QuadPart - start.QuadPart) / frequency.QuadPart;

    std::cout << "Time taken by foo(): " << elapsedTime << " seconds" << std::endl;

故乡的水塔

前一段看新闻,忽然意识到现在生活的城市已经很少见到这样的庞然巨物了。

我是出生和成长在黑龙江大庆市的。“大庆”这个城市名称始终和“工业学大庆”这个口号联系在一起的,同时和“大油田”深深的绑定。小时候有一段时间,我非常反感有文章用“小油苗”来称呼本地的少年儿童。甚至深深的怀疑第一个编造出这个称呼的人是没有见过“原油”的。那是一种黏糊糊散发着异常味道的物质,稍微有一些化学知识的人都会了解其中含有大量的“苯”“烃””烯“致癌成分,更不会有人产生可爱的联想。

长大之后,我去成都求学,在那里也曾经看到过很多水塔,通常都是下面飞盘造型,蓝白相间的颜色在阴郁的天空下会显得无比深沉。

大庆的水塔没有如此的艺术感,完全像一个木柄手榴弹,外立面不会有任何的装饰,赤裸着红砖努力展现着质朴和实用的主题。估计建设之初一定是本着“又不是不能用”的原则。

这样的水塔下方通常都是机房,靠近能听到其中机器的嘶吼,电力驱动的泵机将水送到顶层,用于维持这一片区供水压力。小时候站在下面,仰望水塔,会有一阵阵的眩晕感,高大的水塔仿佛随时会扑下来的巨人一般。

很长一段时间,大庆的居民楼,甚至是学校都是统一造型的。比如,下面这种样式就是典型的居民楼。从飞机上看下去都是类似的火柴盒。若干年后,我大约知道这种楼被称作“赫鲁晓夫楼”,是一种风格统一的工业化设计,能够在短期内满足改善当地人居住条件的建筑。

更早期大庆的典型住宅是被称作“干打垒”的房子。在东北,保暖性能是第一需要考虑的问题,冬季最冷的时候会达到零下三四十度。只有足够厚度的墙体才能抵御外部的寒冷。干打垒有很大一部分处于地以下,这样能够显著的节省建筑材料。也是因为这样的原因,听说早年间遇到特别大的雪天,早晨门会被积雪掩盖,通常需要从窗口跳出去,铲开门口的雪才能打开房门。

所有的学校也是相同的造型【参考1】。这是我初中的学校,之前原名是大庆市第二十四中学,原来自己独占一个大院的,后来挤进来五十六中学,再后来二者合并改名为“三十六中学”。

参考:

1.https://baike.baidu.com/item/%E5%A4%A7%E5%BA%86%E5%B8%82%E7%AC%AC%E4%B8%89%E5%8D%81%E5%85%AD%E4%B8%AD%E5%AD%A6/2428328

自制Windows 11精简版

先说最后制作出来的结果:镜像文件2.78GB.虚拟机中安装后内存占用1.6GB,硬盘占用 10GB

接下来介绍制作方法:

  1. 项目是来自https://github.com/ntdevlabs/tiny11builder , 项目通过运行脚本文件来提供Windows 11安装镜像中的文件再重新生成一个安装 ISO
  2. 运行方法是首先挂载一个 Windows 11安装镜像,比如,这里我挂接到 f:
  3. 以管理员权限打开Windows Power  Shell 首先运行如下命令
Set-ExecutionPolicy unrestricted

4.运行 tiny11Coremaker.ps1,这个过程中会要求你选择制作的类型,推荐选择  Windows 11 pro

5.接下来等待即可,结束之后会有提示

原来的项目中会需要联网下载 oscdimg.exe 文件的,这里我直接都打包在一起,可以离线使用。

Windows Service 服务框架例子

这里提供一个Service 的例子,实现对c:\log.txt 每隔一段写入当前时间。


// MyService.cpp : 定义控制台应用程序的入口点。
//


#include <Windows.h>
#include <tchar.h>
#include <iostream>

using namespace std;

/*
BOOL IsInstalled();
BOOL Install();
BOOL Uninstall();
void LogEvent(LPCTSTR pszFormat, ...);
void WINAPI ServiceMain();
void WINAPI ServiceStrl(DWORD dwOpcode);
TCHAR szServiceName[] = _T("MyService");
BOOL bInstall;
SERVICE_STATUS_HANDLE hServiceStatus;
SERVICE_STATUS status;
DWORD dwThreadID;
SC_HANDLE hSCM;
SC_HANDLE hService;
*/

/*
OpenSCManager 用于打开服务控制管理器;
CreateService 用于创建服务;
OpenService用于打开已有的服务,返回该服务的句柄;
ControlService则用于控制已打开的服务状态,这里是让服务停止后才删除;
DeleteService 用于删除指定服务。
RegisterServiceCtrlHandler 注册服务控制
*/

//定义全局函数变量  
void Init();
BOOL IsInstalled();
BOOL Install();
BOOL Uninstall();
void LogEvent(LPCTSTR pszFormat, ...);
void WINAPI ServiceMain();
void WINAPI ServiceStrl(DWORD dwOpcode);

TCHAR szServiceName[] = _T("MyService");
BOOL bInstall;
SERVICE_STATUS_HANDLE hServiceStatus;
SERVICE_STATUS status;
DWORD dwThreadID;

int APIENTRY _tWinMain(HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPTSTR    lpCmdLine,
	int       nCmdShow)
{
	Init();
	dwThreadID = ::GetCurrentThreadId();
	SERVICE_TABLE_ENTRY st[] =
	{
		{ szServiceName, (LPSERVICE_MAIN_FUNCTION)ServiceMain },
		{ NULL, NULL }
	};

	if (_tcscmp(lpCmdLine, _T("/install")) == 0)
	{
		Install();
	}
	else if (_tcscmp(lpCmdLine, _T("/uninstall")) == 0)
	{
		Uninstall();
	}
	else
	{
		if (!::StartServiceCtrlDispatcher(st))
		{
			LogEvent(_T("Register Service Main Function Error!"));
		}
	}

	return 0;
}

//初始化
void Init()
{
	hServiceStatus = NULL;
	status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
	status.dwCurrentState = SERVICE_START_PENDING;
	status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
	status.dwWin32ExitCode = 0;
	status.dwServiceSpecificExitCode = 0;
	status.dwCheckPoint = 0;
	status.dwWaitHint = 0;
}

//服务主函数,这在里进行控制对服务控制的注册
void WINAPI ServiceMain()
{
	status.dwCurrentState = SERVICE_START_PENDING;
	status.dwControlsAccepted = SERVICE_ACCEPT_STOP;

	//注册服务控制  
	hServiceStatus = RegisterServiceCtrlHandler(szServiceName, ServiceStrl);
	if (hServiceStatus == NULL)
	{
		LogEvent(_T("Handler not installed"));
		return;
	}
	SetServiceStatus(hServiceStatus, &status);

	status.dwWin32ExitCode = S_OK;
	status.dwCheckPoint = 0;
	status.dwWaitHint = 0;
	status.dwCurrentState = SERVICE_RUNNING;
	SetServiceStatus(hServiceStatus, &status);

	//模拟服务的运行。应用时将主要任务放于此即可  
	//可在此写上服务需要执行的代码,一般为死循环  
	while (1)
	{
		FILE* p=NULL;
		errno_t  err =_tfopen_s(&p,_T("c:\\log.txt"), _T("ab+"));
		if (err != 0) {
			TCHAR errMsg[256];
			_wcserror_s(errMsg, 256, err);
			_tprintf(_T("err! %s"), errMsg);
			return ;
		}
		SYSTEMTIME st;
		GetSystemTime(&st);
		TCHAR time[100] = { 0 };
		_stprintf_s(time, 100, _T("%4d-%02d-%02d %02d:%02d:%02d\r\n"), st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
		if (p == NULL) {
			return;
		}
		fwrite(time, sizeof(TCHAR), _tcsclen(time), p);
		fclose(p);

		Sleep(5000);
	}
	status.dwCurrentState = SERVICE_STOPPED;
	SetServiceStatus(hServiceStatus, &status);
}

//Description:          服务控制主函数,这里实现对服务的控制,  
//                      当在服务管理器上停止或其它操作时,将会运行此处代码  
void WINAPI ServiceStrl(DWORD dwOpcode)
{
	switch (dwOpcode)
	{
	case SERVICE_CONTROL_STOP:
		status.dwCheckPoint = 1;
		status.dwCurrentState = SERVICE_STOP_PENDING;
		SetServiceStatus(hServiceStatus, &status);
		Sleep(500);
		status.dwCheckPoint = 0;
		status.dwCurrentState = SERVICE_STOPPED;
		SetServiceStatus(hServiceStatus, &status);

		PostThreadMessage(dwThreadID, WM_CLOSE, 0, 0);
		break;
	case SERVICE_CONTROL_PAUSE:
		break;
	case SERVICE_CONTROL_CONTINUE:
		break;
	case SERVICE_CONTROL_INTERROGATE:
		break;
	case SERVICE_CONTROL_SHUTDOWN:
		exit(0);
		break;
	default:
		LogEvent(_T("Bad service request"));
	}
}

//判断服务是否已经被安装
BOOL IsInstalled()
{ 
	BOOL bResult = FALSE;

	//打开服务控制管理器  
	SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

	if (hSCM != NULL)
	{
		//打开服务  
		SC_HANDLE hService = ::OpenService(hSCM, szServiceName, SERVICE_QUERY_CONFIG);
		if (hService != NULL)
		{
			bResult = TRUE;
			::CloseServiceHandle(hService);
		}
		::CloseServiceHandle(hSCM);
	}
	return bResult;
}

//安装服务函数
BOOL Install()
{
	//检测是否安装过
	if (IsInstalled())
		return TRUE;

	//打开服务控制管理器  
	SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
	if (hSCM == NULL)
	{
		MessageBox(NULL, _T("Couldn't open service manager"), szServiceName, MB_OK);
		return FALSE;
	}

	//获取程序目录
	TCHAR szFilePath[MAX_PATH];
	::GetModuleFileName(NULL, szFilePath, MAX_PATH);

	//创建服务  
	SC_HANDLE hService = ::CreateService(hSCM, szServiceName, szServiceName,
		SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL,
		szFilePath, NULL, NULL, _T(""), NULL, NULL);

	//检测创建是否成功
	if (hService == NULL)
	{
		::CloseServiceHandle(hSCM);
		MessageBox(NULL, _T("Couldn't create service"), szServiceName, MB_OK);
		return FALSE;
	}

	//释放资源
	::CloseServiceHandle(hService);
	::CloseServiceHandle(hSCM);
	return TRUE;
}

//删除服务函数
BOOL Uninstall()
{
	//检测是否安装过
	if (!IsInstalled())
		return TRUE;

	//打开服务控制管理器
	SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
	if (hSCM == NULL)
	{
		MessageBox(NULL, _T("Couldn't open service manager"), szServiceName, MB_OK);
		return FALSE;
	}

	//打开具体服务
	SC_HANDLE hService = ::OpenService(hSCM, szServiceName, SERVICE_STOP | DELETE);
	if (hService == NULL)
	{
		::CloseServiceHandle(hSCM);
		MessageBox(NULL, _T("Couldn't open service"), szServiceName, MB_OK);
		return FALSE;
	}

	//先停止服务
	SERVICE_STATUS status;
	::ControlService(hService, SERVICE_CONTROL_STOP, &status);

	//删除服务  
	BOOL bDelete = ::DeleteService(hService);
	::CloseServiceHandle(hService);
	::CloseServiceHandle(hSCM);

	if (bDelete)  return TRUE;
	LogEvent(_T("Service could not be deleted"));
	return FALSE;
}

//记录服务事件
void LogEvent(LPCTSTR pFormat, ...)
{
	TCHAR    chMsg[256];
	HANDLE  hEventSource;
	LPTSTR  lpszStrings[1];
	va_list pArg;

	va_start(pArg, pFormat);
	_vsntprintf_s(chMsg, sizeof(chMsg),_TRUNCATE,pFormat, pArg);
	va_end(pArg);

	lpszStrings[0] = chMsg;

	hEventSource = RegisterEventSource(NULL, szServiceName);
	if (hEventSource != NULL)
	{
		ReportEvent(hEventSource, EVENTLOG_INFORMATION_TYPE, 0, 0, NULL, 1, 0, (LPCTSTR*)&lpszStrings[0], NULL);
		DeregisterEventSource(hEventSource);
	}
}

完整代码:

参考:

1.https://cloud.tencent.com/developer/article/1857390

2https://learn.microsoft.com/zh-cn/windows/win32/services/writing-a-service-program-s-main-function

3.https://blog.csdn.net/hsy12342611/article/details/133557759

招商价值成长混合C 投资盘点

2021 年8月买了招商价值成长混合C (012004) 8万元,2024年10月抛了,剩下 52000,亏了35%。

 好奇心驱使下,查了一下资料:https://finance.sina.com.cn/fund/quotes/012004/bc.shtml,这个基金几乎从开始就一直在亏损:

基金经理是 郭锐,盈利能力 74% 的意思是超过 74% 的基金经理。

关于他的公开信息在 https://stock.finance.sina.com.cn/manager/view/mInfo.php?mid=30176884,看历史业绩(不知道为什么没有列出来我买的这支),2020年之后的基本上都在亏钱。

结论:基金有风险,投资需谨慎。投资之前最好看看以往业绩。

PVD:Battery 虚拟电池

前面的 PVD(Physical Virtual Device)设计过普通鼠标,绝对值鼠标, 这次带来的是一个虚拟电池的设计。在进行功耗和性能测试的时候,电池状态(AC/DC)对于Windows性能释放有着很大的影响。因此需要有手段来虚拟电池,之前我设计过2款虚拟电池软件的,但是这种软件是通过驱动来实现的,在具体使用时会有很大局限性。

这次带来的是使用 CH554模拟的USB HID 设备,它将自身报告为一个 UPS 设备,然后通过 USB 接口将当前电池信息报告给 Windows。代码是 Arduino 写成的,通俗易懂,只需要有 USB 知识就可以掌握。整体框架来自另外一个基于 Leonardo 的Arduino 项目。

硬件部分非常简单,就是一个 CH554e的最小系统(MSOP10)封装,非常适于制作小型设备。

#ifndef USER_USB_RAM
#error "This example needs to be compiled with a USER USB setting"
#endif

#include <WS2812.h>
#include "src/CdcHidCombo/USBCDC.h"
#include "src/CdcHidCombo/USBHIDKeyboardMouse.h"
#include "src/CdcHidCombo/PowerDevice.h"
#include "src/CdcHidCombo/USBconstant.h"

#define NUM_LEDS 1
#define COLOR_PER_LEDS 3
#define NUM_BYTES (NUM_LEDS*COLOR_PER_LEDS)

__xdata uint8_t ledData[NUM_BYTES];


#define MINUPDATEINTERVAL   26000UL
#define OnBoardLED       0x03

const byte bDeviceChemistry = IDEVICECHEMISTRY;
const byte bOEMVendor = IOEMVENDOR;

uint16_t iPresentStatus = 0, iPreviousStatus = 0;

byte bRechargable = 1;
byte bCapacityMode = 0;  // units are in mWh

// Physical parameters
const uint16_t iConfigVoltage = 1380;
uint16_t iVoltage = 1300, iPrevVoltage = 0;
uint16_t iRunTimeToEmpty = 0, iPrevRunTimeToEmpty = 0;
uint16_t iAvgTimeToFull = 7200;
uint16_t iAvgTimeToEmpty = 7200;
uint16_t iRemainTimeLimit = 600;
int16_t  iDelayBe4Reboot = -1;
int16_t  iDelayBe4ShutDown = -1;

byte iAudibleAlarmCtrl = 2; // 1 - Disabled, 2 - Enabled, 3 - Muted


// Parameters for ACPI compliancy
uint8_t iDesignCapacity = 0xFF;
byte iWarnCapacityLimit = 10; // warning at 10%
byte iRemnCapacityLimit = 5; // low at 5%
const byte bCapacityGranularity1 = 1;
const byte bCapacityGranularity2 = 1;
uint8_t iFullChargeCapacity = 0xFF;

uint8_t iRemaining = 0xFF, iPrevRemaining = 0;

int iRes = 0;
unsigned long iIntTimer=0;
// Data format
// Keyboard(Total 9 bytes): 01(ReportID 01) + Keyboard data (8 Bytes)
// Mouse(Total 5 bytes): 02(ReportID 02) + Mouse Data (4 Bytes)
uint8_t recvStr[9];
uint8_t recvStrPtr = 0;
unsigned long Elsp;

uint8_t FeatureBuffer[256];
FeatureType FeatureList[32];
uint8_t FeatureRecord = 0;
uint16_t iManufacturerDate = 0,bCycles=20;

void setFeature(uint8_t id, uint8_t* Data, int Len)
{
  /*
      Serial0_print("ID:");
      Serial0_print(id);
      Serial0_print_c(' ');
      Serial0_print(Data[0]);
      Serial0_print_c(' ');
      if (Len>1) {
          Serial0_print(Data[1]);
          Serial0_print_c(' ');
        }
      Serial0_print(Len);
      Serial0_println_c(' ');
  */
  FeatureList[id].Index = FeatureRecord;
  FeatureList[id].Size = Len;
  for (uint8_t i = 0; i < Len; i++) {
    FeatureBuffer[FeatureRecord] = Data[i];
    FeatureRecord++;
  }
}

void setup() {
  Serial0_begin(500000);
  delay(1000);
  Serial0_println("st");
  uint8_t strIndex;
  strIndex = 5;
  setFeature(HID_PD_IPRODUCT, &strIndex, sizeof(strIndex));
  strIndex = 6;
  setFeature(HID_PD_MANUFACTURER, &strIndex, sizeof(strIndex));
  strIndex = 7;
  setFeature(HID_PD_SERIAL, &strIndex, sizeof(strIndex));
  strIndex = 8;
  setFeature(HID_PD_IDEVICECHEMISTRY, &strIndex, sizeof(strIndex));

  setFeature(HID_PD_PRESENTSTATUS, &iPresentStatus, sizeof(iPresentStatus));

  setFeature(HID_PD_RUNTIMETOEMPTY, &iRunTimeToEmpty, sizeof(iRunTimeToEmpty));
  setFeature(HID_PD_AVERAGETIME2FULL, &iAvgTimeToFull, sizeof(iAvgTimeToFull));
  setFeature(HID_PD_AVERAGETIME2EMPTY, &iAvgTimeToEmpty, sizeof(iAvgTimeToEmpty));
  setFeature(HID_PD_REMAINTIMELIMIT, &iRemainTimeLimit, sizeof(iRemainTimeLimit));
  setFeature(HID_PD_DELAYBE4REBOOT, &iDelayBe4Reboot, sizeof(iDelayBe4Reboot));
  setFeature(HID_PD_DELAYBE4SHUTDOWN, &iDelayBe4ShutDown, sizeof(iDelayBe4ShutDown));

  setFeature(HID_PD_RECHARGEABLE, &bRechargable, sizeof(bRechargable));
  setFeature(HID_PD_CAPACITYMODE, &bCapacityMode, sizeof(bCapacityMode));
  setFeature(HID_PD_CONFIGVOLTAGE, &iConfigVoltage, sizeof(iConfigVoltage));
  setFeature(HID_PD_VOLTAGE, &iVoltage, sizeof(iVoltage));

  setFeature(HID_PD_IOEMINFORMATION, &bOEMVendor, sizeof(bOEMVendor));

  setFeature(HID_PD_AUDIBLEALARMCTRL, &iAudibleAlarmCtrl, sizeof(iAudibleAlarmCtrl));

  setFeature(HID_PD_DESIGNCAPACITY, &iDesignCapacity, sizeof(iDesignCapacity));
  setFeature(HID_PD_FULLCHRGECAPACITY, &iFullChargeCapacity, sizeof(iFullChargeCapacity));
  setFeature(HID_PD_REMAININGCAPACITY, &iRemaining, sizeof(iRemaining));
  setFeature(HID_PD_WARNCAPACITYLIMIT, &iWarnCapacityLimit, sizeof(iWarnCapacityLimit));
  setFeature(HID_PD_REMNCAPACITYLIMIT, &iRemnCapacityLimit, sizeof(iRemnCapacityLimit));
  setFeature(HID_PD_CPCTYGRANULARITY1, &bCapacityGranularity1, sizeof(bCapacityGranularity1));
  setFeature(HID_PD_CPCTYGRANULARITY2, &bCapacityGranularity2, sizeof(bCapacityGranularity2));
  setFeature(HID_PD_CYCLECOUNT,&bCycles,sizeof(bCycles));
  setFeature(HID_PD_CONFIGVOLTAGE, &iConfigVoltage, sizeof(iConfigVoltage));
  iManufacturerDate = (2025 - 1980) * 512 + 1 * 32 + 1;
  setFeature(HID_PD_MANUFACTUREDATE, &iManufacturerDate, sizeof(iManufacturerDate));
  /*
    for (uint8_t i=0;i<32;i++) {
      FeatureList[i].Index=i;
      FeatureList[i].Size=1;
      FeatureBuffer[i]=i;
    }
    for (uint8_t i=0;i<32;i++) {
        Serial0_print(i);
        Serial0_print_c(' ');
        Serial0_print(FeatureList[i].Index);
        Serial0_print_c(' ');
        Serial0_print(FeatureList[i].Size);
        Serial0_println_c(' ');
      }
    for (uint8_t i=0;i<FeatureRecord;i++) {
        Serial0_print(FeatureBuffer[i]); Serial0_print_c(' ');
      }
  */
  USBInit();

  bitSet(iPresentStatus, PRESENTSTATUS_CHARGING);
  bitSet(iPresentStatus, PRESENTSTATUS_ACPRESENT);
  bitSet(iPresentStatus , PRESENTSTATUS_BATTPRESENT);
  bitSet(iPresentStatus , PRESENTSTATUS_PRIMARYBATTERY);
  recvStr[0] = HID_PD_PRESENTSTATUS;
  recvStr[1] = iPresentStatus & 0xFF;
  recvStr[2] = (iPresentStatus >> 8) & 0xFF;;
  USB_EP3_send(recvStr, 3);
  setFeature(HID_PD_PRESENTSTATUS, &iPresentStatus, sizeof(iPresentStatus));

/*
  iRemaining = 40;
  recvStr[0] = HID_PD_REMAININGCAPACITY;
  recvStr[1] = iRemaining & 0xFF;
  USB_EP3_send(recvStr, 2);
  setFeature(HID_PD_REMAININGCAPACITY, &iRemaining, sizeof(iRemaining));
  */
}

void loop() {
  while (USBSerial_available()) {
    uint8_t serialChar = USBSerial_read();
    recvStr[recvStrPtr++] = serialChar;
    if (recvStrPtr == 4) {
      /*
        for (uint8_t i = 0; i < 9; i++) {
        Serial0_write(recvStr[i]);
        }
      */


      if (recvStr[0] == HID_PD_PRESENTSTATUS) {
        //USB_EP3_send(recvStr, 3);
        iPresentStatus=recvStr[1]+(recvStr[2]<<8);
        Serial0_print("ps:");
        Serial0_println(iPresentStatus);
      }
      if (recvStr[0] == HID_PD_REMAININGCAPACITY) {
        iRemaining=recvStr[1];
        Serial0_print("rm:");
        Serial0_println(iRemaining);
      }
      if (recvStr[0] == OnBoardLED) {
        set_pixel_for_GRB_LED(ledData, 0, recvStr[1], recvStr[2], recvStr[3]);
        neopixel_show_P1_5(ledData, NUM_BYTES);
      }

      recvStrPtr = 0;
    }
    Elsp = millis();
  }
  // If there is no data in 100ms, clear the receive buffer
  if (millis() - Elsp > 100) {
    recvStrPtr = 0;
    Elsp = millis();
  }

  if((iPresentStatus != iPreviousStatus) || (iRemaining!=iPrevRemaining) || (millis()-iIntTimer>MINUPDATEINTERVAL) ) {
    recvStr[0]=HID_PD_REMAININGCAPACITY;
    recvStr[1]=iRemaining;
    USB_EP3_send(recvStr, 2);
    setFeature(HID_PD_REMAININGCAPACITY, &iRemaining, sizeof(iRemaining));
      
    recvStr[0]=HID_PD_PRESENTSTATUS;
    recvStr[1]=iPresentStatus&0xFF;
    recvStr[2]=(iPresentStatus>>8)&0xFF;
    USB_EP3_send(recvStr, 3);
    setFeature(HID_PD_PRESENTSTATUS, &iPresentStatus, sizeof(iPresentStatus));
    
    Serial0_println("a:");

    iPreviousStatus=iPresentStatus;
    iPrevRemaining=iRemaining;
    iIntTimer=millis();
  }
}

简单的说,开始之后,通过 HID Descriptor 报告当前设备属性,其中有很多 Feature项目。之后 Arduino 代码通过下面这种将描述符中的 Report ID 和 数值关联起来。后面,当Ch554收到 Feature Request 之后就根据前面的注册信息返回对应值。

  setFeature(HID_PD_DESIGNCAPACITY, &iDesignCapacity, sizeof(iDesignCapacity));

除了USB HID 设备,Ch554还实现了一个 USB CDC 设备,在 loop 中我们接收来自USB 串口的数据,如果是以HID_PD_PRESENTSTATUS 开头的,或者HID_PD_REMAININGCAPACITY开头的,那么直接更改状态,然后从HID 对应的 EndPoint中发送出去,这样 Windows 接收到后会更新电池状态。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Management;

namespace ConsoleApp4
{
    class Program
    {
        static void Main(string[] args)
        {
            string targetVid = "VID_1209";
            string targetPid = "PID_C55C";
            string portName = FindUsbDevicePort(targetVid, targetPid);

            if (!string.IsNullOrEmpty(portName))
            {
                Console.WriteLine($"Found device on port: {portName}");
            }
            else
            {
                Console.WriteLine("Device not found.");
            }
            Console.ReadKey();
        }

        static string FindUsbDevicePort(string vid, string pid)
        {
            string query = "SELECT * FROM Win32_PnPEntity WHERE DeviceID LIKE '%" + vid + "&" + pid + "%'";
            using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(query))
            {
                foreach (ManagementObject device in searcher.Get())
                {
                    string deviceId = device["DeviceID"]?.ToString();
                    if (deviceId != null && deviceId.Contains(vid) && deviceId.Contains(pid))
                    {
                        string caption = device["Caption"]?.ToString();
                        if (caption != null && caption.Contains("(COM"))
                        {
                            int startIndex = caption.IndexOf("(COM") + 1;
                            int endIndex = caption.IndexOf(")", startIndex);
                            return caption.Substring(startIndex, endIndex - startIndex);
                        }
                    }
                }
            }
            return null;
        }
    }
}

完整的 Arduino 代码:

完整的 C#代码:

网站故障说明

2015年1月8日早晨,我和平时一样打开浏览器,准备登录网站查看留言,结果发现无法登录。开始想着是偶尔的维护,过一会又试验了一下发现仍然没有恢复。只好登录辰讯云(https://www.chenxunyun.com/)联系客服,随后得知了一个消息:服务器硬盘损坏,数据正在恢复中。大约到了中午,再次询问的时候,得到的消息是完全损坏,全部丢失。

不幸中的万幸是每个月我都会手工备份,上一次的备份时2024年12月中旬,于是等到再次上线只好重装系统和一干软件,重新搭建服务器所需内容。盘点下来网站内容丢失不多。

如果你在使用中遇到网站任何问题,欢迎在本文留言。