HomeAssistant自定义组件学习-【二】
#要说的话#
前面把中盛科技的控制器组件写完了。稍稍熟悉了一些HA,现在准备写窗帘控制组件,构想的东西会比较多,估计有些难度,过程会比较长,边写边记录吧!
#设备和场景环境#
使用的是Novo的电机,淘宝链接在【前言】文章中有,轨道应该是佳丽斯的,买电机的时候一起配的。电机提供的是RS485接口,所以需要增加一个RS485的服务器,我选用的是“亿佰特”的网口转RS485的模块,型号是NA111-A(使用220V电源供电,就避免了再加一个模块)统一放置在机柜那里,把原来的网络拿两路出来(蓝色和棕色组)作为485的线路,保留100M的网线功能。远端使用的是多功能的面板,带2+3的电源插口和8+4的网络口,网络口刚好可以分成百兆网和485接口。
#组件思路#
流程:选择Novo组件-->选择485设备类型-->发现485设备-->(可选:配置485设备)
-->通过485连接Novo设备-->配置Novo设备地址(需要按SET按钮)-->设置电机转向
-->设置开合范围-->完成配置
思路:
- 1、选择组件后,提供界面选择使用的485设备类型,使用下拉框给用户选择;
- 2、依据选择的485设备类型触发对应的自发现流程,并列出发现的485设备;
- 3、用户点击选择485设备,组件建立与485设备的链接,并显示通讯正常;
- 4、组件通过485设备向Novo电机发送查询命令,有返回则说明链路建立成功;
- 5、由于是并联了多个Novo电机,所以会返回多个查询回复,所以只能轮循查询,需要提供界面配置的Novo电机地址和通道;
- 6、点击485设备条目进行配置,配置界面自动生成Novo电机地址和通道,点击提交,然后到Novo电机上按下相应配置按钮,完成Novo电机的地址配置(电机正反转两次),该条目移至已经配置好的列表;
- 7、在已经配置好地址的列表里点击Novo电机,进入到电机的配置:可以配置电机转向、窗帘开合范围,检查开、合是否正确,都正确无误后完成配置(后续版本再做)。
-
#代码实现#
- 通过命令:
-
python3 -m script.scaffold integration
初始化组件代码。添加discover方法:
-
def ebyte_discover(ip_address: str | None = None) -> dict[str, EbyteConfig]:"""亿佰特-发现设备方法."""sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)sock.bind(("", BROADCAST_SRC_PORT))sock.settimeout(2)found_devices: dict[str, EbyteConfig] = {}if ip_address is None:addrs = enum_all_broadcast()else:addrs = [ip_address]for addr in addrs:try:sock.sendto(BROADCAST_MSG, (addr, BROADCAST_PORT))except Exception: # noqa: BLE001_LOGGER.warning(f"Can't access network {addr}") # noqa: G004while True:try:data, addr = sock.recvfrom(512)# 返回:fd 06 54 14 a7 dc 74 16 03 01# 返回:fd 06 54 14 a7 dc 72 83 03 01# 分析:fd 06 为帧头# 54 14 a7 dc 74 16 为MAC地址# 03 01 为帧尾mac = ebyte_get_mac(data)if len(mac[0]) > 6:found_devices[mac[0]] = EbyteConfig(mac[0], "Ebyte_RS485", mac[0], addr[0], 0)found_devices[mac[0]].set_mac_bytes(mac[1])except TimeoutError:breakexcept OSError as e:_LOGGER.error(f"Socket error: {e!r}") # noqa: G004if len(found_devices) > 0:# 查询相关信息for conf in found_devices.values():bts: bytearray = QUERY_CMD_FORMAT.copy()for i in range(len(conf.mac_bytes)):bts[i + QUERY_CMD_FORMAT_MAC_IDX] = conf.mac_bytes[i]# 获取名称、版本号和序列号bts[QUERY_CMD_FORMAT_CMD_IDX] = 0x05sock.sendto(bts, (conf.base_ip, BROADCAST_PORT))res = sock.recv(1024)mac = ebyte_get_mac(res)[0]if mac in found_devices:found_devices[mac].load_data(res)# 获取网络配置bts[QUERY_CMD_FORMAT_CMD_IDX] = 0x00sock.sendto(bts, (conf.base_ip, BROADCAST_PORT))res = sock.recv(1024)mac = ebyte_get_mac(res)[0]if mac in found_devices:found_devices[mac].load_data(res)return found_devices
这里绑定UDP的源端口,是转为亿佰特的设备返回的数据是另外一个固定端口。不绑定源端口的话,返回数据收不到。
-
修改config_flow.py中的函数,显示485设备列表:
-
async def async_step_user(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:"""Handle the initial step."""errors: dict[str, str] = {}ent_data: dict[str, Any] = {}if user_input is not None:try:# 获取用户选择的设备IDconf: EbyteConfig = self.get_ebyteconfig_by_id(user_input[CONF_USER_INPURT_ID])ent_data[CONF_HOST] = conf.base_ipent_data[CONF_PORT] = conf.base_portent_data[CONF_MAC] = conf.base_macexcept Exception: # noqa: BLE001_LOGGER.exception("Unexpected exception")errors["base"] = "unknown"dp: dict[str, str] = {}dp["MAC"] = conf.mac_bytes.hex(":")dp["IP"] = conf.base_ipdp["Port"] = conf.base_portret: ConfigFlowResult = self.async_create_entry(# 生成集成条目title=f"Ebyte Device[{conf.base_ip}]",description="This is a Ebyte RS485 communication device!",description_placeholders=dp,options=dp,data=ent_data,)self._ebytelink = static_ebyte_manager.get_link_by_mac(conf.base_mac, conf.base_ip, conf.base_port)# # 创建对应的RS485通讯链路设备# 错误:在此处不能创建设备,只能在__init__.py里创建else: # noqa: RET505self._confs = ebyte_discover()options: dict[str, str] = {}if len(self._confs) < 1:_LOGGER.error("No ebyte communications!")else:for conf in self._confs.values():options[conf.base_mac] = (f"{conf.base_name}[{conf.base_ip}:{conf.base_port}]")ops = []# 从系统中获取已经配置了的条目clist: list[ConfigEntry] = self.hass.config_entries.async_entries(DOMAIN)cd: dict[str, str] = {}for cf in clist:cd[cf.data[CONF_MAC]] = cf.data[CONF_HOST]for k, v in options.items():if k not in cd:ops.append(SelectOptionDict(value=k, label=v))if len(ops) > 0:ebyteschema = vol.Schema({vol.Required(CONF_USER_INPURT_ID): SelectSelector(SelectSelectorConfig(options=ops,mode=SelectSelectorMode.DROPDOWN,)),})else:ebyteschema = vol.Schema({"ERROR:": "No Ebyte RS485 device founded!"})return self.async_show_form(step_id="user", data_schema=ebyteschema, errors=errors)
这里使用SelectSelector提供选择项。
-
生成的效果如下:
-
要显示集成条目右边的配置按钮,需要在Flow类中添加指定的方法:
@staticmethod@callbackdef async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:"""增加本函数后,会在集成条目位置增加“配置”按钮."""return NovoOptionsFlow(config_entry)
完成RS485设备(集成条目)的添加,后续就是在点击“配置”的时候,弹出界面给Novo电机写地址和通道号,代码放在NovoOptionsFlow中,如下:
async def async_step_init(self, user_input: dict[str, Any] | None = None) -> FlowResult:"""Manage the options."""errors: dict[str, str] = {}if user_input is not None:# 有输入信息if not user_input[CONFIG_FLOW_TYPE]:# 未知的步骤_LOGGER.error("Unkown flow type!")elif user_input[CONFIG_FLOW_TYPE] == OPTIONS_FLOW_TYPE_SET_ADDRESS:# 当前为输入电机地址步骤,需要配置电机idb = bytes.fromhex(user_input[CONFIG_ID])did: int = int.from_bytes(idb, "big")channel: int = user_input["channel"]edata: dict[str, Any] = {}res = await self._device.async_set_motor_addr(did, channel)if res:# 写入成功,则保存相关信息edata[CONF_MAC] = self._entry.data[CONF_MAC]edata[CONF_HOST] = self._entry.data[CONF_HOST]edata[CONF_PORT] = self._entry.data[CONF_PORT]edata[CONFIG_ID] = didedata[CONFIG_CHANNEL] = channelself._save_device_config(edata)return self.async_create_entry(title="", data=edata)# 自动生成电机IDida = np.random.random_integers(161, 254, size=(2))idab: bytearray = bytearray(2)idab[0] = ida[0]idab[1] = ida[1]ebyteschema = vol.Schema({vol.Required(CONFIG_FLOW_TYPE, default=OPTIONS_FLOW_TYPE_SET_ADDRESS): vol.In(ADD_WAY),vol.Required(schema=CONFIG_ID,description="Enetry RS485 address.",default=idab.hex(" "),): str,vol.Required(schema=CONFIG_CHANNEL, description="Entery Novo channel.", default=4): int,})return self.async_show_form(step_id="init", data_schema=ebyteschema, errors=errors)
显示效果:
在这里做了选择器,最初是想把设置地址和配置旋转方向一起的,所以留在这里了。后续再完善。
这里配置完成后,我不知道应该怎么保存配置并使用,看了美的的组件,就直接用他的代码了,就是保存json文件到本地,使用的时候读取就是了。对应的代码:
def _save_device_config(self, data: dict):os.makedirs(self.hass.config.path(f"{STORAGE_PATH}/{data[CONF_MAC]}"), exist_ok=True)record_file = self.hass.config.path(f"{STORAGE_PATH}/{data[CONF_MAC]}/{data[CONFIG_ID]}.json")save_json(record_file, data)
添加并配置Novo电机就完成了,后而就是怎么加载这些实体,加载的工作必须放在__init__.py文件中,不能放在其他地方,回到__init__.py文件,修改对应的代码:
# Update entry annotation
async def async_setup_entry(hass: HomeAssistant, entry: NovoConfigEntry) -> bool:"""Set up Novo from a config entry."""# 说明:此方法,每个集成条目都会调用一次# 1. Create API instance# 2. Validate the API connection (and authentication)# 3. Store an API object for your platforms to accessip: str = entry.data[CONF_HOST]port: int = entry.data[CONF_PORT]mac: str = entry.data[CONF_MAC]link: EbyteRS485Link = static_ebyte_manager.get_link_by_mac(mac, ip, port)if link is None:_LOGGER.error(f"Device[{mac},{ip}:{port}] lost!") # noqa: G004# 注册通讯设备:当前是亿佰特的RS485设备device_registry = dr.async_get(hass)if link.is_connected:device_registry.async_get_or_create(config_entry_id=entry.entry_id,configuration_url=f"http://{link.tcp_ip}:{link.tcp_port}/",identifiers={("mac", mac)},connections={("mac", mac), ("ip", link.tcp_ip), ("port", link.tcp_port)},manufacturer="Ebyte Tech",model=link.DOMAIN,name=link.base_name,serial_number=mac,sw_version=link.version,translation_key="Ebyte communication",)await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)novo: NovoDevice = static_novo_device_manager.get_novo_device(mac, link)# conf: NovoConfigd_id = 0c_id = 0# 从保存的配置文件中获取实体jsons = _load_device_config(hass, mac)if len(jsons) > 0:for js in jsons:# _LOGGER.error(f'get json:{js}')d_id = js[CONFIG_ID]c_id = js[CONFIG_CHANNEL]c_mac = js[CONF_MAC]if c_mac == mac:# 发送消息给实体类,创建实体async_dispatcher_send(hass,DATA_DISCOVER_COMPONENT.format(NovoCoverEntry.DOMAIN),novo,entry,d_id,c_id,)return True
因为考虑到需要支持多RS485设备,不同的电机是挂在不同的RS485设备上的,加载的时候就需要一一对应上,在这里的思路就是使用RS485设备的MAC地址作为文件夹区分,并通过文件遍历的方式获取到配置的json文件(每个json文件对应一台电机),遍历的代码:
def _load_device_config(hass: HomeAssistant, device_id):# 列表出文件夹下所有的文件files = []jsons = []pypath = hass.config.path(f"{STORAGE_PATH}/{device_id}/")if not os.path.isdir(pypath):_LOGGER.error(f"file path :{pypath} not exists!") # noqa: G004return jsonsfor filename in os.listdir(pypath):filepath = os.path.join(pypath, filename)if os.path.isfile(filepath):files.append(filename)record_file = hass.config.path(f"{STORAGE_PATH}/{device_id}/{filename}")jsons.append(load_json(record_file, default={}))return jsons
在__init__.py中怎么把配置信息传到实体类,在我的上一篇文章中已经说了,不清楚的可以看看。这里使用了if c_mac == mac:进行限制是否生成实体,是因为在多个RS485(集成条目)的情况下,相应的代码就是执行多次,有冲突后,就不能正确给实体配置上对应的链路。
最后就是实体类Cover了,完整代码如下:
"""窗帘实体类."""import logging
from typing import Anyfrom homeassistant.components.cover import (ATTR_POSITION,CoverEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallbackfrom .const import DATA_DISCOVER_COMPONENT
from .core.NovoDevice import NovoDevice_LOGGER = logging.getLogger("novocover")async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:"""配置窗帘实体."""@callbackdef async_discover(device: NovoDevice, entry: ConfigEntry, id: int, ch: int) -> None:"""发现电机回调及添加方法."""nentry = NovoCoverEntry(device, entry, id, ch)async_add_entities([nentry])hass.data[DATA_DISCOVER_COMPONENT.format(NovoCoverEntry.DOMAIN)] = (async_dispatcher_connect(hass,DATA_DISCOVER_COMPONENT.format(NovoCoverEntry.DOMAIN),async_discover,))class NovoCoverEntry(CoverEntity):"""Novo电机窗帘实体类."""DOMAIN = "cover"_attr_has_entity_name = True_id: int_ch: int_entry: ConfigEntry_device: NovoDevice_pos: int_closed: booldef __init__(self, device: NovoDevice, entry: ConfigEntry, id: int, ch: int) -> None:"""初始化Novo电机窗帘实体."""super().__init__()self._id = idself._entry = entryself._unique_id = f"{self.DOMAIN}.novo_curtain_{id}"self.entity_id = self._unique_idself._closed = Trueself._ch = chself._device = deviceself._pos = 0self._attr_unique_id = f"{self.DOMAIN}.novo_curtain_{id}"self._attr_name = f"NovoCurtain_{id}"@propertydef unique_id(self) -> str | None:"""设备标识符."""return self._attr_unique_id@propertydef is_closed(self) -> bool:"""窗帘是否已经关闭."""return self._closeddef open_cover(self, **kwargs: Any) -> None:"""打开窗帘."""self._device.async_open_cover_by_id(self._id, self._ch, 0)async def async_open_cover(self, **kwargs: Any) -> None:"""异步打开窗帘."""return await self._device.async_open_cover_by_id(self._id, self._ch, 0)def close_cover(self, **kwargs: Any) -> None:"""关闭窗帘."""self._device.async_close_cover_by_id(self._id, self._ch, 100)async def async_close_cover(self, **kwargs: Any) -> None:"""异步关闭窗帘."""return await self._device.async_close_cover_by_id(self._id, self._ch, 100)def set_cover_position(self, **kwargs: Any) -> None:"""设置窗帘位置."""position = int(kwargs.get(ATTR_POSITION))self._device.async_set_cover_position_by_id(self._id, self._ch, position)async def async_update(self) -> None:"""更新代码."""await self._device.async_query_position_by_id(self._id, self._ch)self._pos = self._device.get_position_by_id(self._id)self._attr_current_cover_position = self._posif self._pos >= 99:self._attr_is_closed = Falseself._closed = Falseelse:self._attr_is_closed = Trueself._closed = True
最后运行结果:
学到的知识点:
1、使用界面获取用户输入:async_show_form方法的使用;
2、集成条目的使用:async_create_entry方法;
3、集成条目配置:async_get_options_flow方法
存在的问题:
1、Novo电机会主动发送信息,需要在链路代码里增加循环读取socket信息的功能,而不是现在一发一收的模式;
2、Novo电机的Update方法是直接调用链路模块发送命令的,有可能会出现冲突的情况,需要封装链路模块,在模块中处理好冲突问题;
3、使用集成条目的配置功能添加Novo电机时,不会自动删除原有的配置文件(json);
4、使用配置功能添加Novo电机后,不会自动刷新实体列表,需要手动“重新加载”集成条目。
增加了自动化:早上7点05分,开灯、开窗帘。早上自动打开了。
Novo窗帘的组件基本功能完成,家里的零冷水泵到了,后续就是把零冷水泵添加到HA中。还有人体传感器、空气质量传感器、电动水阀都有了,都得花时间把这些东西加进去……,又是得花时间折腾……
另外:最近想把项目放到HA里面去,因为需要用到厂家的LOGO,目前在跟厂家沟通,获得授权后,把LOGO加上,就可以放到HA里面了。
相关文章:

HomeAssistant自定义组件学习-【二】
#要说的话# 前面把中盛科技的控制器组件写完了。稍稍熟悉了一些HA,现在准备写窗帘控制组件,构想的东西会比较多,估计有些难度,过程会比较长,边写边记录吧! #设备和场景环境# 使用的是Novo的电机…...

如何看待AI技术的应用前景?
文章目录 如何看待AI技术的应用前景引言AI技术的现状1. AI的定义与分类2. 当前AI技术的应用领域 AI技术的应用前景1. 经济效益2. 社会影响3. 技术进步 AI技术应用面临的挑战1. 数据隐私与安全2. 可解释性与信任3. 技能短缺与就业影响 AI技术的未来发展方向1. 人工智能的伦理与法…...

Unity中的屏幕坐标系
获得视口宽高 拖动视口会改变屏幕宽高数值 MousePosition 屏幕坐标系的原点在左下角,MousePosition返回Z为0也就是纵深为0的Vector3 但是如果鼠标超出屏幕范围不会做限制,所以可能出现负数或者大于屏幕宽高的情况,做鼠标拖拽物体时需要注…...

标题点击可跳转网页
要实现点击标题跳转到网页的功能,你可以在Vue组件中使用<a>标签(锚点标签)并设置href属性为网页的URL。如果你希望使用uni-app的特性来控制页面跳转,可以使用uni.navigateTo方法(这适用于uni-app环境,…...

易语言模拟真人动态生成鼠标滑动路径
一.简介 鼠标轨迹算法是一种模拟人类鼠标操作的程序,它能够模拟出自然而真实的鼠标移动路径。 鼠标轨迹算法的底层实现采用C/C语言,原因在于C/C提供了高性能的执行能力和直接访问操作系统底层资源的能力。 鼠标轨迹算法具有以下优势: 模拟…...

Linux:生态与软件安装
文章目录 前言一、Linux下安装软件的方案二、包管理器是什么?三、生态问题相关的理解1. 什么操作系统是好的操作系统?2. 什么是生态?3. 软件包是谁写的?这些工程师为什么要写?钱的问题怎么解决? 四、我的服务器怎么知…...

R 语言与其他编程语言的区别
R 语言与其他编程语言的区别 R 语言作为一种专门用于统计计算和图形的编程语言,与其他编程语言相比有一些独特的特点和区别。本文将详细介绍这些区别,帮助你更好地理解 R 语言的优势和适用场景。 1. 专为统计和数据分析设计 统计功能 内置统计函数&…...

RC低通滤波器Bode图分析(传递函数零极点)
RC低通滤波器 我们使得R1K,C1uF;电容C的阻抗为Xc; 传递函数 H ( s ) u o u i X C X C R 1 s C 1 s C R 1 1 s R C (其中 s j ω ) H(s)\frac{u_{o} }{u_{i} } \frac{X_{C} }{X_{C}R} \frac{\frac{1}{sC} }{\…...

基于深度学习的网络入侵检测
基于深度学习的网络入侵检测是一种利用深度学习技术对网络流量进行实时监测与分析的方法,旨在识别并防范网络攻击和恶意活动。随着网络环境日益复杂,传统的入侵检测系统(IDS)在面对不断变化的攻击模式时,往往难以保持高…...

《构建一个具备从后端数据库获取数据并再前端显示的内容页面:前后端实现解析》
一、前端页面:布局与功能 1. 页面结构 我们先来看前端页面的 HTML 结构,它主要由以下几个部分组成: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewp…...

Rust 力扣 - 59. 螺旋矩阵 II
文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 使用一个全局变量current记录当前遍历到的元素的值 我们只需要一圈一圈的从外向内遍历矩阵,每一圈遍历顺序为上边、右边、下边、左边,每遍历完一个元素后current 我们需要注意的是如果上…...

2.4w字 —TS入门教程
目录 1. 什么是TS 2. TS基本使用 3 TS基础语法 3.1 基础类型约束 3.11 string,number,boolean, null和undefined 3.12 any 3.13 unknown 3.14 void 3.15 数组 3.16 对象 3.2 函数的约束 3.21 普通写法 3.22 函数表达式 3.22 可选…...

java: 未结束的字符文字 报错及解决:将编码全部改为UTF-8或者GBK
报错: 解决: 将编码都改成UTF-8或者GBK:...

Android平台RTSP转RTMP推送之采集麦克风音频转发
技术背景 RTSP转RTMP推送,好多开发者第一想到的是采用ffmpeg命令行的形式,如果对ffmpeg比较熟,而且产品不要额外的定制和更高阶的要求,未尝不可,如果对产品稳定性、时延、断网重连等有更高的技术诉求,比较…...

认证鉴权框架之—sa-token
一、概述 Satoken 是一个 Java 实现的权限认证框架,它主要用于 Web 应用程序的权限控制。Satoken 提供了丰富的功能来简化权限管理的过程,使得开发者可以更加专注于业务逻辑的开发。 二、逻辑流程 1、登录认证 (1)、创建token …...

Spring源码(十一):Spring MVC之DispatchServlet
本篇重点在于分析Spring MVC与Servlet标准的整合,下节将详细讨论Spring MVC的启动/加载流程、处理请求的具体流程。 一、介绍 Spring框架提供了构建Web应用程序的全功能MVC模块。通过策略接口 ,Spring框架是高度可配置的,而且支持多种视图技…...

gitbash简单操作
https://blog.csdn.net/qq_42363495/article/details/104878170 工作区(空间)--暂存区--本地仓库--远程仓库 方法一:创建一个新的分支master,且远程库里没有该分支 只要将.gitignore文件放在文件夹下就可以,.gitignore是文本文档形式的文件…...

pnpm install安装element-plus的版本跟package.json指定的版本不一样
pnpm安装的版本不同于package.json中指定的版本可能是由于以下几种情况导致的: 依赖项冲突:当项目依赖的不同模块或库之间存在版本冲突时,pnpm可能会安装与package.json中指定的版本不同的版本。这可能是因为其他依赖项指定了不同的版本&…...

Java线程池的核心内容详解
文章内容已经收录在《面试进阶之路》,从原理出发,直击面试难点,实现更高维度的降维打击! 目录 文章目录 目录Java线程池的核心内容详解线程池的优势什么场景下要用到线程池呢?线程池中重要的参数【掌握】新加入一个任…...

学习笔记——三小时玩转JQuery
也可以使用在线版,不过在线版需要有网络,网不好的情况下加载也不好 取值的时候也是只会取到有样式的纯文本,不会取到标签,会取到标签效果 prepend和append这两个方法用的比较多,before和affter用的比较少 想要把代码写…...

word试题转excel(最简单的办法,无格式要求)
分享早下班的终极秘诀~ 今天本来是个愉快的周五,心里想着周末的聚会和各种安排,然而突然一个加急任务砸了过来——要求在下周一提交一份精细整理的Excel表格! 打开Word文件一看,成堆的试题内容需要整理到Excel里。看着满屏的题目…...

基于web的中小学成绩管理系统的设计与实现
目录 第一章 研究背景与意义 1.1 研究背景 1.2 研究意义 1.3 研究目的 第二章 关于系统的设计 2.1系统总体架构设计 2.2功能模块设计 2.3数据存储与管理 第三章 系统功能介绍 3.1成绩录入及发布 3.2班级管理和学生管理 3.3成绩分析结果展示 3.4用户反馈与改进 …...

Conmi的正确答案——在Kibana中进入Elasticsearch的索引管理页面
Elasticsearch版本:7.17.25 Kibana版本:7.17.25 注:索引即类似mysql的表。 0、进入首页 1、未创建任何“索引模式”时: 1.1、点击左边的三横菜单; 1.2、点击“Discover”,进入“发现”页面; 2…...

【JavaEE】【多线程】进阶知识
目录 一、常见的锁策略1.1 悲观锁 vs 乐观锁1.2 重量级锁 vs 轻量级锁1.3 挂起等待锁 vs 自旋锁1.4 普通互斥锁 vs 读写锁1.5 可重入锁 vs 不可重入锁1.6 不公平锁 vs 公平锁 二、synchronized特性2.1 synchronized的锁策略2.2 synchronized加锁过程2.3 其它优化措施 三、CAS3.…...

LeetCode100之三数之和(15)--Java
1.问题描述 给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k ,同时还满足 nums[i] nums[j] nums[k] 0 。请你返回所有和为 0 且不重复的三元组。 注意 答案中不可以包含重复的三元组 示例1 输入&…...

并发编程三大特性--可见性和有序性
可见性: 什么是可见性: 可见性是指在数据在收到一个线程的修改时,其他的线程也可以得知并获取修改后的值的属性。这是并发编程的三大特性之一。 为了提高cpu的利用率,cpu在获取数据时,不是直接在主内存读取数据&…...

Android 使用ninja加速编译的方法
ninja的简介 随着Android版本的更迭,makefile体系逐渐增多,导致make单编模块的时间越来越长,每次都需要半个小时甚至更长时间,其原因为每次make都会重新加载所有mk文件,再生成ninja编译,此完整过程十分耗时…...

《Java 实现选择排序:原理剖析与代码详解》
目录 一、引言 二、选择排序原理 三、代码分析 1. 代码整体结构 2. main方法 3. sort方法(选择排序核心逻辑) 四、测试结果 一、引言 排序算法在计算机科学领域中是非常重要的一部分,它能够帮助我们将无序的数据按照特定的顺序进行排列…...

数据结构之双链表——考研笔记
文章目录 一.单链表VS双链表二.创建双链表(带头结点)三.双链表的插入四.双链表删除五.销毁双链表六.双链表遍历七. 循环链表八.静态链表1.用代码定义一个静态链表 一.单链表VS双链表 单链表中只包含指向它后继结点的指针,所以给定一个结点p找…...

Django视图写法
1.View:Django默认的视图基类,Django的HttpRequeset对象 2.APIView:REST-framework提供的所有视图的基类,继承自Django的View REST framework的Request对象 Request对象的数据是自动根据前端发送数据的格式进行解析之后的结果。 serializer Book…...