├── README.md ├── custom_components └── ha_baidu_map │ ├── __init__.py │ ├── api_config.py │ ├── api_storage.py │ ├── api_view.py │ ├── config_flow.py │ ├── const.py │ ├── local │ ├── ha-panel-baidu-map.js │ └── travel.html │ └── manifest.json └── hacs.json /README.md: -------------------------------------------------------------------------------- 1 | 本插件停止更新,推荐使用新版插件:https://github.com/shaonianzhentan/google_maps 2 | 3 | #### 关注我的微信订阅号,了解更多HomeAssistant相关知识 4 | HomeAssistant家庭助理 5 | 6 | # 百度地图 7 | 在HA里使用的百度地图,支持GPS定位轨迹显示 8 | 9 | # 使用方式 10 | 11 | > 不懂就看视频哈 12 | - https://www.bilibili.com/video/BV1z54y1e7vE 13 | - https://www.bilibili.com/video/BV1zD4y127vG 14 | 15 | ``` 16 | ha_baidu_map: 17 | ak: 百度地图【浏览器的浏览器的浏览器的AK密钥】 - 如果不会配置ak就把这行删掉,初次安装空白就刷新一下页面 18 | ``` 19 | 20 | ## 在GPSLogger应用里的配置 21 | ``` 22 | http://IP:8123/ha_baidu_map-location-【自动生成的key】?entity_id=【实体ID】&latitude=%LAT&longitude=%LON&battery=%BATT&sts=%STARTTIMESTAMP 23 | 24 | ``` 25 | 26 | # 更新日志 27 | 28 | ### v2.4.3 29 | - 修复地图点击后出现地点详情的问题 30 | - 增加设备里的编码地理位置显示 31 | - 隐藏百度地图版权信息 32 | - 调整头部高度样式 33 | - 支持集成添加 34 | 35 | ### v2.4 36 | - 将百度HTTP协议改为HTTPS 37 | - 移除隐藏默认地图功能 38 | - 新增定位数据添加接口 39 | - 修复不显示person实体的问题 40 | - 更换实体名称 41 | - 添加地图卡片 42 | - 不是管理员也能显示百度地图侧边栏 43 | 44 | ### v2.3 45 | - 修复图标不显示的问题 46 | - 右上角新增编辑图标 47 | 48 | ### v2.2 49 | - 修复在HA的APP中无法加载GPS轨迹的问题 50 | - 解决移动端不能点击实体的问题 51 | 52 | ### v2.1 53 | - 修复https下无法加载接口的问题 54 | 55 | ### v2.0 56 | - 修改为本地json文件存储(文件在.storage/ha_baidu_map/) 57 | - 支持多设备分类记录 58 | - 修复设备多次重复记录问题 59 | - 支持配置隐藏自带地图 60 | 61 | ### v1.0.5 62 | - 修复菜单图标一直显示的问题 63 | 64 | ### v1.0.4(测试版) 65 | - 加入数据库存储(目前只支持HA自带的数据库) 66 | - 加入GPS运动轨迹显示 67 | 68 | ### v1.0 69 | - 区域设置passive属性为true会自动隐藏(同官方地图) 70 | - 设备设置hidden属性为true会自动隐藏(同官方地图) 71 | - 修复更新坐标时,会出现重复的问题 72 | - 修复多个区域无法加载的问题 73 | - 修复头像和官方地图显示不一致的问题 74 | - 加入区域可以点击查看的功能(这个官方没有) 75 | 76 | 77 | ## 如果这个项目对你有帮助,请我喝杯咖啡奶茶吧😘 78 | |支付宝|微信| 79 | |---|---| 80 | 支付宝 | 微信支付 81 | -------------------------------------------------------------------------------- /custom_components/ha_baidu_map/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from homeassistant.helpers.network import get_url 3 | from .const import NAME, ICON, DOMAIN, VERSION, URL, ROOT_PATH 4 | 5 | from .api_storage import ApiStorage 6 | from .api_view import HassGateView, mouted_view 7 | 8 | _LOGGER = logging.getLogger(__name__) 9 | 10 | def setup(hass, config): 11 | # 没有配置和已经运行则不操作 12 | if DOMAIN not in config or DOMAIN in hass.data: 13 | return True 14 | 15 | cfg = config[DOMAIN] 16 | ak = cfg.get("ak", "hNT4WeW0AGvh2GuzuO92OfM6hCW25HhX") 17 | hass.data[DOMAIN] = ApiStorage(hass) 18 | # 定位地址 19 | LOCATION_URL = '/' + DOMAIN + '-location-' + ak[0:5] 20 | # 绝对路径 21 | ABSOLUTE_LOCATION_URL = get_url(hass) + LOCATION_URL 22 | 23 | _LOGGER.info(''' 24 | ------------------------------------------------------------------- 25 | 26 | 百度地图【作者QQ:635147515】 27 | 版本:''' + VERSION + ''' 28 | 定位地址:''' + ABSOLUTE_LOCATION_URL + ''' 29 | 项目地址:https://github.com/shaonianzhentan/ha_baidu_map 30 | 31 | -------------------------------------------------------------------''') 32 | # 设置状态 33 | hass.states.async_set('map.baidu', VERSION, { 34 | "friendly_name": NAME, "icon": ICON, 35 | 'api': 'https://api.map.baidu.com/getscript?v=3.0&ak=' + ak, 36 | 'location':ABSOLUTE_LOCATION_URL + '?latitude=%LAT&longitude=%LON&battery=%BATT&sts=%STARTTIMESTAMP&entity_id=实体ID' 37 | }) 38 | # 注册静态目录 39 | hass.http.register_static_path(ROOT_PATH, hass.config.path("custom_components/" + DOMAIN + "/local"), False) 40 | hass.components.frontend.add_extra_js_url(hass, ROOT_PATH + '/ha-panel-baidu-map.js') 41 | hass.components.frontend.async_register_built_in_panel( 42 | "baidu-map", NAME, ICON, 43 | frontend_url_path='ha_baidu_map', 44 | config={"ak": ak, "url_path": ROOT_PATH}, 45 | require_admin=False) 46 | hass.http.register_view(HassGateView) 47 | mouted_view(hass, LOCATION_URL) 48 | return True 49 | 50 | # 集成安装 51 | async def async_setup_entry(hass, entry): 52 | setup(hass, { DOMAIN: entry.data }) 53 | return True 54 | -------------------------------------------------------------------------------- /custom_components/ha_baidu_map/api_config.py: -------------------------------------------------------------------------------- 1 | import json, os 2 | 3 | class ApiConfig(): 4 | 5 | def __init__(self, _dir): 6 | if os.path.exists(_dir) == False: 7 | self.mkdir(_dir) 8 | self.dir = _dir 9 | 10 | # 创建文件夹 11 | def mkdir(self, path): 12 | folders = [] 13 | while not os.path.isdir(path): 14 | path, suffix = os.path.split(path) 15 | folders.append(suffix) 16 | for folder in folders[::-1]: 17 | path = os.path.join(path, folder) 18 | os.mkdir(path) 19 | 20 | def get_dirs(self, _path): 21 | file_name = os.listdir(_path) 22 | _list = [] 23 | for file in file_name: 24 | abs_path = os.path.join(_path, file) 25 | if os.path.isdir(abs_path): 26 | fileinfo = os.stat(abs_path) 27 | _list.append({ 28 | 'name': file, 29 | 'path': abs_path, 30 | 'size': fileinfo.st_size, 31 | 'size_name': self.format_byte(fileinfo.st_size), 32 | 'edit_time': fileinfo.st_mtime, 33 | }) 34 | return _list 35 | 36 | def get_files(self, _path): 37 | file_name = os.listdir(_path) 38 | _list = [] 39 | for file in file_name: 40 | abs_path = os.path.join(_path, file) 41 | if os.path.isfile(abs_path): 42 | fileinfo = os.stat(abs_path) 43 | _list.append({ 44 | 'name': file, 45 | 'path': abs_path, 46 | 'size': fileinfo.st_size, 47 | 'size_name': self.format_byte(fileinfo.st_size), 48 | 'edit_time': fileinfo.st_mtime, 49 | }) 50 | return _list 51 | 52 | # 格式化文件大小的函数 53 | def format_byte(self, number): 54 | for (scale, label) in [(1024*1024*1024, "GB"), (1024*1024,"MB"), (1024,"KB")]: 55 | if number >= scale: 56 | return "%.2f %s" %(number*1.0/scale,lable) 57 | elif number == 1: 58 | return "1字节" 59 | else: #小于1字节 60 | byte = "%.2f" % (number or 0) 61 | return ((byte[:-3]) if byte.endswith(".00") else byte) + "字节" 62 | 63 | def get_path(self, name): 64 | return self.dir + '/' + name 65 | 66 | def read(self, name): 67 | fn = self.get_path(name) 68 | if os.path.isfile(fn): 69 | with open(fn,'r', encoding='utf-8') as f: 70 | content = json.load(f) 71 | return content 72 | return None 73 | 74 | def write(self, name, obj): 75 | with open(self.get_path(name), 'w', encoding='utf-8') as f: 76 | json.dump(obj, f, ensure_ascii=False) -------------------------------------------------------------------------------- /custom_components/ha_baidu_map/api_storage.py: -------------------------------------------------------------------------------- 1 | import time, os 2 | from .api_config import ApiConfig 3 | 4 | class ApiStorage(): 5 | 6 | def __init__(self, hass): 7 | self.hass = hass 8 | self.cache_dir = './.shaonianzhentan/ha_baidu_map' 9 | # 初始化文件夹 10 | self.cfg = ApiConfig(hass.config.path(self.cache_dir)) 11 | 12 | # 获取所有列表信息 13 | def get_list(self): 14 | _path = self.hass.config.path(self.cache_dir) 15 | _list = [] 16 | dirs = self.cfg.get_dirs(_path) 17 | for dir in dirs: 18 | # state = hass.states.get('device_tracker.' + dir['name']) 19 | _list.append({'list': [], 'name': dir['name']}) 20 | lastIndex = len(_list) - 1 21 | files = self.cfg.get_files(dir['path']) 22 | for file in files: 23 | # 小于2kb数据直接丢弃 24 | if file['size'] < 2048: 25 | continue 26 | _name = file['name'].replace('.json', '') 27 | _list[lastIndex]['list'].append({'sts': _name, 'cdate': time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(int(_name)))}) 28 | 29 | return _list 30 | 31 | # 获取数据信息 32 | def get_info(self, name): 33 | _list = self.cfg.read(name + '.json') 34 | if _list == None: 35 | _list = [] 36 | return _list 37 | 38 | # 添加数据 39 | def add(self, entity_id, gps_info): 40 | _dir = self.hass.config.path(self.cache_dir + '/' + entity_id) 41 | if os.path.exists(_dir) == False: 42 | os.mkdir(_dir) 43 | sts_name = entity_id + '/' + gps_info['sts'] + '.json' 44 | _list = self.cfg.read(sts_name) 45 | if _list == None: 46 | _list = [] 47 | # 如果本次传入的数据,与最后一次一样,则不记录 48 | _len = len(_list) 49 | if _len > 0: 50 | attr = _list[_len-1] 51 | if attr['latitude'] == gps_info['latitude'] and attr['longitude'] == gps_info['longitude']: 52 | return 53 | # 添加数据 54 | _list.append(gps_info) 55 | # 写入数据 56 | self.cfg.write(sts_name, _list) 57 | -------------------------------------------------------------------------------- /custom_components/ha_baidu_map/api_view.py: -------------------------------------------------------------------------------- 1 | 2 | import time 3 | from homeassistant.components.http import HomeAssistantView 4 | from .const import DOMAIN, URL 5 | 6 | 7 | def timestamp_to_str(timestamp=None, format='%Y-%m-%d %H:%M:%S'): 8 | if timestamp: 9 | time_tuple = time.localtime(timestamp) # 把时间戳转换成时间元祖 10 | result = time.strftime(format, time_tuple) # 把时间元祖转换成格式化好的时间 11 | return result 12 | else: 13 | return time.strptime(format) 14 | 15 | # 获取信息 16 | class HassGateView(HomeAssistantView): 17 | 18 | url = URL 19 | name = DOMAIN 20 | requires_auth = True 21 | 22 | async def post(self, request): 23 | hass = request.app["hass"] 24 | try: 25 | res = await request.json() 26 | 27 | sql = hass.data[DOMAIN] 28 | if res['type'] == 'get_info': 29 | # 获取同一时刻的GPS记录,生成运动轨迹 30 | _list = sql.get_info(res['sts']) 31 | return self.json(_list) 32 | elif res['type'] == 'get_list': 33 | # 获取 有5条记录 的所有时刻 34 | _list = sql.get_list() 35 | return self.json(_list) 36 | # 删除某些时刻的运动轨迹 37 | # 删除所有数据 38 | return self.json(res) 39 | except Exception as e: 40 | print(e) 41 | return self.json({'code':1, 'msg': '出现异常'}) 42 | 43 | # 安装网关 44 | def mouted_view(hass, LOCATION_URL): 45 | # 记录信息 46 | class LocationGateView(HomeAssistantView): 47 | url = LOCATION_URL 48 | name = DOMAIN 49 | requires_auth = False 50 | 51 | async def get(self, request): 52 | query = request.query 53 | print(query) 54 | try: 55 | entity_id = query['entity_id'] 56 | latitude = query['latitude'] 57 | longitude = query['longitude'] 58 | battery = query.get('battery', 0) 59 | sts = query['sts'] 60 | entity = hass.states.get(entity_id) 61 | if entity is not None and hasattr(entity, 'attributes'): 62 | attributes = { 63 | **entity.attributes, 64 | 'latitude': latitude, 65 | 'longitude': longitude, 66 | 'battery': battery, 67 | 'sts': sts, 68 | 'sts_date': timestamp_to_str(int(sts)), 69 | } 70 | hass.states.async_set(entity_id, entity.state, attributes) 71 | # 存储定位信息 72 | sql.add(entity_id, attributes) 73 | return self.json({'code':0, 'msg': '定位发送成功'}) 74 | except Exception as ex: 75 | print(ex) 76 | return self.json({'code':1, 'msg': '出现异常'}) 77 | hass.http.register_view(LocationGateView) 78 | -------------------------------------------------------------------------------- /custom_components/ha_baidu_map/config_flow.py: -------------------------------------------------------------------------------- 1 | """Config flow for Hello World integration.""" 2 | import logging 3 | 4 | import voluptuous as vol 5 | 6 | from homeassistant import config_entries 7 | 8 | from .const import DOMAIN # pylint:disable=unused-import 9 | 10 | _LOGGER = logging.getLogger(__name__) 11 | 12 | DATA_SCHEMA = vol.Schema({vol.Required("ak", default = "hNT4WeW0AGvh2GuzuO92OfM6hCW25HhX"): str}) 13 | 14 | class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 15 | 16 | VERSION = 1 17 | CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL 18 | 19 | async def async_step_user(self, user_input=None): 20 | errors = {} 21 | if DOMAIN in self.hass.data: 22 | return self.async_abort(reason="single_instance_allowed") 23 | 24 | # 如果输入内容不为空,则进行验证 25 | if user_input is not None: 26 | return self.async_create_entry(title="", data=user_input) 27 | 28 | # 显示表单 29 | return self.async_show_form( 30 | step_id="user", data_schema=DATA_SCHEMA, errors=errors 31 | ) -------------------------------------------------------------------------------- /custom_components/ha_baidu_map/const.py: -------------------------------------------------------------------------------- 1 | NAME = '百度地图' 2 | ICON = 'mdi:map-marker-radius' 3 | DOMAIN = 'ha_baidu_map' 4 | VERSION = '2.4.3' 5 | URL = '/' + DOMAIN + '-api' 6 | ROOT_PATH = '/' + DOMAIN + '-local/' + VERSION -------------------------------------------------------------------------------- /custom_components/ha_baidu_map/local/ha-panel-baidu-map.js: -------------------------------------------------------------------------------- 1 | function rad(d) { 2 | return d * Math.PI / 180.0; 3 | } 4 | // 根据经纬度计算距离,参数分别为第一点的纬度,经度;第二点的纬度,经度 5 | function getDistance(lat1, lng1, lat2, lng2) { 6 | 7 | var radLat1 = rad(lat1); 8 | var radLat2 = rad(lat2); 9 | var a = radLat1 - radLat2; 10 | var b = rad(lng1) - rad(lng2); 11 | var s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + 12 | Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2))); 13 | s = s * 6378.137; // EARTH_RADIUS; 14 | s = Math.round(s * 10000) / 10000; //输出为公里 15 | 16 | var distance = s; 17 | var distance_str = ""; 18 | 19 | if (parseInt(distance) >= 1) { 20 | distance_str = distance.toFixed(1) + "km"; 21 | } else { 22 | distance_str = distance * 1000 + "m"; 23 | } 24 | 25 | //s=s.toFixed(4); 26 | 27 | console.info('lyj 距离是', s); 28 | console.info('lyj 距离是', distance_str); 29 | //return s; 30 | return distance_str 31 | } 32 | 33 | class HaPanelBaiduMap extends HTMLElement { 34 | constructor() { 35 | super() 36 | const shadow = this.attachShadow({ mode: 'open' }); 37 | const div = document.createElement('div', { 'class': 'root' }); 38 | div.innerHTML = ` 39 | 40 | 41 |
42 | 43 | 44 | 73 | ` 74 | 75 | shadow.appendChild(div) 76 | this.shadow = shadow 77 | this.$ = shadow.querySelector.bind(shadow) 78 | } 79 | 80 | // 获取位置 81 | getPosition(attr) { 82 | if (('longitude' in attr && 'latitude' in attr) || 'Longitude' in attr && 'Latitude' in attr) { 83 | return { 84 | longitude: attr.longitude || attr.Longitude, 85 | latitude: attr.latitude || attr.Latitude 86 | } 87 | } else { 88 | return null 89 | } 90 | } 91 | 92 | ready() { 93 | if (window.BMap) { 94 | // 添加标题 95 | let toolbar = this.shadow.querySelector('app-toolbar') 96 | if (toolbar.children.length === 0) { 97 | // console.log(window.BMap) 98 | // console.log('%O',this) 99 | // console.log('%O',toolbar) 100 | top.ha_hass = this.hass 101 | // 添加菜单 102 | let menuButton = document.createElement('ha-menu-button') 103 | menuButton.hass = this.hass 104 | menuButton.narrow = true 105 | toolbar.appendChild(menuButton) 106 | // 添加标题 107 | let title = document.createElement('div') 108 | title.setAttribute('main-title', '') 109 | title.textContent = '百度地图' 110 | title.style.marginLeft = '20px' 111 | toolbar.appendChild(title) 112 | // 添加编辑图标 113 | let haIconButton = document.createElement('ha-icon-button') 114 | haIconButton.setAttribute('icon', 'hass:pencil') 115 | haIconButton.setAttribute('title', '编辑位置') 116 | haIconButton.onclick = () => { 117 | history.pushState(null, null, '/config/zone') 118 | this.fire('location-changed') 119 | } 120 | toolbar.appendChild(haIconButton) 121 | // 添加运动轨迹图标 122 | let haIconButton2 = document.createElement('ha-icon-button') 123 | haIconButton2.setAttribute('icon', 'mdi:crosshairs-gps') 124 | haIconButton2.setAttribute('title', 'GPS运动轨迹') 125 | haIconButton2.onclick = () => { 126 | let gg = this.$('#gps') 127 | gg.src = `${this.url_path}/travel.html?r=${Date.now()}` 128 | gg.classList.toggle('hide') 129 | } 130 | toolbar.appendChild(haIconButton2) 131 | // 添加人员图标 132 | let haIconButton3 = document.createElement('ha-icon-button') 133 | haIconButton3.setAttribute('icon', 'mdi:account-outline') 134 | haIconButton3.setAttribute('title', '定位设备') 135 | haIconButton3.onclick = () => { 136 | let dialog = document.createElement('div') 137 | // 生成遮罩 138 | let mask = document.createElement('div') 139 | mask.style = 'height:100vh;width:100vw;position:fixed;top:0;left:0;background:rgba(0,0,0,.5);' 140 | dialog.appendChild(mask) 141 | 142 | let fm = document.createDocumentFragment() 143 | // 生成标题 144 | let title = document.createElement('div') 145 | title.style = `text-align: right; padding: 15px;` 146 | title.innerHTML = `` 147 | title.onclick = () => { 148 | dialog.remove() 149 | } 150 | fm.appendChild(title) 151 | // 生成设备表格 152 | let table = document.createElement('table') 153 | table.border = true 154 | table.style = 'width:90%;border-spacing: 1px;text-align:left; border-collapse: collapse;margin: 0 auto;' 155 | table.borderSpacing = 10 156 | let tr = document.createElement('tr') 157 | tr.innerHTML = ` 158 | 实体ID 159 | 设备 160 | 距离 161 | 操作` 162 | table.appendChild(tr) 163 | 164 | let states = this.hass.states 165 | // 获取家的坐标 166 | let homeAttr = states['zone.home']['attributes'] 167 | let homelatitude = homeAttr['latitude'] 168 | let homelongitude = homeAttr['longitude'] 169 | // 获取所有定位设备 170 | let keys = Object.keys(states).filter(ele => 171 | ['device_tracker', 'zone', 'person'].includes(ele.split('.')[0]) 172 | || ele.includes('di_li_bian_ma_wei_zhi')) 173 | keys.forEach(key => { 174 | let stateObj = states[key] 175 | let attr = stateObj.attributes 176 | let pos = this.getPosition(attr) 177 | if (pos) { 178 | let { longitude, latitude } = pos 179 | tr = document.createElement('tr') 180 | let valueArr = [key, attr.friendly_name, getDistance(latitude, longitude, homelatitude, homelongitude)] 181 | valueArr.forEach(value => { 182 | let td = document.createElement('td') 183 | td.textContent = value 184 | tr.appendChild(td) 185 | }) 186 | // 操作 187 | let td = document.createElement('td') 188 | td.textContent = '选择' 189 | td.style = 'cursor:pointer; text-align:center;' 190 | td.onclick = () => { 191 | this.translate({ longitude, latitude }).then(res => { 192 | map.centerAndZoom(res[0], 18); 193 | }) 194 | dialog.remove() 195 | } 196 | tr.appendChild(td) 197 | table.appendChild(tr) 198 | } 199 | }) 200 | fm.appendChild(table) 201 | 202 | let panel = document.createElement('div') 203 | panel.style = `height:80vh;width:80vw;left:10vw;top:10vh;position:absolute;background:white;overflow:auto;` 204 | panel.appendChild(fm) 205 | dialog.appendChild(panel) 206 | 207 | document.body.appendChild(dialog) 208 | } 209 | toolbar.appendChild(haIconButton3) 210 | 211 | // 获取当前HA配置的经纬度 212 | let { latitude, longitude } = this.hass.config 213 | // 生成一个对象 214 | var map = new BMap.Map(this.shadow.querySelector('#baidu-map')); 215 | this.map = map 216 | //添加地图类型控件 217 | map.addControl(new BMap.MapTypeControl({ 218 | mapTypes: [ 219 | BMAP_NORMAL_MAP, 220 | BMAP_HYBRID_MAP 221 | ] 222 | })) 223 | // 启用鼠标滚轮缩放 224 | map.enableScrollWheelZoom(); 225 | 226 | // this.actionPanel() 227 | 228 | this.translate({ longitude, latitude }).then(res => { 229 | let mPoint = res[0] 230 | // 中心点 231 | map.centerAndZoom(mPoint, 18); 232 | }) 233 | 234 | this.loadZone() 235 | //this.update() 236 | /* 237 | var top_left_control = new BMap.ScaleControl({anchor: BMAP_ANCHOR_TOP_LEFT});// 左上角,添加比例尺 238 | var top_left_navigation = new BMap.NavigationControl(); //左上角,添加默认缩放平移控件 239 | var top_right_navigation = new BMap.NavigationControl({anchor: BMAP_ANCHOR_TOP_RIGHT, type: BMAP_NAVIGATION_CONTROL_SMALL}); //右上角,仅包含平移和缩放按钮 240 | mp.addControl(top_left_control); 241 | mp.addControl(top_left_navigation); 242 | mp.addControl(top_right_navigation); 243 | */ 244 | //触摸事件(解决点击事件无效)--触摸开始,开启拖拽 245 | map.addEventListener('touchmove', function (e) { 246 | map.enableDragging(); 247 | }); 248 | //触摸结束始,禁止拖拽 249 | map.addEventListener("touchend", function (e) { 250 | map.disableDragging(); 251 | }); 252 | } else { 253 | this.loadDevice() 254 | } 255 | } 256 | } 257 | 258 | /**************************** 加载区域 ********************************/ 259 | 260 | // 添加Icon标记 261 | addIconMarker(point, icon, key) { 262 | let _this = this 263 | const map = this.map 264 | // 复杂的自定义覆盖物 265 | function ComplexCustomOverlay() { } 266 | 267 | ComplexCustomOverlay.prototype = new BMap.Overlay(); 268 | ComplexCustomOverlay.prototype.initialize = function (map) { 269 | this._map = map; 270 | var div = this._div = document.createElement("div"); 271 | div.style.position = "absolute"; 272 | div.style.zIndex = BMap.Overlay.getZIndex(point.lat); 273 | div.style.MozUserSelect = "none"; 274 | div.innerHTML = `` 275 | map.getPanes().labelPane.appendChild(div); 276 | div.onclick = function () { 277 | _this.fire('hass-more-info', { entityId: key }) 278 | } 279 | return div; 280 | } 281 | ComplexCustomOverlay.prototype.draw = function () { 282 | var pixel = map.pointToOverlayPixel(point); 283 | this._div.style.left = pixel.x - 12 + "px"; 284 | this._div.style.top = pixel.y - 12 + "px"; 285 | } 286 | var myCompOverlay = new ComplexCustomOverlay(); 287 | 288 | map.addOverlay(myCompOverlay); 289 | } 290 | 291 | //加载区域 292 | loadZone() { 293 | let map = this.map 294 | this.debounce(async () => { 295 | // 这里添加设备 296 | let states = this.hass.states 297 | let keys = Object.keys(states).filter(ele => ele.indexOf('zone') === 0) 298 | for (let key of keys) { 299 | let stateObj = states[key] 300 | let attr = stateObj.attributes 301 | // 如果有经纬度,并且不在家,则标记 302 | if (!attr['passive'] && 'longitude' in attr && 'latitude' in attr) { 303 | let res = await this.translate({ longitude: attr.longitude, latitude: attr.latitude }) 304 | let point = res[0] 305 | // 添加圆形区域 306 | var circle = new BMap.Circle(point, attr.radius, { fillColor: "#FF9800", strokeColor: 'orange', strokeWeight: 1, fillOpacity: 0.3, strokeOpacity: 0.5 }); 307 | map.addOverlay(circle); 308 | // 添加图标 309 | this.addIconMarker(point, attr.icon, key) 310 | } 311 | } 312 | // 加载完区域之后 313 | this.loadDevice() 314 | }, 1000) 315 | } 316 | 317 | /**************************** 加载设备 ********************************/ 318 | 319 | // 添加设备标记 320 | addEntityMarker(point, { id, name, picture }) { 321 | let _this = this 322 | const map = this.map 323 | 324 | // 删除所有设备 325 | let allOverlay = map.getOverlays(); 326 | if (allOverlay.length > 0) { 327 | let index = allOverlay.findIndex(ele => ele['id'] === id) 328 | if (index >= 0) { 329 | map.removeOverlay(allOverlay[index]); 330 | } 331 | } 332 | // console.log(allOverlay) 333 | setTimeout(() => { 334 | // 复杂的自定义覆盖物 335 | function ComplexCustomOverlay() { } 336 | 337 | ComplexCustomOverlay.prototype = new BMap.Overlay(); 338 | ComplexCustomOverlay.prototype.initialize = function (map) { 339 | this._map = map; 340 | var div = this._div = document.createElement("div"); 341 | div.className = "device-marker"; 342 | div.style.zIndex = BMap.Overlay.getZIndex(point.lat); 343 | // console.log(id,name,picture) 344 | if (picture) { 345 | div.style.backgroundImage = `url(${picture})` 346 | div.style.backgroundSize = 'cover' 347 | } else { 348 | div.textContent = name[0] 349 | } 350 | div.onclick = function () { 351 | _this.fire('hass-more-info', { entityId: id }) 352 | } 353 | map.getPanes().labelPane.appendChild(div); 354 | return div; 355 | } 356 | ComplexCustomOverlay.prototype.draw = function () { 357 | var pixel = map.pointToOverlayPixel(point); 358 | this._div.style.left = pixel.x - 28 + "px"; 359 | this._div.style.top = pixel.y - 28 + "px"; 360 | } 361 | var myCompOverlay = new ComplexCustomOverlay(); 362 | myCompOverlay.id = id 363 | map.addOverlay(myCompOverlay); 364 | }, 0) 365 | } 366 | 367 | // 更新位置 368 | loadDevice() { 369 | this.debounce(async () => { 370 | // 这里添加设备 371 | let states = this.hass.states 372 | let keys = Object.keys(states).filter(ele => ele.indexOf('device_tracker') === 0 373 | || ele.indexOf('person') === 0 374 | || ele.includes('di_li_bian_ma_wei_zhi')) 375 | for (let key of keys) { 376 | let stateObj = states[key] 377 | let attr = stateObj.attributes 378 | let pos = this.getPosition(attr) 379 | if (pos) { 380 | // 如果有经纬度,并且不在家,则标记 381 | if (!attr['hidden'] && stateObj.state != 'home') { 382 | let res = await this.translate(pos) 383 | let point = res[0] 384 | this.addEntityMarker(point, { 385 | id: key, 386 | name: attr.friendly_name, 387 | picture: attr['entity_picture'] 388 | }) 389 | } 390 | } 391 | 392 | } 393 | }, 1000) 394 | } 395 | 396 | // 坐标转换 397 | translate({ longitude, latitude }) { 398 | return new Promise((resolve, reject) => { 399 | var points = [new BMap.Point(longitude, latitude)] 400 | var convertor = new BMap.Convertor(); 401 | convertor.translate(points, 1, 5, function (data) { 402 | if (data.status === 0) { 403 | resolve(data.points) 404 | } 405 | }) 406 | }) 407 | } 408 | 409 | // 触发事件 410 | fire(type, data) { 411 | const event = new Event(type, { 412 | bubbles: true, 413 | cancelable: false, 414 | composed: true 415 | }); 416 | event.detail = data; 417 | this.dispatchEvent(event); 418 | } 419 | 420 | // 操作面板 421 | actionPanel() { 422 | 423 | // 获取所有设备 424 | this.deviceList = [] 425 | 426 | let states = this.hass.states 427 | let keys = Object.keys(states).filter(ele => ele.indexOf('device_tracker') === 0 428 | || ele.indexOf('zone') === 0 429 | || ele.indexOf('person') === 0) 430 | keys.forEach(key => { 431 | let stateObj = states[key] 432 | let attr = stateObj.attributes 433 | if (('longitude' in attr && 'latitude' in attr) || 'Longitude' in attr && 'Latitude' in attr) { 434 | this.deviceList.push({ 435 | id: key, 436 | name: attr.friendly_name, 437 | longitude: attr.longitude || attr.Longitude, 438 | latitude: attr.latitude || attr.Latitude 439 | }) 440 | } 441 | }) 442 | 443 | const map = this.map 444 | // 定义一个控件类,即function 445 | function ZoomControl() { 446 | // 默认停靠位置和偏移量 447 | this.defaultAnchor = BMAP_ANCHOR_TOP_LEFT; 448 | this.defaultOffset = new BMap.Size(10, 10); 449 | } 450 | 451 | // 通过JavaScript的prototype属性继承于BMap.Control 452 | ZoomControl.prototype = new BMap.Control(); 453 | 454 | // 自定义控件必须实现自己的initialize方法,并且将控件的DOM元素返回 455 | // 在本方法中创建个div元素作为控件的容器,并将其添加到地图容器中 456 | ZoomControl.prototype.initialize = (map) => { 457 | // 创建一个DOM元素 458 | var div = document.createElement("div"); 459 | div.className = 'right-action-panel' 460 | div.style.cssText = `background:white;font-size:12px;` 461 | let select = document.createElement('select') 462 | select.className = "select-device" 463 | 464 | // 设备 465 | let optgroup = document.createElement('optgroup') 466 | optgroup.label = "设备" 467 | this.deviceList.forEach(ele => { 468 | let option = document.createElement('option') 469 | option.value = ele.id 470 | option.text = ele.name 471 | optgroup.appendChild(option) 472 | }) 473 | select.appendChild(optgroup) 474 | 475 | select.onchange = () => { 476 | // 这里重新定位 477 | if (select.selectedIndex < this.deviceList.length) { 478 | let { longitude, latitude } = this.deviceList[select.selectedIndex] 479 | this.translate({ longitude, latitude }).then(res => { 480 | map.centerAndZoom(res[0], 18); 481 | }) 482 | } 483 | } 484 | 485 | // 添加文字说明 486 | div.appendChild(select); 487 | // 添加DOM元素到地图中 488 | map.getContainer().appendChild(div); 489 | // 将DOM元素返回 490 | return div; 491 | } 492 | // 创建控件 493 | var myZoomCtrl = new ZoomControl(); 494 | // 添加到地图当中 495 | map.addControl(myZoomCtrl); 496 | 497 | } 498 | 499 | 500 | /** 501 | * 防抖 502 | * @param {Function} fn 503 | * @param {Number} wait 504 | */ 505 | debounce(fn, wait) { 506 | let cache = this.cache || {} 507 | let fnKey = fn.toString() 508 | let timeout = cache[fnKey] 509 | if (timeout != null) clearTimeout(timeout) 510 | cache[fnKey] = setTimeout(() => { 511 | fn() 512 | // 清除内存占用 513 | if (Object.keys(cache).length === 0) { 514 | this.cache = null 515 | } else { 516 | delete this.cache[fnKey] 517 | } 518 | }, wait) 519 | this.cache = cache 520 | } 521 | 522 | 523 | loadScript(src) { 524 | return new Promise((resolve, reject) => { 525 | let id = btoa(src) 526 | let ele = document.getElementById(id) 527 | if (ele) { 528 | resolve() 529 | return 530 | } 531 | let script = document.createElement('script') 532 | script.id = id 533 | script.src = src 534 | script.onload = function () { 535 | resolve() 536 | } 537 | document.querySelector('head').appendChild(script) 538 | }) 539 | } 540 | 541 | set narrow(value) { 542 | let menuButton = this.shadow.querySelector('ha-menu-button') 543 | if (menuButton) { 544 | menuButton.hass = this.hass 545 | menuButton.narrow = value 546 | } 547 | } 548 | 549 | get panel() { 550 | return this._panel 551 | } 552 | 553 | set panel(value) { 554 | this._panel = value 555 | let { ak, url_path } = value.config 556 | this.url_path = url_path 557 | if (ak) { 558 | const _this = this 559 | window.BMap_HaBaiduMap = { 560 | url: `https://api.map.baidu.com/getscript?v=3.0&ak=${ak}`, 561 | close() { 562 | let { $ } = _this 563 | $('#gps').classList.toggle('hide') 564 | } 565 | } 566 | this.loadScript(BMap_HaBaiduMap.url).then(res => { 567 | this.ready() 568 | }) 569 | } else { 570 | alert('请配置百度AK') 571 | } 572 | } 573 | } 574 | 575 | 576 | /* ----------------------------- 卡版 ----------------------------------- */ 577 | class LovelaceBaiduMap extends HTMLElement { 578 | 579 | static getConfigElement() { 580 | return document.createElement("lovelace-baidu-map-editor"); 581 | } 582 | 583 | // 自定义默认配置 584 | static getStubConfig() { 585 | return { entity: "zone.home" } 586 | } 587 | 588 | /* 589 | * 设置配置信息 590 | */ 591 | setConfig(config) { 592 | if (!config.entity) { 593 | throw new Error('你需要定义一个实体'); 594 | } 595 | this._config = config; 596 | // 更新 597 | this.updated() 598 | } 599 | 600 | // 卡片的高度(1 = 50px) 601 | getCardSize() { 602 | return 3; 603 | } 604 | 605 | /* 606 | * 触发事件 607 | * type: 事件名称 608 | * data: 事件参数 609 | */ 610 | fire(type, data) { 611 | const event = new Event(type, { 612 | bubbles: true, 613 | cancelable: false, 614 | composed: true 615 | }); 616 | event.detail = data; 617 | this.dispatchEvent(event); 618 | } 619 | 620 | /* 621 | * 调用服务 622 | * service: 服务名称(例:light.toggle) 623 | * service_data:服务数据(例:{ entity_id: "light.xiao_mi_deng_pao" } ) 624 | */ 625 | callService(service_name, service_data = {}) { 626 | let arr = service_name.split('.') 627 | let domain = arr[0] 628 | let service = arr[1] 629 | this._hass.callService(domain, service, service_data) 630 | } 631 | 632 | // 通知 633 | toast(message) { 634 | this.fire("hass-notification", { message }) 635 | } 636 | 637 | // 显示实体更多信息 638 | showMoreInfo(entityId) { 639 | this.fire('hass-more-info', { entityId }) 640 | } 641 | 642 | /* 643 | * 接收HA核心对象 644 | */ 645 | set hass(hass) { 646 | this._hass = hass 647 | if (this.isCreated === true) { 648 | this.updated(hass) 649 | } else { 650 | this.created(hass) 651 | } 652 | } 653 | 654 | loadScript(src) { 655 | return new Promise((resolve, reject) => { 656 | let id = btoa(src) 657 | let ele = document.getElementById(id) 658 | if (ele) { 659 | resolve() 660 | return 661 | } 662 | let script = document.createElement('script') 663 | script.id = id 664 | script.src = src 665 | script.onload = function () { 666 | resolve() 667 | } 668 | document.querySelector('head').appendChild(script) 669 | }) 670 | } 671 | 672 | // 创建界面 673 | created(hass) { 674 | 675 | /* ***************** 基础代码 ***************** */ 676 | const shadow = this.attachShadow({ mode: 'open' }); 677 | // 创建面板 678 | const ha_card = document.createElement('ha-card'); 679 | ha_card.className = 'lovelace-baidu-map' 680 | ha_card.innerHTML = `` 681 | shadow.appendChild(ha_card) 682 | // 创建样式 683 | const style = document.createElement('style') 684 | style.textContent = ` 685 | .lovelace-baidu-map{} 686 | .device-marker{ 687 | position:absolute; 688 | vertical-align: top; 689 | display: block; 690 | margin: 0 auto; 691 | width: 2.5em; 692 | text-align: center; 693 | height: 2.5em; 694 | line-height: 2.5em; 695 | font-size: 1.5em; 696 | border-radius: 50%; 697 | border: 0.1em solid var(--primary-color); 698 | color: rgb(76, 76, 76); 699 | background-color: white; 700 | } 701 | .hide, 702 | .BMap_shadow,.anchorBL,.BMap_cpyCtrl,.BMap_pop{display:none!important;} 703 | ` 704 | shadow.appendChild(style); 705 | // 保存核心DOM对象 706 | this.shadow = shadow 707 | this.$ = this.shadow.querySelector.bind(this.shadow) 708 | // 创建成功 709 | this.isCreated = true 710 | 711 | /* ***************** 附加代码 ***************** */ 712 | let { _config, $ } = this 713 | // 获取百度API 714 | let baiduApi = hass.states['map.baidu'].attributes['api'] 715 | this.loadScript(baiduApi).then(() => { 716 | setTimeout(() => { 717 | this.ready() 718 | }, 1000) 719 | }) 720 | } 721 | 722 | // 更新界面数据 723 | updated(hass) { 724 | let { $, _config, update_map } = this 725 | if (update_map) update_map(); 726 | } 727 | 728 | // 百度地图加载 729 | ready() { 730 | if (window.BMap) { 731 | // 生成一个对象 732 | let div = this.shadow.querySelector('.lovelace-baidu-map') 733 | div.style.height = div.offsetWidth + 'px' 734 | var map = new BMap.Map(div); 735 | this.map = map 736 | //添加地图类型控件 737 | map.addControl(new BMap.MapTypeControl({ 738 | mapTypes: [ 739 | BMAP_NORMAL_MAP, 740 | BMAP_HYBRID_MAP 741 | ] 742 | })) 743 | // 启用鼠标滚轮缩放 744 | map.enableScrollWheelZoom(); 745 | //触摸事件(解决点击事件无效)--触摸开始,开启拖拽 746 | map.addEventListener('touchmove', function (e) { 747 | map.enableDragging(); 748 | }); 749 | //触摸结束始,禁止拖拽 750 | map.addEventListener("touchend", function (e) { 751 | map.disableDragging(); 752 | }); 753 | // 添加区域 754 | this.loadZone() 755 | 756 | // 更新地图 757 | this.update_map = () => { 758 | let entity_id = this._config['entity'] 759 | let { longitude, latitude, friendly_name, entity_picture, icon, radius } = this._hass.states[entity_id]['attributes'] 760 | this.translate({ longitude, latitude }).then(res => { 761 | let mPoint = res[0] 762 | // 中心点 763 | map.centerAndZoom(mPoint, 18); 764 | if (entity_id.includes('zone.')) return; 765 | this.addEntityMarker(mPoint, { 766 | id: entity_id, 767 | name: friendly_name, 768 | picture: entity_picture 769 | }) 770 | }) 771 | } 772 | this.update_map() 773 | } 774 | } 775 | 776 | //加载区域 777 | loadZone() { 778 | let map = this.map 779 | this.debounce(async () => { 780 | // 这里添加设备 781 | let states = this._hass.states 782 | let keys = Object.keys(states).filter(ele => ele.indexOf('zone') === 0) 783 | for (let key of keys) { 784 | let stateObj = states[key] 785 | let attr = stateObj.attributes 786 | // 如果有经纬度,并且不在家,则标记 787 | if (!attr['passive'] && 'longitude' in attr && 'latitude' in attr) { 788 | let res = await this.translate({ longitude: attr.longitude, latitude: attr.latitude }) 789 | let point = res[0] 790 | // 添加圆形区域 791 | var circle = new BMap.Circle(point, attr.radius, { fillColor: "#FF9800", strokeColor: 'orange', strokeWeight: 1, fillOpacity: 0.3, strokeOpacity: 0.5 }); 792 | map.addOverlay(circle); 793 | // 添加图标 794 | this.addIconMarker(point, attr.icon, key) 795 | } 796 | } 797 | }, 1000) 798 | } 799 | 800 | // 添加Icon标记 801 | addIconMarker(point, icon, key) { 802 | let _this = this 803 | const map = this.map 804 | // 复杂的自定义覆盖物 805 | function ComplexCustomOverlay() { } 806 | 807 | ComplexCustomOverlay.prototype = new BMap.Overlay(); 808 | ComplexCustomOverlay.prototype.initialize = function (map) { 809 | this._map = map; 810 | var div = this._div = document.createElement("div"); 811 | div.style.position = "absolute"; 812 | div.style.zIndex = BMap.Overlay.getZIndex(point.lat); 813 | div.style.MozUserSelect = "none"; 814 | div.innerHTML = `` 815 | map.getPanes().labelPane.appendChild(div); 816 | div.onclick = function () { 817 | _this.fire('hass-more-info', { entityId: key }) 818 | } 819 | return div; 820 | } 821 | ComplexCustomOverlay.prototype.draw = function () { 822 | var pixel = map.pointToOverlayPixel(point); 823 | this._div.style.left = pixel.x - 12 + "px"; 824 | this._div.style.top = pixel.y - 12 + "px"; 825 | } 826 | var myCompOverlay = new ComplexCustomOverlay(); 827 | 828 | map.addOverlay(myCompOverlay); 829 | } 830 | 831 | // 添加设备标记 832 | addEntityMarker(point, { id, name, picture }) { 833 | let _this = this 834 | const map = this.map 835 | 836 | // 删除所有设备 837 | let allOverlay = map.getOverlays(); 838 | if (allOverlay.length > 0) { 839 | let index = allOverlay.findIndex(ele => ele['id'] === id) 840 | if (index >= 0) { 841 | map.removeOverlay(allOverlay[index]); 842 | } 843 | } 844 | // console.log(allOverlay) 845 | setTimeout(() => { 846 | // 复杂的自定义覆盖物 847 | function ComplexCustomOverlay() { } 848 | 849 | ComplexCustomOverlay.prototype = new BMap.Overlay(); 850 | ComplexCustomOverlay.prototype.initialize = function (map) { 851 | this._map = map; 852 | var div = this._div = document.createElement("div"); 853 | div.className = "device-marker"; 854 | div.style.zIndex = BMap.Overlay.getZIndex(point.lat); 855 | // console.log(id, name, picture) 856 | if (picture) { 857 | div.style.backgroundImage = `url(${picture})` 858 | div.style.backgroundSize = 'cover' 859 | } else { 860 | div.textContent = name[0] 861 | } 862 | div.onclick = function () { 863 | _this.fire('hass-more-info', { entityId: id }) 864 | } 865 | map.getPanes().labelPane.appendChild(div); 866 | return div; 867 | } 868 | ComplexCustomOverlay.prototype.draw = function () { 869 | var pixel = map.pointToOverlayPixel(point); 870 | this._div.style.left = pixel.x - 28 + "px"; 871 | this._div.style.top = pixel.y - 28 + "px"; 872 | } 873 | var myCompOverlay = new ComplexCustomOverlay(); 874 | myCompOverlay.id = id 875 | map.addOverlay(myCompOverlay); 876 | }, 0) 877 | } 878 | 879 | // 坐标转换 880 | translate({ longitude, latitude }) { 881 | return new Promise((resolve, reject) => { 882 | var points = [new BMap.Point(longitude, latitude)] 883 | var convertor = new BMap.Convertor(); 884 | convertor.translate(points, 1, 5, function (data) { 885 | if (data.status === 0) { 886 | resolve(data.points) 887 | } 888 | }) 889 | }) 890 | } 891 | 892 | 893 | /** 894 | * 防抖 895 | * @param {Function} fn 896 | * @param {Number} wait 897 | */ 898 | debounce(fn, wait) { 899 | let cache = this.cache || {} 900 | let fnKey = fn.toString() 901 | let timeout = cache[fnKey] 902 | if (timeout != null) clearTimeout(timeout) 903 | cache[fnKey] = setTimeout(() => { 904 | fn() 905 | // 清除内存占用 906 | if (Object.keys(cache).length === 0) { 907 | this.cache = null 908 | } else { 909 | delete this.cache[fnKey] 910 | } 911 | }, wait) 912 | this.cache = cache 913 | } 914 | } 915 | 916 | 917 | /* ******************** 编辑预览 *************************** */ 918 | 919 | // 编辑预览 920 | class LovelaceBaiduMapEditor extends HTMLElement { 921 | 922 | setConfig(config) { 923 | // console.log('预览配置', config) 924 | this._config = config; 925 | } 926 | 927 | configChanged(newConfig) { 928 | const event = new Event("config-changed", { 929 | bubbles: true, 930 | composed: true 931 | }); 932 | event.detail = { config: newConfig }; 933 | // console.log('更新预览配置', newConfig) 934 | this.dispatchEvent(event); 935 | } 936 | 937 | /* 938 | * 接收HA核心对象 939 | */ 940 | set hass(hass) { 941 | this._hass = hass 942 | if (this.isCreated === true) { 943 | this.updated(hass) 944 | } else { 945 | this.created(hass) 946 | } 947 | } 948 | 949 | // 创建界面 950 | created(hass) { 951 | /* ***************** 基础代码 ***************** */ 952 | const shadow = this.attachShadow({ mode: 'open' }); 953 | 954 | let arr = [], list = [], states = hass.states 955 | Object.keys(states).filter(k => { 956 | // return true 957 | // 过滤设备 958 | if (k.indexOf('person') === 0 || k.indexOf('device_tracker') === 0) { 959 | let attributes = states[k]['attributes'] 960 | return Reflect.has(attributes, 'latitude') && Reflect.has(attributes, 'longitude') 961 | } 962 | return false 963 | }).forEach(k => { 964 | list.push(states[k]) 965 | arr.push(` 966 | ${states[k]['attributes']['friendly_name'] || ''} 967 |
${k}
968 |
`) 969 | }) 970 | // 创建面板 971 | const ha_card = document.createElement('ha-card'); 972 | ha_card.className = 'lovelace-baidu-map-editor' 973 | ha_card.innerHTML = ` 974 | 975 | 976 | ${arr.join('')} 977 | 978 | 979 | ` 980 | shadow.appendChild(ha_card) 981 | // 创建样式 982 | const style = document.createElement('style') 983 | style.textContent = ` 984 | .lovelace-baidu-map-editor{padding:20px;} 985 | .lovelace-baidu-map-editor paper-item-body{ 986 | padding:5px 10px; 987 | display:block; 988 | border-bottom:1px solid white; 989 | } 990 | ` 991 | shadow.appendChild(style); 992 | // 保存核心DOM对象 993 | this.shadow = shadow 994 | this.$ = this.shadow.querySelector.bind(this.shadow) 995 | // 创建成功 996 | this.isCreated = true 997 | 998 | /* ***************** 附加代码 ***************** */ 999 | let { _config, $ } = this 1000 | let _this = this 1001 | // // 定义事件 1002 | $('.lovelace-baidu-map-editor paper-listbox').addEventListener('selected-changed', function () { 1003 | let obj = list[this.selected] 1004 | _this.configChanged({ 1005 | type: 'custom:lovelace-baidu-map', 1006 | entity: obj.entity_id 1007 | }) 1008 | }) 1009 | } 1010 | 1011 | // 更新界面数据 1012 | updated(hass) { 1013 | let { $, _config } = this 1014 | // $('p').textContent = `当前实体ID:${_config.entity}` 1015 | } 1016 | } 1017 | 1018 | 1019 | // 百度地图使用HTTPS协议 1020 | window.BMAP_PROTOCOL = "https" 1021 | window.BMap_loadScriptTime = (new Date).getTime() 1022 | 1023 | // 定义DOM对象元素 1024 | if (!customElements.get('ha-panel-baidu-map')) customElements.define('ha-panel-baidu-map', HaPanelBaiduMap); 1025 | if (!customElements.get('lovelace-baidu-map')) customElements.define('lovelace-baidu-map', LovelaceBaiduMap); 1026 | if (!customElements.get('lovelace-baidu-map-editor')) customElements.define('lovelace-baidu-map-editor', LovelaceBaiduMapEditor); 1027 | 1028 | // 添加预览 1029 | window.customCards = window.customCards || []; 1030 | window.customCards.push({ 1031 | type: "lovelace-baidu-map", 1032 | name: "百度地图", 1033 | preview: true, 1034 | description: "百度地图卡片" 1035 | }); -------------------------------------------------------------------------------- /custom_components/ha_baidu_map/local/travel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 52 | GPS运动轨迹 53 | 54 | 55 |
56 | 59 | 关闭 60 |
61 |
62 | 233 | 234 | 235 | 236 | 237 | -------------------------------------------------------------------------------- /custom_components/ha_baidu_map/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "ha_baidu_map", 3 | "name": "\u767E\u5EA6\u5730\u56FE", 4 | "version": "1.0", 5 | "config_flow": true, 6 | "documentation": "https://github.com/shaonianzhentan/ha_baidu_map", 7 | "requirements": [], 8 | "dependencies": [], 9 | "codeowners": [] 10 | } 11 | -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "百度地图", 3 | "country": "CN", 4 | "render_readme": true, 5 | "domains": [] 6 | } --------------------------------------------------------------------------------