안에 지금 몇 시지?나는 시계 만들기 프로젝트를 설명하기 시작했습니다. 나는 프로젝트를 중간에(희망합니다!) 시작했으며 시작 위치와 약간 다릅니다. 이전에는 인터넷에서 시간을 얻는 데 중점을 두었습니다. GPS가 신호를 받지 못하는 경우에도 백업으로 사용할 수 있지만 기본 시간 소스는 아마도 GPS일 것입니다.
그러나 어느 경우든 시간은 현지 시간이 아닌 UTC로 도착합니다. UTC를 로컬로 변환하는 방법은 실제 운영 체제를 실행하는 컴퓨터에서 문제가 되지 않습니다. 실제 OS가 설치되면 America/New_York 등과 같은 시간대가 제공됩니다. 이것이 OS에 필요한 모든 것입니다(이름에 대한 데이터 테이블, UTC에서의 오프셋, 일광 절약 시간 시작 및 종료 시간 포함). Windows, Mac 및 Linux OS로.)
그만큼 ESP32 기반 클럭은 총 메모리가 약 4MB에 불과하며 다음 버전을 실행합니다. FreeRTOS , 소형 컴퓨터용 실시간 OS. 표준 시간대 이름에서 표준 시간대 규칙으로 변환하는 테이블은 포함되어 있지 않지만 규칙은 이해합니다. 규칙은 UTC에서의 오프셋, 일광(여름) 시간 준수 여부, 일광 시간 시작 및 중지 시간을 나타내는 짧은 문자열입니다.
GPS가 있는 시계는 시간대 규칙에 대한 위도/경도 데이터의 전체 지도가 있는 경우 자체적으로 현지 시간으로 설정할 수 있어야 합니다. 해당 맵을 만들고 <2MB로 압축하는 것이 이 JSL이 하는 일입니다. 주석은 JSL을 사용하여 ESP32 SoC에 플래시할 실행 길이 인코딩 데이터의 이진 덩어리를 빌드하는 이야기를 알려줍니다( 시스템 온 칩 .) if(0) 섹션은 코드가 처음 실행될 때 필요합니다. 그들 중 일부는 시간이 오래 걸리고 코드가 개발될 때 다시 실행할 필요가 없었습니다. JSL의 다양한 섹션은 가장 잘 압축된 상태에 도달하기 위해 약 3번 실행해야 합니다. 영역 그리기 순서가 변경되어 실행 코드의 사용 횟수가 변경되어 가장 적게 사용되기 때문에 그래프에서 픽셀이 가려지는 방식에 영향을 미치기 때문입니다. 표시되는지 확인하기 위해 마지막으로 그려집니다.
또한 : 이것을 사용하면 해상도가 1 마일보다 나쁩니다. 자신의 테스트를 수행하십시오!
/*
Problem one: A GPS device returns both the lat,lon location and the
UTC time. How can you get the local time from that information?
You can't. You also need to know what timezone boundary the lat,lon
falls in. There's a nice github project, updated frequently, with
changes.
https://github.com/evansiroky/timezone-boundary-builder
There's also one or more web sites like
https://timezonedb.com that provide an API to some database similar
to the boundaries.
Problem two: Build a clock using an ESP32 and a GPS. How can it get the
timezone? It only has about 2MB of memory and it can only access the
internet if it has wifi access and a password.
Since a clock should "just work", the internet can be the primary
answer, used if available. A fallback answer is needed too; the data
for the timezones needs to be compressed into a structure that is
small, sufficiently detailed, and reasonably fast. (I probably will
not use the website because I'd have to bake in my API-key...)
Timezones are identified by IANA with names like America/New_York
(no longer Eastern Standard Time, EDT, DST for daylight saving time, etc
because "lots of places have an east.") America/New_York implies
when the time changes happen, but it requires another table to
get those rules. The names change, sometimes, as do the rules.
America/Godthab changed its name to America/Nuuk in 2020, and
everyone has experienced their local leaders playing with "summer time".
https://data.iana.org/time-zones/tzdb/NEWS
Linux systems have another way to represent timezones, using a rule
rather than a name. (Going out on a limb here...) a Linux system has
tables for converting names to rules. A tiny ESP32 system only
understands the rules. A rule looks something like
<-03>3<-02>,M3.5.0/-2,M10.5.0/-1 (for America/Nuuk)
or
EST5EDT,M3.2.0,M11.1.0 (for America/New_York)
I think those are called "posix" rules. EST5 means 5 hours behind
UTC, the EDT means daylight time is observed in the summer. The two
time-like values says when to start and end daylight time using, for
the New York example, third month, second week, sunday with a 2AM default.
Nuuk is 3 or 2 hours behind and uses the last(5) week and 1AM (or
is that 11PM?) Fortunately, we don't need to read or write the rules, the OS does.
The name-based America/New_York and the rule-based EST5EDT,M3.2.0,M11.1.0
provide different benefits for different use cases. The name-based system
uses a database to look up the name and can provide historical information
about whether daylight time was in effect in May of 1970, for example.
The rule-based system needs no database and encodes everything needed
to convert any time the rule covers (maybe not 1970!) between UTC and local.
In this project, I realized a bit late that I needed the rule-based answer;
had the name-based answers been converted to rule-based earlier, collapsing
adjacent names with the same rule into one area instead of several, the
file would be a bit smaller. But, it is small enough.
This is the JSL to build the timezone data structure that is stored
into the ESP32 flash memory (4MB which also holds the C code.) The
C language code on the ESP32 will use the resulting binary data.
*/
// data from https://github.com/evansiroky/timezone-boundary-builder
// https://github.com/evansiroky/timezone-boundary-builder/releases/download/2020d/timezones.geojson.zip
// https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv to convert America/New_York to EST5EDT,M3.2.0,M11.1.0
// https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
// made my own using the gen-tz.py script, modified with freqCodeToName names added, America/Nuuk was missing
/* here's the import of the file that converts from America/New_York to EST5EDT,M3.2.0,M11.1.0
I open the JMP table with the from and to column names below...
dtTrans = open("f:/ClockScripts/gen-tz.csv",Import Settings(
End Of Line( CRLF, CR, LF ),
End Of Field( Comma, CSV( 1 ) ),
Strip Quotes( 1 ),
Use Apostrophe as Quotation Mark( 0 ),
Use Regional Settings( 0 ),
Scan Whole File( 1 ),
Treat empty columns as numeric( 0 ),
CompressNumericColumns( 0 ),
CompressCharacterColumns( 0 ),
CompressAllowListCheck( 0 ),
Labels( 0 ),
Column Names Start( 1 ),
Data Starts( 1 ),
Lines To Read( "All" ),
Year Rule( "20xx" )
));*/
dtTrans = open("$desktop/timezoneNameConversion.jmp"); // cols from and to, used way below to make an associative array
/* this file was missing Nuuk, above was the regenerated file including Nuuk (run Python on Linux system on the gen-tz)
open("https://raw.githubusercontent.com/nayarsystems/posix_tz_db/master/zones.csv",Import Settings(
End Of Line( CRLF, CR, LF ),
End Of Field( Comma, CSV( 1 ) ),
Strip Quotes( 1 ),
Use Apostrophe as Quotation Mark( 0 ),
Use Regional Settings( 0 ),
Scan Whole File( 1 ),
Treat empty columns as numeric( 0 ),
CompressNumericColumns( 0 ),
CompressCharacterColumns( 0 ),
CompressAllowListCheck( 0 ),
Labels( 0 ),
Column Names Start( 1 ),
Data Starts( 1 ),
Lines To Read( "All" ),
Year Rule( "20xx" )
));
*/
if( 0, // about 2 minutes...run once... download from https://github.com/evansiroky/timezone-boundary-builder/releases/download/2020d/timezones.geojson.zip
za = Open( "z:/timezones.geojson.zip", "zip" );
blob = za << read( "combined.json", Format( blob ) );
p = Parse JSON( Blob To Char( blob ) ); // this grinds for a bit, it is 135MB
Save Text File( "$desktop/rlejson.jsl", char(p) ); // this is what is needed next time
);
/* run the above to generate this file the first time */
P = Eval( Parse( Load Text File( "$desktop/rlejson.jsl" ) ) ); // loads faster this way
/*
this is the only interesting key, grab it once. probably this is what should have been saved/reloaded.
WATER: there is another version of the geojson that includes oceans. I did not look at it. The
JSL presets the bitmap to 0, then fills in polygons with values > 0 for timezones. This works out
well; the C array is indexed from 0 and has a entry for water there. The JSL test code detects
the 0 to avoid an index error and reports "water";
*/
featureList = P["features"]; // list of 426 AAs (no water)
/*
the "P" is for Parsed json. This is the best way to load this .json
data; using the json wizard almost works but does not handle the nested
arrays used to represent disjoint polygons.
The next two files are saved from a prev run. find "if(0, // regenerate" below
to generate them; takes a while and this saves time if nothing else changed.
If you are lucky enough to only run this code once, you won't care. Saved a lot
of development time further down in the file.
*/
rle = Eval( Parse( Load Text File( "$desktop/rletext.jsl" ) ) );
/*
you won't have this file the first time; I think freqCodeToName={"water"}; is the correct choice,
BUT you'll need to do a second run to get good compression! This list *should* have the
"America/New_York" names listed in order of how many times they create a run in the
run length encoding. The 128 most frequent ones get represented as 1-byte codes, the
remaining 300 or so get a 2-byte code. They get counted and saved in this file later.
*/
freqCodeToName = Eval( Parse( Load Text File( "$desktop/rlename.jsl" ) ) );
/*
Mostly this JSL uses the "America/New_York" names, in the order of the feature list.
This simple-minded list of those names can be indexed by the feature index (usually) or
using loc() (rarely) to get the index from the name.
The for loop ignores the geometry for now and make the iFeatureToName list.
*/
iFeatureToName = {};
for( global:iFeature = 1, global:iFeature <= N Items( featureList ), global:iFeature += 1,
tzdata = featureList[global:iFeature]; // aa {"geometry", "properties", "type"}
tzproperties = tzdata["properties"]; // aa ["tzid" => "Africa/Abidjan"]
tzname=tzproperties["tzid"];
insertinto(iFeatureToName,tzname);
if(!contains(freqCodeToName,tzname),
write("\!nadd ",tzname);
insertinto(freqCodeToName, tzname); // see comment above about "freqCodeToName={"water"};" -- this makes the uncompressed version
);
);
// sanity check, will need work or removal in the future.
if( freqCodeToName[1] != "water" | freqCodeToName[2] != "Asia/Shanghai" | freqCodeToName[426] != "Europe/Busingen" | freqCodeToName[427] != "Europe/Vatican",
Throw( "freqCodeToName" ) // probably comment this out on the first run which won't be sorted, and maybe later if the world changes.
);
/*
two helper functions for the graphics script to call
dopoly does one disjoint part of a timezone.
Further down, the graphbox will be recreated over and over for each global:iFeature because
there is an anti-aliased edge that has pixels of a different color than the center pixels
but they are all the same timezone...so doing the timezones one-at-a-time is the easiest way,
though it will take a long time because the bitmap is 16K x 30K huge.
*/
dopoly = function( {tzName, tzcoords},
{i, m, xs,ys},
for( i = 1, i <= N Items( tzcoords ), i += 1,
/*
I'm still not sure what the 2,3,... polys represent. Holes maybe,
in a single disjoint poly. Render same color, smaller polys render later (2nd run).
this is part of the data that is not right with the json wizard (comments at top)
*/
//if(i>1,fillcolor("magenta"));
m = Matrix( tzcoords[i] );
xs = m[0, 1];
ys = m[0, 2];
//pencolor("white");pensize(1);
polygon/*line*/(xs, ys);// xmatrix,ymatrix
)
);
/*
drawpolys does all the disjoint parts of one timezone; a timezone with only 1 part is not nested
quite as deep as a timezone with multiple disjoint parts.
*/
drawpolys = function( {}, //
{tzdata, tzgeometry, tzproperties, tztype, tzGeometryType, tzcoordinates, code, greenbits, bluebits, redbits, ipoly},
tzdata = featureList[global:iFeature]; // aa {"geometry", "properties", "type"}
tzgeometry = tzdata["geometry"]; // aa {"coordinates", "type"}
tzproperties = tzdata["properties"]; // aa ["tzid" => "Africa/Abidjan"]
/* sanity checking, these should match the log line that follows from the driver loop below */
write("\!n drawing ",global:iFeature,": ", tzproperties["tzid"] );
tztype = tzdata["type"]; // string "Feature"
tzGeometryType = tzgeometry["type"];//"Polygon" or "MultiPolygon"
tzcoordinates = tzgeometry["coordinates"]; // list
Fill Color( "white" );
if(
tzGeometryType == "MultiPolygon", /* a disjoint time zone */
for( ipoly = 1, ipoly <= N Items( tzcoordinates ), ipoly += 1,
dopoly( tzproperties["tzid"], tzcoordinates[ipoly] ) /* deeper nest */
);//
, /*else if*/tzGeometryType == "Polygon", /* single poly timezone */
dopoly( tzproperties["tzid"], tzcoordinates );// no extra nest
, /*else*/
Throw( "tzGeometryType" || Char( tzGeometryType ) )
);
);
/*
this needs to run the first time to generate the bitmap to get the initial frequencies of the
run length codes. It needs to run the 2nd time too, using the frequency ordering. Maybe.
Originally 1-pixel zones like Europe/Vatican reported their name. Now they report thier
description, and in this case, the surrounding Europe/Rome has the same description. There
could be some other edge cases that still matter.
"composition" is a large matrix (3.5GB), if rerunning this code, set it to 0 to release the
old one before making a new one. otherwise 2*3.5GB must be in memory at the same time.
*/
composition = 0;
composition = J( 16000, 30000, 0 );42;
if(0, // let this code run to rebuild the composition
for( ifreq = 2/* 1 is water */, ifreq <= N Items( freqCodeToName ), ifreq += 1,
/*
this is the big-to-small list of timezones; get the name of the zone to do next...
*/
thisname=freqCodeToName[ifreq];
/*
this global variable tells the poly routine what to work on; locate the name and use the index from loc
*/
global:iFeature = loc(iFeatureToName,thisname)[1];// draw smallest last
/*
Surprisingly nothing happens when the graphbox is made without a window, until...
*/
gb = Graph Box(
X Scale( -180, 180 ),
Y Scale( -90, 90 ),
framesize( 30000 + 1, 16000 + 1 ), // drawing beyond 30,000 may not always work, 16x30 is about as big as it can be.
<<BACKGROUNDCOLOR>0];
rle = {};
for( irow = 1, irow <= N Rows( composition ), irow += 1,
if(mod(irow,100)==0,write(irow," ");wait(.01));
row = {}; // a list for one row
code = -1; // initial no match
length = 0; // initial no length
for( icol = 1, icol <= N Cols( composition ), icol += 1,
if( composition[irow, icol] != code, // new run begins
if( length > 0, // initial no match has length 0
run = {}; // output code&length of prev run
codecounts[code]+=1;
run[1] = code;
run[2] = length;
row[N Items( row ) + 1] = run;
);
length = 1;
code = composition[irow, icol];//
, // else
length += 1
)
);
run = {};// handle the last one on the row
codecounts[code]+=1;
run[1] = code;
run[2] = length;
row[N Items( row ) + 1] = run;
rle[N Items( rle ) + 1] = Matrix( row );// convert to Nx2 matrix and store at end of rle list
);
countkeys = codecounts<<GETKEYS>0,
iFeatureToName[code];
,//
"water"
)
);
/*
draw a bunch of markers, color coded for timezone, over a map.
This give a view of the accuracy near rivers and obvious political boundaries.
*/
//////////////////////////////////////////////////////////////////
markersOver = function({clat,clon,name,grid=1},
dtVat=New Table( name,
New Column( "tz", Character, "Nominal" ),
New Column( "lat", Numeric, "Continuous", Format( "Best", 12 ) ),
New Column( "lon", Numeric, "Continuous", Format( "Best", 12 ) )
);
for(ylat=clat-.5*grid, ylat<=clat+.5*grid,ylat+=.004*grid,
for(xlon=clon-.5*grid,xlon<=clon+.5*grid,xlon+=.006*grid,
dtVat<<ADDROWS></ADDROWS></GETKEYS></BACKGROUNDCOLOR>인디애나에는 정말 11개의 시간대가 있습니까?
blob이 위도/경도를 일부 지도 작성과 관련된 시간대로 변환하는지 확인하기 위한 테스트의 일부입니다. 이 지도의 JSL은 영역에 대한 데이터 포인트 그리드를 만듭니다. 인디애나는 미국 중부에 있는 주입니다. 그것은 동부에서 중부 시간으로의 변화의 가장자리에 있습니다. 그만큼 위키피디아 기사 (흥미로운 읽기)는 일광 절약 시간제가 시간대의 중심에서 멀리 떨어져 얼마나 고통스러운지 제안하여 주 내의 카운티가 수년에 걸쳐 다른 선택을 하게 만듭니다. 영역 이름은 과거에 일광 시간이 시작되고 중지된 시기와 멈춤, 동부 표준시와 중부 표준시 사이의 미묘하지 않은 선택에 따라 미묘한 차이가 있었을 수 있습니다. 이러한 영역 이름( ~에서 열) 인디애나주:
New_York, Chicago, and Kentucky zones extend way beyond Indiana.New_York, Chicago 및 Kentucky 지역은 인디애나 너머까지 확장됩니다.
두 가지 고유한 규칙( 에게 열) 인디애나의 시간 동안. 중부 표준시를 사용하는 인디애나 지역은 중부 표준시를 사용하는 인디애나 이외의 대도시와 가깝습니다.
Eleven zones in one state.한 주에 11개의 구역이 있습니다.
캔자스시간대
캔자스는 인디애나에서 서쪽으로 15도(시간대 전체)이며 서쪽 국경에서도 비슷한 문제가 있습니다. 4개 카운티는 시카고(중부) 시간 대신 덴버(산) 시간을 사용하기로 결정했습니다. 이렇게 하면 카운티 윤곽선을 사용하여 왼쪽-오른쪽 정렬뿐만 아니라 위아래 정렬도 볼 수 있는 좋은 테스트가 됩니다. 여기에 표시된 상태 개요. Wikipedia의 캔자스 이야기 인디애나만큼 흥미롭지 않습니다(좋습니다!)
The blue Denver time is used by four counties on the western side of Kansas. The Red Chicago time is used by most of the state.블루 덴버 시간은 캔자스 서부의 4개 카운티에서 사용됩니다. 레드 시카고 시간은 대부분의 주에서 사용됩니다.
ESP32에 데이터 플래싱:
Linux shell window. I should probably rename my computer. I dropped that subscription years ago after I got to the end.리눅스 쉘 창. 아마도 내 컴퓨터의 이름을 바꿔야 할 것입니다. 나는 끝에 도달한 후 몇 년 전에 그 구독을 중단했습니다.
마지막으로 요점은 이아나 인터넷이 작동하도록 만드는 역할을 합니다.