tbr123psu has asked for the wisdom of the Perl Monks concerning the following question:

esteemed monks:

greetings all! i've been struggling with this for days now, and figured it was finally time to ask for some help.

I've been following the instructions found at http://virantha.com/2014/01/09/hacking-together-a-wifi-photo-frame-with-a-toshiba-flashair-sd-card-wireless-photo-uploads/ to set up a wifi SD card for use in a photoframe. I wanted to use Perl rather than the python referenced on the page because I generally prefer perl and because i kinda wanted to do it myself.

it's been smooth sailing, right up to the point of having to submit a FAT32 timestamp to set the creation date of the uploaded file. This part has me completely stumped.

i've been all over google, and the best reference I can find about the required formats is from this thread on stackoverflow: http://stackoverflow.com/questions/15763259/unix-timestamp-to-fat-timestamp. There's no search results here for fat32.

interestingly enough, I was able to figure out how interpret the dates coming off the card (for the purposes of knowing which photos were the last ones added, so I can only upload new ones), but trying to apply the same logic in reverse did not work so well. I'm also not entirely clear on why it worked the way it did, such that they come out split up but when going in, the card wants just one string, but I suppose that's just quirkiness in the API i've got to live with.

anyhow, i suspect the answer has something to do with pack, but i won't lie, i'm just flinging stuff at the wall in the hopes that it sticks. I thought i was making progress, in that I have some idea what the actual value for a current timestamp is that would be submitted to the api call (through trial and error/guesswork -- manually trying to make things up, based on the example given in the API documentation https://flashair-developers.com/en/documents/api/uploadcgi/).

so i've got some idea what the value needs to look like, but for the life of me i can't get that output to generate programatically. it seems that it's inclusive of 0-9 and a-f, so it seems like a hexadecimal number (as per http://www.microbuilder.eu/Tutorials/Fundamentals/Hexadecimal.aspx), but being honest, this gets a little deeper than i usually go. thinking about it sometimes makes my head hurt.

has anyone ever bumped into anything like this before or can offer any insight? code snippets below that do the relevant stuff with dates/times.

here's the code that pulls the time and date from the card and interprets it.

#Each row of the list is returned in the following format. #<directory>, <filename>, <size>, <attribute>, <date>, <time> # date 16 bit int -- bit 15-9 value based on 0 as 1980, bit 8-5 month +value from 1 to 12, bit 4-0 day, value from 1 to 31 # time 16 bit int -- bit 15-11 hour, 10-5, minute, 4-0 second / 2 # size my $fileList = getHttp($cardip, "command.cgi?op=100&DIR=/"); #print ($fileList . "\n"); my @fileArray = split("\n", $fileList); my @fileTimesArray; my $lastTime = 0; print (ref(@fileArray) . "\n"); foreach my $file (@fileArray) { if (index($file, ",") != -1) { print "working with file $file\n"; my ($directory, $name, $size, $att, $date, $time) = split(",", + $file); # example date, time = 18151,39092 my $day = ($date >> 0) & (2**5-1); my $month = ($date >> 5) & (2**4-1); my $year = ($date >> 9) & (2**7-1); $year = 1980 + $year; my $second = ($time >> 0) & (2**5-1); my $minute = ($time >> 5) & (2**6-1); my $hour = ($time >> 11) & (2**5-1); $second = $second * 2; print ("file: $name | $month - $day - $year | $hour : $minute +: $second | $epochTime\n"); #print ("file: $name | $date | $time\n"); #print ("day: $day\n"); #print ("month: $month\n"); #print ("year: $year\n"); #print ("second: $second\n"); #print ("minute: $minute\n"); #print ("hour: $hour\n"); my $perlMonth = $month - 1; my $epochTime = timelocal($second,$minute,$hour,$day,$perlMont +h,$year); if ($epochTime > $lastTime) { $lastTime = $epochTime; } push @fileTimesArray, { file => $name, epoch => $epochTime, si +ze => $size }; } }

and here's the code for trying to generate a timestamp based on the timestamp returned from the file on disk.

@info = stat($path->{file}); my $createdtime = $info[10]; print "regular created time is: $createdtime\n"; my ($sec, $min, $hour, $day,$month,$year) = (localtime($createdtim +e))[0,1,2,3,4,5]; # You can use 'gmtime' for GMT/UTC dates instead of 'localtime' $month++; my $displayYear = $year; my $realYear = $year; $year = $year - 80; $second = ceil($sec / 2); # example date, time = 18151,39092 --> not same format!?! # time 16 bit int -- bit 15-11 hour, 10-5, minute, 4-0 second / 2 #my $encSecond = $second & (2**5-1); #my $encMinute = $minute & (2**6-1); #my $encHour = $hour & (2**5-1); # date 16 bit int -- bit 15-9 value based on 0 as 1980, bit 8-5 mo +nth value from 1 to 12, bit 4-0 day, value from 1 to 31 #my $encDay = $day & (2**5-1); #my $encMonth = $month & (2**4-1); #my $encYear = $month & (2**7-1); #use integer; my $data = $year . $month . $day . $hour . $min . $second; print "using $data as input for pack\n"; #my $data = $second . " " . $minute . " " . $hour . " " . $day . " + " . $month . " " . $year; #my $createdtimeFat = pack "N8", $data; my $createdtimeFat = pack "N8", $year,$month,$day,$hour,$min,$sec +ond; #my $createdtimeFat = ($year << 25) | ($month << 21) | ($day << 1 +6) | ($hour << 11) | ($min << 5) | ($second << 0); #my $createdtimeFat = (($year & (2**7-1)) << 25) | (($month & (2* +*4-1)) << 21) | (($day & (2**5-1)) << 16) | (($hour & (2**5-1)) << 11 +) | (($min & (2**6-1)) << 5) | (($second & (2**5-1)) << 0); # 8 digits # 8 = year # 7 = year + month # 6 = month + day # 5 = day #### # 4 = hour # 3 = hour + month # 2 = minute + second # 4 = second #my $createdtimeFat = '469f9f01'; #my $hex = sprintf("0x%x", $createdtimeFat); #my $hex = printf("%x",$createdtimeFat); print "Unix time ".$createdtime." converts to ".$month." ".$day.", + ".($displayYear+1900)." ".$hour.":".$min.":".$sec." year (in offset +from 1980) is $year [real year is $realYear]\n"; #print $encSecond ." ". $encMinute ." ". $encHour ." ". $encDay ." + ". $encMonth ." ". $encYear . "\n"; print "createdtimeFat should look something like 46ef99c6\n"; print "createdtimeFat is $createdtimeFat\n"; my @unpacked = unpack("N8",$createdtimeFat); print "and unpacked: " . @unpacked . "\n"; my $setdate = getHttp($cardip, "upload.cgi?FTIME=0x" . $createdtim +eFat); print "result of setdate operation: $setdate\n";

and this is the getHttp function/sub

sub getHttp() { my $ip = shift; my $args = shift; my $status; my $url = "http://" . $ip . "/" . $args; #print ("accessing " . $url . "\n"); # set custom HTTP request header fields my $req = HTTP::Request->new(GET => $url); my $resp = $ua->request($req); if ($resp->is_success) { my $message = $resp->decoded_content; #print "Received reply: $message\n"; $status = $resp->decoded_content; } else { print "HTTP GET error code: ", $resp->code, "\n"; print "HTTP GET error message: ", $resp->message, "\n"; $status = $resp->message; } return $status }

many thanks in advance for any help or guidance anyone can offer - i'm at wit's end, and i feel like i've got to be missing something!

Replies are listed 'Best First'.
Re: Pack + Fat32 timestamp (|, <<)
by tye (Sage) on Jul 25, 2015 at 22:57 UTC

    pack is the wrong function for what you are trying to do, for two reasons. First, pack() really doesn't deal with things smaller than bytes (which is not completely obvious from the documentation). Second, pack() gives you a string whose bytes represent binary values in various formats and what you want is a numeric value expressed as hexademical.

    Putting the bits together into a numeric value is much closer to your working extraction code but using | instead of & and << instead of >>:

    my $date = $day | ($month<<5) | ($year<<9); my $time = $second | ($min<<5) | ($hour<<11); my $fat32 = sprintf "0x%08x", $time | ($date<<16);

    Update: Another approach is to use a base-2 string of '0' and '1' characters, using Perl's support for numbers like 0b10011 (also via oct) and sprintf's %b format, like so:

    my $bits = sprintf "%07b%04b%05b%05b%06b%05b", $year, $month, $day, $hour, $min, $sec; my $fat32 = sprintf "0x%08x", oct( "0b$bits" );

    Here is example data from testing those two snippets:

    Sat Jul 25 16:09:52 2015 Binary: year mon day hour min sec 0100011 0111 11001 10000 001001 11010 35 7 25 16 9 26 2015 52 Hex: 0100 0110 1111 1001 1000 0001 0011 1010 0x46f9813a

    - tye        

      tye:

      thank you a million times! you absolutely, hands down, no doubt win my internet hero of the week award! the very first post, pre-edit, did the trick!

      i was thinking that felt right, but the & was throwing me off and i wasn't making any progress, so i went off in search of other means and that's how i stumbled across pack. Thanks for the explanation of why pack was the wrong choice! your post was both helpful and educational! very much appreciated.

Re: Pack + Fat32 timestamp
by anonymized user 468275 (Curate) on Jul 28, 2015 at 14:44 UTC
    Take a look at that stack overflow reply that shows how the bitmask is constructed. This bitmask breakdown should be almost a blueprint to your code. I also tend to struggle with pack so using the left shift operator for each group of bits:
    my ($sec, $min, $hr, $day, $mth, $yr) = localtime($info[10]); $mth++; $yr -= 80; # change offset from 1900 to 1980 { use integer; $sec /= 2; } # convert sec units from 1 to 2 my $packed = ($year << 25) + ($mth << 22) + ($day << 16) + ($hr << 11) + ($min << 5) + $sec;

    One world, one people