惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Spread Privacy
Spread Privacy
S
Schneier on Security
博客园 - 【当耐特】
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
美团技术团队
Application and Cybersecurity Blog
Application and Cybersecurity Blog
MongoDB | Blog
MongoDB | Blog
NISL@THU
NISL@THU
N
Netflix TechBlog - Medium
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Webroot Blog
Webroot Blog
月光博客
月光博客
T
The Exploit Database - CXSecurity.com
Forbes - Security
Forbes - Security
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - 叶小钗
Recent Announcements
Recent Announcements
IT之家
IT之家
B
Blog
C
CERT Recently Published Vulnerability Notes
S
SegmentFault 最新的问题
Recent Commits to openclaw:main
Recent Commits to openclaw:main
F
Fortinet All Blogs
Martin Fowler
Martin Fowler
Know Your Adversary
Know Your Adversary
Security Latest
Security Latest
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
T
Troy Hunt's Blog
O
OpenAI News
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
CXSECURITY Database RSS Feed - CXSecurity.com
V2EX - 技术
V2EX - 技术
L
Lohrmann on Cybersecurity
C
Cyber Attacks, Cyber Crime and Cyber Security
H
Help Net Security
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Last Week in AI
Last Week in AI
Help Net Security
Help Net Security
Hacker News: Ask HN
Hacker News: Ask HN
A
About on SuperTechFans
Y
Y Combinator Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
Engineering at Meta
Engineering at Meta
T
Threat Research - Cisco Blogs
Vercel News
Vercel News
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Recorded Future
Recorded Future
C
Cisco Blogs
Project Zero
Project Zero

herrkaefer

"Vibe planning \u003e vibe coding" "Anything to Markdown" "Built anocus: anonymous commenting for static sites" "日记与小说 -- AI 续写小说欣赏" "Any-podcast: from newsletters to a podcast" "Made MicPipe: a simple voice input tool using ChatGPT dictation" "关于 Tools 和 Skills 的一点感想" "Introducing SwiftEdgeTTS" "Thoughts on the philosophy of building AI-native apps" "jelly鼻屎" "等饭的人" "Use home assistant to motivate my kid to brush teeth" "Migrated Blog to Hugo and Cloudflare Pages" "Easy Aspen monitoring for Chicago parents" "Introducing HabitBuilder: A simple Telegram bot for habit tracking" "鼓捣" "Open folder or file with Sublime Text from Finder toolbar" "Python dev workflow on macOS" "Create new text file from Finder toolbar" "Uno reinvented for 3-year-old kids" "Uno变身儿童数字游戏" "自动转发Twitter到微博" "Handle annoying operations of objects in Realm DB" "Move Jekyll blog to Ubuntu VPS" "Introducing Mole" "Note taking without note taking app" "Deploy Python web application on Ubuntu server" "Setup Shadowsocks / VPN on Ubuntu Server" "Linode Notes - Basic Setup" "CLASS Style Adapted for Embedded Systems" "psycopgr Tutorial" "pgRouting Notes" "PostgreSQL Notes" "阿城三王" "这一年,这一把日子" "另一面的发现——读《坟》" "定理" "封面与腰封" "Google book下载" "lulu最新写真出炉" "The Big Bang Theory第三季" "自拍婚纱照1" "日全食" "期待动画片" "《麦兜响当当》动画电影主题曲" "转:饶毅--“杂志拜物教”:何时发Cell Nature Science 论文害你" "转:饶毅--提醒年轻人:何时SCI害你?" "西安" "3d打印机" "Dropbox" "刷牙" "贴几张照片" "6156167" "永久和凤凰" "老板的想法" "春" "原来奥巴马也是个朗读者" "应邀发Freeware List 2.0" "史上最能睡的淘宝老板" "至少出名的效果是达到了" "错怪了msn" "独立游戏节2009" "114" "馒头" "Crayon Physics Deluxe" "2008,2009" "盖章记" "小虎队附身许巍" "怎么给word文档加索引:排序问题" "怎么给word文档加索引Q\u0026amp;A" "怎么给Word文档加索引" "教我如何不疯掉" "二则" "P" "哦!报告" "蓝天" "萧翰" "lm" "故宫印象" "转:美国历任总统像" "time can kill itself" "嗯" "建议,只是建议哦" "奥地利行记3" "奥地利行记2" "奥地利行记1" "叶子" "GayBoy" "天使教你扔frisbie" "门徒因何面容愁?" "手机教堂" "丝竹管弦之盛" "残奥" "争座位" "秋意浅" "总理府" "流觞曲水" "映带左右引以为" "咚咚咚 续" "茂林修竹又有"
"Realtime monitoring of ComEd hourly price"
"herrkaefer" · 2025-11-17 · via herrkaefer

After changing to use ComEd’s hourly pricing, we have been paid more attention to the realtime electricity price before using some appliances like dryer and EV charger. I’ve added a chip on home assistant which call ComEd’s api every 10 min and display it and it’s very convenient. Yes ComEd provides APIs surprisingly.

But not everyone has home assistant installed. Of course the straightforward method is to put a shortcut of this live price page on the home screen, and click it and wait for the page showing the prices. But this feels slow.

Or, we can use shortcuts (on iPhone). Shortcut can fetch the price and show a notification. You can also let Siri trigger it. Cool.

If you want to see a widget showing the prices for you, go with the wonderful Scriptable app. This app lets your run custom javascript scripts and show a widget.

Below is the script for the widget. The background color changes based on the price range. You can modify the maxLowPrice and maxMediumPrice to your own preference.

/********************************************
 * ComEd Hourly Price Widget (Monospaced + Gradient BG)
 * - Curved trend arrow appended to price
 * - Today's low/high with time
 * - Unified font sizes for updated/low/high
 ********************************************/

const maxLowPrice = 8;
const maxMediumPrice = 14;

const API_CURRENT = "https://hourlypricing.comed.com/api?type=currenthouraverage";
const API_5MIN = "https://hourlypricing.comed.com/api?type=5minutefeed";

// ===== CURRENT PRICE =====
let dataCurrent = await new Request(API_CURRENT).loadJSON();
let currentItem = dataCurrent[0];

let millis = parseInt(currentItem.millisUTC);
let price = parseFloat(currentItem.price);

let date = new Date(millis);
let timeStr = date.toLocaleTimeString("en-US", {
  hour: "2-digit",
  minute: "2-digit",
  hour12: false,
});

// ===== 5-MIN FEED =====
let data5m = await new Request(API_5MIN).loadJSON();
let entries = data5m.map(x => ({
  ts: parseInt(x.millisUTC),
  price: parseFloat(x.price)
}));

// ===== 15-MIN TREND (using 3rd data point back) =====

// 5-minute feed: entries[0] = latest, entries[3] ≈ 15 minutes ago
let past15Entry = entries[3];   // 3 points back = 15 minutes
let past15Price = past15Entry ? past15Entry.price : price;

// Trend arrow using bold curved arrows
let diff = price - past15Price;

// Curved bold arrows
let trend = "→";
if (diff > 0.8) trend = "⬈";
else if (diff < -0.8) trend = "⬊";

// ===== 24-hr LOW / HIGH =====
let lowEntry = entries.reduce((a, b) => a.price < b.price ? a : b);
let highEntry = entries.reduce((a, b) => a.price > b.price ? a : b);

let lowTime = new Date(lowEntry.ts).toLocaleTimeString("en-US", {
  hour: "2-digit",
  minute: "2-digit",
  hour12: false,
});
let highTime = new Date(highEntry.ts).toLocaleTimeString("en-US", {
  hour: "2-digit",
  minute: "2-digit",
  hour12: false,
});

// ===== BACKGROUND GRADIENT =====
let gradient = new LinearGradient();
gradient.startPoint = new Point(0, 0);
gradient.endPoint = new Point(1, 1);

if (price < maxLowPrice) {
  gradient.colors = [new Color("#b8f7c2"), new Color("#38d169")];
} else if (price < maxMediumPrice) {
  gradient.colors = [new Color("#ffe29a"), new Color("#ff9f1c")];
} else {
  gradient.colors = [new Color("#ffb3c1"), new Color("#ff4d4d")];
}
gradient.locations = [0, 1];

let textColor = new Color("#222222");

// ===== BUILD WIDGET =====
let widget = new ListWidget();
widget.setPadding(10, 10, 10, 10);
widget.backgroundGradient = gradient;

// TITLE
let title = widget.addText("ComEd Current Hour");
title.textColor = textColor;
title.font = Font.italicSystemFont(12);
title.leftAlignText();

widget.addSpacer(6);

// PRICE + UNIT + ARROW
let row = widget.addStack();
row.centerAlignContent();

// Price number
let priceTxt = row.addText(`${price}`);
priceTxt.font = Font.boldMonospacedSystemFont(44);
priceTxt.textColor = textColor;
priceTxt.minimumScaleFactor = 0.6;

// Space
row.addSpacer(3);

// Unit
let unitTxt = row.addText("¢/kWh");
unitTxt.font = Font.regularMonospacedSystemFont(14);
unitTxt.textColor = textColor;

// Arrow
row.addSpacer(6);
let arrowTxt = row.addText(trend);
arrowTxt.font = Font.boldMonospacedSystemFont(20);
arrowTxt.textColor = textColor;

widget.addSpacer(4);

// UPDATED
let updatedTxt = widget.addText(`Updated ${timeStr}`);
updatedTxt.font = Font.mediumMonospacedSystemFont(12);
updatedTxt.textColor = textColor;
updatedTxt.rightAlignText();

widget.addSpacer(4);

// LOW / HIGH (same font size)
let lTxt = widget.addText(`Low ${lowEntry.price}¢ @ ${lowTime}`)
lTxt.font = Font.mediumMonospacedSystemFont(12);
lTxt.textColor = textColor;
lTxt.rightAlignText();

widget.addSpacer(4);

let hTxt = widget.addText(`High ${highEntry.price}¢ @ ${highTime}`);
hTxt.font = Font.mediumMonospacedSystemFont(12);
hTxt.textColor = textColor;
hTxt.rightAlignText();

widget.refreshAfterDate = new Date(Date.now() + 5 * 60 * 1000);

Script.setWidget(widget);
Script.complete();
App.close();

It’d better to put the widget in the Today widget so it seems to get refreshed more often. But the timing of refresh totally depends on iOS system. The solution is tapping the widget if you see the “updated” time is not very recent. The Scriptable app will be opened for the script to run but will automatically close after that, which takes half a second.